Merge "Allow override of -Wno-error=format" into main
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
index 1378dfe..a46ce52 100644
--- a/aconfig/codegen/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -76,7 +76,7 @@
 	}
 }
 
-func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) GenerateSourceJarBuildActions(module *java.GeneratedJavaLibraryModule, ctx android.ModuleContext) android.Path {
+func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) GenerateSourceJarBuildActions(module *java.GeneratedJavaLibraryModule, ctx android.ModuleContext) (android.Path, android.Path) {
 	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
 	declarationsModules := ctx.GetDirectDepsWithTag(declarationsTag)
 	if len(declarationsModules) != 1 {
@@ -129,7 +129,11 @@
 			}},
 	})
 
-	return srcJarPath
+	return srcJarPath, declarations.IntermediateCacheOutputPath
+}
+
+func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) AconfigDeclarations() *string {
+	return proptools.StringPtr(callbacks.properties.Aconfig_declarations)
 }
 
 func isModeSupported(mode string) bool {
diff --git a/android/defs.go b/android/defs.go
index a34d302..78cdea2 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -103,16 +103,6 @@
 			Description: "concatenate files to $out",
 		})
 
-	// ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command
-	// doesn't support -e option. Therefore we force to use /bin/bash when writing out
-	// content to file.
-	writeFile = pctx.AndroidStaticRule("writeFile",
-		blueprint.RuleParams{
-			Command:     `rm -f $out && /bin/bash -c 'echo -e -n "$$0" > $out' $content`,
-			Description: "writing file $out",
-		},
-		"content")
-
 	// Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value
 	localPool = blueprint.NewBuiltinPool("local_pool")
 
diff --git a/android/paths.go b/android/paths.go
index a40f482..2b33f67 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -673,7 +673,8 @@
 		expandedSrcFiles = append(expandedSrcFiles, srcFiles...)
 	}
 
-	return expandedSrcFiles, append(missingDeps, missingExcludeDeps...)
+	// TODO: b/334169722 - Replace with an error instead of implicitly removing duplicates.
+	return FirstUniquePaths(expandedSrcFiles), append(missingDeps, missingExcludeDeps...)
 }
 
 type missingDependencyError struct {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 1e75948..2441b02 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -7114,8 +7114,9 @@
 		"etc/permissions/foo.xml",
 	})
 	// Permission XML should point to the activated path of impl jar of java_sdk_library
-	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_myapex").Rule("java_sdk_xml")
-	ensureMatches(t, sdkLibrary.RuleParams.Command, `<library\\n\s+name=\\\"foo\\\"\\n\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"`)
+	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_myapex").Output("foo.xml")
+	contents := android.ContentFromFileRuleForTests(t, ctx, sdkLibrary)
+	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
 }
 
 func TestJavaSDKLibrary_WithinApex(t *testing.T) {
@@ -11404,3 +11405,121 @@
 		checkHideFromMake(t, ctx, tc.expectedVisibleModuleName, tc.expectedHiddenModuleNames)
 	}
 }
+
+func TestAconfifDeclarationsValidation(t *testing.T) {
+	aconfigDeclarationLibraryString := func(moduleNames []string) (ret string) {
+		for _, moduleName := range moduleNames {
+			ret += fmt.Sprintf(`
+			aconfig_declarations {
+				name: "%[1]s",
+				package: "com.example.package",
+				srcs: [
+					"%[1]s.aconfig",
+				],
+			}
+			java_aconfig_library {
+				name: "%[1]s-lib",
+				aconfig_declarations: "%[1]s",
+			}
+			`, moduleName)
+		}
+		return ret
+	}
+
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.SetApiLibraries([]string{"foo"})
+		}),
+	).RunTestWithBp(t, `
+		java_library {
+			name: "baz-java-lib",
+			static_libs: [
+				"baz-lib",
+			],
+		}
+		filegroup {
+			name: "qux-filegroup",
+			srcs: [
+				":qux-lib{.generated_srcjars}",
+			],
+		}
+		filegroup {
+			name: "qux-another-filegroup",
+			srcs: [
+				":qux-filegroup",
+			],
+		}
+		java_library {
+			name: "quux-java-lib",
+			srcs: [
+				"a.java",
+			],
+			libs: [
+				"quux-lib",
+			],
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: [
+				":qux-another-filegroup",
+			],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
+			test: {
+				enabled: true,
+			},
+			static_libs: [
+				"bar-lib",
+			],
+			libs: [
+				"baz-java-lib",
+				"quux-java-lib",
+			],
+			aconfig_declarations: [
+				"bar",
+			],
+		}
+	`+aconfigDeclarationLibraryString([]string{"bar", "baz", "qux", "quux"}))
+
+	m := result.ModuleForTests("foo.stubs.source", "android_common")
+	outDir := "out/soong/.intermediates"
+
+	// Arguments passed to aconfig to retrieve the state of the flags defined in the
+	// textproto files
+	aconfigFlagArgs := m.Output("released-flagged-apis-exportable.txt").Args["flags_path"]
+
+	// "bar-lib" is a static_lib of "foo" and is passed to metalava as classpath. Thus the
+	// cache file provided by the associated aconfig_declarations module "bar" should be passed
+	// to aconfig.
+	android.AssertStringDoesContain(t, "cache file of a java_aconfig_library static_lib "+
+		"passed as an input",
+		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "bar"))
+
+	// "baz-java-lib", which statically depends on "baz-lib", is a lib of "foo" and is passed
+	// to metalava as classpath. Thus the cache file provided by the associated
+	// aconfig_declarations module "baz" should be passed to aconfig.
+	android.AssertStringDoesContain(t, "cache file of a lib that statically depends on "+
+		"java_aconfig_library passed as an input",
+		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "baz"))
+
+	// "qux-lib" is passed to metalava as src via the filegroup, thus the cache file provided by
+	// the associated aconfig_declarations module "qux" should be passed to aconfig.
+	android.AssertStringDoesContain(t, "cache file of srcs java_aconfig_library passed as an "+
+		"input",
+		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "qux"))
+
+	// "quux-java-lib" is a lib of "foo" and is passed to metalava as classpath, but does not
+	// statically depend on "quux-lib". Therefore, the cache file provided by the associated
+	// aconfig_declarations module "quux" should not be passed to aconfig.
+	android.AssertStringDoesNotContain(t, "cache file of a lib that does not statically "+
+		"depend on java_aconfig_library not passed as an input",
+		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "quux"))
+}
diff --git a/cc/Android.bp b/cc/Android.bp
index 8c86eb7..5ba9427 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -91,6 +91,7 @@
         "afdo_test.go",
         "binary_test.go",
         "cc_test.go",
