Merge "Add target.vendor_ramdisk."
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 210d67a8..221aabc 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -119,7 +119,9 @@
 }
 
 func NewBazelContext(c *config) (BazelContext, error) {
-	if c.Getenv("USE_BAZEL") != "1" {
+	// TODO(cparsons): Assess USE_BAZEL=1 instead once "mixed Soong/Bazel builds"
+	// are production ready.
+	if c.Getenv("USE_BAZEL_ANALYSIS") != "1" {
 		return noopBazelContext{}, nil
 	}
 
diff --git a/android/config.go b/android/config.go
index 004b743..dbae7f7 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1336,11 +1336,6 @@
 	return len(l.jars)
 }
 
-// Apex component of idx-th pair on the list.
-func (l *ConfiguredJarList) apex(idx int) string {
-	return l.apexes[idx]
-}
-
 // Jar component of idx-th pair on the list.
 func (l *ConfiguredJarList) Jar(idx int) string {
 	return l.jars[idx]
@@ -1354,7 +1349,7 @@
 // If the list contains the given (apex, jar) pair.
 func (l *ConfiguredJarList) containsApexJarPair(apex, jar string) bool {
 	for i := 0; i < l.Len(); i++ {
-		if apex == l.apex(i) && jar == l.Jar(i) {
+		if apex == l.apexes[i] && jar == l.jars[i] {
 			return true
 		}
 	}
@@ -1366,32 +1361,43 @@
 	return IndexList(jar, l.jars)
 }
 
+func copyAndAppend(list []string, item string) []string {
+	// Create the result list to be 1 longer than the input.
+	result := make([]string, len(list)+1)
+
+	// Copy the whole input list into the result.
+	count := copy(result, list)
+
+	// Insert the extra item at the end.
+	result[count] = item
+
+	return result
+}
+
 // Append an (apex, jar) pair to the list.
-func (l *ConfiguredJarList) Append(apex string, jar string) {
-	l.apexes = append(l.apexes, apex)
-	l.jars = append(l.jars, jar)
+func (l *ConfiguredJarList) Append(apex string, jar string) ConfiguredJarList {
+	// Create a copy of the backing arrays before appending to avoid sharing backing
+	// arrays that are mutated across instances.
+	apexes := copyAndAppend(l.apexes, apex)
+	jars := copyAndAppend(l.jars, jar)
+
+	return ConfiguredJarList{apexes, jars}
 }
 
 // Filter out sublist.
-func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) {
+func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
 	jars := make([]string, 0, l.Len())
 
 	for i, jar := range l.jars {
-		apex := l.apex(i)
+		apex := l.apexes[i]
 		if !list.containsApexJarPair(apex, jar) {
 			apexes = append(apexes, apex)
 			jars = append(jars, jar)
 		}
 	}
 
-	l.apexes = apexes
-	l.jars = jars
-}
-
-// A copy of itself.
-func (l *ConfiguredJarList) CopyOf() ConfiguredJarList {
-	return ConfiguredJarList{CopyOf(l.apexes), CopyOf(l.jars)}
+	return ConfiguredJarList{apexes, jars}
 }
 
 // A copy of the list of strings containing jar components.
@@ -1404,7 +1410,7 @@
 	pairs := make([]string, 0, l.Len())
 
 	for i, jar := range l.jars {
-		apex := l.apex(i)
+		apex := l.apexes[i]
 		pairs = append(pairs, apex+":"+jar)
 	}
 
@@ -1420,6 +1426,26 @@
 	return paths
 }
 
+// Called when loading configuration from JSON into a configuration structure.
+func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
+	// Try and unmarshal into a []string each item of which contains a pair
+	// <apex>:<jar>.
+	var list []string
+	err := json.Unmarshal(b, &list)
+	if err != nil {
+		// Did not work so return
+		return err
+	}
+
+	apexes, jars, err := splitListOfPairsIntoPairOfLists(list)
+	if err != nil {
+		return err
+	}
+	l.apexes = apexes
+	l.jars = jars
+	return nil
+}
+
 func ModuleStem(module string) string {
 	// b/139391334: the stem of framework-minus-apex is framework. This is hard coded here until we
 	// find a good way to query the stem of a module before any other mutators are run.
@@ -1454,29 +1480,49 @@
 	return paths
 }
 
+func (l *ConfiguredJarList) String() string {
+	var pairs []string
+	for i := 0; i < l.Len(); i++ {
+		pairs = append(pairs, l.apexes[i]+":"+l.jars[i])
+	}
+	return strings.Join(pairs, ",")
+}
+
+func splitListOfPairsIntoPairOfLists(list []string) ([]string, []string, error) {
+	// Now we need to populate this list by splitting each item in the slice of
+	// pairs and appending them to the appropriate list of apexes or jars.
+	apexes := make([]string, len(list))
+	jars := make([]string, len(list))
+
+	for i, apexjar := range list {
+		apex, jar, err := splitConfiguredJarPair(apexjar)
+		if err != nil {
+			return nil, nil, err
+		}
+		apexes[i] = apex
+		jars[i] = jar
+	}
+
+	return apexes, jars, nil
+}
+
 // Expected format for apexJarValue = <apex name>:<jar name>
-func splitConfiguredJarPair(ctx PathContext, str string) (string, string) {
+func splitConfiguredJarPair(str string) (string, string, error) {
 	pair := strings.SplitN(str, ":", 2)
 	if len(pair) == 2 {
-		return pair[0], pair[1]
+		return pair[0], pair[1], nil
 	} else {
-		ReportPathErrorf(ctx, "malformed (apex, jar) pair: '%s', expected format: <apex>:<jar>", str)
-		return "error-apex", "error-jar"
+		return "error-apex", "error-jar", fmt.Errorf("malformed (apex, jar) pair: '%s', expected format: <apex>:<jar>", str)
 	}
 }
 
-func CreateConfiguredJarList(ctx PathContext, list []string) ConfiguredJarList {
-	apexes := make([]string, 0, len(list))
-	jars := make([]string, 0, len(list))
-
-	l := ConfiguredJarList{apexes, jars}
-
-	for _, apexjar := range list {
-		apex, jar := splitConfiguredJarPair(ctx, apexjar)
-		l.Append(apex, jar)
+func CreateTestConfiguredJarList(list []string) ConfiguredJarList {
+	apexes, jars, err := splitListOfPairsIntoPairOfLists(list)
+	if err != nil {
+		panic(err)
 	}
 
-	return l
+	return ConfiguredJarList{apexes, jars}
 }
 
 func EmptyConfiguredJarList() ConfiguredJarList {
@@ -1487,9 +1533,8 @@
 
 func (c *config) BootJars() []string {
 	return c.Once(earlyBootJarsKey, func() interface{} {
-		ctx := NullPathContext{Config{c}}
-		list := CreateConfiguredJarList(ctx,
-			append(CopyOf(c.productVariables.BootJars), c.productVariables.UpdatableBootJars...))
-		return list.CopyOfJars()
+		list := c.productVariables.BootJars.CopyOfJars()
+		list = append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
+		return list
 	}).([]string)
 }
diff --git a/android/config_test.go b/android/config_test.go
index 274d59f..68f68a0 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -91,3 +91,49 @@
 		t.Errorf("Expected false")
 	}
 }
+
+func assertStringEquals(t *testing.T, expected, actual string) {
+	if actual != expected {
+		t.Errorf("expected %q found %q", expected, actual)
+	}
+}
+
+func TestConfiguredJarList(t *testing.T) {
+	list1 := CreateTestConfiguredJarList([]string{"apex1:jarA"})
+
+	t.Run("create", func(t *testing.T) {
+		assertStringEquals(t, "apex1:jarA", list1.String())
+	})
+
+	list2 := list1.Append("apex2", "jarB")
+	t.Run("append", func(t *testing.T) {
+		assertStringEquals(t, "apex1:jarA,apex2:jarB", list2.String())
+	})
+
+	t.Run("append does not modify", func(t *testing.T) {
+		assertStringEquals(t, "apex1:jarA", list1.String())
+	})
+
+	// Make sure that two lists created by appending to the same list do not share storage.
+	list3 := list1.Append("apex3", "jarC")
+	t.Run("append does not share", func(t *testing.T) {
+		assertStringEquals(t, "apex1:jarA,apex2:jarB", list2.String())
+		assertStringEquals(t, "apex1:jarA,apex3:jarC", list3.String())
+	})
+
+	list4 := list3.RemoveList(list1)
+	t.Run("remove", func(t *testing.T) {
+		assertStringEquals(t, "apex3:jarC", list4.String())
+	})
+
+	t.Run("remove does not modify", func(t *testing.T) {
+		assertStringEquals(t, "apex1:jarA,apex3:jarC", list3.String())
+	})
+
+	// Make sure that two lists created by removing from the same list do not share storage.
+	list5 := list3.RemoveList(CreateTestConfiguredJarList([]string{"apex3:jarC"}))
+	t.Run("remove", func(t *testing.T) {
+		assertStringEquals(t, "apex3:jarC", list4.String())
+		assertStringEquals(t, "apex1:jarA", list5.String())
+	})
+}
diff --git a/android/paths.go b/android/paths.go
index 4eb9d20..2fb5f25 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1237,7 +1237,12 @@
 type InstallPath struct {
 	basePath
 
-	baseDir string // "../" for Make paths to convert "out/soong" to "out", "" for Soong paths
+	// partitionDir is the part of the InstallPath that is automatically determined according to the context.
+	// For example, it is host/<os>-<arch> for host modules, and target/product/<device>/<partition> for device modules.
+	partitionDir string
+
+	// makePath indicates whether this path is for Soong (false) or Make (true).
+	makePath bool
 }
 
 func (p InstallPath) buildDir() string {
@@ -1250,7 +1255,23 @@
 func (p InstallPath) writablePath() {}
 
 func (p InstallPath) String() string {
-	return filepath.Join(p.config.buildDir, p.baseDir, p.path)
+	if p.makePath {
+		// Make path starts with out/ instead of out/soong.
+		return filepath.Join(p.config.buildDir, "../", p.path)
+	} else {
+		return filepath.Join(p.config.buildDir, p.path)
+	}
+}
+
+// PartitionDir returns the path to the partition where the install path is rooted at. It is
+// out/soong/target/product/<device>/<partition> for device modules, and out/soong/host/<os>-<arch> for host modules.
+// The ./soong is dropped if the install path is for Make.
+func (p InstallPath) PartitionDir() string {
+	if p.makePath {
+		return filepath.Join(p.config.buildDir, "../", p.partitionDir)
+	} else {
+		return filepath.Join(p.config.buildDir, p.partitionDir)
+	}
 }
 
 // Join creates a new InstallPath with paths... joined with the current path. The
@@ -1271,7 +1292,7 @@
 // ToMakePath returns a new InstallPath that points to Make's install directory instead of Soong's,
 // i.e. out/ instead of out/soong/.
 func (p InstallPath) ToMakePath() InstallPath {
-	p.baseDir = "../"
+	p.makePath = true
 	return p
 }
 
@@ -1301,10 +1322,10 @@
 func pathForInstall(ctx PathContext, os OsType, arch ArchType, partition string, debug bool,
 	pathComponents ...string) InstallPath {
 
-	var outPaths []string
+	var partionPaths []string
 
 	if os.Class == Device {
-		outPaths = []string{"target", "product", ctx.Config().DeviceName(), partition}
+		partionPaths = []string{"target", "product", ctx.Config().DeviceName(), partition}
 	} else {
 		osName := os.String()
 		if os == Linux {
@@ -1320,30 +1341,33 @@
 		if os.Class == Host && (arch == X86_64 || arch == Common) {
 			archName = "x86"
 		}
-		outPaths = []string{"host", osName + "-" + archName, partition}
+		partionPaths = []string{"host", osName + "-" + archName, partition}
 	}
 	if debug {
-		outPaths = append([]string{"debug"}, outPaths...)
+		partionPaths = append([]string{"debug"}, partionPaths...)
 	}
-	outPaths = append(outPaths, pathComponents...)
 
-	path, err := validatePath(outPaths...)
+	partionPath, err := validatePath(partionPaths...)
 	if err != nil {
 		reportPathError(ctx, err)
 	}
 
-	ret := InstallPath{basePath{path, ctx.Config(), ""}, ""}
+	base := InstallPath{
+		basePath:     basePath{partionPath, ctx.Config(), ""},
+		partitionDir: partionPath,
+		makePath:     false,
+	}
 
-	return ret
+	return base.Join(ctx, pathComponents...)
 }
 
 func pathForNdkOrSdkInstall(ctx PathContext, prefix string, paths []string) InstallPath {
-	paths = append([]string{prefix}, paths...)
-	path, err := validatePath(paths...)
-	if err != nil {
-		reportPathError(ctx, err)
+	base := InstallPath{
+		basePath:     basePath{prefix, ctx.Config(), ""},
+		partitionDir: prefix,
+		makePath:     false,
 	}
-	return InstallPath{basePath{path, ctx.Config(), ""}, ""}
+	return base.Join(ctx, paths...)
 }
 
 func PathForNdkInstall(ctx PathContext, paths ...string) InstallPath {
@@ -1378,8 +1402,12 @@
 				partition += "/system"
 			}
 		} else if ctx.InstallInVendorRamdisk() {
+			// The module is only available after switching root into
+			// /first_stage_ramdisk. To expose the module before switching root
+			// on a device without a dedicated recovery partition, install the
+			// recovery variant.
 			if ctx.DeviceConfig().BoardMoveRecoveryResourcesToVendorBoot() {
-				partition = "recovery/root/first_stage_ramdisk"
+				partition = "vendor-ramdisk/first_stage_ramdisk"
 			} else {
 				partition = "vendor-ramdisk"
 			}
diff --git a/android/paths_test.go b/android/paths_test.go
index 9ecf4a1..51e4ba5 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -264,10 +264,11 @@
 	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
 
 	testCases := []struct {
-		name string
-		ctx  *moduleInstallPathContextImpl
-		in   []string
-		out  string
+		name         string
+		ctx          *moduleInstallPathContextImpl
+		in           []string
+		out          string
+		partitionDir string
 	}{
 		{
 			name: "host binary",
@@ -277,8 +278,9 @@
 					target: hostTarget,
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "host/linux-x86/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "host/linux-x86/bin/my_test",
+			partitionDir: "host/linux-x86",
 		},
 
 		{
@@ -289,8 +291,9 @@
 					target: deviceTarget,
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/system/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/system/bin/my_test",
+			partitionDir: "target/product/test_device/system",
 		},
 		{
 			name: "vendor binary",
@@ -303,8 +306,9 @@
 					},
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/vendor/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/vendor/bin/my_test",
+			partitionDir: "target/product/test_device/vendor",
 		},
 		{
 			name: "odm binary",
@@ -317,8 +321,9 @@
 					},
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/odm/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/odm/bin/my_test",
+			partitionDir: "target/product/test_device/odm",
 		},
 		{
 			name: "product binary",
@@ -331,8 +336,9 @@
 					},
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/product/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/product/bin/my_test",
+			partitionDir: "target/product/test_device/product",
 		},
 		{
 			name: "system_ext binary",
@@ -345,8 +351,9 @@
 					},
 				},
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/system_ext/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/system_ext/bin/my_test",
+			partitionDir: "target/product/test_device/system_ext",
 		},
 		{
 			name: "root binary",
@@ -357,8 +364,9 @@
 				},
 				inRoot: true,
 			},
-			in:  []string{"my_test"},
-			out: "target/product/test_device/root/my_test",
+			in:           []string{"my_test"},
+			out:          "target/product/test_device/root/my_test",
+			partitionDir: "target/product/test_device/root",
 		},
 		{
 			name: "recovery binary",
@@ -369,8 +377,9 @@
 				},
 				inRecovery: true,
 			},
