Merge "Replace android.WriteFile rule with android.WriteFileRule"
diff --git a/android/Android.bp b/android/Android.bp
index 7bd1450..8f89a59 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -21,6 +21,7 @@
         "defaults.go",
         "defs.go",
         "depset.go",
+        "deptag.go",
         "expand.go",
         "filegroup.go",
         "hooks.go",
@@ -68,6 +69,7 @@
         "config_test.go",
         "csuite_config_test.go",
         "depset_test.go",
+        "deptag_test.go",
         "expand_test.go",
         "module_test.go",
         "mutator_test.go",
diff --git a/android/arch.go b/android/arch.go
index 98ff07a..16211f8 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -598,6 +598,10 @@
 	// has dependencies on all the OS variants.
 	CommonOS = NewOsType("common_os", Generic, false)
 
+	// CommonArch is the Arch for all modules that are os-specific but not arch specific,
+	// for example most Java modules.
+	CommonArch = Arch{ArchType: Common}
+
 	osArchTypeMap = map[OsType][]ArchType{
 		Linux:       []ArchType{X86, X86_64},
 		LinuxBionic: []ArchType{Arm64, X86_64},
@@ -661,7 +665,7 @@
 	if _, found := commonTargetMap[name]; found {
 		panic(fmt.Errorf("Found Os type duplicate during OsType registration: %q", name))
 	} else {
-		commonTargetMap[name] = Target{Os: os, Arch: Arch{ArchType: Common}}
+		commonTargetMap[name] = Target{Os: os, Arch: CommonArch}
 	}
 
 	return os
@@ -819,9 +823,6 @@
 // Identifies the dependency from CommonOS variant to the os specific variants.
 var commonOsToOsSpecificVariantTag = archDepTag{name: "common os to os specific"}
 
-// Identifies the dependency from arch variant to the common variant for a "common_first" multilib.
-var firstArchToCommonArchDepTag = archDepTag{name: "first arch to common arch"}
-
 // Get the OsType specific variants for the current CommonOS variant.
 //
 // The returned list will only contain enabled OsType specific variants of the
@@ -960,12 +961,6 @@
 		addTargetProperties(m, targets[i], multiTargets, i == 0)
 		m.base().setArchProperties(mctx)
 	}
-
-	if multilib == "common_first" && len(modules) >= 2 {
-		for i := range modules[1:] {
-			mctx.AddInterVariantDependency(firstArchToCommonArchDepTag, modules[i+1], modules[0])
-		}
-	}
 }
 
 func addTargetProperties(m Module, target Target, multiTargets []Target, primaryTarget bool) {
diff --git a/android/deptag.go b/android/deptag.go
new file mode 100644
index 0000000..be5c35c
--- /dev/null
+++ b/android/deptag.go
@@ -0,0 +1,45 @@
+// Copyright 2020 Google Inc. 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 android
+
+import "github.com/google/blueprint"
+
+// Dependency tags can implement this interface and return true from InstallDepNeeded to annotate
+// that the installed files of the parent should depend on the installed files of the child.
+type InstallNeededDependencyTag interface {
+	// If InstallDepNeeded returns true then the installed files of the parent will depend on the
+	// installed files of the child.
+	InstallDepNeeded() bool
+}
+
+// Dependency tags can embed this struct to annotate that the installed files of the parent should
+// depend on the installed files of the child.
+type InstallAlwaysNeededDependencyTag struct{}
+
+func (i InstallAlwaysNeededDependencyTag) InstallDepNeeded() bool {
+	return true
+}
+
+var _ InstallNeededDependencyTag = InstallAlwaysNeededDependencyTag{}
+
+// IsInstallDepNeeded returns true if the dependency tag implements the InstallNeededDependencyTag
+// interface and the InstallDepNeeded returns true, meaning that the installed files of the parent
+// should depend on the installed files of the child.
+func IsInstallDepNeeded(tag blueprint.DependencyTag) bool {
+	if i, ok := tag.(InstallNeededDependencyTag); ok {
+		return i.InstallDepNeeded()
+	}
+	return false
+}
diff --git a/android/deptag_test.go b/android/deptag_test.go
new file mode 100644
index 0000000..bdd449e
--- /dev/null
+++ b/android/deptag_test.go
@@ -0,0 +1,135 @@
+// Copyright 2020 Google Inc. 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 android
+
+import (
+	"testing"
+
+	"github.com/google/blueprint"
+)
+
+type testInstallDependencyTagModule struct {
+	ModuleBase
+	Properties struct {
+		Install_deps []string
+		Deps         []string
+	}
+}
+
+func (t *testInstallDependencyTagModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	outputFile := PathForModuleOut(ctx, "out")
+	ctx.Build(pctx, BuildParams{
+		Rule:   Touch,
+		Output: outputFile,
+	})
+	ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+}
+
+var testInstallDependencyTagAlwaysDepTag = struct {
+	blueprint.DependencyTag
+	InstallAlwaysNeededDependencyTag
+}{}
+
+var testInstallDependencyTagNeverDepTag = struct {
+	blueprint.DependencyTag
+}{}
+
+func (t *testInstallDependencyTagModule) DepsMutator(ctx BottomUpMutatorContext) {
+	ctx.AddVariationDependencies(nil, testInstallDependencyTagAlwaysDepTag, t.Properties.Install_deps...)
+	ctx.AddVariationDependencies(nil, testInstallDependencyTagNeverDepTag, t.Properties.Deps...)
+}
+
+func testInstallDependencyTagModuleFactory() Module {
+	module := &testInstallDependencyTagModule{}
+	InitAndroidArchModule(module, HostAndDeviceDefault, MultilibCommon)
+	module.AddProperties(&module.Properties)
+	return module
+}
+
+func TestInstallDependencyTag(t *testing.T) {
+	bp := `
+		test_module {
+			name: "foo",
+			deps: ["dep"],
+			install_deps: ["install_dep"],
+		}
+
+		test_module {
+			name: "install_dep",
+			install_deps: ["transitive"],
+		}
+
+		test_module {
+			name: "transitive",
+		}
+
+		test_module {
+			name: "dep",
+		}
+	`
+
+	config := TestArchConfig(buildDir, nil, bp, nil)
+	ctx := NewTestArchContext(config)
+
+	ctx.RegisterModuleType("test_module", testInstallDependencyTagModuleFactory)
+
+	ctx.Register()
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	hostFoo := ctx.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
+	hostInstallDep := ctx.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostTransitive := ctx.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
+	hostDep := ctx.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
+
+	if g, w := hostFoo.Implicits.Strings(), hostInstallDep.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostFoo.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostInstallDep.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostFoo.Implicits.Strings(), hostDep.Output.String(); InList(w, g) {
+		t.Errorf("expected no host dependency %q, got %q", w, g)
+	}
+
+	deviceFoo := ctx.ModuleForTests("foo", "android_common").Description("install")
+	deviceInstallDep := ctx.ModuleForTests("install_dep", "android_common").Description("install")
+	deviceTransitive := ctx.ModuleForTests("transitive", "android_common").Description("install")
+	deviceDep := ctx.ModuleForTests("dep", "android_common").Description("install")
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceInstallDep.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceInstallDep.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceDep.Output.String(); InList(w, g) {
+		t.Errorf("expected no device dependency %q, got %q", w, g)
+	}
+}
diff --git a/android/module.go b/android/module.go
index d677406..ef1b0bd 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1242,14 +1242,18 @@
 	return m.commonProperties.NamespaceExportedToMake
 }
 
+// computeInstallDeps finds the installed paths of all dependencies that have a dependency
+// tag that is annotated as needing installation via the IsInstallDepNeeded method.
 func (m *ModuleBase) computeInstallDeps(ctx blueprint.ModuleContext) InstallPaths {
-
 	var result InstallPaths
-	// TODO(ccross): we need to use WalkDeps and have some way to know which dependencies require installation
-	ctx.VisitDepsDepthFirst(func(m blueprint.Module) {
-		if a, ok := m.(Module); ok {
-			result = append(result, a.FilesToInstall()...)
+	ctx.WalkDeps(func(child, parent blueprint.Module) bool {
+		if a, ok := child.(Module); ok {
+			if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
+				result = append(result, a.FilesToInstall()...)
+				return true
+			}
 		}
+		return false
 	})
 
 	return result
diff --git a/apex/apex.go b/apex/apex.go
index 8268966..91770f4 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -63,7 +63,6 @@
 	testTag        = dependencyTag{name: "test", payload: true}
 	keyTag         = dependencyTag{name: "key"}
 	certificateTag = dependencyTag{name: "certificate"}
-	usesTag        = dependencyTag{name: "uses"}
 	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
 	rroTag         = dependencyTag{name: "rro", payload: true}
 	bpfTag         = dependencyTag{name: "bpf", payload: true}
@@ -764,7 +763,6 @@
 	ctx.BottomUp("apex", apexMutator).Parallel()
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
-	ctx.BottomUp("apex_uses", apexUsesMutator).Parallel()
 	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
 }
 
@@ -1007,12 +1005,6 @@
 	}
 }
 
-func apexUsesMutator(mctx android.BottomUpMutatorContext) {
-	if ab, ok := mctx.Module().(*apexBundle); ok {
-		mctx.AddFarVariationDependencies(nil, usesTag, ab.properties.Uses...)
-	}
-}
-
 var (
 	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
 )