+        "cc_test_only_property_test.go",
         "compiler_test.go",
         "gen_test.go",
         "genrule_test.go",
diff --git a/cc/cc.go b/cc/cc.go
index 69ecc78..6cbf458 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -845,6 +845,7 @@
 
 	VendorProperties VendorProperties
 	Properties       BaseProperties
+	sourceProperties android.SourceProperties
 
 	// initialize before calling Init
 	hod        android.HostOrDeviceSupported
@@ -1262,6 +1263,10 @@
 	for _, feature := range c.features {
 		c.AddProperties(feature.props()...)
 	}
+	// Allow test-only on libraries that are not cc_test_library
+	if c.library != nil && !c.testLibrary() {
+		c.AddProperties(&c.sourceProperties)
+	}
 
 	android.InitAndroidArchModule(c, c.hod, c.multilib)
 	android.InitApexModule(c)
@@ -2135,6 +2140,18 @@
 	if c.testModule {
 		android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 	}
+
+	// If Test_only is set on a module in bp file, respect the setting, otherwise
+	// see if is a known test module type.
+	testOnly := c.testModule || c.testLibrary()
+	if c.sourceProperties.Test_only != nil {
+		testOnly = Bool(c.sourceProperties.Test_only)
+	}
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       testOnly,
+		TopLevelTarget: c.testModule,
+	})
+
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: deps.GeneratedSources.Strings()})
 
 	android.CollectDependencyAconfigFiles(ctx, &c.mergedAconfigFiles)
diff --git a/cc/cc_test_only_property_test.go b/cc/cc_test_only_property_test.go
new file mode 100644
index 0000000..972e86b
--- /dev/null
+++ b/cc/cc_test_only_property_test.go
@@ -0,0 +1,187 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"android/soong/android"
+	"android/soong/android/team_proto"
+	"log"
+	"strings"
+	"testing"
+
+	"github.com/google/blueprint"
+	"google.golang.org/protobuf/proto"
+)
+
+func TestTestOnlyProvider(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+			ctx.RegisterModuleType("cc_test_host", TestHostFactory)
+		}),
+	).RunTestWithBp(t, `
+                // These should be test-only
+                cc_fuzz { name: "cc-fuzz" }
+                cc_test { name: "cc-test", gtest:false }
+                cc_benchmark { name: "cc-benchmark" }
+                cc_library { name: "cc-library-forced",
+                             test_only: true }
+                cc_test_library {name: "cc-test-library", gtest: false}
+                cc_test_host {name: "cc-test-host", gtest: false}
+
+                // These should not be.
+                cc_genrule { name: "cc_genrule", cmd: "echo foo", out: ["out"] }
+                cc_library { name: "cc_library" }
+                cc_library { name: "cc_library_false", test_only: false }
+                cc_library_static { name: "cc_static" }
+                cc_library_shared { name: "cc_library_shared" }
+
+                cc_object { name: "cc-object" }
+	`)
+
+	// Visit all modules and ensure only the ones that should
+	// marked as test-only are marked as test-only.
+
+	actualTestOnly := []string{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok {
+			if provider.TestOnly {
+				actualTestOnly = append(actualTestOnly, m.Name())
+			}
+		}
+	})
+	expectedTestOnlyModules := []string{
+		"cc-test",
+		"cc-library-forced",
+		"cc-fuzz",
+		"cc-benchmark",
+		"cc-test-library",
+		"cc-test-host",
+	}
+
+	notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly)
+	if notEqual {
+		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
+	}
+}
+
+func TestTestOnlyInTeamsProto(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		android.PrepareForTestWithTeamBuildComponents,
+		prepareForCcTest,
+		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+			ctx.RegisterParallelSingletonType("all_teams", android.AllTeamsFactory)
+			ctx.RegisterModuleType("cc_test_host", TestHostFactory)
+
+		}),
+	).RunTestWithBp(t, `
+                package { default_team: "someteam"}
+
+                // These should be test-only
+                cc_fuzz { name: "cc-fuzz" }
+                cc_test { name: "cc-test", gtest:false }
+                cc_benchmark { name: "cc-benchmark" }
+                cc_library { name: "cc-library-forced",
+                             test_only: true }
+                cc_test_library {name: "cc-test-library", gtest: false}
+                cc_test_host {name: "cc-test-host", gtest: false}
+
+                // These should not be.
+                cc_genrule { name: "cc_genrule", cmd: "echo foo", out: ["out"] }
+                cc_library { name: "cc_library" }
+                cc_library_static { name: "cc_static" }
+                cc_library_shared { name: "cc_library_shared" }
+
+                cc_object { name: "cc-object" }
+		team {
+			name: "someteam",
+			trendy_team_id: "cool_team",
+		}
+	`)
+
+	var teams *team_proto.AllTeams
+	teams = getTeamProtoOutput(t, ctx)
+
+	// map of module name -> trendy team name.
+	actualTrueModules := []string{}
+	for _, teamProto := range teams.Teams {
+		if Bool(teamProto.TestOnly) {
+			actualTrueModules = append(actualTrueModules, teamProto.GetTargetName())
+		}
+	}
+	expectedTestOnlyModules := []string{
+		"cc-test",
+		"cc-library-forced",
+		"cc-fuzz",
+		"cc-benchmark",
+		"cc-test-library",
+		"cc-test-host",
+	}
+
+	notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTrueModules)
+	if notEqual {
+		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
+	}
+}
+
+// Don't allow setting test-only on things that are always tests or never tests.
+func TestInvalidTestOnlyTargets(t *testing.T) {
+	testCases := []string{
+		` cc_test {  name: "cc-test", test_only: true, gtest: false, srcs: ["foo.cc"],  } `,
+		` cc_binary {  name: "cc-binary", test_only: true, srcs: ["foo.cc"],  } `,
+		` cc_test_library {name: "cc-test-library", test_only: true, gtest: false} `,
+		` cc_test_host {name: "cc-test-host", test_only: true, gtest: false} `,
+		` cc_defaults {name: "cc-defaults", test_only: true} `,
+	}
+
+	for i, bp := range testCases {
+		ctx := android.GroupFixturePreparers(
+			prepareForCcTest,
+			android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+				ctx.RegisterModuleType("cc_test_host", TestHostFactory)
+			})).
+			ExtendWithErrorHandler(android.FixtureIgnoreErrors).
+			RunTestWithBp(t, bp)
+		if len(ctx.Errs) == 0 {
+			t.Errorf("Expected err setting test_only in testcase #%d", i)
+		}
+		if len(ctx.Errs) > 1 {
+			t.Errorf("Too many errs: [%s] %v", bp, ctx.Errs)
+		}
+
+		if len(ctx.Errs) == 1 {
+			if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") {
+				t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp)
+			}
+		}
+	}
+}
+
+func getTeamProtoOutput(t *testing.T, ctx *android.TestResult) *team_proto.AllTeams {
+	teams := new(team_proto.AllTeams)
+	config := ctx.SingletonForTests("all_teams")
+	allOutputs := config.AllOutputs()
+
+	protoPath := allOutputs[0]
+
+	out := config.MaybeOutput(protoPath)
+	outProto := []byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, out))
+	if err := proto.Unmarshal(outProto, teams); err != nil {
+		log.Fatalln("Failed to parse teams proto:", err)
+	}
+	return teams
+}
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index fb81e42..beb68e19 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -103,6 +103,8 @@
 		flags := arm64Cflags
 		if ctx.Config().NoBionicPageSizeMacro() {
 			flags = append(flags, "-D__BIONIC_NO_PAGE_SIZE_MACRO")
+		} else {
+			flags = append(flags, "-D__BIONIC_DEPRECATED_PAGE_SIZE_MACRO")
 		}
 		return strings.Join(flags, " ")
 	})
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index 171ab4f..5aa2a7e 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -112,6 +112,8 @@
 		flags := x86_64Cflags
 		if ctx.Config().NoBionicPageSizeMacro() {
 			flags = append(flags, "-D__BIONIC_NO_PAGE_SIZE_MACRO")
+		} else {
+			flags = append(flags, "-D__BIONIC_DEPRECATED_PAGE_SIZE_MACRO")
 		}
 		return strings.Join(flags, " ")
 	})
diff --git a/java/aar.go b/java/aar.go
index fef0d8c..a366267 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -922,7 +922,8 @@
 	module.Module.addHostAndDeviceProperties()
 	module.AddProperties(
 		&module.aaptProperties,
-		&module.androidLibraryProperties)
+		&module.androidLibraryProperties,
+		&module.sourceProperties)
 
 	module.androidLibraryProperties.BuildAAR = true
 	module.Module.linter.library = true
@@ -978,6 +979,7 @@
 
 	headerJarFile                      android.WritablePath
 	implementationJarFile              android.WritablePath
+	implementationAndResourcesJarFile  android.WritablePath
 	proguardFlags                      android.WritablePath
 	exportPackage                      android.WritablePath
 	transitiveAaptResourcePackagesFile android.Path
@@ -1013,7 +1015,7 @@
 	case ".aar":
 		return []android.Path{a.aarPath}, nil
 	case "":
-		return []android.Path{a.implementationJarFile}, nil
+		return []android.Path{a.implementationAndResourcesJarFile}, nil
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 	}
@@ -1155,8 +1157,9 @@
 		TransformJetifier(ctx, a.aarPath.(android.WritablePath), inputFile)
 	}
 
+	jarName := ctx.ModuleName() + ".jar"
 	extractedAARDir := android.PathForModuleOut(ctx, "aar")
-	classpathFile := extractedAARDir.Join(ctx, ctx.ModuleName()+".jar")
+	classpathFile := extractedAARDir.Join(ctx, jarName)
 	a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
 	a.rTxt = extractedAARDir.Join(ctx, "R.txt")
 	a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
@@ -1272,6 +1275,7 @@
 
 	var staticJars android.Paths
 	var staticHeaderJars android.Paths
+	var staticResourceJars android.Paths
 	ctx.VisitDirectDeps(func(module android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			tag := ctx.OtherModuleDependencyTag(module)
@@ -1279,6 +1283,7 @@
 			case staticLibTag:
 				staticJars = append(staticJars, dep.ImplementationJars...)
 				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
+				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
 			}
 		}
 		addCLCFromDep(ctx, module, a.classLoaderContexts)
@@ -1287,18 +1292,39 @@
 	var implementationJarFile android.OutputPath
 	if len(staticJars) > 0 {
 		combineJars := append(android.Paths{classpathFile}, staticJars...)
-		implementationJarFile = android.PathForModuleOut(ctx, "combined", ctx.ModuleName()+".jar").OutputPath
+		implementationJarFile = android.PathForModuleOut(ctx, "combined", jarName).OutputPath
 		TransformJarsToJar(ctx, implementationJarFile, "combine", combineJars, android.OptionalPath{}, false, nil, nil)
 	} else {
 		implementationJarFile = classpathFile
 	}
 
+	var resourceJarFile android.Path
+	if len(staticResourceJars) > 1 {
+		combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName)
+		TransformJarsToJar(ctx, combinedJar, "for resources", staticResourceJars, android.OptionalPath{},
+			false, nil, nil)
+		resourceJarFile = combinedJar
+	} else if len(staticResourceJars) == 1 {
+		resourceJarFile = staticResourceJars[0]
+	}
+
+	// merge implementation jar with resources if necessary
+	implementationAndResourcesJar := implementationJarFile
+	if resourceJarFile != nil {
+		jars := android.Paths{resourceJarFile, implementationAndResourcesJar}
+		combinedJar := android.PathForModuleOut(ctx, "withres", jarName).OutputPath
+		TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{},
+			false, nil, nil)
+		implementationAndResourcesJar = combinedJar
+	}
+
+	a.implementationJarFile = implementationJarFile
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
-	a.implementationJarFile = implementationJarFile.WithoutRel()
+	a.implementationAndResourcesJarFile = implementationAndResourcesJar.WithoutRel()
 
 	if len(staticHeaderJars) > 0 {
 		combineJars := append(android.Paths{classpathFile}, staticHeaderJars...)
-		a.headerJarFile = android.PathForModuleOut(ctx, "turbine-combined", ctx.ModuleName()+".jar")
+		a.headerJarFile = android.PathForModuleOut(ctx, "turbine-combined", jarName)
 		TransformJarsToJar(ctx, a.headerJarFile, "combine header jars", combineJars, android.OptionalPath{}, false, nil, nil)
 	} else {
 		a.headerJarFile = classpathFile
@@ -1306,9 +1332,10 @@
 
 	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(a.headerJarFile),
+		ResourceJars:                   android.PathsIfNonNil(resourceJarFile),
 		TransitiveLibsHeaderJars:       a.transitiveLibsHeaderJars,
 		TransitiveStaticLibsHeaderJars: a.transitiveStaticLibsHeaderJars,
-		ImplementationAndResourcesJars: android.PathsIfNonNil(a.implementationJarFile),
+		ImplementationAndResourcesJars: android.PathsIfNonNil(a.implementationAndResourcesJarFile),
 		ImplementationJars:             android.PathsIfNonNil(a.implementationJarFile),
 		StubsLinkType:                  Implementation,
 		// TransitiveAconfigFiles: // TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
@@ -1346,7 +1373,7 @@
 }
 
 func (a *AARImport) ImplementationAndResourcesJars() android.Paths {
-	return android.Paths{a.implementationJarFile}
+	return android.Paths{a.implementationAndResourcesJarFile}
 }
 
 func (a *AARImport) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