-			in:  []string{"bin/my_test"},
-			out: "target/product/test_device/recovery/root/system/bin/my_test",
+			in:           []string{"bin/my_test"},
+			out:          "target/product/test_device/recovery/root/system/bin/my_test",
+			partitionDir: "target/product/test_device/recovery/root/system",
 		},
 		{
 			name: "recovery root binary",
@@ -382,8 +391,9 @@
 				inRecovery: true,
 				inRoot:     true,
 			},
-			in:  []string{"my_test"},
-			out: "target/product/test_device/recovery/root/my_test",
+			in:           []string{"my_test"},
+			out:          "target/product/test_device/recovery/root/my_test",
+			partitionDir: "target/product/test_device/recovery/root",
 		},
 
 		{
@@ -395,8 +405,9 @@
 				},
 				inData: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data",
 		},
 		{
 			name: "vendor native test binary",
@@ -410,8 +421,9 @@
 				},
 				inData: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data",
 		},
 		{
 			name: "odm native test binary",
@@ -425,8 +437,9 @@
 				},
 				inData: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data",
 		},
 		{
 			name: "product native test binary",
@@ -440,8 +453,9 @@
 				},
 				inData: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data",
 		},
 
 		{
@@ -456,8 +470,9 @@
 				},
 				inData: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data",
 		},
 
 		{
@@ -469,8 +484,9 @@
 				},
 				inSanitizerDir: true,
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/data/asan/system/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/data/asan/system/bin/my_test",
+			partitionDir: "target/product/test_device/data/asan/system",
 		},
 		{
 			name: "sanitized vendor binary",
@@ -484,8 +500,9 @@
 				},
 				inSanitizerDir: true,
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/data/asan/vendor/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/data/asan/vendor/bin/my_test",
+			partitionDir: "target/product/test_device/data/asan/vendor",
 		},
 		{
 			name: "sanitized odm binary",
@@ -499,8 +516,9 @@
 				},
 				inSanitizerDir: true,
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/data/asan/odm/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/data/asan/odm/bin/my_test",
+			partitionDir: "target/product/test_device/data/asan/odm",
 		},
 		{
 			name: "sanitized product binary",
@@ -514,8 +532,9 @@
 				},
 				inSanitizerDir: true,
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/data/asan/product/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/data/asan/product/bin/my_test",
+			partitionDir: "target/product/test_device/data/asan/product",
 		},
 
 		{
@@ -530,8 +549,9 @@
 				},
 				inSanitizerDir: true,
 			},
-			in:  []string{"bin", "my_test"},
-			out: "target/product/test_device/data/asan/system_ext/bin/my_test",
+			in:           []string{"bin", "my_test"},
+			out:          "target/product/test_device/data/asan/system_ext/bin/my_test",
+			partitionDir: "target/product/test_device/data/asan/system_ext",
 		},
 
 		{
@@ -544,8 +564,9 @@
 				inData:         true,
 				inSanitizerDir: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data/asan/data",
 		},
 		{
 			name: "sanitized vendor native test binary",
@@ -560,8 +581,9 @@
 				inData:         true,
 				inSanitizerDir: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data/asan/data",
 		},
 		{
 			name: "sanitized odm native test binary",
@@ -576,8 +598,9 @@
 				inData:         true,
 				inSanitizerDir: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data/asan/data",
 		},
 		{
 			name: "sanitized product native test binary",
@@ -592,8 +615,9 @@
 				inData:         true,
 				inSanitizerDir: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data/asan/data",
 		},
 		{
 			name: "sanitized system_ext native test binary",
@@ -608,8 +632,9 @@
 				inData:         true,
 				inSanitizerDir: true,
 			},
-			in:  []string{"nativetest", "my_test"},
-			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+			in:           []string{"nativetest", "my_test"},
+			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
+			partitionDir: "target/product/test_device/data/asan/data",
 		}, {
 			name: "device testcases",
 			ctx: &moduleInstallPathContextImpl{
@@ -619,8 +644,9 @@
 				},
 				inTestcases: true,
 			},
-			in:  []string{"my_test", "my_test_bin"},
-			out: "target/product/test_device/testcases/my_test/my_test_bin",
+			in:           []string{"my_test", "my_test_bin"},
+			out:          "target/product/test_device/testcases/my_test/my_test_bin",
+			partitionDir: "target/product/test_device/testcases",
 		}, {
 			name: "host testcases",
 			ctx: &moduleInstallPathContextImpl{
@@ -630,8 +656,9 @@
 				},
 				inTestcases: true,
 			},
-			in:  []string{"my_test", "my_test_bin"},
-			out: "host/linux-x86/testcases/my_test/my_test_bin",
+			in:           []string{"my_test", "my_test_bin"},
+			out:          "host/linux-x86/testcases/my_test/my_test_bin",
+			partitionDir: "host/linux-x86/testcases",
 		}, {
 			name: "forced host testcases",
 			ctx: &moduleInstallPathContextImpl{
@@ -643,8 +670,9 @@
 				forceOS:     &Linux,
 				forceArch:   &X86,
 			},
-			in:  []string{"my_test", "my_test_bin"},
-			out: "host/linux-x86/testcases/my_test/my_test_bin",
+			in:           []string{"my_test", "my_test_bin"},
+			out:          "host/linux-x86/testcases/my_test/my_test_bin",
+			partitionDir: "host/linux-x86/testcases",
 		},
 	}
 
@@ -657,10 +685,48 @@
 					output.basePath.path,
 					tc.out)
 			}
+			if output.partitionDir != tc.partitionDir {
+				t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n",
+					output.partitionDir, tc.partitionDir)
+			}
 		})
 	}
 }
 
+func TestBaseDirForInstallPath(t *testing.T) {
+	testConfig := pathTestConfig("")
+	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
+
+	ctx := &moduleInstallPathContextImpl{
+		baseModuleContext: baseModuleContext{
+			os:     deviceTarget.Os,
+			target: deviceTarget,
+		},
+	}
+	ctx.baseModuleContext.config = testConfig
+
+	actual := PathForModuleInstall(ctx, "foo", "bar")
+	expectedBaseDir := "target/product/test_device/system"
+	if actual.partitionDir != expectedBaseDir {
+		t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", actual.partitionDir, expectedBaseDir)
+	}
+	expectedRelPath := "foo/bar"
+	if actual.Rel() != expectedRelPath {
+		t.Errorf("unexpected Rel():\n got: %q\nwant: %q\n", actual.Rel(), expectedRelPath)
+	}
+
+	actualAfterJoin := actual.Join(ctx, "baz")
+	// partitionDir is preserved even after joining
+	if actualAfterJoin.partitionDir != expectedBaseDir {
+		t.Errorf("unexpected partitionDir after joining:\n got: %q\nwant: %q\n", actualAfterJoin.partitionDir, expectedBaseDir)
+	}
+	// Rel() is updated though
+	expectedRelAfterJoin := "baz"
+	if actualAfterJoin.Rel() != expectedRelAfterJoin {
+		t.Errorf("unexpected Rel() after joining:\n got: %q\nwant: %q\n", actualAfterJoin.Rel(), expectedRelAfterJoin)
+	}
+}
+
 func TestDirectorySortedPaths(t *testing.T) {
 	config := TestConfig("out", nil, "", map[string][]byte{
 		"Android.bp": nil,
diff --git a/android/variable.go b/android/variable.go
index 23fc6c2..7999f0f 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -249,8 +249,8 @@
 	UncompressPrivAppDex             *bool    `json:",omitempty"`
 	ModulesLoadedByPrivilegedModules []string `json:",omitempty"`
 
-	BootJars          []string `json:",omitempty"`
-	UpdatableBootJars []string `json:",omitempty"`
+	BootJars          ConfiguredJarList `json:",omitempty"`
+	UpdatableBootJars ConfiguredJarList `json:",omitempty"`
 
 	IntegerOverflowExcludePaths []string `json:",omitempty"`
 
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index c7223c4..b3cf8d5 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -419,7 +419,9 @@
 ndk_crtend_so.21(minSdkVersion:(no version))
 ndk_crtend_so.27(minSdkVersion:(no version))
 ndk_libc++_static(minSdkVersion:(no version))
+ndk_libc++_static(minSdkVersion:16)
 ndk_libc++abi(minSdkVersion:(no version))
+ndk_libc++abi(minSdkVersion:16)
 net-utils-framework-common(minSdkVersion:current)
 netd_aidl_interface-unstable-java(minSdkVersion:29)
 netd_event_listener_interface-ndk_platform(minSdkVersion:29)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index fcd734c..7e83070 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -5791,12 +5791,9 @@
 	var err string
 	var transform func(*dexpreopt.GlobalConfig)
 
-	config := android.TestArchConfig(buildDir, nil, "", nil)
-	ctx := android.PathContextForTesting(config)
-
 	t.Run("updatable jar from ART apex in the ART boot image => ok", func(t *testing.T) {
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.ArtApexJars = android.CreateConfiguredJarList(ctx, []string{"com.android.art.something:some-art-lib"})
+			config.ArtApexJars = android.CreateTestConfiguredJarList([]string{"com.android.art.something:some-art-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, "", transform)
 	})
@@ -5804,7 +5801,7 @@
 	t.Run("updatable jar from ART apex in the framework boot image => error", func(t *testing.T) {
 		err = `module "some-art-lib" from updatable apexes \["com.android.art.something"\] is not allowed in the framework boot image`
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.BootJars = android.CreateConfiguredJarList(ctx, []string{"com.android.art.something:some-art-lib"})
+			config.BootJars = android.CreateTestConfiguredJarList([]string{"com.android.art.something:some-art-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
@@ -5812,7 +5809,7 @@
 	t.Run("updatable jar from some other apex in the ART boot image => error", func(t *testing.T) {
 		err = `module "some-updatable-apex-lib" from updatable apexes \["some-updatable-apex"\] is not allowed in the ART boot image`
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.ArtApexJars = android.CreateConfiguredJarList(ctx, []string{"some-updatable-apex:some-updatable-apex-lib"})
+			config.ArtApexJars = android.CreateTestConfiguredJarList([]string{"some-updatable-apex:some-updatable-apex-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
@@ -5820,7 +5817,7 @@
 	t.Run("non-updatable jar from some other apex in the ART boot image => error", func(t *testing.T) {
 		err = `module "some-non-updatable-apex-lib" is not allowed in the ART boot image`
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.ArtApexJars = android.CreateConfiguredJarList(ctx, []string{"some-non-updatable-apex:some-non-updatable-apex-lib"})
+			config.ArtApexJars = android.CreateTestConfiguredJarList([]string{"some-non-updatable-apex:some-non-updatable-apex-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
@@ -5828,14 +5825,14 @@
 	t.Run("updatable jar from some other apex in the framework boot image => error", func(t *testing.T) {
 		err = `module "some-updatable-apex-lib" from updatable apexes \["some-updatable-apex"\] is not allowed in the framework boot image`
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.BootJars = android.CreateConfiguredJarList(ctx, []string{"some-updatable-apex:some-updatable-apex-lib"})
+			config.BootJars = android.CreateTestConfiguredJarList([]string{"some-updatable-apex:some-updatable-apex-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
 
 	t.Run("non-updatable jar from some other apex in the framework boot image => ok", func(t *testing.T) {
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.BootJars = android.CreateConfiguredJarList(ctx, []string{"some-non-updatable-apex:some-non-updatable-apex-lib"})
+			config.BootJars = android.CreateTestConfiguredJarList([]string{"some-non-updatable-apex:some-non-updatable-apex-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, "", transform)
 	})
@@ -5843,7 +5840,7 @@
 	t.Run("nonexistent jar in the ART boot image => error", func(t *testing.T) {
 		err = "failed to find a dex jar path for module 'nonexistent'"
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.ArtApexJars = android.CreateConfiguredJarList(ctx, []string{"platform:nonexistent"})
+			config.ArtApexJars = android.CreateTestConfiguredJarList([]string{"platform:nonexistent"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
@@ -5851,7 +5848,7 @@
 	t.Run("nonexistent jar in the framework boot image => error", func(t *testing.T) {
 		err = "failed to find a dex jar path for module 'nonexistent'"
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.BootJars = android.CreateConfiguredJarList(ctx, []string{"platform:nonexistent"})
+			config.BootJars = android.CreateTestConfiguredJarList([]string{"platform:nonexistent"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
@@ -5859,14 +5856,14 @@
 	t.Run("platform jar in the ART boot image => error", func(t *testing.T) {
 		err = `module "some-platform-lib" is not allowed in the ART boot image`
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.ArtApexJars = android.CreateConfiguredJarList(ctx, []string{"platform:some-platform-lib"})
+			config.ArtApexJars = android.CreateTestConfiguredJarList([]string{"platform:some-platform-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, err, transform)
 	})
 
 	t.Run("platform jar in the framework boot image => ok", func(t *testing.T) {
 		transform = func(config *dexpreopt.GlobalConfig) {
-			config.BootJars = android.CreateConfiguredJarList(ctx, []string{"platform:some-platform-lib"})
+			config.BootJars = android.CreateTestConfiguredJarList([]string{"platform:some-platform-lib"})
 		}
 		testNoUpdatableJarsInBootImage(t, "", transform)
 	})
@@ -5905,7 +5902,7 @@
 	for _, apexBootJar := range apexBootJars {
 		updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
 	}
-	config.TestProductVariables.UpdatableBootJars = updatableBootJars
+	config.TestProductVariables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
 
 	ctx.Register(config)
 
diff --git a/bazel/bazelenv.sh b/bazel/bazelenv.sh
index 2ca8baf..fcf71f1 100755
--- a/bazel/bazelenv.sh
+++ b/bazel/bazelenv.sh
@@ -59,12 +59,16 @@
     export BAZEL_PATH="$(which bazel)"
 fi
 
-export USE_BAZEL=1
+# TODO(cparsons): Use USE_BAZEL=1 instead once "mixed Soong/Bazel builds" are
+# production ready.
+export USE_BAZEL_ANALYSIS=1
+# TODO(cparsons): Retrieve this information in either envsetup.sh or 
+# bazel.sh.
 export BAZEL_HOME="$BASE_DIR/bazelhome"
 export BAZEL_OUTPUT_BASE="$BASE_DIR/output"
 export BAZEL_WORKSPACE="$(gettop)"
 
-echo "USE_BAZEL=${USE_BAZEL}"
+echo "USE_BAZEL_ANALYSIS=${USE_BAZEL_ANALYSIS}"
 echo "BAZEL_PATH=${BAZEL_PATH}"
 echo "BAZEL_HOME=${BAZEL_HOME}"
 echo "BAZEL_OUTPUT_BASE=${BAZEL_OUTPUT_BASE}"
diff --git a/build_test.bash b/build_test.bash
index ee979e7..a53a585 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -43,5 +43,16 @@
     ;;
 esac
 
+function bazel_cleanup {
+  "${TOP}/tools/bazel" shutdown
+}
+trap bazel_cleanup EXIT
+
+echo
+echo "Running Bazel smoke test..."
+"${TOP}/tools/bazel" info
+
+echo
+echo "Running Soong test..."
 soong_build_go multiproduct_kati android/soong/cmd/multiproduct_kati
 exec "$(getoutdir)/multiproduct_kati" "$@"
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 91eb886..38269cb 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -487,9 +487,21 @@
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		c.libraryDecorator.androidMkWriteExportedFlags(entries)
 
+		// Specifying stem is to pass check_elf_files when vendor modules link against vndk prebuilt.
+		// We can't use install path because VNDKs are not installed. Instead, Srcs is directly used.
+		_, file := filepath.Split(c.properties.Srcs[0])
+		stem, suffix, ext := android.SplitFileExt(file)
+		entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+
 		if c.tocFile.Valid() {
 			entries.SetString("LOCAL_SOONG_TOC", c.tocFile.String())
 		}
+
+		// VNDK libraries available to vendor are not installed because
+		// they are packaged in VNDK APEX and installed by APEX packages (apex/apex.go)
+		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
 	})
 }
 
diff --git a/cc/cc.go b/cc/cc.go
index dc57426..3b01fb2 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -255,10 +255,18 @@
 	// file
 	Logtags []string
 
-	// Make this module available when building for ramdisk
+	// Make this module available when building for ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Ramdisk_available *bool
 
-	// Make this module available when building for vendor ramdisk
+	// Make this module available when building for vendor ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Vendor_ramdisk_available *bool
 
 	// Make this module available when building for recovery
diff --git a/cc/vndk.go b/cc/vndk.go
index 6d5b074..7d8777c 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -740,7 +740,7 @@
 	/*
 		module_paths.txt contains paths on which VNDK modules are defined.
 		e.g.,
-			libbase.so system/core/base
+			libbase.so system/libbase
 			libc.so bionic/libc
 			...
 	*/
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index 82a7732..dddd5ac 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -232,6 +232,14 @@
 		&prebuilt.properties,
 	)
 
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		// empty BOARD_VNDK_VERSION implies that the device won't support
+		// system only OTA. In this case, VNDK snapshots aren't needed.
+		if ctx.DeviceConfig().VndkVersion() == "" {
+			ctx.Module().Disable()
+		}
+	})
+
 	return module
 }
 
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 774a872..a1b0c8e 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -470,7 +470,13 @@
 		ctx.Fatal("done")
 	}
 
-	toBuild := build.BuildAll
+	var toBuild int
+	if _, ok := config.Environment().Get("USE_BAZEL"); ok {
+		toBuild = build.BuildAllWithBazel
+	} else {
+		toBuild = build.BuildAll
+	}
+
 	if config.Checkbuild() {
 		toBuild |= build.RunBuildTests
 	}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 21f7bb3..2052847 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -100,20 +100,30 @@
 	ConstructContext android.Path
 }
 
-// These libs are added as optional dependencies (<uses-library> with android:required set to false).
-// This is because they haven't existed prior to certain SDK version, but classes in them were in
-// bootclasspath jars, etc. So making them hard dependencies (android:required=true) would prevent
-// apps from being installed to such legacy devices.
-var OptionalCompatUsesLibs = []string{
-	"org.apache.http.legacy",
-	"android.test.base",
-	"android.test.mock",
-}
+// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
+// app manifest is less than the specified version. This is needed because these libraries haven't
+// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
+// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
+// so that if this library is missing this in not a build or run-time error.
+var OrgApacheHttpLegacy = "org.apache.http.legacy"
+var AndroidTestBase = "android.test.base"
+var AndroidTestMock = "android.test.mock"
+var AndroidHidlBase = "android.hidl.base-V1.0-java"
+var AndroidHidlManager = "android.hidl.manager-V1.0-java"
 
-var CompatUsesLibs = []string{
-	"android.hidl.base-V1.0-java",
-	"android.hidl.manager-V1.0-java",
+var OptionalCompatUsesLibs28 = []string{
+	OrgApacheHttpLegacy,
 }
+var OptionalCompatUsesLibs30 = []string{
+	AndroidTestBase,
+	AndroidTestMock,
+}
+var CompatUsesLibs29 = []string{
+	AndroidHidlBase,
+	AndroidHidlManager,
+}
+var OptionalCompatUsesLibs = append(android.CopyOf(OptionalCompatUsesLibs28), OptionalCompatUsesLibs30...)
+var CompatUsesLibs = android.CopyOf(CompatUsesLibs29)
 
 const UnknownInstallLibraryPath = "error"
 
@@ -268,12 +278,8 @@
 
 		// Copies of entries in GlobalConfig that are not constructable without extra parameters.  They will be
 		// used to construct the real value manually below.
-		BootJars                  []string
-		UpdatableBootJars         []string
-		ArtApexJars               []string
-		UpdatableSystemServerJars []string
-		DirtyImageObjects         string
-		BootImageProfiles         []string
+		DirtyImageObjects string
+		BootImageProfiles []string
 	}
 
 	config := GlobalJSONConfig{}
@@ -283,10 +289,6 @@
 	}
 
 	// Construct paths that require a PathContext.
-	config.GlobalConfig.BootJars = android.CreateConfiguredJarList(ctx, config.BootJars)
-	config.GlobalConfig.UpdatableBootJars = android.CreateConfiguredJarList(ctx, config.UpdatableBootJars)
-	config.GlobalConfig.ArtApexJars = android.CreateConfiguredJarList(ctx, config.ArtApexJars)
-	config.GlobalConfig.UpdatableSystemServerJars = android.CreateConfiguredJarList(ctx, config.UpdatableSystemServerJars)
 	config.GlobalConfig.DirtyImageObjects = android.OptionalPathForPath(constructPath(ctx, config.DirtyImageObjects))
 	config.GlobalConfig.BootImageProfiles = constructPaths(ctx, config.BootImageProfiles)
 
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 814b75d..903677f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -195,6 +195,9 @@
 }
 
 type classLoaderContext struct {
+	// Library names
+	Names []string
+
 	// The class loader context using paths in the build.
 	Host android.Paths
 
@@ -209,7 +212,7 @@
 // targetSdkVersion in the manifest or APK is less than that API version.
 type classLoaderContextMap map[int]*classLoaderContext
 
-const anySdkVersion int = 9999 // should go last in class loader context
+const AnySdkVersion int = 9999 // should go last in class loader context
 
 func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
 	if _, ok := m[sdkVer]; !ok {
@@ -218,14 +221,19 @@
 	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 {
 	clc := m.getValue(sdkVer)
 	for _, lib := range libs {
 		if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath {
-			clc.Host = append(clc.Host, p.Host)
-			clc.Target = append(clc.Target, p.Device)
+			clc.addLib(lib, p.Host, p.Device)
 		} else {
-			if sdkVer == anySdkVersion {
+			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.
 				android.ReportPathErrorf(ctx, "dexpreopt cannot find path for <uses-library> '%s'", lib)
@@ -239,11 +247,17 @@
 	return true
 }
 
+func (m classLoaderContextMap) usesLibs() []string {
+	if clc, ok := m[AnySdkVersion]; ok {
+		return clc.Names
+	}
+	return nil
+}
+
 func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
 	clc := m.getValue(sdkVer)
 	for _, lib := range libs {
-		clc.Host = append(clc.Host, SystemServerDexJarHostPath(ctx, lib))
-		clc.Target = append(clc.Target, filepath.Join("/system/framework", lib+".jar"))
+		clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar"))
 	}
 }
 
@@ -261,7 +275,7 @@
 //    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.
 //
-// 2. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
+// 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.
@@ -273,21 +287,19 @@
 	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]...)
+		classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
 
 	} else if module.EnforceUsesLibraries {
 		// Unconditional class loader context.
 		usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
-		if !classLoaderContexts.addLibs(ctx, anySdkVersion, module, usesLibs...) {
+		if !classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...) {
 			return nil
 		}
 
 		// Conditional class loader context for API version < 28.
 		const httpLegacy = "org.apache.http.legacy"
-		if !contains(usesLibs, httpLegacy) {
-			if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) {
-				return nil
-			}
+		if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) {
+			return nil
 		}
 
 		// Conditional class loader context for API version < 29.
@@ -301,10 +313,8 @@
 
 		// Conditional class loader context for API version < 30.
 		const testBase = "android.test.base"
-		if !contains(usesLibs, testBase) {
-			if !classLoaderContexts.addLibs(ctx, 30, module, testBase) {
-				return nil
-			}
+		if !classLoaderContexts.addLibs(ctx, 30, module, testBase) {
+			return nil
 		}
 
 	} else {
@@ -314,9 +324,32 @@
 		// to the &.
 	}
 
+	fixConditionalClassLoaderContext(classLoaderContexts)
+
 	return &classLoaderContexts
 }
 
+// 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.
+func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
+	usesLibs := clcMap.usesLibs()
+
+	for sdkVer, clc := range clcMap {
+		if sdkVer == AnySdkVersion {
+			continue
+		}
+		clcMap[sdkVer] = &classLoaderContext{}
+		for i, lib := range clc.Names {
+			if android.InList(lib, usesLibs) {
+				// skip compatibility libraries that are already included in unconditional context
+			} else {
+				clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i])
+			}
+		}
+	}
+}
+
 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) {
@@ -363,7 +396,7 @@
 
 		checkSystemServerOrder(ctx, jarIndex)
 
-		clc := classLoaderContexts[anySdkVersion]
+		clc := classLoaderContexts[AnySdkVersion]
 		rule.Command().
 			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clc.Host.Strings(), ":") + "]").
 			Implicits(clc.Host).
@@ -391,14 +424,15 @@
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`)
 		for _, ver := range android.SortedIntKeys(classLoaderContexts) {
-			clc := classLoaderContexts.getValue(ver)
-			verString := fmt.Sprintf("%d", ver)
-			if ver == anySdkVersion {
-				verString = "any" // a special keyword that means any SDK version
+			if clc := classLoaderContexts.getValue(ver); len(clc.Host) > 0 {
+				verString := fmt.Sprintf("%d", ver)
+				if ver == AnySdkVersion {
+					verString = "any" // a special keyword that means any SDK version
+				}
+				cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")).
+					Implicits(clc.Host).
+					Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":"))
 			}
-			cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")).
-				Implicits(clc.Host).
-				Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":"))
 		}
 		cmd.Text(`)"`)
 	} else {
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 337ad88..44b8149 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -59,9 +59,17 @@
 	Filename_from_src *bool `android:"arch_variant"`
 
 	// Make this module available when building for ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Ramdisk_available *bool
 
 	// Make this module available when building for vendor ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Vendor_ramdisk_available *bool
 
 	// Make this module available when building for recovery.
diff --git a/java/app_test.go b/java/app_test.go
index cec8a62..98945da 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2766,7 +2766,7 @@
 			name: "prebuilt",
 			apk: "prebuilts/apk/app.apk",
 			certificate: "platform",
-			uses_libs: ["foo"],
+			uses_libs: ["foo", "android.test.runner"],
 			optional_uses_libs: [
 				"bar",
 				"baz",
@@ -2804,7 +2804,7 @@
 
 	cmd = prebuilt.Rule("verify_uses_libraries").RuleParams.Command
 
-	if w := `uses_library_names="foo"`; !strings.Contains(cmd, w) {
+	if w := `uses_library_names="foo android.test.runner"`; !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
@@ -2812,20 +2812,48 @@
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
-	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
+	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs.
 	cmd = app.Rule("dexpreopt").RuleParams.Command
 	w := `--target-classpath-for-sdk any` +
 		` /system/framework/foo.jar` +
 		`:/system/framework/quuz.jar` +
 		`:/system/framework/qux.jar` +
 		`:/system/framework/runtime-library.jar` +
-		`:/system/framework/bar.jar`
+		`:/system/framework/bar.jar `
 	if !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
+	// Test conditional context for target SDK version 28.
+	if w := `--target-classpath-for-sdk 28` +
+		` /system/framework/org.apache.http.legacy.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 29.
+	if w := `--target-classpath-for-sdk 29` +
+		` /system/framework/android.hidl.base-V1.0-java.jar` +
+		`:/system/framework/android.hidl.manager-V1.0-java.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 30.
+	if w := `--target-classpath-for-sdk 30` +
+		` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
 	cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
-	if w := `--target-classpath-for-sdk any /system/framework/foo.jar:/system/framework/bar.jar`; !strings.Contains(cmd, w) {
+	if w := `--target-classpath-for-sdk any` +
+		` /system/framework/foo.jar` +
+		`:/system/framework/android.test.runner.jar` +
+		`:/system/framework/bar.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 30.
+	if w := `--target-classpath-for-sdk 30` +
+		` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 }
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index 47110c9..ab31958 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -48,7 +48,7 @@
 
 	pathCtx := android.PathContextForTesting(config)
 	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
-	dexpreoptConfig.BootJars = android.CreateConfiguredJarList(pathCtx, []string{"platform:foo", "platform:bar", "platform:baz"})
+	dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList([]string{"platform:foo", "platform:bar", "platform:baz"})
 	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
 
 	ctx := testContext()
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 0f8888a..c315124 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -81,13 +81,12 @@
 		targets := dexpreoptTargets(ctx)
 		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
-		artModules := global.ArtApexJars.CopyOf()
+		artModules := global.ArtApexJars
 		// With EMMA_INSTRUMENT_FRAMEWORK=true the Core libraries depend on jacoco.
 		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-			artModules.Append("com.android.art", "jacocoagent")
+			artModules = artModules.Append("com.android.art", "jacocoagent")
 		}
-		frameworkModules := global.BootJars.CopyOf()
-		frameworkModules.RemoveList(artModules)
+		frameworkModules := global.BootJars.RemoveList(artModules)
 
 		artSubdir := "apex/art_boot_images/javalib"
 		frameworkSubdir := "system/framework"
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 344b15e..1e09d64 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -262,6 +262,10 @@
 	// TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately.
 	Generate_stubs *bool
 
+	// if set to true, provides a hint to the build system that this rule uses a lot of memory,
+	// whicih can be used for scheduling purposes
+	High_mem *bool
+
 	// is set to true, Metalava will allow framework SDK to contain API levels annotations.
 	Api_levels_annotations_enabled *bool
 
@@ -1260,8 +1264,6 @@
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
 	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths, implicitsRsp android.WritablePath, sandbox bool) *android.RuleBuilderCommand {
-	// Metalava uses lots of memory, restrict the number of metalava jobs that can run in parallel.
-	rule.HighMem()
 	cmd := rule.Command()
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
 		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
@@ -1343,6 +1345,11 @@
 
 	rule := android.NewRuleBuilder()
 
+	if BoolDefault(d.properties.High_mem, false) {
+		// This metalava run uses lots of memory, restrict the number of metalava jobs that can run in parallel.
+		rule.HighMem()
+	}
+
 	generateStubs := BoolDefault(d.properties.Generate_stubs, true)
 	var stubsDir android.OptionalPath
 	if generateStubs {
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index dbdab7a..7acaae7 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -25,7 +25,7 @@
 
 func testConfigWithBootJars(bp string, bootJars []string) android.Config {
 	config := testConfig(nil, bp, nil)
-	config.TestProductVariables.BootJars = bootJars
+	config.TestProductVariables.BootJars = android.CreateTestConfiguredJarList(bootJars)
 	return config
 }
 
diff --git a/java/java_test.go b/java/java_test.go
index c751ea4..cdb4375 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1232,31 +1232,24 @@
 func TestDroidstubs(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
 		droiddoc_exported_dir {
-		    name: "droiddoc-templates-sdk",
-		    path: ".",
+			name: "droiddoc-templates-sdk",
+			path: ".",
 		}
 
 		droidstubs {
-		    name: "bar-stubs",
-		    srcs: [
-		        "bar-doc/a.java",
-				],
-				api_levels_annotations_dirs: [
-					"droiddoc-templates-sdk",
-				],
-				api_levels_annotations_enabled: true,
+			name: "bar-stubs",
+			srcs: ["bar-doc/a.java"],
+			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
+			api_levels_annotations_enabled: true,
 		}
 
 		droidstubs {
-		    name: "bar-stubs-other",
-		    srcs: [
-		        "bar-doc/a.java",
-				],
-				api_levels_annotations_dirs: [
-					"droiddoc-templates-sdk",
-				],
-				api_levels_annotations_enabled: true,
-				api_levels_jar_filename: "android.other.jar",
+			name: "bar-stubs-other",
+			srcs: ["bar-doc/a.java"],
+			high_mem: true,
+			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
+			api_levels_annotations_enabled: true,
+			api_levels_jar_filename: "android.other.jar",
 		}
 		`,
 		map[string][]byte{
@@ -1265,23 +1258,31 @@
 	testcases := []struct {
 		moduleName          string
 		expectedJarFilename string
+		high_mem            bool
 	}{
 		{
 			moduleName:          "bar-stubs",
 			expectedJarFilename: "android.jar",
+			high_mem:            false,
 		},
 		{
 			moduleName:          "bar-stubs-other",
 			expectedJarFilename: "android.other.jar",
+			high_mem:            true,
 		},
 	}
 	for _, c := range testcases {
 		m := ctx.ModuleForTests(c.moduleName, "android_common")
 		metalava := m.Rule("metalava")
+		rp := metalava.RuleParams
 		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
-		if actual := metalava.RuleParams.Command; !strings.Contains(actual, expected) {
+		if actual := rp.Command; !strings.Contains(actual, expected) {
 			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
 		}
+
+		if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem {
+			t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual)
+		}
 	}
 }
 
diff --git a/java/testing.go b/java/testing.go
index 461fd3f..ab13121 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -22,6 +22,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	"android/soong/python"
 
 	"github.com/google/blueprint"
@@ -152,6 +153,24 @@
 		`, extra)
 	}
 
+	// For class loader context and <uses-library> tests.
+	dexpreoptModules := []string{"android.test.runner"}
+	dexpreoptModules = append(dexpreoptModules, dexpreopt.CompatUsesLibs...)
+	dexpreoptModules = append(dexpreoptModules, dexpreopt.OptionalCompatUsesLibs...)
+
+	for _, extra := range dexpreoptModules {
+		bp += fmt.Sprintf(`
+			java_library {
+				name: "%s",
+				srcs: ["a.java"],
+				sdk_version: "none",
+				system_modules: "stable-core-platform-api-stubs-system-modules",
+				compile_dex: true,
+				installable: true,
+			}
+		`, extra)
+	}
+
 	bp += `
 		java_library {
 			name: "framework",
@@ -166,48 +185,7 @@
 		android_app {
 			name: "framework-res",
 			sdk_version: "core_platform",
-		}
-
-		java_library {
-			name: "android.hidl.base-V1.0-java",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "android.hidl.manager-V1.0-java",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "org.apache.http.legacy",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "android.test.base",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-  
-		java_library {
-			name: "android.test.mock",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-	`
+		}`
 
 	systemModules := []string{
 		"core-current-stubs-system-modules",
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 6f40ae4..7e5c344 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -66,9 +66,17 @@
 	Symlinks []string `android:"arch_variant"`
 
 	// Make this module available when building for ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Ramdisk_available *bool
 
 	// Make this module available when building for vendor ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
 	Vendor_ramdisk_available *bool
 
 	// Make this module available when building for recovery.
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 4ef2721..c314b7b 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -39,6 +39,7 @@
         "blueprint-microfactory",
     ],
     srcs: [
+        "bazel.go",
         "build.go",
         "cleanbuild.go",
         "config.go",
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
new file mode 100644
index 0000000..c5702c0
--- /dev/null
+++ b/ui/build/bazel.go
@@ -0,0 +1,54 @@
+// 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 build
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+func runBazel(ctx Context, config Config) {
+	// "droid" is the default ninja target.
+	outputGroups := "droid"
+	if len(config.ninjaArgs) > 0 {
+		// At this stage, the residue slice of args passed to ninja
+		// are the ninja targets to build, which can correspond directly
+		// to ninja_build's output_groups.
+		outputGroups = strings.Join(config.ninjaArgs, ",")
+	}
+
+	bazelExecutable := filepath.Join("tools", "bazel")
+	args := []string{
+		"build",
+		"--verbose_failures",
+		"--show_progress_rate_limit=0.05",
+		"--color=yes",
+		"--curses=yes",
+		"--show_timestamps",
+		"--announce_rc",
+		"--output_groups=" + outputGroups,
+		"//:" + config.TargetProduct() + "-" + config.TargetBuildVariant(),
+	}
+
+	cmd := Command(ctx, config, "bazel", bazelExecutable, args...)
+
+	cmd.Environment.Set("DIST_DIR", config.DistDir())
+	cmd.Environment.Set("SHELL", "/bin/bash")
+
+	ctx.Println(cmd.Cmd)
+	cmd.Dir = filepath.Join(config.OutDir(), "..")
+	ctx.Status.Status("Starting Bazel..")
+	cmd.RunAndStreamOrFatal()
+}
diff --git a/ui/build/build.go b/ui/build/build.go
index 396f54c..1cf2023 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -84,8 +84,10 @@
 	BuildSoong         = 1 << iota
 	BuildKati          = 1 << iota
 	BuildNinja         = 1 << iota
+	BuildBazel         = 1 << iota
 	RunBuildTests      = 1 << iota
 	BuildAll           = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
+	BuildAllWithBazel  = BuildProductConfig | BuildSoong | BuildKati | BuildBazel
 )
 
 func checkProblematicFiles(ctx Context) {
@@ -257,6 +259,10 @@
 		// Run ninja
 		runNinja(ctx, config)
 	}
+
+	if what&BuildBazel != 0 {
+		runBazel(ctx, config)
+	}
 }
 
 // distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures