Merge "Clean up hard-coded min_sdk_version/apex_available"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 28dc8b4..9aae6fd 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -108,6 +108,7 @@
 		"external/icu":                                       Bp2BuildDefaultTrueRecursively,
 		"external/icu/android_icu4j":                         Bp2BuildDefaultFalse, // java rules incomplete
 		"external/icu/icu4j":                                 Bp2BuildDefaultFalse, // java rules incomplete
+		"external/jarjar":                                    Bp2BuildDefaultTrueRecursively,
 		"external/javapoet":                                  Bp2BuildDefaultTrueRecursively,
 		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
 		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
@@ -178,6 +179,7 @@
 		"system/libprocinfo":                                 Bp2BuildDefaultTrue,
 		"system/libziparchive":                               Bp2BuildDefaultTrueRecursively,
 		"system/logging/liblog":                              Bp2BuildDefaultTrueRecursively,
+		"system/memory/libmemunreachable":                    Bp2BuildDefaultTrueRecursively,
 		"system/sepolicy/apex":                               Bp2BuildDefaultTrueRecursively,
 		"system/timezone/apex":                               Bp2BuildDefaultTrueRecursively,
 		"system/timezone/output_data":                        Bp2BuildDefaultTrueRecursively,
@@ -308,6 +310,7 @@
 		"libprotobuf-internal-python-srcs", // TODO(b/210751803), we don't handle path property for filegroups
 		"libprotobuf-java-full",            // TODO(b/210751803), we don't handle path property for filegroups
 		"libprotobuf-java-util-full",       // TODO(b/210751803), we don't handle path property for filegroups
+		"auto_value_plugin_resources",      // TODO(b/210751803), we don't handle path property for filegroups
 
 		// go deps:
 		"analyze_bcpf",                                                                               // depends on bpmodify a blueprint_go_binary.
diff --git a/android/fixture.go b/android/fixture.go
index 728f031..0690a5a 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -586,6 +586,18 @@
 	})
 }
 
+// FixtureExpectsOneErrorPattern returns an error handler that will cause the test to fail
+// if there is more than one error or the error does not match the pattern.
+//
+// If the test fails this handler will call `result.FailNow()` which will exit the goroutine within
+// which the test is being run which means that the RunTest() method will not return.
+func FixtureExpectsOneErrorPattern(pattern string) FixtureErrorHandler {
+	return FixtureCustomErrorHandler(func(t *testing.T, result *TestResult) {
+		t.Helper()
+		CheckErrorsAgainstExpectations(t, result.Errs, []string{pattern})
+	})
+}
+
 // FixtureCustomErrorHandler creates a custom error handler
 func FixtureCustomErrorHandler(function func(t *testing.T, result *TestResult)) FixtureErrorHandler {
 	return simpleErrorHandler{
diff --git a/android/namespace_test.go b/android/namespace_test.go
index ea399da..87d1320 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"errors"
 	"path/filepath"
 	"reflect"
 	"testing"
@@ -24,577 +23,555 @@
 )
 
 func TestDependingOnModuleInSameNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a in the same namespace")
 	}
 }
 
 func TestDependingOnModuleInRootNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
-			test_module {
-				name: "a",
-			}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
+				test_module {
+					name: "a",
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b in root namespace does not depend on module a in the root namespace")
 	}
 }
 
 func TestImplicitlyImportRootNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			test_module {
-				name: "a",
-			}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			blueprint_test_module {
-				name: "a",
-			}
+				blueprint_test_module {
+					name: "a",
+				}
 			`,
 			"dir1": `
-			soong_namespace {
-			}
-			blueprint_test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				blueprint_test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestDependingOnModuleInImportedNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a in the same namespace")
 	}
 }
 
 func TestDependingOnModuleInNonImportedNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(
-			`dir3/Android.bp:4:4: "b" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:4:5: "b" depends on undefined module "a"
 Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."]
-Module "a" can be found in these namespaces: ["dir1" "dir2"]`),
-	}
-
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1" "dir2"]\E`)).
+		RunTest(t)
 }
 
 func TestDependingOnModuleByFullyQualifiedReference(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["//dir1:a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["//dir1:a"],
+				}
 			`,
-		},
-	)
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+		}),
+	).RunTest(t)
+
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a")
 	}
 }
 
 func TestSameNameInTwoNamespaces(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id: "1",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-				id: "2",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id: "1",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+					id: "2",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"3",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-				id:"4",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"3",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+					id:"4",
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	one := findModuleById(ctx, "1")
-	two := findModuleById(ctx, "2")
-	three := findModuleById(ctx, "3")
-	four := findModuleById(ctx, "4")
-	if !dependsOn(ctx, two, one) {
+	one := findModuleById(result, "1")
+	two := findModuleById(result, "2")
+	three := findModuleById(result, "3")
+	four := findModuleById(result, "4")
+	if !dependsOn(result, two, one) {
 		t.Fatalf("Module 2 does not depend on module 1 in its namespace")
 	}
-	if dependsOn(ctx, two, three) {
+	if dependsOn(result, two, three) {
 		t.Fatalf("Module 2 depends on module 3 in another namespace")
 	}
-	if !dependsOn(ctx, four, three) {
+	if !dependsOn(result, four, three) {
 		t.Fatalf("Module 4 does not depend on module 3 in its namespace")
 	}
-	if dependsOn(ctx, four, one) {
+	if dependsOn(result, four, one) {
 		t.Fatalf("Module 4 depends on module 1 in another namespace")
 	}
 }
 
 func TestSearchOrder(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id: "1",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id: "1",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"2",
-			}
-			test_module {
-				name: "b",
-				id:"3",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"2",
+				}
+				test_module {
+					name: "b",
+					id:"3",
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"4",
-			}
-			test_module {
-				name: "b",
-				id:"5",
-			}
-			test_module {
-				name: "c",
-				id:"6",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"4",
+				}
+				test_module {
+					name: "b",
+					id:"5",
+				}
+				test_module {
+					name: "c",
+					id:"6",
+				}
 			`,
 			".": `
-			test_module {
-				name: "a",
-				id: "7",
-			}
-			test_module {
-				name: "b",
-				id: "8",
-			}
-			test_module {
-				name: "c",
-				id: "9",
-			}
-			test_module {
-				name: "d",
-				id: "10",
-			}
+				test_module {
+					name: "a",
+					id: "7",
+				}
+				test_module {
+					name: "b",
+					id: "8",
+				}
+				test_module {
+					name: "c",
+					id: "9",
+				}
+				test_module {
+					name: "d",
+					id: "10",
+				}
 			`,
 			"dir4": `
-			soong_namespace {
-				imports: ["dir1", "dir2", "dir3"]
-			}
-			test_module {
-				name: "test_me",
-				id:"0",
-				deps: ["a", "b", "c", "d"],
-			}
+				soong_namespace {
+					imports: ["dir1", "dir2", "dir3"]
+				}
+				test_module {
+					name: "test_me",
+					id:"0",
+					deps: ["a", "b", "c", "d"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	testMe := findModuleById(ctx, "0")
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "1")) {
+	testMe := findModuleById(result, "0")
+	if !dependsOn(result, testMe, findModuleById(result, "1")) {
 		t.Errorf("test_me doesn't depend on id 1")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "3")) {
+	if !dependsOn(result, testMe, findModuleById(result, "3")) {
 		t.Errorf("test_me doesn't depend on id 3")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "6")) {
+	if !dependsOn(result, testMe, findModuleById(result, "6")) {
 		t.Errorf("test_me doesn't depend on id 6")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "10")) {
+	if !dependsOn(result, testMe, findModuleById(result, "10")) {
 		t.Errorf("test_me doesn't depend on id 10")
 	}
-	if numDeps(ctx, testMe) != 4 {
-		t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(ctx, testMe))
+	if numDeps(result, testMe) != 4 {
+		t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(result, testMe))
 	}
 }
 
 func TestTwoNamespacesCanImportEachOther(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-				imports: ["dir2"]
-			}
-			test_module {
-				name: "a",
-			}
-			test_module {
-				name: "c",
-				deps: ["b"],
-			}
+				soong_namespace {
+					imports: ["dir2"]
+				}
+				test_module {
+					name: "a",
+				}
+				test_module {
+					name: "c",
+					deps: ["b"],
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestImportingNonexistentNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-				imports: ["a_nonexistent_namespace"]
-			}
-			test_module {
-				name: "a",
-				deps: ["a_nonexistent_module"]
-			}
+				soong_namespace {
+					imports: ["a_nonexistent_namespace"]
+				}
+				test_module {
+					name: "a",
+					deps: ["a_nonexistent_module"]
+				}
 			`,
-		},
-	)
-
-	// should complain about the missing namespace and not complain about the unresolvable dependency
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		// should complain about the missing namespace and not complain about the unresolvable dependency
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:2:5: module "soong_namespace": namespace a_nonexistent_namespace does not exist\E`)).
+		RunTest(t)
 }
 
 func TestNamespacesDontInheritParentNamespaces(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1/subdir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/subdir1/Android.bp:4:4: "b" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/subdir1/Android.bp:4:5: "b" depends on undefined module "a"
 Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."]
-Module "a" can be found in these namespaces: ["dir1"]`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1"]\E`)).
+		RunTest(t)
 }
 
 func TestModulesDoReceiveParentNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1/subdir": `
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestNamespaceImportsNotTransitive(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-				imports: ["dir2"],
-			}
-			test_module {
-				name: "c",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir2"],
+				}
+				test_module {
+					name: "c",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir3/Android.bp:5:4: "c" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:5:5: "c" depends on undefined module "a"
 Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."]
-Module "a" can be found in these namespaces: ["dir1"]`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1"]\E`)).
+		RunTest(t)
 }
 
 func TestTwoNamepacesInSameDir(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			soong_namespace {
-			}
+				soong_namespace {
+				}
+				soong_namespace {
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:4:4: namespace dir1 already exists`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:4:5: namespace dir1 already exists\E`)).
+		RunTest(t)
 }
 
 func TestNamespaceNotAtTopOfFile(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			test_module {
-				name: "a"
-			}
-			soong_namespace {
-			}
+				test_module {
+					name: "a"
+				}
+				soong_namespace {
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:5:4: a namespace must be the first module in the file`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:5:5: a namespace must be the first module in the file\E`)).
+		RunTest(t)
 }
 
 func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a"
-			}
-			test_module {
-				name: "a"
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a"
+				}
+				test_module {
+					name: "a"
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:7:4: module "a" already defined
-       dir1/Android.bp:4:4 <-- previous definition here`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:7:5: module "a" already defined
+       dir1/Android.bp:4:5 <-- previous definition here\E`)).
+		RunTest(t)
 }
 
 func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) {
-	_, errs := setupTestFromFiles(t,
-		map[string][]byte{
-			"Android.bp": []byte(`
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		FixtureWithRootAndroidBp(`
 				build = ["include.bp"]
-			`),
-			"include.bp": []byte(`
+		`),
+		FixtureAddTextFile("include.bp", `
 				soong_namespace {
 				}
-			`),
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`include.bp:2:5: A namespace may only be declared in a file named Android.bp`),
-	}
-
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		`),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(
+			`\Qinclude.bp:2:5: A namespace may only be declared in a file named Android.bp\E`,
+		)).
+		RunTest(t)
 }
 
 // so that the generated .ninja file will have consistent names
 func TestConsistentNamespaceNames(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": "soong_namespace{}",
 			"dir2": "soong_namespace{}",
 			"dir3": "soong_namespace{}",
-		})
+		}),
+	).RunTest(t)
 
