Merge "Create the filegroup to collect cert files for otatools.zip" into main
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index e3ef7dd..f3c68c3 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -95,11 +95,44 @@
 	})
 }
 
+func GenerateExportedFlagCheck(ctx android.ModuleContext, outputPath android.WritablePath,
+	parsedFlagsFile android.Path, apiSurface ApiSurfaceContributorProperties) {
+
+	apiSignatureFiles := android.Paths{}
+	for _, apiSignatureFile := range apiSurface.Api_signature_files.GetOrDefault(ctx, nil) {
+		if path := android.PathForModuleSrc(ctx, apiSignatureFile); path != nil {
+			apiSignatureFiles = append(apiSignatureFiles, path)
+		}
+	}
+	finalizedFlagsFile := android.PathForModuleSrc(ctx, apiSurface.Finalized_flags_file)
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   ExportedFlagCheckRule,
+		Inputs: append(apiSignatureFiles, finalizedFlagsFile, parsedFlagsFile),
+		Output: outputPath,
+		Args: map[string]string{
+			"api_signature_files":  android.JoinPathsWithPrefix(apiSignatureFiles, "--api-signature-file "),
+			"finalized_flags_file": "--finalized-flags-file " + finalizedFlagsFile.String(),
+			"parsed_flags_file":    "--parsed-flags-file " + parsedFlagsFile.String(),
+		},
+	})
+}
+
 func (this *allAconfigDeclarationsSingleton) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	parsedFlagsFile := android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
 	this.finalizedFlags = android.PathForIntermediates(ctx, "finalized-flags.txt")
 	GenerateFinalizedFlagsForApiSurface(ctx, this.finalizedFlags, parsedFlagsFile, this.properties)
-	ctx.Phony("all_aconfig_declarations", this.finalizedFlags)
+
+	depsFiles := android.Paths{this.finalizedFlags}
+	if checkExportedFlag, ok := ctx.Config().GetBuildFlag("RELEASE_EXPORTED_FLAG_CHECK"); ok {
+		if checkExportedFlag == "true" {
+			invalidExportedFlags := android.PathForIntermediates(ctx, "invalid_exported_flags.txt")
+			GenerateExportedFlagCheck(ctx, invalidExportedFlags, parsedFlagsFile, this.properties)
+			depsFiles = append(depsFiles, invalidExportedFlags)
+		}
+	}
+
+	ctx.Phony("all_aconfig_declarations", depsFiles...)
 
 	android.SetProvider(ctx, allAconfigDeclarationsInfoProvider, allAconfigDeclarationsInfo{
 		parsedFlagsFile: parsedFlagsFile,
diff --git a/aconfig/init.go b/aconfig/init.go
index b2fe5a3..d8d5470 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -77,6 +77,13 @@
 				"${record-finalized-flags}",
 			},
 		}, "api_signature_files", "finalized_flags_file", "parsed_flags_file")
+	ExportedFlagCheckRule = pctx.AndroidStaticRule("ExportedFlagCheckRule",
+		blueprint.RuleParams{
+			Command: `${exported-flag-check} ${parsed_flags_file} ${finalized_flags_file} ${api_signature_files} > ${out}`,
+			CommandDeps: []string{
+				"${exported-flag-check}",
+			},
+		}, "api_signature_files", "finalized_flags_file", "parsed_flags_file")
 
 	CreateStorageRule = pctx.AndroidStaticRule("aconfig_create_storage",
 		blueprint.RuleParams{
@@ -124,6 +131,7 @@
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("soong_zip", "soong_zip")
 	pctx.HostBinToolVariable("record-finalized-flags", "record-finalized-flags")
+	pctx.HostBinToolVariable("exported-flag-check", "exported-flag-check")
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
diff --git a/android/androidmk.go b/android/androidmk.go
index 84eef62..694f5d6 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -528,6 +528,14 @@
 
 	fmt.Fprintf(&a.header, "\ninclude $(CLEAR_VARS)  # type: %s, name: %s, variant: %s\n", ctx.ModuleType(mod), base.BaseModuleName(), ctx.ModuleSubDir(mod))
 
+	// Add the TestSuites from the provider to LOCAL_SOONG_PROVIDER_TEST_SUITES.
+	// LOCAL_SOONG_PROVIDER_TEST_SUITES will be compared against LOCAL_COMPATIBILITY_SUITES
+	// in make and enforced they're the same, to ensure we've successfully translated all
+	// LOCAL_COMPATIBILITY_SUITES usages to the provider.
+	if testSuiteInfo, ok := OtherModuleProvider(ctx, mod, TestSuiteInfoProvider); ok {
+		a.AddStrings("LOCAL_SOONG_PROVIDER_TEST_SUITES", testSuiteInfo.TestSuites...)
+	}
+
 	// Collect make variable assignment entries.
 	a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
 	a.SetString("LOCAL_MODULE", name+a.SubName)
@@ -1476,12 +1484,17 @@
 	a.Host_required = append(a.Host_required, commonInfo.HostRequiredModuleNames...)
 	a.Target_required = append(a.Target_required, commonInfo.TargetRequiredModuleNames...)
 
-	for _, distString := range a.GetDistForGoals(ctx, mod, commonInfo) {
-		a.HeaderStrings = append(a.HeaderStrings, distString)
-	}
-
+	a.HeaderStrings = append(a.HeaderStrings, a.GetDistForGoals(ctx, mod, commonInfo)...)
 	a.HeaderStrings = append(a.HeaderStrings, fmt.Sprintf("\ninclude $(CLEAR_VARS)  # type: %s, name: %s, variant: %s", ctx.ModuleType(mod), commonInfo.BaseModuleName, ctx.ModuleSubDir(mod)))
 
+	// Add the TestSuites from the provider to LOCAL_SOONG_PROVIDER_TEST_SUITES.
+	// LOCAL_SOONG_PROVIDER_TEST_SUITES will be compared against LOCAL_COMPATIBILITY_SUITES
+	// in make and enforced they're the same, to ensure we've successfully translated all
+	// LOCAL_COMPATIBILITY_SUITES usages to the provider.
+	if testSuiteInfo, ok := OtherModuleProvider(ctx, mod, TestSuiteInfoProvider); ok {
+		helperInfo.AddStrings("LOCAL_SOONG_PROVIDER_TEST_SUITES", testSuiteInfo.TestSuites...)
+	}
+
 	// Collect make variable assignment entries.
 	helperInfo.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
 	helperInfo.SetString("LOCAL_MODULE", name+a.SubName)
diff --git a/android/config.go b/android/config.go
index 9ccd099..2a4b927 100644
--- a/android/config.go
+++ b/android/config.go
@@ -200,6 +200,11 @@
 	return c.config.productVariables.ReleaseAconfigValueSets
 }
 
+// If native modules should have symbols stripped by default. Default false, enabled for build tools
+func (c Config) StripByDefault() bool {
+	return proptools.Bool(c.config.productVariables.StripByDefault)
+}
+
 func (c Config) ReleaseAconfigExtraReleaseConfigs() []string {
 	result := []string{}
 	if val, ok := c.config.productVariables.BuildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
diff --git a/android/module.go b/android/module.go
index 3d643f9..c6c4fd8 100644
--- a/android/module.go
+++ b/android/module.go
@@ -520,6 +520,11 @@
 
 	// names of other modules to install on target if this module is installed
 	Target_required []string `android:"arch_variant"`
+
+	// If this is a soong config module, this property will be set to the name of the original
+	// module type. This is used by neverallow to ensure you can't bypass a ModuleType() matcher
+	// just by creating a soong config module type.
+	Soong_config_base_module_type *string `blueprint:"mutated"`
 }
 
 type distProperties struct {
diff --git a/android/neverallow.go b/android/neverallow.go
index eca8eb3..a7bfd2d 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -412,7 +412,8 @@
 			continue
 		}
 
-		if !n.appliesToModuleType(ctx.ModuleType()) {
+		modType := proptools.StringDefault(m.base().baseProperties.Soong_config_base_module_type, ctx.ModuleType())
+		if !n.appliesToModuleType(modType) {
 			continue
 		}
 
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index c74d5ff..3ccc883 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -388,6 +388,30 @@
 			`module type not allowed to be defined in bp file`,
 		},
 	},
+	// Test the a neverallowed module type can't be smuggled through a soong config module type
+	{
+		name: `smuggling module types through soong config modules`,
+		fs: map[string][]byte{
+			"a/b/Android.bp": []byte(`
+				soong_config_bool_variable {
+					name: "my_var",
+				}
+				soong_config_module_type {
+					name: "smuggled_prebuilt_usr_srec",
+					module_type: "prebuilt_usr_srec",
+					config_namespace: "ANDROID",
+					variables: ["my_var"],
+					properties: ["enabled"],
+				}
+				smuggled_prebuilt_usr_srec {
+					name: "foo",
+				}
+			`),
+		},
+		expectedErrors: []string{
+			`module type not allowed to be defined in bp file`,
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
@@ -399,6 +423,7 @@
 		ctx.RegisterModuleType("filesystem", newMockFilesystemModule)
 		ctx.RegisterModuleType("prebuilt_usr_srec", newMockPrebuiltUsrSrecModule)
 	}),