diff --git a/java/aar_test.go b/java/aar_test.go
index 3361bf9..d6dbe3c 100644
--- a/java/aar_test.go
+++ b/java/aar_test.go
@@ -136,18 +136,19 @@
 		android_library {
 			name: "foo",
 			srcs: ["a.java"],
+			java_resources: ["foo.txt"],
 		}
 
 		android_library_import {
 			name: "bar",
-			aars: ["bar.aar"],
+			aars: ["bar_prebuilt.aar"],
 
 		}
 
 		android_library_import {
 			name: "baz",
-			aars: ["baz.aar"],
-			static_libs: ["bar"],
+			aars: ["baz_prebuilt.aar"],
+			static_libs: ["foo", "bar"],
 		}
 	`)
 
@@ -160,11 +161,11 @@
 	bazOutputPath := android.OutputFileForModule(android.PathContext(nil), baz.Module(), "")
 
 	android.AssertPathRelativeToTopEquals(t, "foo output path",
-		"out/soong/.intermediates/foo/android_common/javac/foo.jar", fooOutputPath)
+		"out/soong/.intermediates/foo/android_common/withres/foo.jar", fooOutputPath)
 	android.AssertPathRelativeToTopEquals(t, "bar output path",
 		"out/soong/.intermediates/bar/android_common/aar/bar.jar", barOutputPath)
 	android.AssertPathRelativeToTopEquals(t, "baz output path",
-		"out/soong/.intermediates/baz/android_common/combined/baz.jar", bazOutputPath)
+		"out/soong/.intermediates/baz/android_common/withres/baz.jar", bazOutputPath)
 
 	android.AssertStringEquals(t, "foo relative output path",
 		"foo.jar", fooOutputPath.Rel())
diff --git a/java/app.go b/java/app.go
old mode 100755
new mode 100644
index 7b25775..1aa3afe
--- a/java/app.go
+++ b/java/app.go
@@ -329,6 +329,10 @@
 		a.aapt.manifestValues.applicationId = *applicationId
 	}
 	a.generateAndroidBuildActions(ctx)
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly: true,
+	})
+
 }
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1191,7 +1195,8 @@
 	module.AddProperties(
 		&module.aaptProperties,
 		&module.appProperties,
-		&module.overridableAppProperties)
+		&module.overridableAppProperties,
+		&module.Library.sourceProperties)
 
 	module.usesLibrary.enforce = true
 
@@ -1340,6 +1345,11 @@
 		TestSuites:              a.testProperties.Test_suites,
 		IsHost:                  false,
 	})
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       true,
+		TopLevelTarget: true,
+	})
+
 }
 
 func (a *AndroidTest) FixTestConfig(ctx android.ModuleContext, testConfig android.Path) android.Path {
@@ -1532,9 +1542,13 @@
 	android.OverrideModuleBase
 }
 
-func (i *OverrideAndroidTest) GenerateAndroidBuildActions(_ android.ModuleContext) {
+func (i *OverrideAndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// All the overrides happen in the base module.
 	// TODO(jungjw): Check the base module type.
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       true,
+		TopLevelTarget: true,
+	})
 }
 
 // override_android_test is used to create an android_app module based on another android_test by overriding
diff --git a/java/app_test.go b/java/app_test.go
index 8262777..0c28000 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4432,6 +4432,44 @@
 	android.AssertBoolEquals(t, "dexpreopt should be disabled if optional_uses_libs does not have an implementation", true, dexpreopt == nil)
 }
 
+func TestTestOnlyApp(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+                // These should be test-only
+                android_test {
+                        name: "android-test",
+                }
+                android_test_helper_app {
+                        name: "helper-app",
+                }
+                override_android_test {
+                        name: "override-test",
+                        base: "android-app",
+                }
+                // And these should not be
+		android_app {
+			name: "android-app",
+                        srcs: ["b.java"],
+			sdk_version: "current",
+		}
+	`)
+
+	expectedTestOnly := []string{
+		"android-test",
+		"helper-app",
+		"override-test",
+	}
+
+	expectedTopLevel := []string{
+		"android-test",
+		"override-test",
+	}
+
+	assertTestOnlyAndTopLevel(t, ctx, expectedTestOnly, expectedTopLevel)
+}
+
 func TestAppStem(t *testing.T) {
 	ctx := testApp(t, `
 				android_app {
diff --git a/java/base.go b/java/base.go
index e2f20ce..ef61f1c 100644
--- a/java/base.go
+++ b/java/base.go
@@ -197,6 +197,9 @@
 	// Additional srcJars tacked in by GeneratedJavaLibraryModule
 	Generated_srcjars []android.Path `android:"mutated"`
 
+	// intermediate aconfig cache file tacked in by GeneratedJavaLibraryModule
+	Aconfig_Cache_files []android.Path `android:"mutated"`
+
 	// If true, then only the headers are built and not the implementation jar.
 	Headers_only *bool
 
@@ -432,6 +435,7 @@
 	deviceProperties DeviceProperties
 
 	overridableProperties OverridableProperties
+	sourceProperties      android.SourceProperties
 
 	// jar file containing header classes including static library dependencies, suitable for
 	// inserting into the bootclasspath/classpath of another compile
@@ -541,6 +545,11 @@
 	jarjarRenameRules map[string]string
 
 	stubsLinkType StubsLinkType
+
+	// Paths to the aconfig intermediate cache files that are provided by the
+	// java_aconfig_library or java_library modules that are statically linked
+	// to this module. Does not contain cache files from all transitive dependencies.
+	aconfigCacheFiles android.Paths
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -1197,6 +1206,8 @@
 	// final R classes from the app.
 	flags.classpath = append(android.CopyOf(extraClasspathJars), flags.classpath...)
 
+	j.aconfigCacheFiles = append(deps.aconfigProtoFiles, j.properties.Aconfig_Cache_files...)
+
 	// If compiling headers then compile them and skip the rest
 	if proptools.Bool(j.properties.Headers_only) {
 		if srcFiles.HasExt(".kt") {
@@ -1736,7 +1747,7 @@
 		ExportedPluginDisableTurbine:        j.exportedDisableTurbine,
 		JacocoReportClassesFile:             j.jacocoReportClassesFile,
 		StubsLinkType:                       j.stubsLinkType,
-		AconfigIntermediateCacheOutputPaths: deps.aconfigProtoFiles,
+		AconfigIntermediateCacheOutputPaths: j.aconfigCacheFiles,
 	})
 
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
@@ -2350,7 +2361,10 @@
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs()...)
 			}
 		} else if dep, ok := android.OtherModuleProvider(ctx, module, android.CodegenInfoProvider); ok {
-			deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
+			switch tag {
+			case staticLibTag:
+				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
+			}
 		} else {
 			switch tag {
 			case bootClasspathTag:
diff --git a/java/device_host_converter_test.go b/java/device_host_converter_test.go
index 3413da0..6ccc5c1 100644
--- a/java/device_host_converter_test.go
+++ b/java/device_host_converter_test.go
@@ -16,7 +16,7 @@
 
 import (
 	"android/soong/android"
-	"reflect"
+	"slices"
 	"strings"
 	"testing"
 )
@@ -84,7 +84,7 @@
 		deviceImportCombined.Output,
 	}
 
-	if !reflect.DeepEqual(combined.Inputs, expectedInputs) {
+	if !slices.Equal(combined.Inputs.Strings(), expectedInputs.Strings()) {
 		t.Errorf("expected host_module combined inputs:\n%q\ngot:\n%q",
 			expectedInputs, combined.Inputs)
 	}
@@ -95,7 +95,7 @@
 		deviceRes.Output,
 	}
 
-	if !reflect.DeepEqual(resCombined.Inputs, expectedInputs) {
+	if !slices.Equal(resCombined.Inputs.Strings(), expectedInputs.Strings()) {
 		t.Errorf("expected host_module res combined inputs:\n%q\ngot:\n%q",
 			expectedInputs, resCombined.Inputs)
 	}
@@ -165,7 +165,7 @@
 		hostImportCombined.Output,
 	}
 
-	if !reflect.DeepEqual(combined.Inputs, expectedInputs) {
+	if !slices.Equal(combined.Inputs.Strings(), expectedInputs.Strings()) {
 		t.Errorf("expected device_module combined inputs:\n%q\ngot:\n%q",
 			expectedInputs, combined.Inputs)
 	}
@@ -176,7 +176,7 @@
 		hostRes.Output,
 	}
 
-	if !reflect.DeepEqual(resCombined.Inputs, expectedInputs) {
+	if !slices.Equal(resCombined.Inputs.Strings(), expectedInputs.Strings()) {
 		t.Errorf("expected device_module res combined inputs:\n%q\ngot:\n%q",
 			expectedInputs, resCombined.Inputs)
 	}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index aec40b3..176779e 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -391,12 +391,14 @@
 			} else if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
+				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
 			} else if dep, ok := module.(android.SourceFileProducer); ok {
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
 			} else {
 				ctx.ModuleErrorf("depends on non-java module %q", otherName)
 			}
+
 		case java9LibTag:
 			if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
@@ -429,6 +431,19 @@
 	srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs)
 	j.implicits = append(j.implicits, srcFiles...)
 
+	// Module can depend on a java_aconfig_library module using the ":module_name{.tag}" syntax.
+	// Find the corresponding aconfig_declarations module name for such case.
+	for _, src := range j.properties.Srcs {
+		if moduleName, tag := android.SrcIsModuleWithTag(src); moduleName != "" {
+			otherModule := android.GetModuleFromPathDep(ctx, moduleName, tag)
+			if otherModule != nil {
+				if dep, ok := android.OtherModuleProvider(ctx, otherModule, android.CodegenInfoProvider); ok {
+					deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
+				}
+			}
+		}
+	}
+
 	filterByPackage := func(srcs []android.Path, filterPackages []string) []android.Path {
 		if filterPackages == nil {
 			return srcs
diff --git a/java/fuzz.go b/java/fuzz.go
index dc4c6be..fb31ce7 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -64,6 +64,8 @@
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
 	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
+	module.Module.sourceProperties.Top_level_test_target = true
 
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
 		disableLinuxBionic := struct {
diff --git a/java/generated_java_library.go b/java/generated_java_library.go
index e8316cc..d5e6d8f 100644
--- a/java/generated_java_library.go
+++ b/java/generated_java_library.go
@@ -34,7 +34,7 @@
 
 	// Called from inside GenerateAndroidBuildActions. Add the build rules to
 	// make the srcjar, and return the path to it.
-	GenerateSourceJarBuildActions(module *GeneratedJavaLibraryModule, ctx android.ModuleContext) android.Path
+	GenerateSourceJarBuildActions(module *GeneratedJavaLibraryModule, ctx android.ModuleContext) (android.Path, android.Path)
 }
 
 // GeneratedJavaLibraryModuleFactory provides a utility for modules that are generated
@@ -103,8 +103,10 @@
 	checkPropertyEmpty(ctx, module, "plugins", module.Library.properties.Plugins)
 	checkPropertyEmpty(ctx, module, "exported_plugins", module.Library.properties.Exported_plugins)
 
-	srcJarPath := module.callbacks.GenerateSourceJarBuildActions(module, ctx)
+	srcJarPath, cacheOutputPath := module.callbacks.GenerateSourceJarBuildActions(module, ctx)
+
 	module.Library.properties.Generated_srcjars = append(module.Library.properties.Generated_srcjars, srcJarPath)
+	module.Library.properties.Aconfig_Cache_files = append(module.Library.properties.Aconfig_Cache_files, cacheOutputPath)
 	module.Library.GenerateAndroidBuildActions(ctx)
 }
 
diff --git a/java/generated_java_library_test.go b/java/generated_java_library_test.go
index be816cd..a5c4be1 100644
--- a/java/generated_java_library_test.go
+++ b/java/generated_java_library_test.go
@@ -36,8 +36,8 @@
 func (callbacks *JavaGenLibTestCallbacks) DepsMutator(module *GeneratedJavaLibraryModule, ctx android.BottomUpMutatorContext) {
 }
 
-func (callbacks *JavaGenLibTestCallbacks) GenerateSourceJarBuildActions(module *GeneratedJavaLibraryModule, ctx android.ModuleContext) android.Path {
-	return android.PathForOutput(ctx, "blah.srcjar")
+func (callbacks *JavaGenLibTestCallbacks) GenerateSourceJarBuildActions(module *GeneratedJavaLibraryModule, ctx android.ModuleContext) (android.Path, android.Path) {
+	return android.PathForOutput(ctx, "blah.srcjar"), android.PathForOutput(ctx, "blah.pb")
 }
 
 func testGenLib(t *testing.T, errorHandler android.FixtureErrorHandler, bp string) *android.TestResult {
diff --git a/java/java.go b/java/java.go
index fb5bb1c..ca99e69 100644
--- a/java/java.go
+++ b/java/java.go
@@ -958,6 +958,11 @@
 		}
 		j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
+
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       Bool(j.sourceProperties.Test_only),
+		TopLevelTarget: j.sourceProperties.Top_level_test_target,
+	})
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -1123,6 +1128,7 @@
 	module := &Library{}
 
 	module.addHostAndDeviceProperties()
+	module.AddProperties(&module.sourceProperties)
 
 	module.initModuleAndImport(module)
 
@@ -1604,6 +1610,8 @@
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
 	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
+	module.Module.sourceProperties.Top_level_test_target = true
 
 	InitJavaModule(module, android.HostAndDeviceSupported)
 	return module
@@ -1619,6 +1627,7 @@
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
 	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
 
 	InitJavaModule(module, android.HostAndDeviceSupported)
 	return module
@@ -1674,6 +1683,8 @@
 	th.properties.Installable = installable
 	th.testProperties.Auto_gen_config = autoGenConfig
 	th.testProperties.Test_suites = testSuites
+	th.sourceProperties.Test_only = proptools.BoolPtr(true)
+	th.sourceProperties.Top_level_test_target = true
 }
 
 //
@@ -1799,7 +1810,7 @@
 	module := &Binary{}
 
 	module.addHostAndDeviceProperties()
-	module.AddProperties(&module.binaryProperties)
+	module.AddProperties(&module.binaryProperties, &module.sourceProperties)
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 
@@ -2564,7 +2575,7 @@
 	// header jar for this module.
 	reuseImplementationJarAsHeaderJar := slices.Equal(staticJars, staticHeaderJars)
 
-	var headerOutputFile android.WritablePath
+	var headerOutputFile android.ModuleOutPath
 	if reuseImplementationJarAsHeaderJar {
 		headerOutputFile = outputFile
 	} else {
@@ -2587,8 +2598,12 @@
 			headerOutputFile = outputFile
 		}
 	}
-	j.combinedHeaderFile = headerOutputFile
-	j.combinedImplementationFile = outputFile
+
+	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource.
+	// Also strip the relative path from the header output file so that the reuseImplementationJarAsHeaderJar check
+	// in a module that depends on this module considers them equal.
+	j.combinedHeaderFile = headerOutputFile.WithoutRel()
+	j.combinedImplementationFile = outputFile.WithoutRel()
 
 	j.maybeInstall(ctx, jarName, outputFile)
 
diff --git a/java/java_test.go b/java/java_test.go
index 2676aa5..a1192bb 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2838,6 +2838,99 @@
 	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
 }
 
+func TestTestOnly(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+                // These should be test-only
+		java_library {
+			name: "lib1-test-only",
+                        srcs: ["a.java"],
+                        test_only: true,
+		}
+                java_test {
+                        name: "java-test",
+                }
+                java_test_host {
+                        name: "java-test-host",
+                }
+                java_test_helper_library {
+                        name: "helper-library",
+                }
+                java_binary {
+                        name: "java-data-binary",
+			srcs: ["foo.java"],
+			main_class: "foo.bar.jb",
+                        test_only: true,
+                }
+
+                // These are NOT
+		java_library {
+			name: "lib2-app",
+                        srcs: ["b.java"],
+		}
+		java_import {
+			name: "bar",
+			jars: ["bar.jar"],
+		}
+                java_binary {
+                        name: "java-binary",
+			srcs: ["foo.java"],
+			main_class: "foo.bar.jb",
+                }
+	`)
+
+	expectedTestOnlyModules := []string{
+		"lib1-test-only",
+		"java-test",
+		"java-test-host",
+		"helper-library",
+		"java-data-binary",
+	}
+	expectedTopLevelTests := []string{
+		"java-test",
+		"java-test-host",
+	}
+	assertTestOnlyAndTopLevel(t, ctx, expectedTestOnlyModules, expectedTopLevelTests)
+}
+
+// Don't allow setting test-only on things that are always tests or never tests.
+func TestInvalidTestOnlyTargets(t *testing.T) {
+	testCases := []string{
+		` java_test {  name: "java-test", test_only: true, srcs: ["foo.java"],  } `,
+		` java_test_host {  name: "java-test-host", test_only: true, srcs: ["foo.java"],  } `,
+		` java_test_import {  name: "java-test-import", test_only: true, } `,
+		` java_api_library {  name: "java-api-library", test_only: true, } `,
+		` java_test_helper_library { name: "test-help-lib", test_only: true, } `,
+		` java_defaults { name: "java-defaults", test_only: true, } `,
+	}
+
+	for i, bp := range testCases {
+		android.GroupFixturePreparers(prepareForJavaTest).
+			ExtendWithErrorHandler(
+				expectOneError("unrecognized property \"test_only\"",
+					fmt.Sprintf("testcase: %d", i))).
+			RunTestWithBp(t, bp)
+	}
+}
+
+// Expect exactly one that matches 'expected'.
+// Append 'msg' to the Errorf that printed.
+func expectOneError(expected string, msg string) android.FixtureErrorHandler {
+	return android.FixtureCustomErrorHandler(func(t *testing.T, result *android.TestResult) {
+		t.Helper()
+		if len(result.Errs) != 1 {
+			t.Errorf("Expected exactly one error, but found: %d when  setting test_only on: %s", len(result.Errs), msg)
+			return
+		}
+		actualErrMsg := result.Errs[0].Error()
+		if !strings.Contains(actualErrMsg, expected) {
+			t.Errorf("Different error than expected.  Received: [%v] on %s expected: %s", actualErrMsg, msg, expected)
+		}
+	})
+}
+
 func TestJavaLibHostWithStem(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library_host {
@@ -2872,3 +2965,79 @@
 		t.Errorf("Module output does not contain expected jar %s", "foo-new.jar")
 	}
 }