-	ns1, _ := ctx.NameResolver.namespaceAt("dir1")
-	ns2, _ := ctx.NameResolver.namespaceAt("dir2")
-	ns3, _ := ctx.NameResolver.namespaceAt("dir3")
+	ns1, _ := result.NameResolver.namespaceAt("dir1")
+	ns2, _ := result.NameResolver.namespaceAt("dir2")
+	ns3, _ := result.NameResolver.namespaceAt("dir3")
 	actualIds := []string{ns1.id, ns2.id, ns3.id}
 	expectedIds := []string{"1", "2", "3"}
 	if !reflect.DeepEqual(actualIds, expectedIds) {
@@ -604,103 +581,88 @@
 
 // so that the generated .ninja file will have consistent names
 func TestRename(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				deps: ["c"],
-			}
-			test_module {
-				name: "b",
-				rename: "c",
-			}
-		`})
-	// setupTest will report any errors
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					deps: ["c"],
+				}
+				test_module {
+					name: "b",
+					rename: "c",
+				}
+			`,
+		}),
+	).RunTest(t)
+
+	// RunTest will report any errors
 }
 
 // some utils to support the tests
 
-func mockFiles(bps map[string]string) (files map[string][]byte) {
-	files = make(map[string][]byte, len(bps))
+var prepareForTestWithNamespace = GroupFixturePreparers(
+	FixtureRegisterWithContext(registerNamespaceBuildComponents),
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.PreArchMutators(RegisterNamespaceMutator)
+	}),
+	FixtureModifyContext(func(ctx *TestContext) {
+		ctx.RegisterModuleType("test_module", newTestModule)
+		ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
+		ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+			ctx.BottomUp("rename", renameMutator)
+		})
+	}),
+)
+
+// dirBpToPreparer takes a map from directory to the contents of the Android.bp file and produces a
+// FixturePreparer.
+func dirBpToPreparer(bps map[string]string) FixturePreparer {
+	files := make(MockFS, len(bps))
 	files["Android.bp"] = []byte("")
 	for dir, text := range bps {
 		files[filepath.Join(dir, "Android.bp")] = []byte(text)
 	}
-	return files
+	return files.AddToFixture()
 }
 
-func setupTestFromFiles(t *testing.T, bps MockFS) (ctx *TestContext, errs []error) {
-	result := GroupFixturePreparers(
-		FixtureModifyContext(func(ctx *TestContext) {
-			ctx.RegisterModuleType("test_module", newTestModule)
-			ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
-			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.BottomUp("rename", renameMutator)
-			})
-		}),
-		PrepareForTestWithNamespace,
-		bps.AddToFixture(),
-	).
-		// Ignore errors for now so tests can check them later.
-		ExtendWithErrorHandler(FixtureIgnoreErrors).
-		RunTest(t)
-
-	return result.TestContext, result.Errs
-}
-
-func setupTestExpectErrs(t *testing.T, bps map[string]string) (ctx *TestContext, errs []error) {
-	files := make(map[string][]byte, len(bps))
-	files["Android.bp"] = []byte("")
-	for dir, text := range bps {
-		files[filepath.Join(dir, "Android.bp")] = []byte(text)
-	}
-	return setupTestFromFiles(t, files)
-}
-
-func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) {
-	t.Helper()
-	ctx, errs := setupTestExpectErrs(t, bps)
-	FailIfErrored(t, errs)
-	return ctx
-}
-
-func dependsOn(ctx *TestContext, module TestingModule, possibleDependency TestingModule) bool {
+func dependsOn(result *TestResult, module TestingModule, possibleDependency TestingModule) bool {
 	depends := false
 	visit := func(dependency blueprint.Module) {
 		if dependency == possibleDependency.module {
 			depends = true
 		}
 	}
-	ctx.VisitDirectDeps(module.module, visit)
+	result.VisitDirectDeps(module.module, visit)
 	return depends
 }
 
-func numDeps(ctx *TestContext, module TestingModule) int {
+func numDeps(result *TestResult, module TestingModule) int {
 	count := 0
 	visit := func(dependency blueprint.Module) {
 		count++
 	}
-	ctx.VisitDirectDeps(module.module, visit)
+	result.VisitDirectDeps(module.module, visit)
 	return count
 }
 
-func getModule(ctx *TestContext, moduleName string) TestingModule {
-	return ctx.ModuleForTests(moduleName, "")
+func getModule(result *TestResult, moduleName string) TestingModule {
+	return result.ModuleForTests(moduleName, "")
 }
 
-func findModuleById(ctx *TestContext, id string) (module TestingModule) {
+func findModuleById(result *TestResult, id string) (module TestingModule) {
 	visit := func(candidate blueprint.Module) {
 		testModule, ok := candidate.(*testModule)
 		if ok {
 			if testModule.properties.Id == id {
-				module = newTestingModule(ctx.config, testModule)
+				module = newTestingModule(result.config, testModule)
 			}
 		}
 	}
-	ctx.VisitAllModules(visit)
+	result.VisitAllModules(visit)
 	return module
 }
 
@@ -747,7 +709,7 @@
 	}
 }
 
-func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+func (b *blueprintTestModule) DynamicDependencies(_ blueprint.DynamicDependerModuleContext) []string {
 	return b.properties.Deps
 }
 
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 416e430..8afbe7e 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -234,10 +234,10 @@
 		if n != 0 {
 			split := splitFunc(s, n)
 			if n != -1 {
-				if len(split) > n {
+				if len(split) > n || len(split) == 0 {
 					panic("oops!")
 				} else {
-					n -= len(split)
+					n -= len(split) - 1
 				}
 			}
 			curMs.appendString(split[0])
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index e243ece..7e842a5 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -75,6 +75,16 @@
 			genMakeString(""),
 		},
 	},
+	{
+		// "x$(var1)y bar"
+		in:  genMakeString("x", "var1", "y bar"),
+		sep: " ",
+		n:   2,
+		expected: []*MakeString{
+			genMakeString("x", "var1", "y"),
+			genMakeString("bar"),
+		},
+	},
 }
 
 func TestMakeStringSplitN(t *testing.T) {
diff --git a/apex/apex.go b/apex/apex.go
index 52870f2..80db7c2 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -19,6 +19,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -49,7 +50,7 @@
 	ctx.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
 	ctx.RegisterModuleType("apex_defaults", defaultsFactory)
 	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	ctx.RegisterModuleType("override_apex", overrideApexFactory)
+	ctx.RegisterModuleType("override_apex", OverrideApexFactory)
 	ctx.RegisterModuleType("apex_set", apexSetFactory)
 
 	ctx.PreArchMutators(registerPreArchMutators)
@@ -976,6 +977,7 @@
 
 // apexInfoMutator delegates the work of identifying which modules need an ApexInfo and apex
 // specific variant to modules that support the ApexInfoMutator.
+// It also propagates updatable=true to apps of updatable apexes
 func apexInfoMutator(mctx android.TopDownMutatorContext) {
 	if !mctx.Module().Enabled() {
 		return
@@ -983,8 +985,8 @@
 
 	if a, ok := mctx.Module().(ApexInfoMutator); ok {
 		a.ApexInfoMutator(mctx)
-		return
 	}
+	enforceAppUpdatability(mctx)
 }
 
 // apexStrictUpdatibilityLintMutator propagates strict_updatability_linting to transitive deps of a mainline module
@@ -1015,6 +1017,22 @@
 	}
 }
 
+// enforceAppUpdatability propagates updatable=true to apps of updatable apexes
+func enforceAppUpdatability(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if apex, ok := mctx.Module().(*apexBundle); ok && apex.Updatable() {
+		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
+		mctx.VisitDirectDeps(func(module android.Module) {
+			// ignore android_test_app
+			if app, ok := module.(*java.AndroidApp); ok {
+				app.SetUpdatable(true)
+			}
+		})
+	}
+}
+
 // TODO: b/215736885 Whittle the denylist
 // Transitive deps of certain mainline modules baseline NewApi errors
 // Skip these mainline modules for now
@@ -1653,7 +1671,20 @@
 var _ androidApp = (*java.AndroidApp)(nil)
 var _ androidApp = (*java.AndroidAppImport)(nil)
 
-const APEX_VERSION_PLACEHOLDER = "__APEX_VERSION_PLACEHOLDER__"
+func sanitizedBuildIdForPath(ctx android.BaseModuleContext) string {
+	buildId := ctx.Config().BuildId()
+
+	// The build ID is used as a suffix for a filename, so ensure that
+	// the set of characters being used are sanitized.
+	// - any word character: [a-zA-Z0-9_]
+	// - dots: .
+	// - dashes: -
+	validRegex := regexp.MustCompile(`^[\w\.\-\_]+$`)
+	if !validRegex.MatchString(buildId) {
+		ctx.ModuleErrorf("Unable to use build id %s as filename suffix, valid characters are [a-z A-Z 0-9 _ . -].", buildId)
+	}
+	return buildId
+}
 
 func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp androidApp) apexFile {
 	appDir := "app"
@@ -1664,7 +1695,7 @@
 	// TODO(b/224589412, b/226559955): Ensure that the subdirname is suffixed
 	// so that PackageManager correctly invalidates the existing installed apk
 	// in favour of the new APK-in-APEX.  See bugs for more information.
-	dirInApex := filepath.Join(appDir, aapp.InstallApkName()+"@"+APEX_VERSION_PLACEHOLDER)
+	dirInApex := filepath.Join(appDir, aapp.InstallApkName()+"@"+sanitizedBuildIdForPath(ctx))
 	fileToCopy := aapp.OutputFile()
 
 	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
@@ -1903,7 +1934,7 @@
 					// suffixed so that PackageManager correctly invalidates the
 					// existing installed apk in favour of the new APK-in-APEX.
 					// See bugs for more information.
-					appDirName := filepath.Join(appDir, ap.BaseModuleName()+"@"+APEX_VERSION_PLACEHOLDER)
+					appDirName := filepath.Join(appDir, ap.BaseModuleName()+"@"+sanitizedBuildIdForPath(ctx))
 					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(), appDirName, appSet, ap)
 					af.certificate = java.PresignedCertificate
 					filesInfo = append(filesInfo, af)
@@ -2420,6 +2451,7 @@
 type OverrideApex struct {
 	android.ModuleBase
 	android.OverrideModuleBase
+	android.BazelModuleBase
 }
 
 func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -2428,16 +2460,64 @@
 
 // override_apex is used to create an apex module based on another apex module by overriding some of
 // its properties.
-func overrideApexFactory() android.Module {
+func OverrideApexFactory() android.Module {
 	m := &OverrideApex{}
 
 	m.AddProperties(&overridableProperties{})
 
 	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	android.InitOverrideModule(m)
+	android.InitBazelModule(m)
 	return m
 }
 
+func (o *OverrideApex) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	if ctx.ModuleType() != "override_apex" {
+		return
+	}
+
+	baseApexModuleName := o.OverrideModuleBase.GetOverriddenModuleName()
+	baseModule, baseApexExists := ctx.ModuleFromName(baseApexModuleName)
+	if !baseApexExists {
+		panic(fmt.Errorf("Base apex module doesn't exist: %s", baseApexModuleName))
+	}
+
+	a, baseModuleIsApex := baseModule.(*apexBundle)
+	if !baseModuleIsApex {
+		panic(fmt.Errorf("Base module is not apex module: %s", baseApexModuleName))
+	}
+	attrs, props := convertWithBp2build(a, ctx)
+
+	for _, p := range o.GetProperties() {
+		overridableProperties, ok := p.(*overridableProperties)
+		if !ok {
+			continue
+		}
+		// Key
+		if overridableProperties.Key != nil {
+			attrs.Key = bazel.LabelAttribute{}
+			attrs.Key.SetValue(android.BazelLabelForModuleDepSingle(ctx, *overridableProperties.Key))
+		}
+
+		// Certificate
+		if overridableProperties.Certificate != nil {
+			attrs.Certificate = bazel.LabelAttribute{}
+			attrs.Certificate.SetValue(android.BazelLabelForModuleDepSingle(ctx, *overridableProperties.Certificate))
+		}
+
+		// Prebuilts
+		prebuiltsLabelList := android.BazelLabelForModuleDeps(ctx, overridableProperties.Prebuilts)
+		attrs.Prebuilts = bazel.MakeLabelListAttribute(prebuiltsLabelList)
+
+		// Compressible
+		if overridableProperties.Compressible != nil {
+			attrs.Compressible = bazel.BoolAttribute{Value: overridableProperties.Compressible}
+		}
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: o.Name()}, &attrs)
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Vality check routines
 //