+	PrepareForTestWithSoongConfigModuleBuildComponents,
 )
 
 func TestNeverallow(t *testing.T) {
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 1932225..0178f76 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -607,11 +607,6 @@
 		if !moduleInFamily.ExportedToMake() {
 			continue
 		}
-		// Skip for the top-level java_sdk_library_(_import). This has some special cases that need to be addressed first.
-		// This does not run into non-determinism because PrebuiltPostDepsMutator also has the special case
-		if sdkLibrary, ok := moduleInFamily.(interface{ SdkLibraryName() *string }); ok && sdkLibrary.SdkLibraryName() != nil {
-			continue
-		}
 		if p := GetEmbeddedPrebuilt(moduleInFamily); p != nil && p.properties.UsePrebuilt {
 			if selectedPrebuilt == nil {
 				selectedPrebuilt = moduleInFamily
@@ -638,26 +633,10 @@
 	if p := GetEmbeddedPrebuilt(m); p != nil {
 		bmn, _ := m.(baseModuleName)
 		name := bmn.BaseModuleName()
-		psi := PrebuiltSelectionInfoMap{}
-		ctx.VisitDirectDepsWithTag(AcDepTag, func(am Module) {
-			psi, _ = OtherModuleProvider(ctx, am, PrebuiltSelectionInfoProvider)
-		})
 
 		if p.properties.UsePrebuilt {
 			if p.properties.SourceExists {
 				ctx.ReplaceDependenciesIf(name, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
-					if sdkLibrary, ok := m.(interface{ SdkLibraryName() *string }); ok && sdkLibrary.SdkLibraryName() != nil {
-						// Do not replace deps to the top-level prebuilt java_sdk_library hook.
-						// This hook has been special-cased in #isSelected to be _always_ active, even in next builds
-						// for dexpreopt and hiddenapi processing.
-						// If we do not special-case this here, rdeps referring to a java_sdk_library in next builds via libs
-						// will get prebuilt stubs
-						// TODO (b/308187268): Remove this after the apexes have been added to apex_contributions
-						if psi.IsSelected(name) {
-							return false
-						}
-					}
-
 					if t, ok := tag.(ReplaceSourceWithPrebuilt); ok {
 						return t.ReplaceSourceWithPrebuilt()
 					}
@@ -679,23 +658,13 @@
 // java_sdk_library_import is a macro that creates
 // 1. top-level "impl" library
 // 2. stub libraries (suffixed with .stubs...)
-//
-// the impl of java_sdk_library_import is a "hook" for hiddenapi and dexpreopt processing. It does not have an impl jar, but acts as a shim
-// to provide the jar deapxed from the prebuilt apex
-//
-// isSelected uses `all_apex_contributions` to supersede source vs prebuilts selection of the stub libraries. It does not supersede the
-// selection of the top-level "impl" library so that this hook can work
-//
-// TODO (b/308174306) - Fix this when we need to support multiple prebuilts in main
 func isSelected(psi PrebuiltSelectionInfoMap, m Module) bool {
 	if sdkLibrary, ok := m.(interface{ SdkLibraryName() *string }); ok && sdkLibrary.SdkLibraryName() != nil {
 		sln := proptools.String(sdkLibrary.SdkLibraryName())
 
 		// This is the top-level library
-		// Do not supersede the existing prebuilts vs source selection mechanisms
-		// TODO (b/308187268): Remove this after the apexes have been added to apex_contributions
 		if bmn, ok := m.(baseModuleName); ok && sln == bmn.BaseModuleName() {
-			return false
+			return psi.IsSelected(m.Name())
 		}
 
 		// Stub library created by java_sdk_library_import
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index e0b1d7c..a61c9d3 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -506,6 +506,10 @@
 		conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
 		props = append(props, conditionalProps.Interface())
 
+		if m, ok := module.(Module); ok {
+			m.base().baseProperties.Soong_config_base_module_type = &moduleType.BaseModuleType
+		}
+
 		// Regular Soong operation wraps the existing module factory with a
 		// conditional on Soong config variables by reading the product
 		// config variables from Make.
diff --git a/android/test_suites.go b/android/test_suites.go
index 39317ec..9eaf785 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -29,10 +29,7 @@
 	return &testSuiteFiles{}
 }
 
-type testSuiteFiles struct {
-	robolectric []Path
-	ravenwood   []Path
-}
+type testSuiteFiles struct{}
 
 type TestSuiteModule interface {
 	Module
@@ -61,22 +58,22 @@
 		}
 	})
 
-	t.robolectric = robolectricTestSuite(ctx, files["robolectric-tests"])
-	ctx.Phony("robolectric-tests", t.robolectric...)
+	robolectricZip, robolectrictListZip := buildTestSuite(ctx, "robolectric-tests", files["robolectric-tests"])
+	ctx.Phony("robolectric-tests", robolectricZip, robolectrictListZip)
+	ctx.DistForGoal("robolectric-tests", robolectricZip, robolectrictListZip)
 
-	t.ravenwood = ravenwoodTestSuite(ctx, files["ravenwood-tests"])
-	ctx.Phony("ravenwood-tests", t.ravenwood...)
-	ctx.DistForGoal("robolectric-tests", t.robolectric...)
-	ctx.DistForGoal("ravenwood-tests", t.ravenwood...)
+	ravenwoodZip, ravenwoodListZip := buildTestSuite(ctx, "ravenwood-tests", files["ravenwood-tests"])
+	ctx.Phony("ravenwood-tests", ravenwoodZip, ravenwoodListZip)
+	ctx.DistForGoal("ravenwood-tests", ravenwoodZip, ravenwoodListZip)
 }
 
-func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
+func buildTestSuite(ctx SingletonContext, suiteName string, files map[string]InstallPaths) (Path, Path) {
 	var installedPaths InstallPaths
 	for _, module := range SortedKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
 
-	outputFile := pathForPackaging(ctx, "robolectric-tests.zip")
+	outputFile := pathForPackaging(ctx, suiteName+".zip")
 	rule := NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
@@ -85,8 +82,8 @@
 		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
 		Flag("-sha256") // necessary to save cas_uploader's time
 
-	testList := buildTestList(ctx, "robolectric-tests_list", installedPaths)
-	testListZipOutputFile := pathForPackaging(ctx, "robolectric-tests_list.zip")
+	testList := buildTestList(ctx, suiteName+"_list", installedPaths)
+	testListZipOutputFile := pathForPackaging(ctx, suiteName+"_list.zip")
 
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", testListZipOutputFile).
@@ -94,38 +91,9 @@
 		FlagWithInput("-f ", testList).
 		Flag("-sha256")
 
-	rule.Build("robolectric_tests_zip", "robolectric-tests.zip")
+	rule.Build(strings.ReplaceAll(suiteName, "-", "_")+"_zip", suiteName+".zip")
 
-	return []Path{outputFile, testListZipOutputFile}
-}
-
-func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
-	var installedPaths InstallPaths
-	for _, module := range SortedKeys(files) {
-		installedPaths = append(installedPaths, files[module]...)
-	}
-
-	outputFile := pathForPackaging(ctx, "ravenwood-tests.zip")
-	rule := NewRuleBuilder(pctx, ctx)
-	rule.Command().BuiltTool("soong_zip").
-		FlagWithOutput("-o ", outputFile).
-		FlagWithArg("-P ", "host/testcases").
-		FlagWithArg("-C ", pathForTestCases(ctx).String()).
-		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
-		Flag("-sha256") // necessary to save cas_uploader's time
-
-	testList := buildTestList(ctx, "ravenwood-tests_list", installedPaths)
-	testListZipOutputFile := pathForPackaging(ctx, "ravenwood-tests_list.zip")
-
-	rule.Command().BuiltTool("soong_zip").
-		FlagWithOutput("-o ", testListZipOutputFile).
-		FlagWithArg("-C ", pathForPackaging(ctx).String()).
-		FlagWithInput("-f ", testList).
-		Flag("-sha256")
-
-	rule.Build("ravenwood_tests_zip", "ravenwood-tests.zip")
-
-	return []Path{outputFile, testListZipOutputFile}
+	return outputFile, testListZipOutputFile
 }
 
 func buildTestList(ctx SingletonContext, listFile string, installedPaths InstallPaths) Path {
diff --git a/android/variable.go b/android/variable.go
index 853d8ef..c59857a 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -554,6 +554,8 @@
 	OdmManifestFiles       []string `json:",omitempty"`
 
 	UseSoongNoticeXML *bool `json:",omitempty"`
+
+	StripByDefault *bool `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
@@ -654,6 +656,7 @@
 	ProductUseDynamicPartitions       bool                                     `json:",omitempty"`
 	ProductRetrofitDynamicPartitions  bool                                     `json:",omitempty"`
 	ProductBuildSuperPartition        bool                                     `json:",omitempty"`
+	BuildingSuperEmptyImage           bool                                     `json:",omitempty"`
 	BoardSuperPartitionSize           string                                   `json:",omitempty"`
 	BoardSuperPartitionMetadataDevice string                                   `json:",omitempty"`
 	BoardSuperPartitionBlockDevices   []string                                 `json:",omitempty"`
diff --git a/cc/binary.go b/cc/binary.go
index 627d5e5..608251a 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -551,6 +551,10 @@
 	binary.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
 }
 
+func (binary *binaryDecorator) testSuiteInfo(ctx ModuleContext) {
+	// not a test
+}
+
 var _ overridable = (*binaryDecorator)(nil)
 
 func init() {
diff --git a/cc/cc.go b/cc/cc.go
index 16e8df7..ae6f3b0 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -797,6 +797,8 @@
 	defaultDistFiles() []android.Path
 
 	moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON)
+
+	testSuiteInfo(ctx ModuleContext)
 }
 
 // specifiedDeps is a tuple struct representing dependencies of a linked binary owned by the linker.
@@ -2408,6 +2410,8 @@
 			name := v.ImplementationModuleName(ctx.OtherModuleName(c))
 			ccInfo.LinkerInfo.ImplementationModuleName = &name
 		}
+
+		c.linker.testSuiteInfo(ctx)
 	}
 	if c.library != nil {
 		ccInfo.LibraryInfo = &LibraryInfo{
diff --git a/cc/library.go b/cc/library.go
index b248224..5299771 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1078,6 +1078,10 @@
 	library.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
 }
 
+func (library *libraryDecorator) testSuiteInfo(ctx ModuleContext) {
+	// not a test
+}
+
 func (library *libraryDecorator) linkStatic(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
diff --git a/cc/object.go b/cc/object.go
index 95a8beb..ea3ed61 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -250,3 +250,7 @@
 	object.baseLinker.moduleInfoJSON(ctx, moduleInfoJSON)
 	moduleInfoJSON.Class = []string{"STATIC_LIBRARIES"}
 }
+
+func (object *objectLinker) testSuiteInfo(ctx ModuleContext) {
+	// not a test
+}
diff --git a/cc/strip.go b/cc/strip.go
index 32ea38d..42c9137 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -52,7 +52,7 @@
 func (stripper *Stripper) NeedsStrip(actx android.ModuleContext) bool {
 	forceDisable := Bool(stripper.StripProperties.Strip.None)
 	// Strip is enabled by default for device variants.
-	defaultEnable := actx.Device()
+	defaultEnable := actx.Device() || actx.Config().StripByDefault()
 	forceEnable := Bool(stripper.StripProperties.Strip.All) ||
 		Bool(stripper.StripProperties.Strip.Keep_symbols) ||
 		Bool(stripper.StripProperties.Strip.Keep_symbols_and_debug_frame)
diff --git a/cc/test.go b/cc/test.go
index 8b68c55..9c276b8 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -274,6 +274,12 @@
 	}
 }
 
+func (test *testDecorator) testSuiteInfo(ctx ModuleContext) {
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: test.InstallerProperties.Test_suites,
+	})
+}
+
 func NewTestInstaller() *baseInstaller {
 	return NewBaseInstaller("nativetest", "nativetest64", InstallInData)
 }
@@ -342,6 +348,10 @@
 
 }
 
+func (test *testBinary) testSuiteInfo(ctx ModuleContext) {
+	test.testDecorator.testSuiteInfo(ctx)
+}
+
 func (test *testBinary) installerProps() []interface{} {
 	return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
 }
@@ -578,6 +588,10 @@
 	test.testDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
 }
 
+func (test *testLibrary) testSuiteInfo(ctx ModuleContext) {
+	test.testDecorator.testSuiteInfo(ctx)
+}
+
 func (test *testLibrary) installerProps() []interface{} {
 	return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
 }
@@ -695,6 +709,12 @@
 	}
 }
 
+func (benchmark *benchmarkDecorator) testSuiteInfo(ctx ModuleContext) {
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: benchmark.Properties.Test_suites,
+	})
+}
+
 func NewBenchmark(hod android.HostOrDeviceSupported) *Module {
 	module, binary := newBinary(hod)
 	module.multilib = android.MultilibBoth
diff --git a/ci_tests/ci_test_package_zip.go b/ci_tests/ci_test_package_zip.go
index 451dac4..f8c4578 100644
--- a/ci_tests/ci_test_package_zip.go
+++ b/ci_tests/ci_test_package_zip.go
@@ -20,6 +20,7 @@
 	"strings"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -214,7 +215,13 @@
 		ctx.ModuleErrorf("Module %s doesn't set InstallFilesProvider", m.Name())
 	}
 
-	for _, installedFile := range installedFilesInfo.InstallFiles {
+	for _, spec := range installedFilesInfo.PackagingSpecs {
+		if spec.SrcPath() == nil {
+			// Probably a symlink
+			continue
+		}
+		installedFile := spec.FullInstallPath()
+
 		ext := installedFile.Ext()
 		// there are additional installed files for some app-class modules, we only need the .apk, .odex and .vdex files in the test package
 		excludeInstalledFile := ext != ".apk" && ext != ".odex" && ext != ".vdex"
@@ -253,7 +260,9 @@
 
 		tempOut := android.PathForModuleOut(ctx, "STAGING", f)
 		builder.Command().Text("mkdir").Flag("-p").Text(filepath.Join(stagingDir.String(), dir))
-		builder.Command().Text("cp").Flag("-Rf").Input(installedFile).Output(tempOut)
+		// Copy srcPath instead of installedFile because some rules like target-files.zip
+		// are non-hermetic and would be affected if we built the installed files.
+		builder.Command().Text("cp").Flag("-Rf").Input(spec.SrcPath()).Output(tempOut)
 		builder.Temporary(tempOut)
 	}
 }
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 22ad7cf..45a8620 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -623,6 +623,10 @@
 					builder.Command().Textf("cp ").Input(info.SubImageInfo[partition].MapFile).Textf(" %s/IMAGES/", targetFilesDir.String())
 				}
 			}
+			// super_empty.img
+			if info.SuperEmptyImage != nil {
+				builder.Command().Textf("cp ").Input(info.SuperEmptyImage).Textf(" %s/IMAGES/", targetFilesDir.String())
+			}
 		} else {
 			ctx.ModuleErrorf("Super partition %s does set SuperImageProvider\n", superPartition.Name())
 		}
diff --git a/filesystem/super_image.go b/filesystem/super_image.go
index 5108025..8e2b532 100644
--- a/filesystem/super_image.go
+++ b/filesystem/super_image.go
@@ -80,6 +80,8 @@
 	}
 	// Whether the super image will be disted in the update package
 	Super_image_in_update_package *bool
+	// Whether a super_empty.img should be created
+	Create_super_empty *bool
 }
 
 type PartitionGroupsInfo struct {
@@ -118,6 +120,8 @@
 	SubImageInfo map[string]FilesystemInfo
 
 	DynamicPartitionsInfo android.Path
+
+	SuperEmptyImage android.Path
 }
 
 var SuperImageProvider = blueprint.NewProvider[SuperImageInfo]()
@@ -163,7 +167,7 @@
 }
 
 func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx)
+	miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx, false)
 	builder := android.NewRuleBuilder(pctx, ctx)
 	output := android.PathForModuleOut(ctx, s.installFileName())
 	lpMake := ctx.Config().HostToolPath(ctx, "lpmake")
@@ -176,10 +180,27 @@
 		Implicits(deps).
 		Output(output)
 	builder.Build("build_super_image", fmt.Sprintf("Creating super image %s", s.BaseModuleName()))
+	var superEmptyImage android.WritablePath
+	if proptools.Bool(s.properties.Create_super_empty) {
+		superEmptyImageBuilder := android.NewRuleBuilder(pctx, ctx)
+		superEmptyImage = android.PathForModuleOut(ctx, "super_empty.img")
+		superEmptyMiscInfo, superEmptyDeps, _ := s.buildMiscInfo(ctx, true)
+		if superEmptyDeps != nil {
+			ctx.ModuleErrorf("TODO: Handle additional deps when building super_empty.img")
+		}
+		superEmptyImageBuilder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir).
+			BuiltTool("build_super_image").
+			Text("-v").
+			Input(superEmptyMiscInfo).
+			Implicit(lpMake).
+			Output(superEmptyImage)
+		superEmptyImageBuilder.Build("build_super_empty_image", fmt.Sprintf("Creating super empty image %s", s.BaseModuleName()))
+	}
 	android.SetProvider(ctx, SuperImageProvider, SuperImageInfo{
 		SuperImage:            output,
 		SubImageInfo:          subImageInfos,
 		DynamicPartitionsInfo: s.generateDynamicPartitionsInfo(ctx),
+		SuperEmptyImage:       superEmptyImage,
 	})
 	ctx.SetOutputFiles([]android.Path{output}, "")
 	ctx.CheckbuildFile(output)
@@ -191,7 +212,7 @@
 	return "super.img"
 }
 
-func (s *superImage) buildMiscInfo(ctx android.ModuleContext) (android.Path, android.Paths, map[string]FilesystemInfo) {
+func (s *superImage) buildMiscInfo(ctx android.ModuleContext, superEmpty bool) (android.Path, android.Paths, map[string]FilesystemInfo) {
 	var miscInfoString strings.Builder
 	partitionList := s.dumpDynamicPartitionInfo(ctx, &miscInfoString)
 	addStr := func(name string, value string) {
@@ -201,6 +222,11 @@
 		miscInfoString.WriteRune('\n')
 	}
 	addStr("ab_update", strconv.FormatBool(proptools.Bool(s.properties.Ab_update)))
+	if superEmpty {
+		miscInfo := android.PathForModuleOut(ctx, "misc_info_super_empty.txt")
+		android.WriteFileRule(ctx, miscInfo, miscInfoString.String())
+		return miscInfo, nil, nil
+	}
 
 	subImageInfo := make(map[string]FilesystemInfo)
 	var deps android.Paths
@@ -300,6 +326,9 @@
 	}
 
 	addStr("build_super_partition", "true")
+	if proptools.Bool(s.properties.Create_super_empty) {
+		addStr("build_super_empty_partition", "true")
+	}
 	addStr("use_dynamic_partitions", strconv.FormatBool(proptools.Bool(s.properties.Use_dynamic_partitions)))
 	if proptools.Bool(s.properties.Retrofit) {
 		addStr("dynamic_partition_retrofit", "true")
diff --git a/fsgen/super_img.go b/fsgen/super_img.go
index f564636..1d610f6 100644
--- a/fsgen/super_img.go
+++ b/fsgen/super_img.go
@@ -46,6 +46,7 @@
 		Retrofit:                      proptools.BoolPtr(partitionVars.ProductRetrofitDynamicPartitions),
 		Use_dynamic_partitions:        proptools.BoolPtr(partitionVars.ProductUseDynamicPartitions),
 		Super_image_in_update_package: proptools.BoolPtr(partitionVars.BoardSuperImageInUpdatePackage),
+		Create_super_empty:            proptools.BoolPtr(partitionVars.BuildingSuperEmptyImage),
 	}
 	if partitionVars.ProductVirtualAbOta {
 		superImageProps.Virtual_ab.Enable = proptools.BoolPtr(true)
diff --git a/java/app.go b/java/app.go
index f1dcf41..560129b 100644
--- a/java/app.go
+++ b/java/app.go
@@ -425,6 +425,10 @@
 	} else {
 		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
 	}
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: a.appTestHelperAppProperties.Test_suites,
+	})
 }
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1703,6 +1707,10 @@
 		moduleInfoJSON.AutoTestConfig = []string{"true"}
 	}
 	moduleInfoJSON.TestMainlineModules = append(moduleInfoJSON.TestMainlineModules, a.testProperties.Test_mainline_modules...)
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: a.testProperties.Test_suites,
+	})
 }
 
 func testcaseRel(paths android.Paths) []string {
diff --git a/java/app_import.go b/java/app_import.go
index 37c673c..b9e91cd 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -787,6 +787,10 @@
 	a.updateModuleInfoJSON(ctx)
 
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: a.testProperties.Test_suites,
+	})
 }
 
 func (a *AndroidTestImport) updateModuleInfoJSON(ctx android.ModuleContext) {
diff --git a/java/base.go b/java/base.go
index 19fb2bc..8453a68 100644
--- a/java/base.go
+++ b/java/base.go
@@ -888,6 +888,10 @@
 	// Add dependency on libraries that provide additional hidden api annotations.
 	ctx.AddVariationDependencies(nil, hiddenApiAnnotationsTag, j.properties.Hiddenapi_additional_annotations...)
 
+	// Add dependency on (soft) downstream libs from which to trace references during optimization.
+	traceRefs := j.dexProperties.Optimize.Trace_references_from.GetOrDefault(ctx, []string{})
+	ctx.AddVariationDependencies(nil, traceReferencesTag, traceRefs...)
+
 	// For library dependencies that are component libraries (like stubs), add the implementation
 	// as a dependency (dexpreopt needs to be against the implementation library, not stubs).
 	for _, dep := range libDeps {
diff --git a/java/dex.go b/java/dex.go
index b32d5ae..ed9c82b 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -116,6 +116,21 @@
 		//
 		// By default all classes are compiled using R8 when Optimize.Enabled is set.
 		Exclude *string `android:"path"`
+
+		// Optional list of downstream (Java) libraries from which to trace and preserve references
+		// when optimizing. Note that this requires that the source reference does *not* have
+		// a strict lib dependency on this target; dependencies should be on intermediate targets
+		// statically linked into this target, e.g., if A references B, and we want to trace and
+		// keep references from A when optimizing B, you would create an intermediate B.impl (
+		// containing all static code), have A depend on `B.impl` via libs, and set
+		// `trace_references_from: ["A"]` on B.
+		//
+		// Also note that these are *not* inherited across targets, they must be specified at the
+		// top-level target that is optimized.
+		//
+		// TODO(b/212737576): Handle this implicitly using bottom-up deps mutation and implicit
+		// creation of a proxy `.impl` library.
+		Trace_references_from proptools.Configurable[[]string] `android:"arch_variant"`
 	}
 
 	// Keep the data uncompressed. We always need uncompressed dex for execution,
@@ -458,6 +473,20 @@
 
 	flagFiles = append(flagFiles, android.PathsForModuleSrc(ctx, opt.Proguard_flags_files)...)
 
+	traceReferencesSources := android.Paths{}
+	ctx.VisitDirectDepsProxyWithTag(traceReferencesTag, func(m android.ModuleProxy) {
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			traceReferencesSources = append(traceReferencesSources, dep.ImplementationJars...)
+		}
+	})
+	if len(traceReferencesSources) > 0 {
+		traceTarget := dexParams.classesJar
+		traceLibs := android.FirstUniquePaths(append(flags.bootClasspath.Paths(), flags.dexClasspath.Paths()...))
+		traceReferencesFlags := android.PathForModuleOut(ctx, "proguard", "trace_references.flags")
+		TraceReferences(ctx, traceReferencesSources, traceTarget, traceLibs, traceReferencesFlags)
+		flagFiles = append(flagFiles, traceReferencesFlags)
+	}
+
 	flagFiles = android.FirstUniquePaths(flagFiles)
 
 	r8Flags = append(r8Flags, android.JoinWithPrefix(flagFiles.Strings(), "-include "))
diff --git a/java/dex_test.go b/java/dex_test.go
index 66d801d..e94864b 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -866,3 +866,46 @@
 		})
 	}
 }
+
+func TestTraceReferences(t *testing.T) {
+	t.Parallel()
+	bp := `
+		android_app {
+			name: "app",
+			libs: ["lib.impl"],
+			srcs: ["foo.java"],
+			platform_apis: true,
+		}
+
+		java_library {
+			name: "lib",
+			optimize: {
+				enabled: true,
+				trace_references_from: ["app"],
+			},
+			srcs: ["bar.java"],
+			static_libs: ["lib.impl"],
+			installable: true,
+		}
+
+		java_library {
+			name: "lib.impl",
+			srcs: ["baz.java"],
+		}
+	`
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, bp)
+
+	appJar := result.ModuleForTests(t, "app", "android_common").Output("combined/app.jar").Output
+	libJar := result.ModuleForTests(t, "lib", "android_common").Output("combined/lib.jar").Output
+	libTraceRefs := result.ModuleForTests(t, "lib", "android_common").Rule("traceReferences")
+	libR8 := result.ModuleForTests(t, "lib", "android_common").Rule("r8")
+
+	android.AssertStringDoesContain(t, "expected trace reference source from app jar",
+		libTraceRefs.Args["sources"], "--source "+appJar.String())
+	android.AssertStringEquals(t, "expected trace reference target into lib jar",
+		libJar.String(), libTraceRefs.Input.String())
+	android.AssertStringDoesContain(t, "expected trace reference proguard flags in lib r8 flags",
+		libR8.Args["r8Flags"], "trace_references.flags")
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index b21cfc9..e8e1cd4 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -650,6 +650,9 @@
 		// for now just exclude any known irrelevant dependencies that would lead to incorrect errors.
 		if _, ok := tag.(bootclasspathDependencyTag); ok {
 			return false
+		} else if tag == traceReferencesTag {
+			// Allow ordering inversion if the dependency is purely for tracing references.
+			return false
 		}
 		depIndex := jars.IndexOfJar(dep.Name())
 		if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars {
diff --git a/java/java.go b/java/java.go
index 235a27d..7f897a0 100644
--- a/java/java.go
+++ b/java/java.go
@@ -578,6 +578,7 @@
 	extraLintCheckTag       = dependencyTag{name: "extra-lint-check", toolchain: true}
 	jniLibTag               = dependencyTag{name: "jnilib", runtimeLinked: true}
 	r8LibraryJarTag         = dependencyTag{name: "r8-libraryjar", runtimeLinked: true}
+	traceReferencesTag      = dependencyTag{name: "trace-references"}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	javaApiContributionTag  = dependencyTag{name: "java-api-contribution"}
 	aconfigDeclarationTag   = dependencyTag{name: "aconfig-declaration"}
@@ -608,6 +609,7 @@
 		kotlinPluginTag,
 		syspropPublicStubDepTag,
 		instrumentationForTag,
+		traceReferencesTag,
 	}
 )
 
@@ -1954,6 +1956,10 @@
 			ctx.InstallFile(pathInTestCases, ctx.ModuleName()+".jar", j.outputFile)
 		}
 	}
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: j.testProperties.Test_suites,
+	})
 }
 
 func (j *TestHelperLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1970,6 +1976,10 @@
 	if optionalConfig.Valid() {
 		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, optionalConfig.String())
 	}
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: j.testHelperLibraryProperties.Test_suites,
+	})
 }
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/python/binary.go b/python/binary.go
index feac72a..af7d30f 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -159,6 +159,10 @@
 		sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
 	}
 	p.androidMkSharedLibs = sharedLibs
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: p.binaryProperties.Test_suites,
+	})
 }
 
 func (p *PythonBinaryModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/rust/benchmark.go b/rust/benchmark.go
index daba964..3aa2f17 100644
--- a/rust/benchmark.go
+++ b/rust/benchmark.go
@@ -146,4 +146,8 @@
 	} else {
 		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
 	}
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: benchmark.Properties.Test_suites,
+	})
 }
diff --git a/rust/test.go b/rust/test.go
index 9b755b4..2fed0d6 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -242,6 +242,10 @@
 		flags.RustFlags = append(flags.RustFlags, "-Z panic_abort_tests")
 	}
 
+	// Add a default rpath to allow tests to dlopen libraries specified in data_libs.
+	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, `-Wl,-rpath,\$$ORIGIN/lib64`)
+	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
+
 	return flags
 }
 
@@ -316,6 +320,10 @@
 	} else {
 		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
 	}
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: test.Properties.Test_suites,
+	})
 }
 
 func rustTestHostMultilib(ctx android.LoadHookContext) {
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 175451e..96dc5a6 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -132,7 +132,7 @@
 
     #pylint: disable=line-too-long
     errmsg = ''.join([
-        'mismatch in the <uses-library> tags between the build system and the '
+        'mismatch or misordering in the <uses-library> tags between the build system and the '
         'manifest:\n',
         '\t- required libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(required), C_OFF),
         '\t                 vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_required), C_OFF),
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 894b17f..7041642 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -574,6 +574,10 @@
 		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, s.testConfig.String())
 	}
 	moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, s.extraTestConfigs.Strings()...)
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: s.testProperties.Test_suites,
+	})
 }
 
 func addArch(archType string, paths android.Paths) []string {
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 6dd48eb..2b34128 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -426,6 +426,10 @@
 		LocalCertificate:        m.provider.LocalCertificate,
 		IsUnitTest:              m.provider.IsUnitTest,
 	})
+
+	android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{
+		TestSuites: m.tradefedProperties.Test_suites,
+	})
 }
 
 var _ android.AndroidMkEntriesProvider = (*testModuleConfigHostModule)(nil)