@@ -1132,12 +1124,6 @@
 
 	HideFromMake bool `blueprint:"mutated"`
 
-	// Indicates this APEX provides C++ shared libaries to other APEXes. Default: false.
-	Provide_cpp_shared_libs *bool
-
-	// List of providing APEXes' names so that this APEX can depend on provided shared libraries.
-	Uses []string
-
 	// package format of this apex variant; could be non-flattened, flattened, or zip.
 	// imageApex, zipApex or flattened
 	ApexType apexPackaging `blueprint:"mutated"`
@@ -2181,30 +2167,6 @@
 	var provideNativeLibs []string
 	var requireNativeLibs []string
 
-	// Check if "uses" requirements are met with dependent apexBundles
-	var providedNativeSharedLibs []string
-	useVendor := proptools.Bool(a.properties.Use_vendor)
-	ctx.VisitDirectDepsBlueprint(func(m blueprint.Module) {
-		if ctx.OtherModuleDependencyTag(m) != usesTag {
-			return
-		}
-		otherName := ctx.OtherModuleName(m)
-		other, ok := m.(*apexBundle)
-		if !ok {
-			ctx.PropertyErrorf("uses", "%q is not a provider", otherName)
-			return
-		}
-		if proptools.Bool(other.properties.Use_vendor) != useVendor {
-			ctx.PropertyErrorf("use_vendor", "%q has different value of use_vendor", otherName)
-			return
-		}
-		if !proptools.Bool(other.properties.Provide_cpp_shared_libs) {
-			ctx.PropertyErrorf("uses", "%q does not provide native_shared_libs", otherName)
-			return
-		}
-		providedNativeSharedLibs = append(providedNativeSharedLibs, other.properties.Native_shared_libs...)
-	})
-
 	var filesInfo []apexFile
 	// TODO(jiyong) do this using WalkPayloadDeps
 	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
@@ -2352,11 +2314,6 @@
 				// tags used below are private (e.g. `cc.sharedDepTag`).
 				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
 					if cc, ok := child.(*cc.Module); ok {
-						if android.InList(cc.Name(), providedNativeSharedLibs) {
-							// If we're using a shared library which is provided from other APEX,
-							// don't include it in this APEX
-							return false
-						}
 						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
 							requireNativeLibs = append(requireNativeLibs, ":vndk")
 							return false
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 321883f..33e5077 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -149,10 +149,8 @@
 		"system/sepolicy/apex/myapex.updatable-file_contexts": nil,
 		"system/sepolicy/apex/myapex2-file_contexts":          nil,
 		"system/sepolicy/apex/otherapex-file_contexts":        nil,
-		"system/sepolicy/apex/commonapex-file_contexts":       nil,
 		"system/sepolicy/apex/com.android.vndk-file_contexts": nil,
 		"mylib.cpp":                                  nil,
-		"mylib_common.cpp":                           nil,
 		"mytest.cpp":                                 nil,
 		"mytest1.cpp":                                nil,
 		"mytest2.cpp":                                nil,
@@ -4202,131 +4200,6 @@
 	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += myapex.flattened")
 }
 