@@ -3388,6 +3468,11 @@
 		return
 	}
 
+	attrs, props := convertWithBp2build(a, ctx)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, &attrs)
+}
+
+func convertWithBp2build(a *apexBundle, ctx android.TopDownMutatorContext) (bazelApexBundleAttributes, bazel.BazelTargetModuleProperties) {
 	var manifestLabelAttribute bazel.LabelAttribute
 	if a.properties.Manifest != nil {
 		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.Manifest))
@@ -3399,8 +3484,15 @@
 	}
 
 	var fileContextsLabelAttribute bazel.LabelAttribute
-	if a.properties.File_contexts != nil {
+	if a.properties.File_contexts == nil {
+		// See buildFileContexts(), if file_contexts is not specified the default one is used, which is //system/sepolicy/apex:<module name>-file_contexts
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, a.Name()+"-file_contexts"))
+	} else if strings.HasPrefix(*a.properties.File_contexts, ":") {
+		// File_contexts is a module
 		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.properties.File_contexts))
+	} else {
+		// File_contexts is a file
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.File_contexts))
 	}
 
 	// TODO(b/219503907) this would need to be set to a.MinSdkVersionValue(ctx) but
@@ -3458,7 +3550,7 @@
 		compressibleAttribute.Value = a.overridableProperties.Compressible
 	}
 
-	attrs := &bazelApexBundleAttributes{
+	attrs := bazelApexBundleAttributes{
 		Manifest:              manifestLabelAttribute,
 		Android_manifest:      androidManifestLabelAttribute,
 		File_contexts:         fileContextsLabelAttribute,
@@ -3479,7 +3571,7 @@
 		Bzl_load_location: "//build/bazel/rules/apex:apex.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+	return attrs, props
 }
 
 // The following conversions are based on this table where the rows are the compile_multilib
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 4a52115..bcb55af 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -223,6 +223,7 @@
 		// not because of these tests specifically (it's not used by the tests)
 		variables.Platform_version_active_codenames = []string{"Q", "Tiramisu"}
 		variables.Platform_vndk_version = proptools.StringPtr("29")
+		variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
 	}),
 )
 
@@ -682,7 +683,7 @@
 		"etc/myetc",
 		"javalib/myjar.jar",
 		"lib64/mylib.so",
-		"app/AppFoo@__APEX_VERSION_PLACEHOLDER__/AppFoo.apk",
+		"app/AppFoo@TEST.BUILD_ID/AppFoo.apk",
 		"overlay/blue/rro.apk",
 		"etc/bpf/bpf.o",
 		"etc/bpf/bpf2.o",
@@ -2393,6 +2394,7 @@
 			key: "myapex.key",
 			apps: ["AppFoo"],
 			min_sdk_version: "29",
+			updatable: false,
 		}
 
 		apex_key {
@@ -5682,8 +5684,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/AppFoo@__APEX_VERSION_PLACEHOLDER__/AppFoo.apk")
-	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPriv@__APEX_VERSION_PLACEHOLDER__/AppFooPriv.apk")
+	ensureContains(t, copyCmds, "image.apex/app/AppFoo@TEST.BUILD_ID/AppFoo.apk")
+	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPriv@TEST.BUILD_ID/AppFooPriv.apk")
 
 	appZipRule := ctx.ModuleForTests("AppFoo", "android_common_apex10000").Description("zip jni libs")
 	// JNI libraries are uncompressed
@@ -5700,6 +5702,36 @@
 	}
 }
 
+func TestApexWithAppImportBuildId(t *testing.T) {
+	invalidBuildIds := []string{"../", "a b", "a/b", "a/b/../c", "/a"}
+	for _, id := range invalidBuildIds {
+		message := fmt.Sprintf("Unable to use build id %s as filename suffix", id)
+		fixture := android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.BuildId = proptools.StringPtr(id)
+		})
+		testApexError(t, message, `apex {
+			name: "myapex",
+			key: "myapex.key",
+			apps: ["AppFooPrebuilt"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		android_app_import {
+			name: "AppFooPrebuilt",
+			apk: "PrebuiltAppFoo.apk",
+			presigned: true,
+			apex_available: ["myapex"],
+		}
+	`, fixture)
+	}
+}
+
 func TestApexWithAppImports(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -5745,8 +5777,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/AppFooPrebuilt@__APEX_VERSION_PLACEHOLDER__/AppFooPrebuilt.apk")
-	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt@__APEX_VERSION_PLACEHOLDER__/AwesomePrebuiltAppFooPriv.apk")
+	ensureContains(t, copyCmds, "image.apex/app/AppFooPrebuilt@TEST.BUILD_ID/AppFooPrebuilt.apk")
+	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt@TEST.BUILD_ID/AwesomePrebuiltAppFooPriv.apk")
 }
 
 func TestApexWithAppImportsPrefer(t *testing.T) {
@@ -5787,7 +5819,7 @@
 	}))
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
-		"app/AppFoo@__APEX_VERSION_PLACEHOLDER__/AppFooPrebuilt.apk",
+		"app/AppFoo@TEST.BUILD_ID/AppFooPrebuilt.apk",
 	})
 }
 
@@ -5820,7 +5852,7 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/TesterHelpAppFoo@__APEX_VERSION_PLACEHOLDER__/TesterHelpAppFoo.apk")
+	ensureContains(t, copyCmds, "image.apex/app/TesterHelpAppFoo@TEST.BUILD_ID/TesterHelpAppFoo.apk")
 }
 
 func TestApexPropertiesShouldBeDefaultable(t *testing.T) {
@@ -6263,8 +6295,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureNotContains(t, copyCmds, "image.apex/app/app@__APEX_VERSION_PLACEHOLDER__/app.apk")
-	ensureContains(t, copyCmds, "image.apex/app/override_app@__APEX_VERSION_PLACEHOLDER__/override_app.apk")
+	ensureNotContains(t, copyCmds, "image.apex/app/app@TEST.BUILD_ID/app.apk")
+	ensureContains(t, copyCmds, "image.apex/app/override_app@TEST.BUILD_ID/override_app.apk")
 
 	ensureNotContains(t, copyCmds, "image.apex/etc/bpf/bpf.o")
 	ensureContains(t, copyCmds, "image.apex/etc/bpf/override_bpf.o")
@@ -7168,7 +7200,7 @@
 	content := bundleConfigRule.Args["content"]
 
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
-	ensureContains(t, content, `"apex_config":{"apex_embedded_apk_config":[{"package_name":"com.android.foo","path":"app/AppFoo@__APEX_VERSION_PLACEHOLDER__/AppFoo.apk"}]}`)
+	ensureContains(t, content, `"apex_config":{"apex_embedded_apk_config":[{"package_name":"com.android.foo","path":"app/AppFoo@TEST.BUILD_ID/AppFoo.apk"}]}`)
 }
 
 func TestAppSetBundle(t *testing.T) {
@@ -7199,9 +7231,9 @@
 	if len(copyCmds) != 3 {
 		t.Fatalf("Expected 3 commands, got %d in:\n%s", len(copyCmds), s)
 	}
-	ensureMatches(t, copyCmds[0], "^rm -rf .*/app/AppSet@__APEX_VERSION_PLACEHOLDER__$")
-	ensureMatches(t, copyCmds[1], "^mkdir -p .*/app/AppSet@__APEX_VERSION_PLACEHOLDER__$")
-	ensureMatches(t, copyCmds[2], "^unzip .*-d .*/app/AppSet@__APEX_VERSION_PLACEHOLDER__ .*/AppSet.zip$")
+	ensureMatches(t, copyCmds[0], "^rm -rf .*/app/AppSet@TEST.BUILD_ID$")
+	ensureMatches(t, copyCmds[1], "^mkdir -p .*/app/AppSet@TEST.BUILD_ID$")
+	ensureMatches(t, copyCmds[2], "^unzip .*-d .*/app/AppSet@TEST.BUILD_ID .*/AppSet.zip$")
 }
 
 func TestAppSetBundlePrebuilt(t *testing.T) {
@@ -9324,6 +9356,69 @@
 	}
 }
 
+// updatable apexes should propagate updatable=true to its apps
+func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: %v,
+			apps: [
+				"myapp",
+			],
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		android_app {
+			name: "myapp",
+			updatable: %v,
+			apex_available: [
+				"myapex",
+			],
+			sdk_version: "current",
+			min_sdk_version: "30",
+		}
+		`
+	testCases := []struct {
+		name                      string
+		apex_is_updatable_bp      bool
+		app_is_updatable_bp       bool
+		app_is_updatable_expected bool
+	}{
+		{
+			name:                      "Non-updatable apex respects updatable property of non-updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: false,
+		},
+		{
+			name:                      "Non-updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex sets updatable=true on non-updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: true,
+		},
+	}
+	for _, testCase := range testCases {
+		result := testApex(t, fmt.Sprintf(bp, testCase.apex_is_updatable_bp, testCase.app_is_updatable_bp))
+		myapp := result.ModuleForTests("myapp", "android_common").Module().(*java.AndroidApp)
+		android.AssertBoolEquals(t, testCase.name, testCase.app_is_updatable_expected, myapp.Updatable())
+	}
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/apex/builder.go b/apex/builder.go
index d4765d0..abbf8ad 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -107,16 +107,14 @@
 			`--canned_fs_config ${canned_fs_config} ` +
 			`--include_build_info ` +
 			`--payload_type image ` +
-			`--key ${key} ` +
-			`--apex_version_placeholder ${apex_version_placeholder} ` +
-			`${opt_flags} ${image_dir} ${out} `,
+			`--key ${key} ${opt_flags} ${image_dir} ${out} `,
 		CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
 			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}",
 			"${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"},
 		Rspfile:        "${out}.copy_commands",
 		RspfileContent: "${copy_commands}",
 		Description:    "APEX ${image_dir} => ${out}",
-	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key", "opt_flags", "manifest", "payload_fs_type", "apex_version_placeholder")
+	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key", "opt_flags", "manifest", "payload_fs_type")
 
 	zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{
 		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
@@ -124,13 +122,12 @@
 			`APEXER_TOOL_PATH=${tool_path} ` +
 			`${apexer} --force --manifest ${manifest} ` +
 			`--payload_type zip ` +
-			`--apex_version_placeholder ${apex_version_placeholder} ` +
 			`${image_dir} ${out} `,
 		CommandDeps:    []string{"${apexer}", "${merge_zips}", "${soong_zip}", "${zipalign}", "${aapt2}"},
 		Rspfile:        "${out}.copy_commands",
 		RspfileContent: "${copy_commands}",
 		Description:    "ZipAPEX ${image_dir} => ${out}",
-	}, "tool_path", "image_dir", "copy_commands", "manifest", "apex_version_placeholder")
+	}, "tool_path", "image_dir", "copy_commands", "manifest")
 
 	apexProtoConvertRule = pctx.AndroidStaticRule("apexProtoConvertRule",
 		blueprint.RuleParams{
@@ -661,15 +658,14 @@
 			Output:      unsignedOutputFile,
 			Description: "apex (" + apexType.name() + ")",
 			Args: map[string]string{
-				"tool_path":                outHostBinDir + ":" + prebuiltSdkToolsBinDir,
-				"image_dir":                imageDir.String(),
-				"copy_commands":            strings.Join(copyCommands, " && "),
-				"manifest":                 a.manifestPbOut.String(),
-				"file_contexts":            fileContexts.String(),
-				"canned_fs_config":         cannedFsConfig.String(),
-				"key":                      a.privateKeyFile.String(),
-				"opt_flags":                strings.Join(optFlags, " "),
-				"apex_version_placeholder": APEX_VERSION_PLACEHOLDER,
+				"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
+				"image_dir":        imageDir.String(),
+				"copy_commands":    strings.Join(copyCommands, " && "),
+				"manifest":         a.manifestPbOut.String(),
+				"file_contexts":    fileContexts.String(),
+				"canned_fs_config": cannedFsConfig.String(),
+				"key":              a.privateKeyFile.String(),
+				"opt_flags":        strings.Join(optFlags, " "),
 			},
 		})
 
@@ -761,11 +757,10 @@
 			Output:      unsignedOutputFile,
 			Description: "apex (" + apexType.name() + ")",
 			Args: map[string]string{
-				"tool_path":                outHostBinDir + ":" + prebuiltSdkToolsBinDir,
-				"image_dir":                imageDir.String(),
-				"copy_commands":            strings.Join(copyCommands, " && "),
-				"manifest":                 a.manifestPbOut.String(),
-				"apex_version_placeholder": APEX_VERSION_PLACEHOLDER,
+				"tool_path":     outHostBinDir + ":" + prebuiltSdkToolsBinDir,
+				"image_dir":     imageDir.String(),
+				"copy_commands": strings.Join(copyCommands, " && "),
+				"manifest":      a.manifestPbOut.String(),
 			},
 		})
 	}
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index 9057189..3ed21db 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -41,9 +41,27 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 }
 
+func runOverrideApexTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerOverrideApexModuleTypes, tc)
+}
+
+func registerOverrideApexModuleTypes(ctx android.RegistrationContext) {
+	// CC module types needed as they can be APEX dependencies
+	cc.RegisterCCBuildComponents(ctx)
+
+	ctx.RegisterModuleType("sh_binary", sh.ShBinaryFactory)
+	ctx.RegisterModuleType("cc_binary", cc.BinaryFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+	ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory)
+	ctx.RegisterModuleType("android_app_certificate", java.AndroidAppCertificateFactory)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("apex", apex.BundleFactory)
+}
+
 func TestApexBundleSimple(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
-		description:                "apex - example with all props",
+		description:                "apex - example with all props, file_context is a module in same Android.bp",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
 		filesystem:                 map[string]string{},
@@ -71,13 +89,11 @@
 	bazel_module: { bp2build_available: false },
 }
 
-// TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
 	name: "pretend_prebuilt_1",
 	bazel_module: { bp2build_available: false },
 }
 
-// TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
 	name: "pretend_prebuilt_2",
 	bazel_module: { bp2build_available: false },
@@ -86,7 +102,7 @@
 filegroup {
 	name: "com.android.apogee-file_contexts",
 	srcs: [
-			"com.android.apogee-file_contexts",
+		"com.android.apogee-file_contexts",
 	],
 	bazel_module: { bp2build_available: false },
 }
@@ -98,7 +114,7 @@
 	name: "com.android.apogee",
 	manifest: "apogee_manifest.json",
 	androidManifest: "ApogeeAndroidManifest.xml",
-	file_contexts: "com.android.apogee-file_contexts",
+	file_contexts: ":com.android.apogee-file_contexts",
 	min_sdk_version: "29",
 	key: "com.android.apogee.key",
 	certificate: "com.android.apogee.certificate",
@@ -157,13 +173,97 @@
 		}})
 }
 
+func TestApexBundleSimple_fileContextsInAnotherAndroidBp(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is a module in another Android.bp",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem: map[string]string{
+			"a/b/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+	file_contexts: ":com.android.apogee-file_contexts",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"//a/b:com.android.apogee-file_contexts"`,
+			}),
+		}})
+}
+
+func TestApexBundleSimple_fileContextsIsFile(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is a file",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+	file_contexts: "file_contexts_file",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"file_contexts_file"`,
+			}),
+		}})
+}
+
+func TestApexBundleSimple_fileContextsIsNotSpecified(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is not specified",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+			}),
+		}})
+}
+
 func TestApexBundleCompileMultilibBoth(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
 		description:                "apex - example with compile_multilib=both",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("both"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("both"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `[
@@ -187,6 +287,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -196,8 +297,16 @@
 		description:                "apex - example with compile_multilib=first",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("first"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("first"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `select({
@@ -226,6 +335,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -235,8 +345,16 @@
 		description:                "apex - example with compile_multilib=32",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("32"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("32"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `[
@@ -247,6 +365,7 @@
         "//build/bazel/platforms/arch:x86": [":native_shared_lib_2"],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -256,8 +375,16 @@
 		description:                "apex - example with compile_multilib=64",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("64"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("64"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_64": `select({
@@ -273,6 +400,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -282,7 +410,15 @@
 		description:                "apex - default property values",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
 		blueprint: `
 apex {
 	name: "com.android.apogee",
@@ -290,7 +426,8 @@
 }
 `,
 		expectedBazelTargets: []string{makeBazelTarget("apex", "com.android.apogee", attrNameToString{
-			"manifest": `"apogee_manifest.json"`,
+			"manifest":      `"apogee_manifest.json"`,
+			"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 		}),
 		}})
 }
@@ -300,7 +437,15 @@
 		description:                "apex - has bazel module props",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
 		blueprint: `
 apex {
 	name: "apogee",
@@ -309,7 +454,8 @@
 }
 `,
 		expectedBazelTargets: []string{makeBazelTarget("apex", "apogee", attrNameToString{
-			"manifest": `"manifest.json"`,
+			"manifest":      `"manifest.json"`,
+			"file_contexts": `"//system/sepolicy/apex:apogee-file_contexts"`,
 		}),
 		}})
 }
@@ -363,3 +509,137 @@
 	},
 }`
 }
+
+func TestBp2BuildOverrideApex(t *testing.T) {
+	runOverrideApexTestCase(t, bp2buildTestCase{
+		description:                "override_apex",
+		moduleTypeUnderTest:        "override_apex",
+		moduleTypeUnderTestFactory: apex.OverrideApexFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex_key {
+	name: "com.android.apogee.key",
+	public_key: "com.android.apogee.avbpubkey",
+	private_key: "com.android.apogee.pem",
+	bazel_module: { bp2build_available: false },
+}
+
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_1",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_2",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "pretend_prebuilt_1",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "pretend_prebuilt_2",
+	bazel_module: { bp2build_available: false },
+}
+
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+cc_binary { name: "cc_binary_1", bazel_module: { bp2build_available: false } }
+sh_binary { name: "sh_binary_2", bazel_module: { bp2build_available: false } }
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	androidManifest: "ApogeeAndroidManifest.xml",
+	file_contexts: ":com.android.apogee-file_contexts",
+	min_sdk_version: "29",
+	key: "com.android.apogee.key",
+	certificate: "com.android.apogee.certificate",
+	updatable: false,
+	installable: false,
+	compressible: false,
+	native_shared_libs: [
+	    "native_shared_lib_1",
+	    "native_shared_lib_2",
+	],
+	binaries: [
+		"cc_binary_1",
+		"sh_binary_2",
+	],
+	prebuilts: [
+	    "pretend_prebuilt_1",
+	    "pretend_prebuilt_2",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+apex_key {
+	name: "com.google.android.apogee.key",
+	public_key: "com.google.android.apogee.avbpubkey",
+	private_key: "com.google.android.apogee.pem",
+	bazel_module: { bp2build_available: false },
+}
+
+android_app_certificate {
+	name: "com.google.android.apogee.certificate",
+	certificate: "com.google.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+override_apex {
+	name: "com.google.android.apogee",
+	base: ":com.android.apogee",
+	key: "com.google.android.apogee.key",
+	certificate: "com.google.android.apogee.certificate",
+	prebuilts: [],
+	compressible: true,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.google.android.apogee", attrNameToString{
+				"android_manifest": `"ApogeeAndroidManifest.xml"`,
+				"binaries": `[
+        ":cc_binary_1",
+        ":sh_binary_2",
+    ]`,
+				"certificate":     `":com.google.android.apogee.certificate"`,
+				"file_contexts":   `":com.android.apogee-file_contexts"`,
+				"installable":     "False",
+				"key":             `":com.google.android.apogee.key"`,
+				"manifest":        `"apogee_manifest.json"`,
+				"min_sdk_version": `"29"`,
+				"native_shared_libs_32": `[
+        ":native_shared_lib_1",
+        ":native_shared_lib_2",
+    ]`,
+				"native_shared_libs_64": `select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+				"prebuilts":    `[]`,
+				"updatable":    "False",
+				"compressible": "True",
+			}),
+		}})
+}
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 3b66369..e4d9cbc 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -267,3 +267,108 @@
 			}),
 		}})
 }