+
+func TestJavaLibraryOutputFilesRel(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+
+		java_import {
+			name: "bar",
+			jars: ["bar.aar"],
+
+		}
+
+		java_import {
+			name: "baz",
+			jars: ["baz.aar"],
+			static_libs: ["bar"],
+		}
+	`)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	bar := result.ModuleForTests("bar", "android_common")
+	baz := result.ModuleForTests("baz", "android_common")
+
+	fooOutputPath := android.OutputFileForModule(android.PathContext(nil), foo.Module(), "")
+	barOutputPath := android.OutputFileForModule(android.PathContext(nil), bar.Module(), "")
+	bazOutputPath := android.OutputFileForModule(android.PathContext(nil), baz.Module(), "")
+
+	android.AssertPathRelativeToTopEquals(t, "foo output path",
+		"out/soong/.intermediates/foo/android_common/javac/foo.jar", fooOutputPath)
+	android.AssertPathRelativeToTopEquals(t, "bar output path",
+		"out/soong/.intermediates/bar/android_common/combined/bar.jar", barOutputPath)
+	android.AssertPathRelativeToTopEquals(t, "baz output path",
+		"out/soong/.intermediates/baz/android_common/combined/baz.jar", bazOutputPath)
+
+	android.AssertStringEquals(t, "foo relative output path",
+		"foo.jar", fooOutputPath.Rel())
+	android.AssertStringEquals(t, "bar relative output path",
+		"bar.jar", barOutputPath.Rel())
+	android.AssertStringEquals(t, "baz relative output path",
+		"baz.jar", bazOutputPath.Rel())
+}
+
+func assertTestOnlyAndTopLevel(t *testing.T, ctx *android.TestResult, expectedTestOnly []string, expectedTopLevel []string) {
+	t.Helper()
+	actualTrueModules := []string{}
+	actualTopLevelTests := []string{}
+	addActuals := func(m blueprint.Module, key blueprint.ProviderKey[android.TestModuleInformation]) {
+		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, key); ok {
+			if provider.TestOnly {
+				actualTrueModules = append(actualTrueModules, m.Name())
+			}
+			if provider.TopLevelTarget {
+				actualTopLevelTests = append(actualTopLevelTests, m.Name())
+			}
+		}
+	}
+
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		addActuals(m, android.TestOnlyProviderKey)
+
+	})
+
+	notEqual, left, right := android.ListSetDifference(expectedTestOnly, actualTrueModules)
+	if notEqual {
+		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
+	}
+
+	notEqual, left, right = android.ListSetDifference(expectedTopLevel, actualTopLevelTests)
+	if notEqual {
+		t.Errorf("top-level: Expected but not found: %v, Found but not expected: %v", left, right)
+	}
+}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 355654f..e7e53a2a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -3238,14 +3238,14 @@
 	if value == nil {
 		return ""
 	}
-	return fmt.Sprintf(`        %s=\"%s\"\n`, attrName, *value)
+	return fmt.Sprintf("        %s=\"%s\"\n", attrName, *value)
 }
 
 func formattedDependenciesAttribute(dependencies []string) string {
 	if dependencies == nil {
 		return ""
 	}
-	return fmt.Sprintf(`        dependency=\"%s\"\n`, strings.Join(dependencies, ":"))
+	return fmt.Sprintf("        dependency=\"%s\"\n", strings.Join(dependencies, ":"))
 }
 
 func (module *sdkLibraryXml) permissionsContents(ctx android.ModuleContext) string {
@@ -3262,28 +3262,28 @@
 	// similarly, min_device_sdk is only understood from T. So if a library is using that, we need to use the apex-library to make sure this library is not loaded before T
 	var libraryTag string
 	if module.properties.Min_device_sdk != nil {
-		libraryTag = `    <apex-library\n`
+		libraryTag = "    <apex-library\n"
 	} else {
-		libraryTag = `    <library\n`
+		libraryTag = "    <library\n"
 	}
 
 	return strings.Join([]string{
-		`<?xml version=\"1.0\" encoding=\"utf-8\"?>\n`,
-		`<!-- Copyright (C) 2018 The Android Open Source Project\n`,
-		`\n`,
-		`    Licensed under the Apache License, Version 2.0 (the \"License\");\n`,
-		`    you may not use this file except in compliance with the License.\n`,
-		`    You may obtain a copy of the License at\n`,
-		`\n`,
-		`        http://www.apache.org/licenses/LICENSE-2.0\n`,
-		`\n`,
-		`    Unless required by applicable law or agreed to in writing, software\n`,
-		`    distributed under the License is distributed on an \"AS IS\" BASIS,\n`,
-		`    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n`,
-		`    See the License for the specific language governing permissions and\n`,
-		`    limitations under the License.\n`,
-		`-->\n`,
-		`<permissions>\n`,
+		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
+		"<!-- Copyright (C) 2018 The Android Open Source Project\n",
+		"\n",
+		"    Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+		"    you may not use this file except in compliance with the License.\n",
+		"    You may obtain a copy of the License at\n",
+		"\n",
+		"        http://www.apache.org/licenses/LICENSE-2.0\n",
+		"\n",
+		"    Unless required by applicable law or agreed to in writing, software\n",
+		"    distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+		"    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+		"    See the License for the specific language governing permissions and\n",
+		"    limitations under the License.\n",
+		"-->\n",
+		"<permissions>\n",
 		libraryTag,
 		libNameAttr,
 		filePathAttr,
@@ -3292,8 +3292,9 @@
 		minSdkAttr,
 		maxSdkAttr,
 		dependenciesAttr,
-		`    />\n`,
-		`</permissions>\n`}, "")
+		"    />\n",
+		"</permissions>\n",
+	}, "")
 }
 
 func (module *sdkLibraryXml) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -3305,12 +3306,7 @@
 	xmlContent := module.permissionsContents(ctx)
 
 	module.outputFilePath = android.PathForModuleOut(ctx, libName+".xml").OutputPath
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		Text("/bin/bash -c \"echo -e '" + xmlContent + "'\" > ").
-		Output(module.outputFilePath)
-
-	rule.Build("java_sdk_xml", "Permission XML")
+	android.WriteFileRuleVerbatim(ctx, module.outputFilePath, xmlContent)
 
 	module.installDirPath = android.PathForModuleInstall(ctx, "etc", module.SubDir())
 }
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index a19d382..5fac255 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -227,19 +227,21 @@
 `)
 
 	// test that updatability attributes are passed on correctly
-	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Rule("java_sdk_xml")
-	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-since=\"U\"`)
-	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-before=\"V\"`)
-	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `min-device-sdk=\"W\"`)
-	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `max-device-sdk=\"X\"`)
+	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Output("fooUpdatable.xml")
+	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-since="U"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-before="V"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `min-device-sdk="W"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `max-device-sdk="X"`)
 
 	// double check that updatability attributes are not written if they don't exist in the bp file
 	// the permissions file for the foo library defined above
-	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
-	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-since`)
-	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-before`)
-	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `min-device-sdk`)
-	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `max-device-sdk`)
+	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
+	fooPermissionsContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooPermissions)
+	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-since`)
+	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-before`)
+	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `min-device-sdk`)
+	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `max-device-sdk`)
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) {
@@ -370,9 +372,10 @@
 		}
 `)
 	// test that updatability attributes are passed on correctly