-func TestApexUsesOtherApex(t *testing.T) {
-	ctx, _ := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib"],
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-			native_shared_libs: ["libcommon"],
-			provide_cpp_shared_libs: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			shared_libs: ["libcommon"],
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "myapex" ],
-		}
-
-		cc_library {
-			name: "libcommon",
-			srcs: ["mylib_common.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			// TODO: remove //apex_available:platform
-			apex_available: [
-				"//apex_available:platform",
-				"commonapex",
-				"myapex",
-			],
-		}
-	`)
-
-	module1 := ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	apexRule1 := module1.Rule("apexRule")
-	copyCmds1 := apexRule1.Args["copy_commands"]
-
-	module2 := ctx.ModuleForTests("commonapex", "android_common_commonapex_image")
-	apexRule2 := module2.Rule("apexRule")
-	copyCmds2 := apexRule2.Args["copy_commands"]
-
-	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
-	ensureListContains(t, ctx.ModuleVariantsForTests("libcommon"), "android_arm64_armv8-a_shared_apex10000")
-	ensureContains(t, copyCmds1, "image.apex/lib64/mylib.so")
-	ensureContains(t, copyCmds2, "image.apex/lib64/libcommon.so")
-	ensureNotContains(t, copyCmds1, "image.apex/lib64/libcommon.so")
-}
-
-func TestApexUsesFailsIfNotProvided(t *testing.T) {
-	testApexError(t, `uses: "commonapex" does not provide native_shared_libs`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-	testApexError(t, `uses: "commonapex" is not a provider`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			uses: ["commonapex"],
-		}
-
-		cc_library {
-			name: "commonapex",
-			system_shared_libs: [],
-			stl: "none",
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-}
-
-func TestApexUsesFailsIfUseVenderMismatch(t *testing.T) {
-	testApexError(t, `use_vendor: "commonapex" has different value of use_vendor`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			use_vendor: true,
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-			provide_cpp_shared_libs: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`, func(fs map[string][]byte, config android.Config) {
-		setUseVendorAllowListForTest(config, []string{"myapex"})
-	})
-}
-
 func TestErrorsIfDepsAreNotEnabled(t *testing.T) {
 	testApexError(t, `module "myapex" .* depends on disabled module "libfoo"`, `
 		apex {
diff --git a/cc/binary.go b/cc/binary.go
index 70cf5ff..da29412 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -95,6 +95,10 @@
 	// Names of symlinks to be installed for use in LOCAL_MODULE_SYMLINKS
 	symlinks []string
 
+	// If the module has symlink_preferred_arch set, the name of the symlink to the
+	// binary for the preferred arch.
+	preferredArchSymlink string
+
 	// Output archive of gcno coverage information
 	coverageOutputFile android.OptionalPath
 
@@ -402,7 +406,9 @@
 			ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix")
 		}
 		if ctx.TargetPrimary() {
-			binary.symlinks = append(binary.symlinks, binary.getStemWithoutSuffix(ctx))
+			symlinkName := binary.getStemWithoutSuffix(ctx)
+			binary.symlinks = append(binary.symlinks, symlinkName)
+			binary.preferredArchSymlink = symlinkName
 		}
 	}
 
@@ -455,12 +461,26 @@
 		binary.baseInstaller.subDir = "bootstrap"
 	}
 	binary.baseInstaller.install(ctx, file)
+
+	var preferredArchSymlinkPath android.OptionalPath
 	for _, symlink := range binary.symlinks {
-		ctx.InstallSymlink(binary.baseInstaller.installDir(ctx), symlink, binary.baseInstaller.path)
+		installedSymlink := ctx.InstallSymlink(binary.baseInstaller.installDir(ctx), symlink,
+			binary.baseInstaller.path)
+		if symlink == binary.preferredArchSymlink {
+			// If this is the preferred arch symlink, save the installed path for use as the
+			// tool path.
+			preferredArchSymlinkPath = android.OptionalPathForPath(installedSymlink)
+		}
 	}
 
 	if ctx.Os().Class == android.Host {
-		binary.toolPath = android.OptionalPathForPath(binary.baseInstaller.path)
+		// If the binary is multilib with a symlink to the preferred architecture, use the
+		// symlink instead of the binary because that's the more "canonical" name.
+		if preferredArchSymlinkPath.Valid() {
+			binary.toolPath = preferredArchSymlinkPath
+		} else {
+			binary.toolPath = android.OptionalPathForPath(binary.baseInstaller.path)
+		}
 	}
 }
 
diff --git a/cc/cc.go b/cc/cc.go
index 5e4faf2..0724a76 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -550,7 +550,15 @@
 	return d.Kind == staticLibraryDependency
 }
 
-// dependencyTag is used for tagging miscellanous dependency types that don't fit into
+// InstallDepNeeded returns true for shared libraries so that shared library dependencies of
+// binaries or other shared libraries are installed as dependencies.
+func (d libraryDependencyTag) InstallDepNeeded() bool {
+	return d.shared()
+}
+
+var _ android.InstallNeededDependencyTag = libraryDependencyTag{}
+
+// dependencyTag is used for tagging miscellaneous dependency types that don't fit into
 // libraryDependencyTag.  Each tag object is created globally and reused for multiple
 // dependencies (although since the object contains no references, assigning a tag to a
 // variable and modifying it will not modify the original).  Users can compare the tag
@@ -560,6 +568,15 @@
 	name string
 }
 
+// installDependencyTag is used for tagging miscellaneous dependency types that don't fit into
+// libraryDependencyTag, but where the dependency needs to be installed when the parent is
+// installed.
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 var (
 	genSourceDepTag       = dependencyTag{name: "gen source"}
 	genHeaderDepTag       = dependencyTag{name: "gen header"}
@@ -571,7 +588,7 @@
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
 	dataLibDepTag         = dependencyTag{name: "data lib"}
-	runtimeDepTag         = dependencyTag{name: "runtime lib"}
+	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
 )
@@ -598,8 +615,7 @@
 }
 
 func IsRuntimeDepTag(depTag blueprint.DependencyTag) bool {
-	ccDepTag, ok := depTag.(dependencyTag)
-	return ok && ccDepTag == runtimeDepTag
+	return depTag == runtimeDepTag
 }
 
 func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index f616cf3..7c60686 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3941,3 +3941,98 @@
 	}
 
 }
+
+func TestInstallSharedLibs(t *testing.T) {
+	bp := `
+		cc_binary {
+			name: "bin",
+			host_supported: true,
+			shared_libs: ["libshared"],
+			runtime_libs: ["libruntime"],
+			srcs: [":gen"],
+		}
+
+		cc_library_shared {
+			name: "libshared",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+		}
+
+		cc_library_shared {
+			name: "libtransitive",
+			host_supported: true,
+		}
+
+		cc_library_shared {
+			name: "libruntime",
+			host_supported: true,
+		}
+
+		cc_binary_host {
+			name: "tool",
+			srcs: ["foo.cpp"],
+		}
+
+		genrule {
+			name: "gen",
+			tools: ["tool"],
+			out: ["gen.cpp"],
+			cmd: "$(location tool) $(out)",
+		}
+	`
+
+	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	ctx := testCcWithConfig(t, config)
+
+	hostBin := ctx.ModuleForTests("bin", config.BuildOSTarget.String()).Description("install")
+	hostShared := ctx.ModuleForTests("libshared", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostRuntime := ctx.ModuleForTests("libruntime", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTransitive := ctx.ModuleForTests("libtransitive", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTool := ctx.ModuleForTests("tool", config.BuildOSTarget.String()).Description("install")
+
+	if g, w := hostBin.Implicits.Strings(), hostShared.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostShared.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostRuntime.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostTool.Output.String(); android.InList(w, g) {
+		t.Errorf("expected no host bin dependency %q, got %q", w, g)
+	}
+
+	deviceBin := ctx.ModuleForTests("bin", "android_arm64_armv8-a").Description("install")
+	deviceShared := ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared").Description("install")
+	deviceTransitive := ctx.ModuleForTests("libtransitive", "android_arm64_armv8-a_shared").Description("install")
+	deviceRuntime := ctx.ModuleForTests("libruntime", "android_arm64_armv8-a_shared").Description("install")
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceShared.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceShared.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceRuntime.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), hostTool.Output.String(); android.InList(w, g) {
+		t.Errorf("expected no device bin dependency %q, got %q", w, g)
+	}
+
+}
diff --git a/cc/testing.go b/cc/testing.go
index 5a311f4..a3235e9 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/genrule"
 )
 
 func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
@@ -24,6 +25,7 @@
 	RegisterBinaryBuildComponents(ctx)
 	RegisterLibraryBuildComponents(ctx)
 	RegisterLibraryHeadersBuildComponents(ctx)
+	genrule.RegisterGenruleBuildComponents(ctx)
 
 	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("llndk_library", LlndkLibraryFactory)
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 6d77812..77d6ee9 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -16,7 +16,7 @@
 
 import (
 	"fmt"
-	"path/filepath"
+	"strconv"
 	"strings"
 
 	"android/soong/android"
@@ -49,39 +49,39 @@
 
 const UnknownInstallLibraryPath = "error"
 
-const AnySdkVersion int = 9999 // should go last in class loader context
+// AnySdkVersion means that the class loader context is needed regardless of the targetSdkVersion
+// of the app. The numeric value affects the key order in the map and, as a result, the order of
+// arguments passed to construct_context.py (high value means that the unconditional context goes
+// last). We use the converntional "current" SDK level (10000), but any big number would do as well.
+const AnySdkVersion int = android.FutureApiLevelInt
 
-// LibraryPath contains paths to the library DEX jar on host and on device.
-type LibraryPath struct {
-	Host   android.Path
+// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
+// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
+// that it can be checked agains the run-time class loader context on device. If there is a mismatch
+// at runtime, AOT-compiled code is rejected.
+type ClassLoaderContext struct {
+	// The name of the library (same as the name of the module that contains it).
+	Name string
+
+	// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
+	Host android.Path
+
+	// On-device install path (used in dex2oat argument --stored-class-loader-context).
 	Device string
+
+	// Nested class loader subcontexts for dependencies.
+	Subcontexts []*ClassLoaderContext
 }
 
-// LibraryPaths is a map from library name to on-host and on-device paths to its DEX jar.
-type LibraryPaths map[string]*LibraryPath
+// ClassLoaderContextMap is a map from SDK version to a class loader context.
+// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
+// Other entries store conditional contexts that should be added for some apps that have
+// targetSdkVersion in the manifest lower than the key SDK version.
+type ClassLoaderContextMap map[int][]*ClassLoaderContext
 
-type classLoaderContext struct {
-	// Library names
-	Names []string
-
-	// The class loader context using paths in the build.
-	Host android.Paths
-
-	// The class loader context using paths as they will be on the device.
-	Target []string
-}
-
-// A map of class loader contexts for each SDK version.
-// A map entry for "any" version contains libraries that are unconditionally added to class loader
-// context. Map entries for existing versions contains libraries that were in the default classpath
-// until that API version, and should be added to class loader context if and only if the
-// targetSdkVersion in the manifest or APK is less than that API version.
-type classLoaderContextMap map[int]*classLoaderContext
-
-// Add a new library path to the map, unless a path for this library already exists.
-// If necessary, check that the build and install paths exist.
-func (libPaths LibraryPaths) addLibraryPath(ctx android.ModuleInstallPathContext, lib string,
-	hostPath, installPath android.Path, strict bool) error {
+// Add class loader context for the given library to the map entry for the given SDK version.
+func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
+	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
 
 	// If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
 	// not found. However, this is likely to result is disabling dexpreopt, as it won't be
@@ -89,263 +89,277 @@
 	strict = strict && !ctx.Config().AllowMissingDependencies()
 
 	if hostPath == nil && strict {
-		return fmt.Errorf("unknown build path to <uses-library> '%s'", lib)
+		return fmt.Errorf("unknown build path to <uses-library> \"%s\"", lib)
 	}
 
+	devicePath := UnknownInstallLibraryPath
 	if installPath == nil {
 		if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) {
 			// Assume that compatibility libraries are installed in /system/framework.
 			installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar")
 		} else if strict {
-			return fmt.Errorf("unknown install path to <uses-library> '%s'", lib)
-		}
-	}
-
-	// Add a library only if the build and install path to it is known.
-	if _, present := libPaths[lib]; !present {
-		var devicePath string
-		if installPath != nil {
-			devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
+			return fmt.Errorf("unknown install path to <uses-library> \"%s\"", lib)
 		} else {
 			// For some stub libraries the only known thing is the name of their implementation
 			// library, but the library itself is unavailable (missing or part of a prebuilt). In
 			// such cases we still need to add the library to <uses-library> tags in the manifest,
-			// but we cannot use if for dexpreopt.
-			devicePath = UnknownInstallLibraryPath
+			// but we cannot use it for dexpreopt.
 		}
-		libPaths[lib] = &LibraryPath{hostPath, devicePath}
 	}
+	if installPath != nil {
+		devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
+	}
+
+	// Nested class loader context shouldn't have conditional part (it is allowed only at the top level).
+	for ver, _ := range nestedClcMap {
+		if ver != AnySdkVersion {
+			clcStr, _ := ComputeClassLoaderContext(nestedClcMap)
+			return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr)
+		}
+	}
+	subcontexts := nestedClcMap[AnySdkVersion]
+
+	// If the library with this name is already present as one of the unconditional top-level
+	// components, do not re-add it.
+	for _, clc := range clcMap[sdkVer] {
+		if clc.Name == lib {
+			return nil
+		}
+	}
+
+	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
+		Name:        lib,
+		Host:        hostPath,
+		Device:      devicePath,
+		Subcontexts: subcontexts,
+	})
 	return nil
 }
 
-// Wrapper around addLibraryPath that does error reporting.
-func (libPaths LibraryPaths) addLibraryPathOrReportError(ctx android.ModuleInstallPathContext, lib string,
-	hostPath, installPath android.Path, strict bool) {
+// Wrapper around addContext that reports errors.
+func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
+	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
 
-	err := libPaths.addLibraryPath(ctx, lib, hostPath, installPath, strict)
+	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
 	if err != nil {
+		ctx.ModuleErrorf(err.Error())
 		android.ReportPathErrorf(ctx, err.Error())
 	}
 }
 
-// Add a new library path to the map. Enforce checks that the library paths exist.
-func (libPaths LibraryPaths) AddLibraryPath(ctx android.ModuleInstallPathContext, lib string, hostPath, installPath android.Path) {
-	libPaths.addLibraryPathOrReportError(ctx, lib, hostPath, installPath, true)
+// Add class loader context. Fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
+	hostPath, installPath android.Path) {
+
+	clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, hostPath, installPath, true, nil)
 }
 
-// Add a new library path to the map, if the library exists (name is not nil).
-// Don't enforce checks that the library paths exist. Some libraries may be missing from the build,
-// but their names still need to be added to <uses-library> tags in the manifest.
-func (libPaths LibraryPaths) MaybeAddLibraryPath(ctx android.ModuleInstallPathContext, lib *string, hostPath, installPath android.Path) {
+// Add class loader context if the library exists. Don't fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
+	hostPath, installPath android.Path) {
+
 	if lib != nil {
-		libPaths.addLibraryPathOrReportError(ctx, *lib, hostPath, installPath, false)
+		clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, hostPath, installPath, false, nil)
 	}
 }
 
-// Add library paths from the second map to the first map (do not override existing entries).
-func (libPaths LibraryPaths) AddLibraryPaths(otherPaths LibraryPaths) {
-	for lib, path := range otherPaths {
-		if _, present := libPaths[lib]; !present {
-			libPaths[lib] = path
+// Add class loader context for the given SDK version. Fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
+	lib string, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
+
+	clcMap.addContextOrReportError(ctx, sdkVer, lib, hostPath, installPath, true, nestedClcMap)
+}
+
+// Merge the other class loader context map into this one, do not override existing entries.
+// The implicitRootLib parameter is the name of the library for which the other class loader
+// context map was constructed. If the implicitRootLib is itself a <uses-library>, it should be
+// already present in the class loader context (with the other context as its subcontext) -- in
+// that case do not re-add the other context. Otherwise add the other context at the top-level.
+func (clcMap ClassLoaderContextMap) AddContextMap(otherClcMap ClassLoaderContextMap, implicitRootLib string) {
+	if otherClcMap == nil {
+		return
+	}
+
+	// If the implicit root of the merged map is already present as one of top-level subtrees, do
+	// not merge it second time.
+	for _, clc := range clcMap[AnySdkVersion] {
+		if clc.Name == implicitRootLib {
+			return
 		}
 	}
-}
 
-func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
-	if _, ok := m[sdkVer]; !ok {
-		m[sdkVer] = &classLoaderContext{}
-	}
-	return m[sdkVer]
-}
-
-func (clc *classLoaderContext) addLib(lib string, hostPath android.Path, targetPath string) {
-	clc.Names = append(clc.Names, lib)
-	clc.Host = append(clc.Host, hostPath)
-	clc.Target = append(clc.Target, targetPath)
-}
-
-func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, module *ModuleConfig,
-	libs ...string) (bool, error) {
-
-	clc := m.getValue(sdkVer)
-	for _, lib := range libs {
-		if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath {
-			clc.addLib(lib, p.Host, p.Device)
-		} else {
-			if sdkVer == AnySdkVersion {
-				// Fail the build if dexpreopt doesn't know paths to one of the <uses-library>
-				// dependencies. In the future we may need to relax this and just disable dexpreopt.
-				return false, fmt.Errorf("dexpreopt cannot find path for <uses-library> '%s'", lib)
-			} else {
-				// No error for compatibility libraries, as Soong doesn't know if they are needed
-				// (this depends on the targetSdkVersion in the manifest).
-				return false, nil
+	for sdkVer, otherClcs := range otherClcMap {
+		for _, otherClc := range otherClcs {
+			alreadyHave := false
+			for _, clc := range clcMap[sdkVer] {
+				if clc.Name == otherClc.Name {
+					alreadyHave = true
+					break
+				}
+			}
+			if !alreadyHave {
+				clcMap[sdkVer] = append(clcMap[sdkVer], otherClc)
 			}
 		}
 	}
-	return true, nil
 }
 
-func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
-	clc := m.getValue(sdkVer)
-	for _, lib := range libs {
-		clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar"))
+// List of libraries in the unconditional class loader context, excluding dependencies of shared libraries.
+func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
+	if clcMap != nil {
+		// compatibility libraries (those in conditional context) are not added to <uses-library> tags
+		ulibs = usesLibsRec(clcMap[AnySdkVersion])
+		ulibs = android.FirstUniqueStrings(ulibs)
 	}
+	return ulibs
 }
 
-func (m classLoaderContextMap) usesLibs() []string {
-	if clc, ok := m[AnySdkVersion]; ok {
-		return clc.Names
+func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
+	for _, clc := range clcs {
+		ulibs = append(ulibs, clc.Name)
+		ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
 	}
-	return nil
-}
-
-// genClassLoaderContext generates host and target class loader context to be passed to the dex2oat
-// command for the dexpreopted module. There are three possible cases:
-//
-// 1. System server jars. They have a special class loader context that includes other system
-//    server jars.
-//
-// 2. Library jars or APKs which have precise list of their <uses-library> libs. Their class loader
-//    context includes build and on-device paths to these libs. In some cases it may happen that
-//    the path to a <uses-library> is unknown (e.g. the dexpreopted module may depend on stubs
-//    library, whose implementation library is missing from the build altogether). In such case
-//    dexpreopting with the <uses-library> is impossible, and dexpreopting without it is pointless,
-//    as the runtime classpath won't match and the dexpreopted code will be discarded. Therefore in
-//    such cases the function returns nil, which disables dexpreopt.
-//
-// 3. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
-//    the unsafe &-classpath workaround that means empty class loader context and absence of runtime
-//    check that the class loader context provided by the PackageManager agrees with the stored
-//    class loader context recorded in the .odex file.
-//
-func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) (*classLoaderContextMap, error) {
-	classLoaderContexts := make(classLoaderContextMap)
-	systemServerJars := NonUpdatableSystemServerJars(ctx, global)
-
-	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
-		// System server jars should be dexpreopted together: class loader context of each jar
-		// should include all preceding jars on the system server classpath.
-		classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
-
-	} else if module.EnforceUsesLibraries {
-		// Unconditional class loader context.
-		usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
-		if ok, err := classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 28.
-		const httpLegacy = "org.apache.http.legacy"
-		if ok, err := classLoaderContexts.addLibs(ctx, 28, module, httpLegacy); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 29.
-		usesLibs29 := []string{
-			"android.hidl.base-V1.0-java",
-			"android.hidl.manager-V1.0-java",
-		}
-		if ok, err := classLoaderContexts.addLibs(ctx, 29, module, usesLibs29...); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 30.
-		if ok, err := classLoaderContexts.addLibs(ctx, 30, module, OptionalCompatUsesLibs30...); !ok {
-			return nil, err
-		}
-
-	} else {
-		// Pass special class loader context to skip the classpath and collision check.
-		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
-		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
-		// to the &.
-	}
-
-	fixConditionalClassLoaderContext(classLoaderContexts)
-
-	return &classLoaderContexts, nil
+	return ulibs
 }
 
 // Now that the full unconditional context is known, reconstruct conditional context.
 // Apply filters for individual libraries, mirroring what the PackageManager does when it
 // constructs class loader context on device.
 //
-// TODO(b/132357300):
-//   - remove android.hidl.manager and android.hidl.base unless the app is a system app.
+// TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps.
 //
-func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
-	usesLibs := clcMap.usesLibs()
+func fixClassLoaderContext(clcMap ClassLoaderContextMap) {
+	usesLibs := clcMap.UsesLibs()
 
-	for sdkVer, clc := range clcMap {
+	for sdkVer, clcs := range clcMap {
 		if sdkVer == AnySdkVersion {
 			continue
 		}
-		clcMap[sdkVer] = &classLoaderContext{}
-		for i, lib := range clc.Names {
-			if android.InList(lib, usesLibs) {
+		fixedClcs := []*ClassLoaderContext{}
+		for _, clc := range clcs {
+			if android.InList(clc.Name, usesLibs) {
 				// skip compatibility libraries that are already included in unconditional context
-			} else if lib == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
+			} else if clc.Name == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
 				// android.test.mock is only needed as a compatibility library (in conditional class
 				// loader context) if android.test.runner is used, otherwise skip it
 			} else {
-				clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i])
+				fixedClcs = append(fixedClcs, clc)
 			}
+			clcMap[sdkVer] = fixedClcs
 		}
 	}
 }
 
-// Return the class loader context as a string and a slice of build paths for all dependencies.
-func computeClassLoaderContext(ctx android.PathContext, clcMap classLoaderContextMap) (clcStr string, paths android.Paths) {
-	for _, ver := range android.SortedIntKeys(clcMap) {
-		clc := clcMap.getValue(ver)
-
-		clcLen := len(clc.Names)
-		if clcLen != len(clc.Host) || clcLen != len(clc.Target) {
-			android.ReportPathErrorf(ctx, "ill-formed class loader context")
+// Return true if all build/install library paths are valid (including recursive subcontexts),
+// otherwise return false. A build path is valid if it's not nil. An install path is valid if it's
+// not equal to a special "error" value.
+func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) {
+	for sdkVer, clcs := range clcMap {
+		if valid, err := validateClassLoaderContextRec(sdkVer, clcs); !valid || err != nil {
+			return valid, err
 		}
+	}
+	return true, nil
+}
 
-		var hostClc, targetClc []string
-		var hostPaths android.Paths
-
-		for i := 0; i < clcLen; i++ {
-			hostStr := "PCL[" + clc.Host[i].String() + "]"
-			targetStr := "PCL[" + clc.Target[i] + "]"
-
-			hostClc = append(hostClc, hostStr)
-			targetClc = append(targetClc, targetStr)
-			hostPaths = append(hostPaths, clc.Host[i])
+func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
+	for _, clc := range clcs {
+		if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
+			if sdkVer == AnySdkVersion {
+				// Return error if dexpreopt doesn't know paths to one of the <uses-library>
+				// dependencies. In the future we may need to relax this and just disable dexpreopt.
+				return false, fmt.Errorf("invalid path for <uses-library> \"%s\"", clc.Name)
+			} else {
+				// No error for compatibility libraries, as Soong doesn't know if they are needed
+				// (this depends on the targetSdkVersion in the manifest), but the CLC is invalid.
+				return false, nil
+			}
 		}
+		if valid, err := validateClassLoaderContextRec(sdkVer, clc.Subcontexts); !valid || err != nil {
+			return valid, err
+		}
+	}
+	return true, nil
+}
 
+// Return the class loader context as a string, and a slice of build paths for all dependencies.
+// Perform a depth-first preorder traversal of the class loader context tree for each SDK version.
+// Return the resulting string and a slice of on-host build paths to all library dependencies.
+func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) {
+	for _, sdkVer := range android.SortedIntKeys(clcMap) { // determinisitc traversal order
+		sdkVerStr := fmt.Sprintf("%d", sdkVer)
+		if sdkVer == AnySdkVersion {
+			sdkVerStr = "any" // a special keyword that means any SDK version
+		}
+		hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer])
 		if hostPaths != nil {
-			sdkVerStr := fmt.Sprintf("%d", ver)
-			if ver == AnySdkVersion {
-				sdkVerStr = "any" // a special keyword that means any SDK version
-			}
-			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, strings.Join(hostClc, "#"))
-			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, strings.Join(targetClc, "#"))
-			paths = append(paths, hostPaths...)
+			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc)
+			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc)
 		}
+		paths = append(paths, hostPaths...)
 	}
-
-	return clcStr, paths
+	return clcStr, android.FirstUniquePaths(paths)
 }
 
+func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
+	var paths android.Paths
+	var clcsHost, clcsTarget []string
+
+	for _, clc := range clcs {
+		subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts)
+		if subPaths != nil {
+			subClcHost = "{" + subClcHost + "}"
+			subClcTarget = "{" + subClcTarget + "}"
+		}
+
+		clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost)
+		clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget)
+
+		paths = append(paths, clc.Host)
+		paths = append(paths, subPaths...)
+	}
+
+	clcHost := strings.Join(clcsHost, "#")
+	clcTarget := strings.Join(clcsTarget, "#")
+
+	return clcHost, clcTarget, paths
+}
+
+// Paths to a <uses-library> on host and on device.
 type jsonLibraryPath struct {
 	Host   string
 	Device string
 }
 
-type jsonLibraryPaths map[string]jsonLibraryPath
+// Class loader contexts that come from Make (via JSON dexpreopt.config) files have simpler
+// structure than Soong class loader contexts: they are flat maps from a <uses-library> name to its
+// on-host and on-device paths. There are no nested subcontexts. It is a limitation of the current
+// Make implementation.
+type jsonClassLoaderContext map[string]jsonLibraryPath
 
-// convert JSON map of library paths to LibraryPaths
-func constructLibraryPaths(ctx android.PathContext, paths jsonLibraryPaths) LibraryPaths {
-	m := LibraryPaths{}
-	for lib, path := range paths {
-		m[lib] = &LibraryPath{
-			constructPath(ctx, path.Host),
-			path.Device,
+// A map from SDK version (represented with a JSON string) to JSON class loader context.
+type jsonClassLoaderContextMap map[string]jsonClassLoaderContext
+
+// Convert JSON class loader context map to ClassLoaderContextMap.
+func fromJsonClassLoaderContext(ctx android.PathContext, jClcMap jsonClassLoaderContextMap) ClassLoaderContextMap {
+	clcMap := make(ClassLoaderContextMap)
+	for sdkVerStr, clc := range jClcMap {
+		sdkVer, ok := strconv.Atoi(sdkVerStr)
+		if ok != nil {
+			if sdkVerStr == "any" {
+				sdkVer = AnySdkVersion
+			} else {
+				android.ReportPathErrorf(ctx, "failed to parse SDK version in dexpreopt.config: '%s'", sdkVerStr)
+			}
+		}
+		for lib, path := range clc {
+			clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
+				Name:        lib,
+				Host:        constructPath(ctx, path.Host),
+				Device:      path.Device,
+				Subcontexts: nil,
+			})
 		}
 	}
-	return m
+	return clcMap
 }
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 51c1a0a..e0a75bf 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -37,91 +37,79 @@
 	//     ├── b
 	//     ├── c
 	//     ├── d
-	//     ├── a2
-	//     ├── b2
-	//     ├── c2
-	//     ├── a1
-	//     ├── b1
+	//     │   ├── a2
+	//     │   ├── b2
+	//     │   └── c2
+	//     │       ├── a1
+	//     │       └── b1
 	//     ├── f
 	//     ├── a3
 	//     └── b3
 	//
 	ctx := testContext()
 
-	lp := make(LibraryPaths)
+	m := make(ClassLoaderContextMap)
 
-	lp.AddLibraryPath(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
-	lp.AddLibraryPath(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
+	m.AddContext(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
+	m.AddContext(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
 
 	// "Maybe" variant in the good case: add as usual.
 	c := "c"
-	lp.MaybeAddLibraryPath(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
+	m.MaybeAddContext(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
 
 	// "Maybe" variant in the bad case: don't add library with unknown name, keep going.
-	lp.MaybeAddLibraryPath(ctx, nil, nil, nil)
+	m.MaybeAddContext(ctx, nil, nil, nil)
 
 	// Add some libraries with nested subcontexts.
 
-	lp1 := make(LibraryPaths)
-	lp1.AddLibraryPath(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
-	lp1.AddLibraryPath(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
+	m1 := make(ClassLoaderContextMap)
+	m1.AddContext(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
+	m1.AddContext(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
 
-	lp2 := make(LibraryPaths)
-	lp2.AddLibraryPath(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
-	lp2.AddLibraryPath(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
-	lp2.AddLibraryPath(ctx, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"))
-	lp2.AddLibraryPaths(lp1)
+	m2 := make(ClassLoaderContextMap)
+	m2.AddContext(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
+	m2.AddContext(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
+	m2.AddContextForSdk(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
 
-	lp.AddLibraryPath(ctx, "d", buildPath(ctx, "d"), installPath(ctx, "d"))
-	lp.AddLibraryPaths(lp2)
+	m3 := make(ClassLoaderContextMap)
+	m3.AddContext(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
+	m3.AddContext(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
 
-	lp3 := make(LibraryPaths)
-	lp3.AddLibraryPath(ctx, "f", buildPath(ctx, "f"), installPath(ctx, "f"))
-	lp3.AddLibraryPath(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
-	lp3.AddLibraryPath(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
-	lp.AddLibraryPaths(lp3)
+	m.AddContextForSdk(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	// When the same library is both in conditional and unconditional context, it should be removed
+	// from conditional context.
+	m.AddContextForSdk(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContextForSdk(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+
+	// Merge map with implicit root library that is among toplevel contexts => does nothing.
+	m.AddContextMap(m1, "c")
+	// Merge map with implicit root library that is not among toplevel contexts => all subcontexts
+	// of the other map are added as toplevel contexts.
+	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	lp.AddLibraryPath(ctx, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil)
-	lp.AddLibraryPath(ctx, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil)
-	lp.AddLibraryPath(ctx, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil)
-
-	module := testSystemModuleConfig(ctx, "test")
-	module.LibraryPaths = lp
-
-	m := make(classLoaderContextMap)
-	valid := true
-
-	ok, err := m.addLibs(ctx, AnySdkVersion, module, "a", "b", "c", "d", "a2", "b2", "c2", "a1", "b1", "f", "a3", "b3")
-	valid = valid && ok && err == nil
-
-	// Add compatibility libraries to conditional CLC for SDK level 29.
-	ok, err = m.addLibs(ctx, 29, module, AndroidHidlManager, AndroidHidlBase)
-	valid = valid && ok && err == nil
+	m.AddContextForSdk(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContextForSdk(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
 
 	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
 	// needed as a compatibility library if "android.test.runner" is in CLC as well.
-	ok, err = m.addLibs(ctx, 30, module, AndroidTestMock)
-	valid = valid && ok && err == nil
+	m.AddContextForSdk(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
 
-	// When the same library is both in conditional and unconditional context, it should be removed
-	// from conditional context.
-	ok, err = m.addLibs(ctx, 42, module, "f")
-	valid = valid && ok && err == nil
+	valid, validationError := validateClassLoaderContext(m)
 
-	fixConditionalClassLoaderContext(m)
+	fixClassLoaderContext(m)
 
 	var haveStr string
 	var havePaths android.Paths
 	var haveUsesLibs []string
-	if valid {
-		haveStr, havePaths = computeClassLoaderContext(ctx, m)
-		haveUsesLibs = m.usesLibs()
+	if valid && validationError == nil {
+		haveStr, havePaths = ComputeClassLoaderContext(m)
+		haveUsesLibs = m.UsesLibs()
 	}
 
 	// Test that validation is successful (all paths are known).
 	t.Run("validate", func(t *testing.T) {
-		if !valid {
+		if !(valid && validationError == nil) {
 			t.Errorf("invalid class loader context")
 		}
 	})
@@ -135,14 +123,14 @@
 			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
 			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
 			" --host-context-for-sdk any " +
-			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]#" +
-			"PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]#" +
-			"PCL[out/a1.jar]#PCL[out/b1.jar]#" +
+			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]" +
+			"{PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]" +
+			"{PCL[out/a1.jar]#PCL[out/b1.jar]}}#" +
 			"PCL[out/f.jar]#PCL[out/a3.jar]#PCL[out/b3.jar]" +
 			" --target-context-for-sdk any " +
-			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]#" +
-			"PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]#" +
-			"PCL[/system/a1.jar]#PCL[/system/b1.jar]#" +
+			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
+			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
+			"{PCL[/system/a1.jar]#PCL[/system/b1.jar]}}#" +
 			"PCL[/system/f.jar]#PCL[/system/a3.jar]#PCL[/system/b3.jar]"
 		if wantStr != haveStr {
 			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
@@ -175,32 +163,50 @@
 // Test that an unexpected unknown build path causes immediate error.
 func TestCLCUnknownBuildPath(t *testing.T) {
 	ctx := testContext()
-	lp := make(LibraryPaths)
-	err := lp.addLibraryPath(ctx, "a", nil, nil, true)
-	checkError(t, err, "unknown build path to <uses-library> 'a'")
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "a", nil, nil, true, nil)
+	checkError(t, err, "unknown build path to <uses-library> \"a\"")
 }
 
 // Test that an unexpected unknown install path causes immediate error.
 func TestCLCUnknownInstallPath(t *testing.T) {
 	ctx := testContext()
-	lp := make(LibraryPaths)
-	err := lp.addLibraryPath(ctx, "a", buildPath(ctx, "a"), nil, true)
-	checkError(t, err, "unknown install path to <uses-library> 'a'")
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, true, nil)
+	checkError(t, err, "unknown install path to <uses-library> \"a\"")
 }
 
 func TestCLCMaybeAdd(t *testing.T) {
 	ctx := testContext()
 
-	lp := make(LibraryPaths)
+	m := make(ClassLoaderContextMap)
 	a := "a"
-	lp.MaybeAddLibraryPath(ctx, &a, nil, nil)
+	m.MaybeAddContext(ctx, &a, nil, nil)
 
-	module := testSystemModuleConfig(ctx, "test")
-	module.LibraryPaths = lp
+	// The library should be added to <uses-library> tags by the manifest_fixer.
+	t.Run("maybe add", func(t *testing.T) {
+		haveUsesLibs := m.UsesLibs()
+		wantUsesLibs := []string{"a"}
+		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
+			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
+		}
+	})
 
-	m := make(classLoaderContextMap)
-	_, err := m.addLibs(ctx, AnySdkVersion, module, "a")
-	checkError(t, err, "dexpreopt cannot find path for <uses-library> 'a'")
+	// But class loader context in such cases should raise an error on validation.
+	t.Run("validate", func(t *testing.T) {
+		_, err := validateClassLoaderContext(m)
+		checkError(t, err, "invalid path for <uses-library> \"a\"")
+	})
+}
+
+// An attempt to add conditional nested subcontext should fail.
+func TestCLCNestedConditional(t *testing.T) {
+	ctx := testContext()
+	m1 := make(ClassLoaderContextMap)
+	m1.AddContextForSdk(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
+	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
 func checkError(t *testing.T, have error, want string) {
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index f77246e..f52ecb4 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -114,10 +114,8 @@
 	ProfileIsTextListing bool
 	ProfileBootListing   android.OptionalPath
 
-	EnforceUsesLibraries  bool
-	OptionalUsesLibraries []string
-	UsesLibraries         []string
-	LibraryPaths          LibraryPaths
+	EnforceUsesLibraries bool
+	ClassLoaderContexts  ClassLoaderContextMap
 
 	Archs                   []android.ArchType
 	DexPreoptImages         []android.Path
@@ -265,7 +263,7 @@
 		DexPath                     string
 		ManifestPath                string
 		ProfileClassListing         string
-		LibraryPaths                jsonLibraryPaths
+		ClassLoaderContexts         jsonClassLoaderContextMap
 		DexPreoptImages             []string
 		DexPreoptImageLocations     []string
 		PreoptBootClassPathDexFiles []string
@@ -283,7 +281,7 @@
 	config.ModuleConfig.DexPath = constructPath(ctx, config.DexPath)
 	config.ModuleConfig.ManifestPath = constructPath(ctx, config.ManifestPath)
 	config.ModuleConfig.ProfileClassListing = android.OptionalPathForPath(constructPath(ctx, config.ProfileClassListing))
-	config.ModuleConfig.LibraryPaths = constructLibraryPaths(ctx, config.LibraryPaths)
+	config.ModuleConfig.ClassLoaderContexts = fromJsonClassLoaderContext(ctx, config.ClassLoaderContexts)
 	config.ModuleConfig.DexPreoptImages = constructPaths(ctx, config.DexPreoptImages)
 	config.ModuleConfig.DexPreoptImageLocations = config.DexPreoptImageLocations
 	config.ModuleConfig.PreoptBootClassPathDexFiles = constructPaths(ctx, config.PreoptBootClassPathDexFiles)
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index a07f1fa..65380fe 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -81,16 +81,18 @@
 	}
 
 	if !dexpreoptDisabled(ctx, global, module) {
-		if clc, err := genClassLoaderContext(ctx, global, module); err != nil {
+		if valid, err := validateClassLoaderContext(module.ClassLoaderContexts); err != nil {
 			android.ReportPathErrorf(ctx, err.Error())
-		} else if clc != nil {
+		} else if valid {
+			fixClassLoaderContext(module.ClassLoaderContexts)
+
 			appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) &&
 				!module.NoCreateAppImage
 
 			generateDM := shouldGenerateDM(module, global)
 
 			for archIdx, _ := range module.Archs {
-				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, *clc, profile, appImage, generateDM)
+				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
 			}
 		}
 	}
@@ -197,8 +199,8 @@
 }
 
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
-	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap,
-	profile android.WritablePath, appImage bool, generateDM bool) {
+	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
+	appImage bool, generateDM bool) {
 
 	arch := module.Archs[archIdx]
 
@@ -235,6 +237,16 @@
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 
 	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
+		// System server jars should be dexpreopted together: class loader context of each jar
+		// should include all preceding jars on the system server classpath.
+
+		var clcHost android.Paths
+		var clcTarget []string
+		for _, lib := range systemServerJars[:jarIndex] {
+			clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib))
+			clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar"))
+		}
+
 		// Copy the system server jar to a predefined location where dex2oat will find it.
 		dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
 		rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
@@ -242,11 +254,11 @@
 
 		checkSystemServerOrder(ctx, jarIndex)
 
-		clc := classLoaderContexts[AnySdkVersion]
 		rule.Command().
-			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clc.Host.Strings(), ":") + "]").
-			Implicits(clc.Host).
-			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clc.Target, ":") + "]")
+			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clcHost.Strings(), ":") + "]").
+			Implicits(clcHost).
+			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]")
+
 	} else if module.EnforceUsesLibraries {
 		// Generate command that saves target SDK version in a shell variable.
 		if module.ManifestPath != nil {
@@ -266,13 +278,15 @@
 		}
 
 		// Generate command that saves host and target class loader context in shell variables.
-		clc, paths := computeClassLoaderContext(ctx, classLoaderContexts)
+		clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts)
 		cmd := rule.Command().
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`).
 			Text(clc).Implicits(paths)
 		cmd.Text(`)"`)
+
 	} else {
+		// Other libraries or APKs for which the exact <uses-library> list is unknown.
 		// Pass special class loader context to skip the classpath and collision check.
 		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
 		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index ec31549..feabd70 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -44,9 +44,7 @@
 		ProfileClassListing:             android.OptionalPath{},
 		ProfileIsTextListing:            false,
 		EnforceUsesLibraries:            false,
-		OptionalUsesLibraries:           nil,
-		UsesLibraries:                   nil,
-		LibraryPaths:                    nil,
+		ClassLoaderContexts:             nil,
 		Archs:                           []android.ArchType{android.Arm},
 		DexPreoptImages:                 android.Paths{android.PathForTesting("system/framework/arm/boot.art")},
 		DexPreoptImagesDeps:             []android.OutputPaths{android.OutputPaths{}},
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 53b9dbe..3067846 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -31,10 +31,10 @@
 )
 
 func init() {
-	registerGenruleBuildComponents(android.InitRegistrationContext)
+	RegisterGenruleBuildComponents(android.InitRegistrationContext)
 }
 
-func registerGenruleBuildComponents(ctx android.RegistrationContext) {
+func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
 
 	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index c19078f..c3c0b97 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -57,7 +57,7 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("tool", toolFactory)
 
-	registerGenruleBuildComponents(ctx)
+	RegisterGenruleBuildComponents(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.Register()
diff --git a/java/aar.go b/java/aar.go
index 157d677..7c3840b 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -109,7 +109,6 @@
 	useEmbeddedNativeLibs   bool
 	useEmbeddedDex          bool
 	usesNonSdkApis          bool
-	sdkLibraries            dexpreopt.LibraryPaths
 	hasNoCode               bool
 	LoggingParent           string
 	resourceFiles           android.Paths
@@ -259,12 +258,11 @@
 		CommandDeps: []string{"${config.Zip2ZipCmd}"},
 	})
 
-func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) {
+func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext,
+	sdkLibraries dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
 
-	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags, sdkLibraries :=
-		aaptLibs(ctx, sdkContext)
-
-	a.sdkLibraries = sdkLibraries
+	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
+		aaptLibs(ctx, sdkContext, sdkLibraries)
 
 	// App manifest file
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
@@ -391,29 +389,31 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, transitiveStaticLibManifests android.Paths,
-	staticRRODirs []rroDir, assets, deps android.Paths, flags []string, sdkLibraries dexpreopt.LibraryPaths) {
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, sdkLibraries dexpreopt.ClassLoaderContextMap) (
+	transitiveStaticLibs, transitiveStaticLibManifests android.Paths, staticRRODirs []rroDir, assets, deps android.Paths, flags []string) {
 
 	var sharedLibs android.Paths
 
+	if sdkLibraries == nil {
+		// Not all callers need to compute class loader context, those who don't just pass nil.
+		// Create a temporary class loader context here (it will be computed, but not used).
+		sdkLibraries = make(dexpreopt.ClassLoaderContextMap)
+	}
+
 	sdkDep := decodeSdkDep(ctx, sdkContext)
 	if sdkDep.useFiles {
 		sharedLibs = append(sharedLibs, sdkDep.jars...)
 	}
 
-	sdkLibraries = make(dexpreopt.LibraryPaths)
-
 	ctx.VisitDirectDeps(func(module android.Module) {
+		depName := ctx.OtherModuleName(module)
+
 		var exportPackage android.Path
 		aarDep, _ := module.(AndroidLibraryDependency)
 		if aarDep != nil {
 			exportPackage = aarDep.ExportPackage()
 		}
 
-		if dep, ok := module.(Dependency); ok {
-			sdkLibraries.AddLibraryPaths(dep.ExportedSdkLibs())
-		}
-
 		switch ctx.OtherModuleDependencyTag(module) {
 		case instrumentationForTag:
 			// Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2.
@@ -426,7 +426,7 @@
 			// (including the java_sdk_library) itself then append any implicit sdk library
 			// names to the list of sdk libraries to be added to the manifest.
 			if component, ok := module.(SdkLibraryComponentDependency); ok {
-				sdkLibraries.MaybeAddLibraryPath(ctx, component.OptionalImplicitSdkLibrary(),
+				sdkLibraries.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
 					component.DexJarBuildPath(), component.DexJarInstallPath())
 			}
 
@@ -439,7 +439,7 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				sdkLibraries.AddLibraryPaths(aarDep.ExportedSdkLibs())
+				sdkLibraries.AddContextMap(aarDep.ExportedSdkLibs(), depName)
 				if aarDep.ExportedAssets().Valid() {
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
@@ -457,6 +457,12 @@
 				}
 			}
 		}
+
+		// Add nested dependencies after processing the direct dependency: if it is a <uses-library>,
+		// nested context is added as its subcontext, and should not be re-added at the top-level.
+		if dep, ok := module.(Dependency); ok {
+			sdkLibraries.AddContextMap(dep.ExportedSdkLibs(), depName)
+		}
 	})
 
 	deps = append(deps, sharedLibs...)
@@ -473,7 +479,7 @@
 	transitiveStaticLibs = android.FirstUniquePaths(transitiveStaticLibs)
 	transitiveStaticLibManifests = android.FirstUniquePaths(transitiveStaticLibManifests)
 
-	return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags, sdkLibraries
+	return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags
 }
 
 type AndroidLibrary struct {
@@ -508,8 +514,8 @@
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
-	a.aapt.buildActions(ctx, sdkContext(a))
-	a.exportedSdkLibs = a.aapt.sdkLibraries
+	a.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
+	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -781,12 +787,11 @@
 	linkFlags = append(linkFlags, "--manifest "+a.manifest.String())
 	linkDeps = append(linkDeps, a.manifest)
 
-	transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags, sdkLibraries :=
-		aaptLibs(ctx, sdkContext(a))
+	transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags :=
+		aaptLibs(ctx, sdkContext(a), nil)
 
 	_ = staticLibManifests
 	_ = staticRRODirs
-	_ = sdkLibraries
 
 	linkDeps = append(linkDeps, libDeps...)
 	linkFlags = append(linkFlags, libFlags...)
@@ -827,7 +832,7 @@
 	return nil
 }
 
-func (a *AARImport) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (a *AARImport) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 62cd112..6b39c35 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -43,8 +43,9 @@
 	"args", "libs")
 
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
-func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext, sdkLibraries dexpreopt.LibraryPaths,
-	isLibrary, useEmbeddedNativeLibs, usesNonSdkApis, useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
+func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
+	sdkLibraries dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
+	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
 
 	var args []string
 	if isLibrary {
@@ -70,7 +71,7 @@
 		args = append(args, "--use-embedded-dex")
 	}
 
-	for _, usesLib := range android.SortedStringKeys(sdkLibraries) {
+	for _, usesLib := range sdkLibraries.UsesLibs() {
 		if inList(usesLib, dexpreopt.OptionalCompatUsesLibs) {
 			args = append(args, "--optional-uses-library", usesLib)
 		} else {
diff --git a/java/androidmk.go b/java/androidmk.go
index e1a661f..c606245 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -115,7 +115,7 @@
 						entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 					}
 
-					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", android.SortedStringKeys(library.exportedSdkLibs)...)
+					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.exportedSdkLibs.UsesLibs()...)
 
 					if len(library.additionalCheckedModules) != 0 {
 						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
diff --git a/java/app.go b/java/app.go
index c24e0c5..9ff413c 100755
--- a/java/app.go
+++ b/java/app.go
@@ -565,9 +565,8 @@
 	aaptLinkFlags = append(aaptLinkFlags, a.additionalAaptFlags...)
 
 	a.aapt.splitNames = a.appProperties.Package_splits
-	a.aapt.sdkLibraries = a.exportedSdkLibs
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, sdkContext(a), aaptLinkFlags...)
+	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -601,7 +600,7 @@
 	return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
 }
 
-func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext, sdkLibs dexpreopt.LibraryPaths) android.Path {
+func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
 	a.dexpreopter.installPath = a.installPath(ctx)
 	if a.dexProperties.Uncompress_dex == nil {
 		// If the value was not force-set by the user, use reasonable default based on the module.
@@ -609,12 +608,8 @@
 	}
 	a.dexpreopter.uncompressedDex = *a.dexProperties.Uncompress_dex
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs
-	a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx)
-	a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx)
-	a.dexpreopter.libraryPaths.AddLibraryPaths(sdkLibs)
+	a.dexpreopter.classLoaderContexts = a.exportedSdkLibs
 	a.dexpreopter.manifestFile = a.mergedManifestFile
-	a.exportedSdkLibs = make(dexpreopt.LibraryPaths)
 
 	if ctx.ModuleName() != "framework-res" {
 		a.Module.compile(ctx, a.aaptSrcJar)
@@ -784,6 +779,8 @@
 		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
 	}
 
+	a.exportedSdkLibs = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
 
@@ -791,7 +788,7 @@
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
 	// Add implicit SDK libraries to <uses-library> list.
-	for _, usesLib := range android.SortedStringKeys(a.aapt.sdkLibraries) {
+	for _, usesLib := range a.exportedSdkLibs.UsesLibs() {
 		a.usesLibrary.addLib(usesLib, inList(usesLib, dexpreopt.OptionalCompatUsesLibs))
 	}
 
@@ -808,7 +805,7 @@
 	a.linter.resources = a.aapt.resourceFiles
 	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
-	dexJarFile := a.dexBuildActions(ctx, a.aapt.sdkLibraries)
+	dexJarFile := a.dexBuildActions(ctx)
 
 	jniLibs, certificateDeps := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
 	jniJarFile := a.jniBuildActions(jniLibs, ctx)
@@ -1540,9 +1537,7 @@
 	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
 
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs
-	a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx)
-	a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx)
+	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	dexOutput := a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
 	if a.dexpreopter.uncompressedDex {
@@ -1852,7 +1847,7 @@
 		aaptLinkFlags = append(aaptLinkFlags,
 			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
 	}
-	r.aapt.buildActions(ctx, r, aaptLinkFlags...)
+	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
 
 	// Sign the built package
 	_, certificates := collectAppDeps(ctx, r, false, false)
@@ -1976,17 +1971,18 @@
 	return optionalUsesLibs
 }
 
-// usesLibraryPaths returns a map of module names of shared library dependencies to the paths
+// Returns a map of module names of shared library dependencies to the paths
 // to their dex jars on host and on device.
-func (u *usesLibrary) usesLibraryPaths(ctx android.ModuleContext) dexpreopt.LibraryPaths {
-	usesLibPaths := make(dexpreopt.LibraryPaths)
+func (u *usesLibrary) classLoaderContextForUsesLibDeps(ctx android.ModuleContext) dexpreopt.ClassLoaderContextMap {
+	clcMap := make(dexpreopt.ClassLoaderContextMap)
 
 	if !ctx.Config().UnbundledBuild() {
 		ctx.VisitDirectDeps(func(m android.Module) {
-			if _, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
+			if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(Dependency); ok {
-					usesLibPaths.AddLibraryPath(ctx, dep, lib.DexJarBuildPath(), lib.DexJarInstallPath())
+					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
+						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ExportedSdkLibs())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
 				} else {
@@ -1996,7 +1992,7 @@
 		})
 	}
 
-	return usesLibPaths
+	return clcMap
 }
 
 // enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs
diff --git a/java/app_test.go b/java/app_test.go
index 446050d..6429ab8 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2815,11 +2815,11 @@
 	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
 	cmd = app.Rule("dexpreopt").RuleParams.Command
 	w := `--target-context-for-sdk any ` +
-		`PCL[/system/framework/foo.jar]#` +
-		`PCL[/system/framework/quuz.jar]#` +
 		`PCL[/system/framework/qux.jar]#` +
-		`PCL[/system/framework/runtime-library.jar]#` +
-		`PCL[/system/framework/bar.jar]`
+		`PCL[/system/framework/quuz.jar]#` +
+		`PCL[/system/framework/foo.jar]#` +
+		`PCL[/system/framework/bar.jar]#` +
+		`PCL[/system/framework/runtime-library.jar]`
 	if !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 40a2280..d8b617e 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -163,7 +163,7 @@
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (d *DeviceHostConverter) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 20dbc66..a21fb76 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -33,11 +33,9 @@
 	isTest              bool
 	isPresignedPrebuilt bool
 
-	manifestFile     android.Path
-	usesLibs         []string
-	optionalUsesLibs []string
-	enforceUsesLibs  bool
-	libraryPaths     dexpreopt.LibraryPaths
+	manifestFile        android.Path
+	enforceUsesLibs     bool
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	builtInstalled string
 }
@@ -193,10 +191,8 @@
 		ProfileIsTextListing: profileIsTextListing,
 		ProfileBootListing:   profileBootListing,
 
-		EnforceUsesLibraries:  d.enforceUsesLibs,
-		OptionalUsesLibraries: d.optionalUsesLibs,
-		UsesLibraries:         d.usesLibs,
-		LibraryPaths:          d.libraryPaths,
+		EnforceUsesLibraries: d.enforceUsesLibs,
+		ClassLoaderContexts:  d.classLoaderContexts,
 
 		Archs:                   archs,
 		DexPreoptImages:         images,
diff --git a/java/java.go b/java/java.go
index 9f09051..d049685 100644
--- a/java/java.go
+++ b/java/java.go
@@ -416,8 +416,8 @@
 	// manifest file to use instead of properties.Manifest
 	overrideManifest android.OptionalPath
 
-	// map of SDK libs exported by this java module to their build and install paths
-	exportedSdkLibs dexpreopt.LibraryPaths
+	// map of SDK version to class loader context
+	exportedSdkLibs dexpreopt.ClassLoaderContextMap
 
 	// list of plugins that this java module is exporting
 	exportedPluginJars android.Paths
@@ -509,7 +509,7 @@
 	ImplementationJars() android.Paths
 	ResourceJars() android.Paths
 	AidlIncludeDirs() android.Paths
-	ExportedSdkLibs() dexpreopt.LibraryPaths
+	ExportedSdkLibs() dexpreopt.ClassLoaderContextMap
 	ExportedPlugins() (android.Paths, []string)
 	SrcJarArgs() ([]string, android.Paths)
 	BaseModuleName() string
@@ -547,6 +547,14 @@
 	name string
 }
 
+// installDependencyTag is a dependency tag that is annotated to cause the installed files of the
+// dependency to be installed when the parent module is installed.
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 type usesLibraryDependencyTag struct {
 	dependencyTag
 	sdkVersion int // SDK version in which the library appared as a standalone library.
@@ -580,6 +588,8 @@
 	instrumentationForTag = dependencyTag{name: "instrumentation_for"}
 	extraLintCheckTag     = dependencyTag{name: "extra-lint-check"}
 	jniLibTag             = dependencyTag{name: "jnilib"}
+	jniInstallTag         = installDependencyTag{name: "jni install"}
+	binaryInstallTag      = installDependencyTag{name: "binary install"}
 	usesLibTag            = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
 	usesLibCompat28Tag    = makeUsesLibraryDependencyTag(28)
 	usesLibCompat29Tag    = makeUsesLibraryDependencyTag(29)
@@ -1027,7 +1037,8 @@
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.MaybeAddLibraryPath(ctx, dep.OptionalImplicitSdkLibrary(), dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.exportedSdkLibs.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
+					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
 			}
@@ -1038,7 +1049,7 @@
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1050,7 +1061,7 @@
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
 				deps.staticResourceJars = append(deps.staticResourceJars, dep.ResourceJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1902,7 +1913,7 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Module) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (j *Module) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
 	return j.exportedSdkLibs
 }
 
@@ -2041,7 +2052,7 @@
 		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
 	}
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.exportedSdkLibs = make(dexpreopt.LibraryPaths)
+	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -2061,11 +2072,12 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), j.DexJarBuildPath(), j.DexJarInstallPath())
+	j.exportedSdkLibs.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+		j.DexJarBuildPath(), j.DexJarInstallPath())
 
 	// A non-SDK library may provide a <uses-library> (the name may be different from the module name).
 	if lib := proptools.String(j.usesLibraryProperties.Provides_uses_lib); lib != "" {
-		j.exportedSdkLibs.AddLibraryPath(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
+		j.exportedSdkLibs.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
 	}
 
 	j.distFiles = j.GenerateTaggedDistFiles(ctx)
@@ -2548,9 +2560,12 @@
 	if ctx.Arch().ArchType == android.Common {
 		j.deps(ctx)
 	} else {
-		// This dependency ensures the host installation rules will install the jni libraries
-		// when the wrapper is installed.
-		ctx.AddVariationDependencies(nil, jniLibTag, j.binaryProperties.Jni_libs...)
+		// These dependencies ensure the host installation rules will install the jar file and
+		// the jni libraries when the wrapper is installed.
+		ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
+		ctx.AddVariationDependencies(
+			[]blueprint.Variation{{Mutator: "arch", Variation: android.CommonArch.String()}},
+			binaryInstallTag, ctx.ModuleName())
 	}
 }
 
@@ -2644,7 +2659,7 @@
 	dexJarFile android.Path
 
 	combinedClasspathFile android.Path
-	exportedSdkLibs       dexpreopt.LibraryPaths
+	exportedSdkLibs       dexpreopt.ClassLoaderContextMap
 	exportAidlIncludeDirs android.Paths
 
 	hideApexVariantFromMake bool
@@ -2719,7 +2734,7 @@
 		TransformJetifier(ctx, outputFile, inputFile)
 	}
 	j.combinedClasspathFile = outputFile
-	j.exportedSdkLibs = make(dexpreopt.LibraryPaths)
+	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
 
@@ -2733,7 +2748,7 @@
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2742,7 +2757,8 @@
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.AddLibraryPath(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.exportedSdkLibs.AddContext(ctx, otherName,
+					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
 	})
@@ -2757,7 +2773,8 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), outputFile, installFile)
+	j.exportedSdkLibs.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+		outputFile, installFile)
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
 
@@ -2839,7 +2856,7 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Import) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (j *Import) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
 	return j.exportedSdkLibs
 }
 
diff --git a/java/java_test.go b/java/java_test.go
index 2a27922..7f51982 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -29,7 +29,6 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
-	"android/soong/genrule"
 	"android/soong/python"
 )
 
@@ -80,7 +79,6 @@
 	RegisterSystemModulesBuildComponents(ctx)
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
 	ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
 	RegisterDocsBuildComponents(ctx)
 	RegisterStubsBuildComponents(ctx)
@@ -1593,8 +1591,8 @@
 	// test if baz has exported SDK lib names foo and bar to qux
 	qux := ctx.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := android.SortedStringKeys(quxLib.ExportedSdkLibs())
-		if w := []string{"bar", "foo", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
+		sdkLibs := quxLib.ExportedSdkLibs().UsesLibs()
+		if w := []string{"foo", "bar", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
 			t.Errorf("qux should export %q but exports %q", w, sdkLibs)
 		}
 	}
diff --git a/python/python.go b/python/python.go
index 945e264..e4c8e94 100644
--- a/python/python.go
+++ b/python/python.go
@@ -300,7 +300,7 @@
 }
 
 func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool {
-	return hasSrcExt(p.properties.Srcs, protoExt)
+	return hasSrcExt(p.properties.Srcs, ext)
 }
 
 func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 14bbd0b..187f0b6 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -286,6 +286,12 @@
 			srcs: ["src/any.h"],
 			out: ["src/any.rs"],
 		}
+		rust_binary_host {
+			name: "any_rust_binary",
+			srcs: [
+				"foo.rs",
+			],
+		}
 		rust_bindgen {
 			name: "libbindings",
 			crate_name: "bindings",
diff --git a/rust/testing.go b/rust/testing.go
index 66877a9..001f322 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -17,7 +17,6 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
-	"android/soong/genrule"
 )
 
 func GatherRequiredDepsForTest() string {
@@ -132,7 +131,6 @@
 	android.RegisterPrebuiltMutators(ctx)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)