+
+func TestJavaLibraryResources(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":      "",
+			"res/b.res":      "",
+			"res/dir1/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resources: ["res/a.res", "res/b.res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resources": `[
+        "res/a.res",
+        "res/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourceDirs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":      "",
+			"res/b.res":      "",
+			"res/dir1/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources": `[
+        "res/a.res",
+        "res/b.res",
+        "res/dir1/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesExcludeDir(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":         "",
+			"res/exclude/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+	exclude_java_resource_dirs: ["res/exclude"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources":             `["res/a.res"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesExcludeFile(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":            "",
+			"res/dir1/b.res":       "",
+			"res/dir1/exclude.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+	exclude_java_resources: ["res/dir1/exclude.res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources": `[
+        "res/a.res",
+        "res/dir1/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesFailsWithMultipleDirs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":  "",
+			"res1/a.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res", "res1"],
+}`,
+		expectedErr:          fmt.Errorf("bp2build does not support more than one directory in java_resource_dirs (b/226423379)"),
+		expectedBazelTargets: []string{},
+	})
+}
diff --git a/cc/Android.bp b/cc/Android.bp
index 60d329e..ce94467 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -91,6 +91,7 @@
     ],
     testSrcs: [
         "afdo_test.go",
+        "binary_test.go",
         "cc_test.go",
         "compiler_test.go",
         "gen_test.go",
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 1b8b4a8..cba5974 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -65,7 +65,7 @@
 	android.AssertStringListContains(t, "missing dependency on linker_scripts",
 		binFoo.Implicits.Strings(), "bar.ld")
 	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
-		libfoo.Args["ldFlags"], "-Wl,--script,foo.ld")
+		binFoo.Args["ldFlags"], "-Wl,--script,foo.ld")
 	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
-		libfoo.Args["ldFlags"], "-Wl,--script,bar.ld")
+		binFoo.Args["ldFlags"], "-Wl,--script,bar.ld")
 }
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 09cc352..2951b5a 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4042,7 +4042,7 @@
 	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
 
 	cflags := []string{"-Wall", "-Werror", "-std=candcpp"}
-	cstd := []string{"-std=gnu99", "-std=conly"}
+	cstd := []string{"-std=gnu11", "-std=conly"}
 	cppstd := []string{"-std=gnu++17", "-std=cpp", "-fno-rtti"}
 
 	lastIncludes := []string{
diff --git a/cc/config/global.go b/cc/config/global.go
index 3caf327..2f00cfa 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -279,15 +279,15 @@
 		"-w",
 	}
 
-	CStdVersion               = "gnu99"
+	CStdVersion               = "gnu11"
 	CppStdVersion             = "gnu++17"
 	ExperimentalCStdVersion   = "gnu11"
 	ExperimentalCppStdVersion = "gnu++2a"
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r450784d"
-	ClangDefaultShortVersion = "14.0.6"
+	ClangDefaultVersion      = "clang-r450784e"
+	ClangDefaultShortVersion = "14.0.7"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index ba1043b..1f90843 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -46,6 +46,7 @@
 			"-misc-no-recursion",
 			"-misc-non-private-member-variables-in-classes",
 			"-misc-unused-parameters",
+			"-performance-no-int-to-ptr",
 			// the following groups are excluded by -*
 			// -altera-*
 			// -cppcoreguidelines-*
@@ -122,6 +123,7 @@
 	{"hardware/qcom", tidyExternalVendor},
 	{"vendor/", tidyExternalVendor},
 	{"vendor/google", tidyDefault},
+	{"vendor/google_arc/libs/org.chromium.arc.mojom", tidyExternalVendor},
 	{"vendor/google_devices", tidyExternalVendor},
 }
 
diff --git a/cc/test.go b/cc/test.go
index ead7877..5703571 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -30,7 +30,8 @@
 	// if set, build against the gtest library. Defaults to true.
 	Gtest *bool
 
-	// if set, use the isolated gtest runner. Defaults to false.
+	// if set, use the isolated gtest runner. Defaults to true if gtest is also true and the arch is Windows, false
+	// otherwise.
 	Isolated *bool
 }
 
@@ -256,6 +257,13 @@
 	return BoolDefault(test.LinkerProperties.Gtest, true)
 }
 
+func (test *testDecorator) isolated(ctx BaseModuleContext) bool {
+	if !ctx.Windows() {
+		return BoolDefault(test.LinkerProperties.Isolated, false)
+	}
+	return BoolDefault(test.LinkerProperties.Isolated, false)
+}
+
 func (test *testDecorator) testBinary() bool {
 	return true
 }
@@ -288,7 +296,7 @@
 	if test.gtest() {
 		if ctx.useSdk() && ctx.Device() {
 			deps.StaticLibs = append(deps.StaticLibs, "libgtest_main_ndk_c++", "libgtest_ndk_c++")
-		} else if BoolDefault(test.LinkerProperties.Isolated, false) {
+		} else if test.isolated(ctx) {
 			deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
 			// The isolated library requires liblog, but adding it
 			// as a static library means unit tests cannot override
@@ -424,7 +432,7 @@
 		var options []tradefed.Option
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
 	}
-	if Bool(test.testDecorator.LinkerProperties.Isolated) {
+	if test.isolated(ctx) {
 		configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
 	}
 	if test.Properties.Test_options.Run_test_as != nil {
diff --git a/cc/tidy.go b/cc/tidy.go
index 03e967d..ac1521b 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -96,10 +96,15 @@
 	if !android.SubstringInList(flags.TidyFlags, "-header-filter=") {
 		defaultDirs := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS")
 		headerFilter := "-header-filter="
+		// Default header filter should include only the module directory,
+		// not the out/soong/.../ModuleDir/...
+		// Otherwise, there will be too many warnings from generated files in out/...
+		// If a module wants to see warnings in the generated source files,
+		// it should specify its own -header-filter flag.
 		if defaultDirs == "" {
-			headerFilter += ctx.ModuleDir() + "/"
+			headerFilter += "^" + ctx.ModuleDir() + "/"
 		} else {
-			headerFilter += "\"(" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
+			headerFilter += "\"(^" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
 		}
 		flags.TidyFlags = append(flags.TidyFlags, headerFilter)
 	}
diff --git a/cmd/path_interposer/main.go b/cmd/path_interposer/main.go
index a4fe3e4..8b9de52 100644
--- a/cmd/path_interposer/main.go
+++ b/cmd/path_interposer/main.go
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This tool tries to prohibit access to tools on the system on which the build
+// is run.
+//
+// The rationale is that if the build uses a binary that is not shipped in the
+// source tree, it is unknowable which version of that binary will be installed
+// and therefore the output of the build will be unpredictable. Therefore, we
+// should make every effort to use only tools under our control.
+//
+// This is currently implemented by a "sandbox" that sets $PATH to a specific,
+// single directory and creates a symlink for every binary in $PATH in it. That
+// symlink will point to path_interposer, which then uses an embedded
+// configuration to determine whether to allow access to the binary (in which
+// case it calls the original executable) or not (in which case it fails). It
+// can also optionally log invocations.
+//
+// This, of course, does not help if one invokes the tool in question with its
+// full path.
 package main
 
 import (
diff --git a/docs/tidy.md b/docs/tidy.md
index 890c3a0..2eb8234 100644
--- a/docs/tidy.md
+++ b/docs/tidy.md
@@ -31,7 +31,7 @@
 
 The global default can be overwritten by module properties in Android.bp.
 
-### `tidy` and `tidy_checks`
+### `tidy`, `tidy_checks`, and `ALLOW_LOCAL_TIDY_TRUE`
 
 For example, in
 [system/bpf/Android.bp](https://android.googlesource.com/platform/system/bpf/+/refs/heads/master/Android.bp),
@@ -52,8 +52,16 @@
 }
 ```
 That means in normal builds, even without `WITH_TIDY=1`,
-the modules that use `bpf_defaults` will run clang-tidy
+the modules that use `bpf_defaults` _should_ run clang-tidy
 over C/C++ source files with the given `tidy_checks`.
+
+However since clang-tidy warnings and its runtime cost might
+not be wanted by all people, the default is to ignore the
+`tidy:true` property unless the environment variable
+`ALLOW_LOCAL_TIDY_TRUE` is set to true or 1.
+To run clang-tidy on all modules that should be tested with clang-tidy,
+`ALLOW_LOCAL_TIDY_TRUE` or `WITH_TIDY` should be set to true or 1.
+
 Note that `clang-analyzer-security*` is included in `tidy_checks`
 but not all `clang-analyzer-*` checks. Check `cert-err34-c` is
 disabled, although `cert-*` is selected.
@@ -80,6 +88,9 @@
 }
 ```
 
+Note that `tidy:false` always disables clang-tidy, no matter
+`ALLOW_LOCAL_TIDY_TRUE` is set or not.
+
 ### `tidy_checks_as_errors`
 
 The global tidy checks are enabled as warnings.
diff --git a/java/androidmk.go b/java/androidmk.go
index f6ea6a9..439b1d1 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -542,6 +542,9 @@
 	if !outputFile.Valid() {
 		outputFile = android.OptionalPathForPath(dstubs.apiFile)
 	}
+	if !outputFile.Valid() {
+		outputFile = android.OptionalPathForPath(dstubs.apiVersionsXml)
+	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: outputFile,
diff --git a/java/app.go b/java/app.go
index 768d9e9..5f14cef 100755
--- a/java/app.go
+++ b/java/app.go
@@ -299,10 +299,6 @@
 
 // If an updatable APK sets min_sdk_version, min_sdk_vesion of JNI libs should match with it.
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
-// b/155209650: until min_sdk_version is properly supported, use sdk_version instead.
-// because, sdk_version is overridden by min_sdk_version (if set as smaller)
-// and sdkLinkType is checked with dependencies so we can be sure that the whole dependency tree
-// will meet the requirements.
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
 	ctx.VisitDirectDeps(func(m android.Module) {
@@ -313,10 +309,10 @@
 		// The domain of cc.sdk_version is "current" and <number>
 		// We can rely on android.SdkSpec to convert it to <number> so that "current" is
 		// handled properly regardless of sdk finalization.
-		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.SdkVersion()).EffectiveVersion(ctx)
+		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.MinSdkVersion()).EffectiveVersion(ctx)
 		if err != nil || minSdkVersion.LessThan(jniSdkVersion) {
-			ctx.OtherModuleErrorf(dep, "sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
-				dep.SdkVersion(), minSdkVersion, ctx.ModuleName())
+			ctx.OtherModuleErrorf(dep, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
+				dep.MinSdkVersion(), minSdkVersion, ctx.ModuleName())
 			return
 		}
 
@@ -842,6 +838,10 @@
 	return Bool(a.appProperties.Updatable)
 }
 
+func (a *AndroidApp) SetUpdatable(val bool) {
+	a.appProperties.Updatable = &val
+}
+
 func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string {
 	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName())
 	if overridden {
diff --git a/java/app_test.go b/java/app_test.go
index 8324dff..b83a333 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -427,7 +427,8 @@
 			name: "libjni",
 			stl: "none",
 			system_shared_libs: [],
-			sdk_version: "29",
+			sdk_version: "current",
+			min_sdk_version: "29",
 		}
 	`
 	fs := map[string][]byte{
@@ -481,12 +482,13 @@
 			name: "libjni",
 			stl: "none",
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
-	testJavaError(t, `"libjni" .*: sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
+	testJavaError(t, `"libjni" .*: min_sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
 }
 
-func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) {
+func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) {
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -503,6 +505,7 @@
 			shared_libs: ["libbar"],
 			system_shared_libs: [],
 			sdk_version: "27",
+			min_sdk_version: "27",
 		}
 
 		cc_library {
@@ -510,6 +513,7 @@
 			stl: "none",
 			system_shared_libs: [],
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
 	testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp)
diff --git a/java/base.go b/java/base.go
index e60e54e..c6f859a 100644
--- a/java/base.go
+++ b/java/base.go
@@ -748,9 +748,7 @@
 		// Kotlin files
 		ctx.AddVariationDependencies(nil, kotlinStdlibTag,
 			"kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8")
-		if len(j.properties.Plugins) > 0 {
-			ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations")
-		}
+		ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations")
 	}
 
 	// Framework libraries need special handling in static coverage builds: they should not have
@@ -1022,6 +1020,7 @@
 		ctx.PropertyErrorf("common_srcs", "common_srcs must be .kt files")
 	}
 
+	nonGeneratedSrcJars := srcFiles.FilterByExt(".srcjar")
 	srcFiles = j.genSources(ctx, srcFiles, flags)
 
 	// Collect javac flags only after computing the full set of srcFiles to
@@ -1106,8 +1105,6 @@
 		flags.classpath = append(flags.classpath, deps.kotlinStdlib...)
 		flags.classpath = append(flags.classpath, deps.kotlinAnnotations...)
 
-		flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...)
-
 		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.bootClasspath...)
 		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.classpath...)
 
@@ -1139,9 +1136,12 @@
 		// Jar kotlin classes into the final jar after javac
 		if BoolDefault(j.properties.Static_kotlin_stdlib, true) {
 			kotlinJars = append(kotlinJars, deps.kotlinStdlib...)
+			kotlinJars = append(kotlinJars, deps.kotlinAnnotations...)
 			kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinStdlib...)
+			kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinAnnotations...)
 		} else {
 			flags.dexClasspath = append(flags.dexClasspath, deps.kotlinStdlib...)
+			flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...)
 		}
 	}
 
@@ -1493,8 +1493,8 @@
 		}
 
 		j.linter.name = ctx.ModuleName()
-		j.linter.srcs = srcFiles
-		j.linter.srcJars = srcJars
+		j.linter.srcs = append(srcFiles, nonGeneratedSrcJars...)
+		j.linter.srcJars, _ = android.FilterPathList(srcJars, nonGeneratedSrcJars)
 		j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...)
 		j.linter.classes = j.implementationJarFile
 		j.linter.minSdkVersion = lintSDKVersion(j.MinSdkVersion(ctx))
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 52ce77d..f4cef7f 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -84,6 +84,9 @@
 		}
 	}
 
+	target := ctx.Module().Target()
+	variations = append(variations, target.Variations()...)
+
 	addedDep := false
 	if ctx.OtherModuleDependencyVariantExists(variations, name) {
 		ctx.AddFarVariationDependencies(variations, tag, name)
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index c3a5d5f..0345aad 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -273,7 +273,7 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, BOOTCLASSPATH)
-	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 
 	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
 		// If code coverage has been enabled for the framework then append the properties with
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index cf39746..513c606 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -138,11 +138,29 @@
     },
 }
 
+// Same as core-module-lib-stubs-for-system-modules, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "core-module-lib-stubs-for-system-modules-no-annotations",
+    visibility: ["//visibility:private"],
+    static_libs: [
+        "core-module-lib-stubs-for-system-modules",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    dist: {
+        dest: "system-modules/module-lib/core-for-system-modules-no-annotations.jar",
+        targets: dist_targets,
+    },
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 // Used when compiling higher-level code with sdk_version "module_current"
 java_system_modules {
     name: "core-module-lib-stubs-system-modules",
     libs: [
-        "core-module-lib-stubs-for-system-modules",
+        "core-module-lib-stubs-for-system-modules-no-annotations",
     ],
     visibility: ["//visibility:public"],
 }
@@ -174,6 +192,24 @@
     patch_module: "java.base",
 }
 
+// Same as legacy.core.platform.api.stubs, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "legacy.core.platform.api.no.annotations.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "legacy.core.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 java_library {
     name: "stable.core.platform.api.stubs",
     visibility: core_platform_visibility,
@@ -191,12 +227,30 @@
     patch_module: "java.base",
 }
 
+// Same as stable.core.platform.api.stubs, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "stable.core.platform.api.no.annotations.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "stable.core.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 // Used when compiling higher-level code against *.core.platform.api.stubs.
 java_system_modules {
     name: "legacy-core-platform-api-stubs-system-modules",
     visibility: core_platform_visibility,
     libs: [
-        "legacy.core.platform.api.stubs",
+        "legacy.core.platform.api.no.annotations.stubs",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
         "core-lambda-stubs-for-system-modules",
@@ -212,7 +266,7 @@
     name: "stable-core-platform-api-stubs-system-modules",
     visibility: core_platform_visibility,
     libs: [
-        "stable.core.platform.api.stubs",
+        "stable.core.platform.api.no.annotations.stubs",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
         "core-lambda-stubs-for-system-modules",
diff --git a/java/core-libraries/jarjar-strip-annotations-rules.txt b/java/core-libraries/jarjar-strip-annotations-rules.txt
new file mode 100644
index 0000000..a1c261b
--- /dev/null
+++ b/java/core-libraries/jarjar-strip-annotations-rules.txt
@@ -0,0 +1,4 @@
+strip-annotation android.annotation.NotNull
+strip-annotation android.annotation.Nullable
+strip-annotation androidx.annotation.RecentlyNonNull
+strip-annotation androidx.annotation.RecentlyNullable
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 3b1f7c0..115388b 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -135,6 +135,9 @@
 	// if set to true, Metalava will allow framework SDK to contain API levels annotations.
 	Api_levels_annotations_enabled *bool
 
+	// Apply the api levels database created by this module rather than generating one in this droidstubs.
+	Api_levels_module *string
+
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
@@ -234,6 +237,7 @@
 var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"}
 var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"}
 var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"}
+var metalavaAPILevelsModuleTag = dependencyTag{name: "metalava-api-levels-module-tag"}
 
 func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) {
 	d.Javadoc.addDeps(ctx)
@@ -255,6 +259,10 @@
 			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
 		}
 	}
+
+	if d.properties.Api_levels_module != nil {
+		ctx.AddDependency(ctx.Module(), metalavaAPILevelsModuleTag, proptools.String(d.properties.Api_levels_module))
+	}
 }
 
 func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
@@ -365,21 +373,35 @@
 }
 
 func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if !Bool(d.properties.Api_levels_annotations_enabled) {
-		return
+	var apiVersions android.Path
+	if proptools.Bool(d.properties.Api_levels_annotations_enabled) {
+		d.apiLevelsGenerationFlags(ctx, cmd)
+		apiVersions = d.apiVersionsXml
+	} else {
+		ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) {
+			if s, ok := m.(*Droidstubs); ok {
+				apiVersions = s.apiVersionsXml
+			} else {
+				ctx.PropertyErrorf("api_levels_module",
+					"module %q is not a droidstubs module", ctx.OtherModuleName(m))
+			}
+		})
 	}
+	if apiVersions != nil {
+		cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
+		cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
+		cmd.FlagWithInput("--apply-api-levels ", apiVersions)
+	}
+}
 
-	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
-
+func (d *Droidstubs) apiLevelsGenerationFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	if len(d.properties.Api_levels_annotations_dirs) == 0 {
 		ctx.PropertyErrorf("api_levels_annotations_dirs",
 			"has to be non-empty if api levels annotations was enabled!")
 	}
 
+	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
 	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
-	cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
 
 	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
 
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 10d99f3..9fdfdde 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -46,6 +46,12 @@
 			api_levels_annotations_enabled: true,
 			api_levels_jar_filename: "android.other.jar",
 		}
+
+		droidstubs {
+			name: "stubs-applying-api-versions",
+			srcs: ["bar-doc/a.java"],
+			api_levels_module: "bar-stubs-other",
+		}
 		`,
 		map[string][]byte{
 			"bar-doc/a.java": nil,
@@ -53,26 +59,37 @@
 	testcases := []struct {
 		moduleName          string
 		expectedJarFilename string
+		generate_xml        bool
 		high_mem            bool
 	}{
 		{
 			moduleName:          "bar-stubs",
+			generate_xml:        true,
 			expectedJarFilename: "android.jar",
 			high_mem:            false,
 		},
 		{
 			moduleName:          "bar-stubs-other",
+			generate_xml:        true,
 			expectedJarFilename: "android.other.jar",
 			high_mem:            true,
 		},
+		{
+			moduleName:   "stubs-applying-api-versions",
+			generate_xml: false,
+		},
 	}
 	for _, c := range testcases {
 		m := ctx.ModuleForTests(c.moduleName, "android_common")
 		manifest := m.Output("metalava.sbox.textproto")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest)
-		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
-		if actual := String(sboxProto.Commands[0].Command); !strings.Contains(actual, expected) {
-			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
+		cmdline := String(sboxProto.Commands[0].Command)
+		android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml)
+		if c.expectedJarFilename != "" {
+			expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
+			if !strings.Contains(cmdline, expected) {
+				t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline)
+			}
 		}
 
 		metalava := m.Rule("metalava")
diff --git a/java/java.go b/java/java.go
index 13f4c80..079d4b9 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2018,7 +2018,49 @@
 	}
 }
 
+type javaResourcesAttributes struct {
+	Resources             bazel.LabelListAttribute
+	Resource_strip_prefix *string
+}
+
+func (m *Library) convertJavaResourcesAttributes(ctx android.TopDownMutatorContext) *javaResourcesAttributes {
+	var resources bazel.LabelList
+	var resourceStripPrefix *string
+
+	if m.properties.Java_resources != nil {
+		resources.Append(android.BazelLabelForModuleSrc(ctx, m.properties.Java_resources))
+	}
+
+	//TODO(b/179889880) handle case where glob includes files outside package
+	resDeps := ResourceDirsToFiles(
+		ctx,
+		m.properties.Java_resource_dirs,
+		m.properties.Exclude_java_resource_dirs,
+		m.properties.Exclude_java_resources,
+	)
+
+	for i, resDep := range resDeps {
+		dir, files := resDep.dir, resDep.files
+
+		resources.Append(bazel.MakeLabelList(android.RootToModuleRelativePaths(ctx, files)))
+
+		// Bazel includes the relative path from the WORKSPACE root when placing the resource
+		// inside the JAR file, so we need to remove that prefix
+		resourceStripPrefix = proptools.StringPtr(dir.String())
+		if i > 0 {
+			// TODO(b/226423379) allow multiple resource prefixes
+			ctx.ModuleErrorf("bp2build does not support more than one directory in java_resource_dirs (b/226423379)")
+		}
+	}
+
+	return &javaResourcesAttributes{
+		Resources:             bazel.MakeLabelListAttribute(resources),
+		Resource_strip_prefix: resourceStripPrefix,
+	}
+}
+
 type javaCommonAttributes struct {
+	*javaResourcesAttributes
 	Srcs      bazel.LabelListAttribute
 	Plugins   bazel.LabelListAttribute
 	Javacopts bazel.StringListAttribute
@@ -2095,7 +2137,8 @@
 	}
 
 	commonAttrs := &javaCommonAttributes{
-		Srcs: javaSrcs,
+		Srcs:                    javaSrcs,
+		javaResourcesAttributes: m.convertJavaResourcesAttributes(ctx),
 		Plugins: bazel.MakeLabelListAttribute(
 			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
 		),
diff --git a/java/java_resources.go b/java/java_resources.go
index 787d74a..b0dc5a1 100644
--- a/java/java_resources.go
+++ b/java/java_resources.go
@@ -33,8 +33,13 @@
 	"**/*~",
 }
 
-func ResourceDirsToJarArgs(ctx android.ModuleContext,
-	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) {
+type resourceDeps struct {
+	dir   android.Path
+	files android.Paths
+}
+
+func ResourceDirsToFiles(ctx android.BaseModuleContext,
+	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (deps []resourceDeps) {
 	var excludeDirs []string
 	var excludeFiles []string
 
@@ -55,21 +60,36 @@
 		dirs := ctx.Glob(android.PathForSource(ctx, ctx.ModuleDir()).Join(ctx, resourceDir).String(), excludeDirs)
 		for _, dir := range dirs {
 			files := ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), excludeFiles)
+			deps = append(deps, resourceDeps{
+				dir:   dir,
+				files: files,
+			})
+		}
+	}
 
+	return deps
+}
+
+func ResourceDirsToJarArgs(ctx android.ModuleContext,
+	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) {
+	resDeps := ResourceDirsToFiles(ctx, resourceDirs, excludeResourceDirs, excludeResourceFiles)
+
+	for _, resDep := range resDeps {
+		dir, files := resDep.dir, resDep.files
+
+		if len(files) > 0 {
+			args = append(args, "-C", dir.String())
 			deps = append(deps, files...)
 
-			if len(files) > 0 {
-				args = append(args, "-C", dir.String())
-
-				for _, f := range files {
-					path := f.String()
-					if !strings.HasPrefix(path, dir.String()) {
-						panic(fmt.Errorf("path %q does not start with %q", path, dir))
-					}
-					args = append(args, "-f", pathtools.MatchEscape(path))
+			for _, f := range files {
+				path := f.String()
+				if !strings.HasPrefix(path, dir.String()) {
+					panic(fmt.Errorf("path %q does not start with %q", path, dir))
 				}
+				args = append(args, "-f", pathtools.MatchEscape(path))
 			}
 		}
+
 	}
 
 	return args, deps
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 435d782..491ce29 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -42,6 +42,11 @@
 		}
 		`)
 
+	kotlinStdlib := ctx.ModuleForTests("kotlin-stdlib", "android_common").
+		Output("turbine-combined/kotlin-stdlib.jar").Output
+	kotlinAnnotations := ctx.ModuleForTests("kotlin-annotations", "android_common").
+		Output("turbine-combined/kotlin-annotations.jar").Output
+
 	fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
 	fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar")
@@ -69,6 +74,16 @@
 			fooJar.Inputs.Strings(), fooKotlincClasses.String())
 	}
 
+	if !inList(kotlinStdlib.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinStdlib.String())
+	}
+
+	if !inList(kotlinAnnotations.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinAnnotations.String())
+	}
+
 	if !inList(fooKotlincHeaderClasses.String(), fooHeaderJar.Inputs.Strings()) {
 		t.Errorf("foo header jar inputs %v does not contain %q",
 			fooHeaderJar.Inputs.Strings(), fooKotlincHeaderClasses.String())
diff --git a/java/lint.go b/java/lint.go
index 426a2af..22c9ec4 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -524,10 +524,18 @@
 		return
 	}
 
-	frameworkDocStubs := findModuleOrErr(ctx, "framework-doc-stubs")
-	if frameworkDocStubs == nil {
+	apiVersionsDb := findModuleOrErr(ctx, "api_versions_public")
+	if apiVersionsDb == nil {
 		if !ctx.Config().AllowMissingDependencies() {
-			ctx.Errorf("lint: missing framework-doc-stubs")
+			ctx.Errorf("lint: missing module api_versions_public")
+		}
+		return
+	}
+
+	sdkAnnotations := findModuleOrErr(ctx, "sdk-annotations.zip")
+	if sdkAnnotations == nil {
+		if !ctx.Config().AllowMissingDependencies() {
+			ctx.Errorf("lint: missing module sdk-annotations.zip")
 		}
 		return
 	}
@@ -542,13 +550,13 @@
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
-		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
+		Input:  android.OutputFileForModule(ctx, sdkAnnotations, ""),
 		Output: copiedAnnotationsZipPath(ctx),
 	})
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
-		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
+		Input:  android.OutputFileForModule(ctx, apiVersionsDb, ".api_versions.xml"),
 		Output: copiedAPIVersionsXmlPath(ctx, "api_versions.xml"),
 	})
 
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 1c2a3ae..6257e49 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -51,6 +51,7 @@
 	var addSourceBootclassPathModule = android.FixtureAddTextFile("source/Android.bp", `
 		java_library {
 			name: "foo",
+			host_supported: true, // verify that b/232106778 is fixed
 			srcs: ["a.java"],
 			system_modules: "none",
 			sdk_version: "none",
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 1eae63f..e59146b 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -1270,7 +1270,28 @@
 	// Handle only the case where the first (or only) word is constant
 	words := ref.SplitN(" ", 2)
 	if !words[0].Const() {
-		return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		if len(words) == 1 {
+			expr := ctx.parseMakeString(node, ref)
+			return &callExpr{
+				object: &identifierExpr{"cfg"},
+				name:   "get",
+				args: []starlarkExpr{
+					expr,
+					&callExpr{
+						object: &identifierExpr{"g"},
+						name:   "get",
+						args: []starlarkExpr{
+							expr,
+							&stringLiteralExpr{literal: ""},
+						},
+						returnType: starlarkTypeUnknown,
+					},
+				},
+				returnType: starlarkTypeUnknown,
+			}
+		} else {
+			return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		}
 	}
 
 	if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
@@ -1574,11 +1595,21 @@
 		}
 	}
 
-	return &foreachExpr{
+	var result starlarkExpr = &foreachExpr{
 		varName: loopVarName,
 		list:    list,
 		action:  action,
 	}
+
+	if action.typ() == starlarkTypeList {
+		result = &callExpr{
+			name:       baseName + ".flatten_2d_list",
+			args:       []starlarkExpr{result},
+			returnType: starlarkTypeList,
+		}
+	}
+
+	return result
 }
 
 func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 7f236bb..a09764c 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -579,7 +579,7 @@
     pass
   if rblf.expand_wildcard("foo*.mk"):
     pass
-  if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:␤
+  if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:
     pass
 `,
 	},
@@ -1363,6 +1363,8 @@
 BOOT_KERNEL_MODULES_LIST := foo.ko
 BOOT_KERNEL_MODULES_LIST += bar.ko
 BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+NESTED_LISTS := $(foreach m,$(SOME_VAR),$(BOOT_KERNEL_MODULES_LIST))
+NESTED_LISTS_2 := $(foreach x,$(SOME_VAR),$(foreach y,$(x),prefix$(y)))
 
 FOREACH_WITH_IF := $(foreach module,\
   $(BOOT_KERNEL_MODULES_LIST),\
@@ -1382,6 +1384,8 @@
   g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
   g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
   g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+  g["NESTED_LISTS"] = rblf.flatten_2d_list([g["BOOT_KERNEL_MODULES_LIST"] for m in rblf.words(g.get("SOME_VAR", ""))])
+  g["NESTED_LISTS_2"] = rblf.flatten_2d_list([["prefix%s" % y for y in rblf.words(x)] for x in rblf.words(g.get("SOME_VAR", ""))])
   g["FOREACH_WITH_IF"] = [("" if rblf.filter(module, "foo.ko") else rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)) for module in g["BOOT_KERNEL_MODULES_LIST"]]
   # Same as above, but not assigning it to a variable allows it to be converted to statements
   for module in g["BOOT_KERNEL_MODULES_LIST"]:
@@ -1574,10 +1578,10 @@
   for x in rblf.words(g.get("MY_LIST_VAR", "")):
     _entry = {
       "foo/font.mk": ("foo/font", _font_init),
-    }.get("foo/%s.mk" % _x)
+    }.get("foo/%s.mk" % x)
     (_varmod, _varmod_init) = _entry if _entry else (None, None)
     if not _varmod_init:
-      rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % _x))
+      rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % x))
     _varmod_init(g, handle)
 `,
 	},
@@ -1595,6 +1599,27 @@
   g["MY_VAR"] = "foo"
 `,
 	},
+	{
+		desc:   "Complicated variable references",
+		mkname: "product.mk",
+		in: `
+MY_VAR := foo
+MY_VAR_2 := MY_VAR
+MY_VAR_3 := $($(MY_VAR_2))
+MY_VAR_4 := $(foo bar)
+MY_VAR_5 := $($(MY_VAR_2) bar)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_VAR"] = "foo"
+  g["MY_VAR_2"] = "MY_VAR"
+  g["MY_VAR_3"] = (cfg).get(g["MY_VAR_2"], (g).get(g["MY_VAR_2"], ""))
+  g["MY_VAR_4"] = rblf.mk2rbc_error("product.mk:5", "cannot handle invoking foo")
+  g["MY_VAR_5"] = rblf.mk2rbc_error("product.mk:6", "reference is too complex: $(MY_VAR_2) bar")
+`,
+	},
 }
 
 var known_variables = []struct {
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index e49f3d4..d1cbd8f 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -36,7 +36,8 @@
 	mergeProvenanceMetaData = pctx.AndroidStaticRule("mergeProvenanceMetaData",
 		blueprint.RuleParams{
 			Command: `rm -rf $out $out.temp && ` +
-				`echo -e "# proto-file: build/soong/provenance/proto/provenance_metadata.proto\n# proto-message: ProvenanceMetaDataList" > $out && ` +
+				`echo "# proto-file: build/soong/provenance/proto/provenance_metadata.proto" > $out && ` +
+				`echo "# proto-message: ProvenanceMetaDataList" >> $out && ` +
 				`touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
 		})
 )
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
index b33f911..f3f4d1f 100644
--- a/provenance/tools/gen_provenance_metadata.py
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -16,6 +16,7 @@
 
 import argparse
 import hashlib
+import os.path
 import sys
 
 import google.protobuf.text_format as text_format
@@ -51,6 +52,11 @@
     h.update(artifact_file.read())
   provenance_metadata.artifact_sha256 = h.hexdigest()
 
+  Log("Check if there is attestation for the artifact")
+  attestation_file_name = args.artifact_path + ".intoto.jsonl"
+  if os.path.isfile(attestation_file_name):
+    provenance_metadata.attestation_path = attestation_file_name
+
   text_proto = [
       "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
       "# proto-message: ProvenanceMetaData",
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
index 2fc04bf..1f69b8f 100644
--- a/provenance/tools/gen_provenance_metadata_test.py
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -100,6 +100,11 @@
     artifact_file = tempfile.mktemp()
     with open(artifact_file,"wt") as f:
       f.write(artifact_content)
+
+    attestation_file = artifact_file + ".intoto.jsonl"
+    with open(attestation_file, "wt") as af:
+      af.write("attestation file")
+
     metadata_file = tempfile.mktemp()
     cmd = ["gen_provenance_metadata"]
     cmd.extend(["--module_name", "a"])
@@ -117,9 +122,11 @@
       self.assertEqual(provenance_metadata.artifact_path, artifact_file)
       self.assertEqual(provenance_metadata.artifact_install_path, "b")
       self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content))
+      self.assertEqual(provenance_metadata.attestation_path, attestation_file)
 
     os.remove(artifact_file)
     os.remove(metadata_file)
+    os.remove(attestation_file)
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
\ No newline at end of file
diff --git a/scripts/hiddenapi/generate_hiddenapi_lists.py b/scripts/hiddenapi/generate_hiddenapi_lists.py
index 35e0948..6546c7f 100755
--- a/scripts/hiddenapi/generate_hiddenapi_lists.py
+++ b/scripts/hiddenapi/generate_hiddenapi_lists.py
@@ -27,6 +27,7 @@
 FLAG_MAX_TARGET_P = 'max-target-p'
 FLAG_MAX_TARGET_Q = 'max-target-q'
 FLAG_MAX_TARGET_R = 'max-target-r'
+FLAG_MAX_TARGET_S = 'max-target-s'
 FLAG_CORE_PLATFORM_API = 'core-platform-api'
 FLAG_PUBLIC_API = 'public-api'
 FLAG_SYSTEM_API = 'system-api'
@@ -41,6 +42,7 @@
     FLAG_MAX_TARGET_P,
     FLAG_MAX_TARGET_Q,
     FLAG_MAX_TARGET_R,
+    FLAG_MAX_TARGET_S,
 ]
 ALL_FLAGS = FLAGS_API_LIST + [
     FLAG_CORE_PLATFORM_API,
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index a99fa1f..9d0c3de 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -796,6 +796,44 @@
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
+		checkInfoContents(`
+[
+  {
+    "@type": "sdk",
+    "@name": "mysdk",
+    "java_header_libs": [
+      "exported-system-module",
+      "system-module"
+    ],
+    "java_sdk_libs": [
+      "myjavalib"
+    ],
+    "java_system_modules": [
+      "my-system-modules"
+    ]
+  },
+  {
+    "@type": "java_library",
+    "@name": "exported-system-module"
+  },
+  {
+    "@type": "java_system_modules",
+    "@name": "my-system-modules",
+    "@deps": [
+      "exported-system-module",
+      "system-module"
+    ]
+  },
+  {
+    "@type": "java_sdk_library",
+    "@name": "myjavalib"
+  },
+  {
+    "@type": "java_library",
+    "@name": "system-module"
+  }
+]
+`),
 	)
 }
 
diff --git a/sdk/sdk.go b/sdk/sdk.go
index c8c7b79..b37eaad 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -75,6 +75,8 @@
 
 	snapshotFile android.OptionalPath
 
+	infoFile android.OptionalPath
+
 	// The builder, preserved for testing.
 	builderForTests *snapshotBuilder
 }
@@ -191,27 +193,32 @@
 		}
 
 		// Generate the snapshot from the member info.
-		p := s.buildSnapshot(ctx, sdkVariants)
-		zip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), p.Base(), p)
-		s.snapshotFile = android.OptionalPathForPath(zip)
+		s.buildSnapshot(ctx, sdkVariants)
 	}
 }
 
 func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
-	if !s.snapshotFile.Valid() {
+	if !s.snapshotFile.Valid() != !s.infoFile.Valid() {
+		panic("Snapshot (%q) and info file (%q) should both be set or neither should be set.")
+	} else if !s.snapshotFile.Valid() {
 		return []android.AndroidMkEntries{}
 	}
 
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "FAKE",
 		OutputFile: s.snapshotFile,
-		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path()),
+		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path(), s.infoFile.Path()),
 		Include:    "$(BUILD_PHONY_PACKAGE)",
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string) {
 				// Allow the sdk to be built by simply passing its name on the command line.
 				fmt.Fprintln(w, ".PHONY:", s.Name())
 				fmt.Fprintln(w, s.Name()+":", s.snapshotFile.String())
+
+				// Allow the sdk info to be built by simply passing its name on the command line.
+				infoTarget := s.Name() + ".info"
+				fmt.Fprintln(w, ".PHONY:", infoTarget)
+				fmt.Fprintln(w, infoTarget+":", s.infoFile.String())
 			},
 		},
 	}}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 40de150..ccbeb8d 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -263,7 +263,10 @@
 	result := testSdkWithFs(t, sdk, nil)
 
 	CheckSnapshot(t, result, "mysdk", "",
-		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`))
+		checkAllOtherCopyRules(`
+.intermediates/mysdk/common_os/mysdk-current.info -> mysdk-current.info
+.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip
+`))
 }
 
 type EmbeddedPropertiesStruct struct {
diff --git a/sdk/testing.go b/sdk/testing.go
index 062f200..b0f5fdc 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -142,6 +142,7 @@
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
+		infoContents:                 sdk.GetInfoContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
 		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
@@ -402,6 +403,17 @@
 	}
 }
 
+// Check that the snapshot's info contents are ciorrect.
+//
+// Both the expected and actual string are both trimmed before comparing.
+func checkInfoContents(expected string) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "info contents do not match",
+			expected, info.infoContents)
+	}
+}
+
 type resultChecker func(t *testing.T, result *android.TestResult)
 
 // snapshotTestPreparer registers a preparer that will be used to customize the specified
@@ -479,6 +491,9 @@
 	// The contents of the versioned Android.bp file
 	androidVersionedBpContents string
 
+	// The contents of the info file.
+	infoContents string
+
 	// The paths, relative to the snapshot root, of all files and directories copied into the
 	// snapshot.
 	snapshotContents []string
diff --git a/sdk/update.go b/sdk/update.go
index 5db604b..71bd042 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -15,6 +15,8 @@
 package sdk
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"sort"
@@ -219,9 +221,19 @@
 				exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
 			}
 
+			var container android.SdkAware
+			if parent != ctx.Module() {
+				container = parent.(android.SdkAware)
+			}
+
 			export := memberTag.ExportMember()
 			s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
-				s, memberType, child.(android.SdkAware), export, exportedComponentsInfo,
+				sdkVariant:             s,
+				memberType:             memberType,
+				variant:                child.(android.SdkAware),
+				container:              container,
+				export:                 export,
+				exportedComponentsInfo: exportedComponentsInfo,
 			})
 
 			// Recurse down into the member's dependencies as it may have dependencies that need to be
@@ -311,7 +323,7 @@
 
 // buildSnapshot is the main function in this source file. It creates rules to copy
 // the contents (header files, stub libraries, etc) into the zip file.
-func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) android.OutputPath {
+func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) {
 
 	// Aggregate all the sdkMemberVariantDep instances from all the sdk variants.
 	hasLicenses := false
@@ -370,9 +382,9 @@
 	// Unversioned modules are not required in that case because the numbered version will be a
 	// finalized version of the snapshot that is intended to be kept separate from the
 	generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
-	snapshotZipFileSuffix := ""
+	snapshotFileSuffix := ""
 	if generateVersioned {
-		snapshotZipFileSuffix = "-" + version
+		snapshotFileSuffix = "-" + version
 	}
 
 	currentBuildRelease := latestBuildRelease()
@@ -489,7 +501,7 @@
 	filesToZip := builder.filesToZip
 
 	// zip them all
-	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotFileSuffix)
 	outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
 	outputDesc := "Building snapshot for " + ctx.ModuleName()
 
@@ -502,7 +514,7 @@
 		zipFile = outputZipFile
 		desc = outputDesc
 	} else {
-		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotFileSuffix)
 		zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
 		desc = "Building intermediate snapshot for " + ctx.ModuleName()
 	}
@@ -527,7 +539,120 @@
 		})
 	}
 
-	return outputZipFile
+	modules := s.generateInfoData(ctx, memberVariantDeps)
+
+	// Output the modules information as pretty printed JSON.
+	info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
+	output, err := json.MarshalIndent(modules, "", "  ")
+	if err != nil {
+		ctx.ModuleErrorf("error generating %q: %s", info, err)
+	}
+	builder.infoContents = string(output)
+	info.generatedContents.UnindentedPrintf("%s", output)
+	info.build(pctx, ctx, nil)
+	infoPath := info.path
+	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath)
+	s.infoFile = android.OptionalPathForPath(installedInfo)
+
+	// Install the zip, making sure that the info file has been installed as well.
+	installedZip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), outputZipFile.Base(), outputZipFile, installedInfo)
+	s.snapshotFile = android.OptionalPathForPath(installedZip)
+}
+
+type moduleInfo struct {
+	// The type of the module, e.g. java_sdk_library
+	moduleType string
+	// The name of the module.
+	name string
+	// A list of additional dependencies of the module.
+	deps []string
+	// Additional dynamic properties.
+	dynamic map[string]interface{}
+}
+
+func (m *moduleInfo) MarshalJSON() ([]byte, error) {
+	buffer := bytes.Buffer{}
+
+	separator := ""
+	writeObjectPair := func(key string, value interface{}) {
+		buffer.WriteString(fmt.Sprintf("%s%q: ", separator, key))
+		b, err := json.Marshal(value)
+		if err != nil {
+			panic(err)
+		}
+		buffer.Write(b)
+		separator = ","
+	}
+
+	buffer.WriteString("{")
+	writeObjectPair("@type", m.moduleType)
+	writeObjectPair("@name", m.name)
+	if m.deps != nil {
+		writeObjectPair("@deps", m.deps)
+	}
+	for _, k := range android.SortedStringKeys(m.dynamic) {
+		v := m.dynamic[k]
+		writeObjectPair(k, v)
+	}
+	buffer.WriteString("}")
+	return buffer.Bytes(), nil
+}
+
+var _ json.Marshaler = (*moduleInfo)(nil)
+
+// generateInfoData creates a list of moduleInfo structures that will be marshalled into JSON.
+func (s *sdk) generateInfoData(ctx android.ModuleContext, memberVariantDeps []sdkMemberVariantDep) interface{} {
+	modules := []*moduleInfo{}
+	sdkInfo := moduleInfo{
+		moduleType: "sdk",
+		name:       ctx.ModuleName(),
+		dynamic:    map[string]interface{}{},
+	}
+	modules = append(modules, &sdkInfo)
+
+	name2Info := map[string]*moduleInfo{}
+	getModuleInfo := func(module android.Module) *moduleInfo {
+		name := module.Name()
+		info := name2Info[name]
+		if info == nil {
+			moduleType := ctx.OtherModuleType(module)
+			// Remove any suffix added when creating modules dynamically.
+			moduleType = strings.Split(moduleType, "__")[0]
+			info = &moduleInfo{
+				moduleType: moduleType,
+				name:       name,
+			}
+			name2Info[name] = info
+		}
+		return info
+	}
+
+	for _, memberVariantDep := range memberVariantDeps {
+		propertyName := memberVariantDep.memberType.SdkPropertyName()
+		var list []string
+		if v, ok := sdkInfo.dynamic[propertyName]; ok {
+			list = v.([]string)
+		}
+
+		memberName := memberVariantDep.variant.Name()
+		list = append(list, memberName)
+		sdkInfo.dynamic[propertyName] = android.SortedUniqueStrings(list)
+
+		if memberVariantDep.container != nil {
+			containerInfo := getModuleInfo(memberVariantDep.container)
+			containerInfo.deps = android.SortedUniqueStrings(append(containerInfo.deps, memberName))
+		}
+
+		// Make sure that the module info is created for each module.
+		getModuleInfo(memberVariantDep.variant)
+	}
+
+	for _, memberName := range android.SortedStringKeys(name2Info) {
+		info := name2Info[memberName]
+		modules = append(modules, info)
+	}
+
+	return modules
 }
 
 // filterOutComponents removes any item from the deps list that is a component of another item in
@@ -1033,6 +1158,10 @@
 	return contents.content.String()
 }
 
+func (s *sdk) GetInfoContentsForTests() string {
+	return s.builderForTests.infoContents
+}
+
 func (s *sdk) GetUnversionedAndroidBpContentsForTests() string {
 	contents := &generatedContents{}
 	generateFilteredBpContents(contents, s.builderForTests.bpFile, func(module *bpModule) bool {
@@ -1087,6 +1216,8 @@
 
 	// The target build release for which the snapshot is to be generated.
 	targetBuildRelease *buildRelease
+
+	infoContents string
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -1322,6 +1453,11 @@
 	// The variant that is added to the sdk.
 	variant android.SdkAware
 
+	// The optional container of this member, i.e. the module that is depended upon by the sdk
+	// (possibly transitively) and whose dependency on this module is why it was added to the sdk.
+	// Is nil if this a direct dependency of the sdk.
+	container android.SdkAware
+
 	// True if the member should be exported, i.e. accessible, from outside the sdk.
 	export bool
 
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 285f156..f56964c 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -245,8 +245,6 @@
 		"BUILD_BROKEN_USES_BUILD_EXECUTABLE",
 		"BUILD_BROKEN_USES_BUILD_FUZZ_TEST",
 		"BUILD_BROKEN_USES_BUILD_HEADER_LIBRARY",
-		"BUILD_BROKEN_USES_BUILD_HOST_DALVIK_JAVA_LIBRARY",
-		"BUILD_BROKEN_USES_BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_HOST_EXECUTABLE",
 		"BUILD_BROKEN_USES_BUILD_HOST_JAVA_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_HOST_PREBUILT",
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 831a80f..b3092ea 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -31,18 +31,25 @@
 	LinuxOnlyPrebuilt bool
 }
 
+// These binaries can be run from $PATH, nonhermetically. There should be as
+// few as possible of these, since this means that the build depends on tools
+// that are not shipped in the source tree and whose behavior is therefore
+// unpredictable.
 var Allowed = PathConfig{
 	Symlink: true,
 	Log:     false,
 	Error:   false,
 }
 
+// This tool is specifically disallowed and calling it will result in an
+// "executable no found" error.
 var Forbidden = PathConfig{
 	Symlink: false,
 	Log:     true,
 	Error:   true,
 }
 
+// This tool is allowed, but access to it will be logged.
 var Log = PathConfig{
 	Symlink: true,
 	Log:     true,
@@ -52,13 +59,16 @@
 // The configuration used if the tool is not listed in the config below.
 // Currently this will create the symlink, but log and error when it's used. In
 // the future, I expect the symlink to be removed, and this will be equivalent
-// to Forbidden.
+// to Forbidden. This applies to every tool not specifically mentioned in the
+// configuration.
 var Missing = PathConfig{
 	Symlink: true,
 	Log:     true,
 	Error:   true,
 }
 
+// This is used for binaries for which we have prebuilt versions, but only for
+// Linux. Thus, their execution from $PATH is only allowed on Mac OS.
 var LinuxOnlyPrebuilt = PathConfig{
 	Symlink:           false,
 	Log:               true,
@@ -73,6 +83,8 @@
 	return Missing
 }
 
+// This list specifies whether a particular binary from $PATH is allowed to be
+// run during the build. For more documentation, see path_interposer.go .
 var Configuration = map[string]PathConfig{
 	"bash":    Allowed,
 	"dd":      Allowed,
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 9f9b863..4bc713b 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -1303,7 +1303,7 @@
 
 	// Modules that are enabled for Mixed Builds.
 	MixedBuildEnabledModules []string `protobuf:"bytes,1,rep,name=mixed_build_enabled_modules,json=mixedBuildEnabledModules" json:"mixed_build_enabled_modules,omitempty"`
-	// Modules that are not currently eligible for MixedBuilds
+	// Modules that are not enabled for MixedBuilds
 	MixedBuildDisabledModules []string `protobuf:"bytes,2,rep,name=mixed_build_disabled_modules,json=mixedBuildDisabledModules" json:"mixed_build_disabled_modules,omitempty"`
 }