-	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
-	android.AssertStringDoesContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<apex-library`)
-	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<library`)
+	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
+	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
+	android.AssertStringDoesContain(t, "foo.xml contents", fooUpdatableContents, `<apex-library`)
+	android.AssertStringDoesNotContain(t, "foo.xml contents", fooUpdatableContents, `<library`)
 }
 
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
@@ -1707,9 +1710,9 @@
 		}
 `)
 
-	barPermissions := result.ModuleForTests("bar.xml", "android_common").Rule("java_sdk_xml")
-
-	android.AssertStringDoesContain(t, "bar.xml java_sdk_xml command", barPermissions.RuleParams.Command, `dependency=\"foo\"`)
+	barPermissions := result.ModuleForTests("bar.xml", "android_common").Output("bar.xml")
+	barContents := android.ContentFromFileRuleForTests(t, result.TestContext, barPermissions)
+	android.AssertStringDoesContain(t, "bar.xml java_sdk_xml command", barContents, `dependency="foo"`)
 }
 
 func TestSdkLibraryExportableStubsLibrary(t *testing.T) {
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 4ae907c..021dd60 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -62,6 +62,7 @@
 				entries.AddStrings("LOCAL_PROC_MACRO_LIBRARIES", mod.Properties.AndroidMkProcMacroLibs...)
 				entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.transitiveAndroidMkSharedLibs.ToList()...)
 				entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
+				entries.AddStrings("LOCAL_HEADER_LIBRARIES", mod.Properties.AndroidMkHeaderLibs...)
 				entries.AddStrings("LOCAL_SOONG_LINK_TYPE", mod.makeLinkType)
 				if mod.InVendor() {
 					entries.SetBool("LOCAL_IN_VENDOR", true)
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 454dd87..11ba74d 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -346,6 +346,6 @@
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
 	deps.StaticLibs = append(deps.StaticLibs, b.ClangProperties.Static_libs...)
-	deps.HeaderLibs = append(deps.StaticLibs, b.ClangProperties.Header_libs...)
+	deps.HeaderLibs = append(deps.HeaderLibs, b.ClangProperties.Header_libs...)
 	return deps
 }
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index 0ba0ff8..0c0a6da 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -17,6 +17,8 @@
 import (
 	"strings"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestRustBindgen(t *testing.T) {
@@ -31,7 +33,21 @@
 			bindgen_flags: ["--bindgen-flag.*"],
 			cflags: ["--clang-flag()"],
 			shared_libs: ["libfoo_shared"],
+		}
+		rust_bindgen {
+			name: "libbindgen_staticlib",
+			wrapper_src: "src/any.h",
+			crate_name: "bindgen_staticlib",
+			stem: "libbindgen_staticlib",
+			source_stem: "bindings",
 			static_libs: ["libfoo_static"],
+		}
+		rust_bindgen {
+			name: "libbindgen_headerlib",
+			wrapper_src: "src/any.h",
+			crate_name: "bindgen_headerlib",
+			stem: "libbindgen_headerlib",
+			source_stem: "bindings",
 			header_libs: ["libfoo_header"],
 		}
 		cc_library_shared {
@@ -52,6 +68,9 @@
 		}
 	`)
 	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenStatic := ctx.ModuleForTests("libbindgen_staticlib", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenHeader := ctx.ModuleForTests("libbindgen_headerlib", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenHeaderModule := ctx.ModuleForTests("libbindgen_headerlib", "android_arm64_armv8-a_source").Module().(*Module)
 	// Ensure that the flags are present and escaped
 	if !strings.Contains(libbindgen.Args["flags"], "'--bindgen-flag.*'") {
 		t.Errorf("missing bindgen flags in rust_bindgen rule: flags %#v", libbindgen.Args["flags"])
@@ -62,12 +81,17 @@
 	if !strings.Contains(libbindgen.Args["cflags"], "-Ishared_include") {
 		t.Errorf("missing shared_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
 	}
-	if !strings.Contains(libbindgen.Args["cflags"], "-Istatic_include") {
-		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
+	if !strings.Contains(libbindgenStatic.Args["cflags"], "-Istatic_include") {
+		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgenStatic.Args["cflags"])
 	}
-	if !strings.Contains(libbindgen.Args["cflags"], "-Iheader_include") {
-		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
+	if !strings.Contains(libbindgenHeader.Args["cflags"], "-Iheader_include") {
+		t.Errorf("missing header_libs exported includes in rust_bindgen rule: cflags %#v", libbindgenHeader.Args["cflags"])
 	}
+
+	if android.InList("libfoo_static", libbindgenHeaderModule.Properties.AndroidMkHeaderLibs) {
+		t.Errorf("Static library dependency should not be in HeaderLibs list")
+	}
+
 	if !strings.Contains(libbindgen.Args["cflags"], "--default-flag") {
 		t.Errorf("rust_bindgen missing cflags defined in cc_defaults: cflags %#v", libbindgen.Args["cflags"])
 	}
diff --git a/rust/config/global.go b/rust/config/global.go
index 2323049..ba08560 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var (
 	pctx = android.NewPackageContext("android/soong/rust/config")
 
-	RustDefaultVersion = "1.76.0"
+	RustDefaultVersion = "1.77.1"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
diff --git a/rust/rust.go b/rust/rust.go
index c4b89d5..c2b6151 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -68,6 +68,7 @@
 	AndroidMkDylibs        []string `blueprint:"mutated"`
 	AndroidMkProcMacroLibs []string `blueprint:"mutated"`
 	AndroidMkStaticLibs    []string `blueprint:"mutated"`
+	AndroidMkHeaderLibs    []string `blueprint:"mutated"`
 
 	ImageVariation string `blueprint:"mutated"`
 	VndkVersion    string `blueprint:"mutated"`
@@ -1399,6 +1400,7 @@
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
+				mod.Properties.AndroidMkHeaderLibs = append(mod.Properties.AndroidMkHeaderLibs, makeLibName)
 			case depTag == cc.CrtBeginDepTag:
 				depPaths.CrtBegin = append(depPaths.CrtBegin, linkObject.Path())
 			case depTag == cc.CrtEndDepTag: