Merge changes Idda28cf7,I15782670 into main

* changes:
  Panic prebuilt etc module gen test if module isn't PrebuiltEtc
  fsgen: Use prebuilt_{root,any} in case install_in_root is set
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index f3c68c3..5a52624 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -129,6 +129,7 @@
 			invalidExportedFlags := android.PathForIntermediates(ctx, "invalid_exported_flags.txt")
 			GenerateExportedFlagCheck(ctx, invalidExportedFlags, parsedFlagsFile, this.properties)
 			depsFiles = append(depsFiles, invalidExportedFlags)
+			ctx.Phony("droidcore", invalidExportedFlags)
 		}
 	}
 
diff --git a/android/Android.bp b/android/Android.bp
index 00dc50a..71e6747 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -83,6 +83,7 @@
         "nothing.go",
         "notices.go",
         "onceper.go",
+        "otatools_package_cert_zip.go",
         "override_module.go",
         "package.go",
         "package_ctx.go",
diff --git a/android/configurable_properties.go b/android/configurable_properties.go
index 2c794a1..bde33e9 100644
--- a/android/configurable_properties.go
+++ b/android/configurable_properties.go
@@ -7,7 +7,8 @@
 // to indicate a "default" case.
 func CreateSelectOsToBool(cases map[string]*bool) proptools.Configurable[bool] {
 	var resultCases []proptools.ConfigurableCase[bool]
-	for pattern, value := range cases {
+	for _, pattern := range SortedKeys(cases) {
+		value := cases[pattern]
 		if pattern == "" {
 			resultCases = append(resultCases, proptools.NewConfigurableCase(
 				[]proptools.ConfigurablePattern{proptools.NewDefaultConfigurablePattern()},
diff --git a/android/otatools_package_cert_zip.go b/android/otatools_package_cert_zip.go
new file mode 100644
index 0000000..03265ca
--- /dev/null
+++ b/android/otatools_package_cert_zip.go
@@ -0,0 +1,62 @@
+// Copyright 2025 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"github.com/google/blueprint"
+)
+
+func init() {
+	RegisterOtatoolsPackageBuildComponents(InitRegistrationContext)
+	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
+}
+
+func RegisterOtatoolsPackageBuildComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("otatools_package_cert_files", OtatoolsPackageFactory)
+}
+
+type OtatoolsPackage struct {
+	ModuleBase
+}
+
+func OtatoolsPackageFactory() Module {
+	module := &OtatoolsPackage{}
+	InitAndroidModule(module)
+	return module
+}
+
+var (
+	otatoolsPackageCertRule = pctx.AndroidStaticRule("otatools_package_cert_files", blueprint.RuleParams{
+		Command:     "echo $out: > ${out}.d && cat $in >> ${out}.d && ${SoongZipCmd} -o $out -l $in",
+		CommandDeps: []string{"${SoongZipCmd}"},
+		Depfile:     "${out}.d",
+		Description: "Zip otatools-package cert files",
+	})
+)
+
+func (fg *OtatoolsPackage) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if ctx.ModuleDir() != "build/make/tools/otatools_package" {
+		ctx.ModuleErrorf("There can only be one otatools_package_cert_files module in build/make/tools/otatools_package")
+		return
+	}
+	fileListFile := PathForArbitraryOutput(ctx, ".module_paths", "OtaToolsCertFiles.list")
+	otatoolsPackageCertZip := PathForModuleOut(ctx, "otatools_package_cert_files.zip")
+	ctx.Build(pctx, BuildParams{
+		Rule:   otatoolsPackageCertRule,
+		Input:  fileListFile,
+		Output: otatoolsPackageCertZip,
+	})
+	ctx.SetOutputFiles([]Path{otatoolsPackageCertZip}, "")
+}
diff --git a/cc/fdo_profile.go b/cc/fdo_profile.go
index 1a33957..c79ea10 100644
--- a/cc/fdo_profile.go
+++ b/cc/fdo_profile.go
@@ -17,6 +17,8 @@
 import (
 	"android/soong/android"
 	"github.com/google/blueprint"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -34,7 +36,7 @@
 }
 
 type fdoProfileProperties struct {
-	Profile *string `android:"arch_variant"`
+	Profile proptools.Configurable[string] `android:"arch_variant,replace_instead_of_append"`
 }
 
 // FdoProfileInfo is provided by FdoProfileProvider
@@ -47,8 +49,9 @@
 
 // GenerateAndroidBuildActions of fdo_profile does not have any build actions
 func (fp *fdoProfile) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if fp.properties.Profile != nil {
-		path := android.PathForModuleSrc(ctx, *fp.properties.Profile)
+	profile := fp.properties.Profile.GetOrDefault(ctx, "")
+	if profile != "" {
+		path := android.PathForModuleSrc(ctx, profile)
 		android.SetProvider(ctx, FdoProfileProvider, FdoProfileInfo{
 			Path: path,
 		})
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 443e80e..feb000d 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -840,6 +840,15 @@
 			Textf("echo avb_enable=true >> %s", miscInfo).
 			Textf("&& echo avb_building_vbmeta_image=true >> %s", miscInfo).
 			Textf("&& echo avb_avbtool=avbtool >> %s", miscInfo)
+		for _, vbmetaPartitionName := range a.partitionProps.Vbmeta_partitions {
+			img := ctx.GetDirectDepProxyWithTag(vbmetaPartitionName, filesystemDepTag)
+			if provider, ok := android.OtherModuleProvider(ctx, img, vbmetaPartitionProvider); ok {
+				builder.Command().Text("cat").Input(provider.PropFileForMiscInfo).Textf(" >> %s", miscInfo)
+			} else {
+				ctx.ModuleErrorf("vbmeta dep %s does not set vbmetaPartitionProvider\n", vbmetaPartitionName)
+			}
+		}
+
 	}
 	if a.partitionProps.Boot_partition_name != nil {
 		builder.Command().Textf("echo boot_images=boot.img >> %s", miscInfo)
@@ -1046,6 +1055,7 @@
 	}
 
 	apkCerts := []string{}
+	var apkCertsFiles android.Paths
 	for _, installedModule := range allInstalledModules {
 		partition := ""
 		if commonInfo, ok := android.OtherModuleProvider(ctx, installedModule, android.CommonModuleInfoProvider); ok {
@@ -1054,7 +1064,11 @@
 			ctx.ModuleErrorf("%s does not set CommonModuleInfoKey", installedModule.Name())
 		}
 		if info, ok := android.OtherModuleProvider(ctx, installedModule, java.AppInfoProvider); ok {
-			apkCerts = append(apkCerts, formatLine(info.Certificate, info.InstallApkName+".apk", partition))
+			if info.AppSet {
+				apkCertsFiles = append(apkCertsFiles, info.ApkCertsFile)
+			} else {
+				apkCerts = append(apkCerts, formatLine(info.Certificate, info.InstallApkName+".apk", partition))
+			}
 		} else if info, ok := android.OtherModuleProvider(ctx, installedModule, java.AppInfosProvider); ok {
 			for _, certInfo := range info {
 				// Partition information of apk-in-apex is not exported to the legacy Make packaging system.
@@ -1075,7 +1089,14 @@
 		}
 	}
 
+	apkCertsInfoWithoutAppSets := android.PathForModuleOut(ctx, "apkcerts_without_app_sets.txt")
+	android.WriteFileRuleVerbatim(ctx, apkCertsInfoWithoutAppSets, strings.Join(apkCerts, "\n")+"\n")
 	apkCertsInfo := android.PathForModuleOut(ctx, "apkcerts.txt")
-	android.WriteFileRuleVerbatim(ctx, apkCertsInfo, strings.Join(apkCerts, "\n")+"\n")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Cat,
+		Description: "combine apkcerts.txt",
+		Output:      apkCertsInfo,
+		Inputs:      append(apkCertsFiles, apkCertsInfoWithoutAppSets),
+	})
 	return apkCertsInfo
 }
diff --git a/filesystem/android_device_product_out.go b/filesystem/android_device_product_out.go
index 7d37f1e..aa06337 100644
--- a/filesystem/android_device_product_out.go
+++ b/filesystem/android_device_product_out.go
@@ -167,7 +167,7 @@
 	}
 
 	if proptools.String(a.deviceProps.Android_info) != "" {
-		installPath := android.PathForModuleInPartitionInstall(ctx, "", "android_info.txt")
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", "android-info.txt")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
 			Input:  android.PathForModuleSrc(ctx, *a.deviceProps.Android_info),
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
index 348c010..32a6cc7 100644
--- a/filesystem/system_other.go
+++ b/filesystem/system_other.go
@@ -120,8 +120,11 @@
 	// TOOD: CopySpecsToDir only exists on PackagingBase, but doesn't use any fields from it. Clean this up.
 	(&android.PackagingBase{}).CopySpecsToDir(ctx, builder, specs, stagingDir)
 
+	fullInstallPaths := []string{}
 	if len(m.properties.Preinstall_dexpreopt_files_from) > 0 {
 		builder.Command().Textf("touch %s", filepath.Join(stagingDir.String(), "system-other-odex-marker"))
+		installPath := android.PathForModuleInPartitionInstall(ctx, "system_other", "system-other-odex-marker")
+		fullInstallPaths = append(fullInstallPaths, installPath.String())
 	}
 	builder.Command().Textf("touch").Output(stagingDirTimestamp)
 	builder.Build("assemble_filesystem_staging_dir", "Assemble filesystem staging dir")
@@ -186,6 +189,10 @@
 
 	ctx.SetOutputFiles(android.Paths{output}, "")
 	ctx.CheckbuildFile(output)
+
+	// Dump compliance metadata
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	complianceMetadataInfo.SetFilesContained(fullInstallPaths)
 }
 
 func (s *systemOtherImage) generateFilesystemConfig(ctx android.ModuleContext, stagingDir, stagingDirTimestamp android.Path) android.Path {
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 01b453e..d59a2ae 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -16,7 +16,10 @@
 
 import (
 	"fmt"
+	"sort"
 	"strconv"
+	"strings"
+	"time"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -124,6 +127,10 @@
 
 	// The output of the vbmeta module
 	Output android.Path
+
+	// Information about the vbmeta partition that will be added to misc_info.txt
+	// created by android_device
+	PropFileForMiscInfo android.Path
 }
 
 var vbmetaPartitionProvider = blueprint.NewProvider[vbmetaPartitionInfo]()
@@ -302,6 +309,7 @@
 		RollbackIndexLocation: ril,
 		PublicKey:             extractedPublicKey,
 		Output:                output,
+		PropFileForMiscInfo:   v.buildPropFileForMiscInfo(ctx),
 	})
 
 	ctx.SetOutputFiles([]android.Path{output}, "")
@@ -310,6 +318,41 @@
 	setCommonFilesystemInfo(ctx, v)
 }
 
+func (v *vbmeta) buildPropFileForMiscInfo(ctx android.ModuleContext) android.Path {
+	var lines []string
+	addStr := func(name string, value string) {
+		lines = append(lines, fmt.Sprintf("%s=%s", name, value))
+	}
+
+	addStr(fmt.Sprintf("avb_%s_algorithm", v.partitionName()), proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096"))
+	if v.properties.Private_key != nil {
+		addStr(fmt.Sprintf("avb_%s_key_path", v.partitionName()), android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key)).String())
+	}
+	if v.properties.Rollback_index_location != nil {
+		addStr(fmt.Sprintf("avb_%s_rollback_index_location", v.partitionName()), strconv.FormatInt(*v.properties.Rollback_index_location, 10))
+	}
+
+	var partitionDepNames []string
+	ctx.VisitDirectDepsProxyWithTag(vbmetaPartitionDep, func(child android.ModuleProxy) {
+		if info, ok := android.OtherModuleProvider(ctx, child, vbmetaPartitionProvider); ok {
+			partitionDepNames = append(partitionDepNames, info.Name)
+		} else {
+			ctx.ModuleErrorf("vbmeta dep %s does not set vbmetaPartitionProvider\n", child)
+		}
+	})
+	if v.partitionName() != "vbmeta" { // skip for vbmeta to match Make's misc_info.txt
+		addStr(fmt.Sprintf("avb_%s", v.partitionName()), strings.Join(android.SortedUniqueStrings(partitionDepNames), " "))
+	}
+
+	addStr(fmt.Sprintf("avb_%s_args", v.partitionName()), fmt.Sprintf("--padding_size 4096 --rollback_index %s", v.rollbackIndexString(ctx)))
+
+	sort.Strings(lines)
+
+	propFile := android.PathForModuleOut(ctx, "prop_file_for_misc_info")
+	android.WriteFileRule(ctx, propFile, strings.Join(lines, "\n"))
+	return propFile
+}
+
 // Returns the embedded shell command that prints the rollback index
 func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
 	if v.properties.Rollback_index != nil {
@@ -320,6 +363,17 @@
 	}
 }
 
+// Similar to rollbackIndexCommand, but guarantees that the rollback index is
+// always computed during Soong analysis, even if v.properties.Rollback_index is nil
+func (v *vbmeta) rollbackIndexString(ctx android.ModuleContext) string {
+	if v.properties.Rollback_index != nil {
+		return fmt.Sprintf("%d", *v.properties.Rollback_index)
+	} else {
+		t, _ := time.Parse(time.DateOnly, ctx.Config().PlatformSecurityPatch())
+		return fmt.Sprintf("%d", t.Unix())
+	}
+}
+
 var _ android.AndroidMkProviderInfoProducer = (*vbmeta)(nil)
 
 func (v *vbmeta) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
diff --git a/java/app.go b/java/app.go
index 5c0a9a6..05b4a96 100644
--- a/java/app.go
+++ b/java/app.go
@@ -82,6 +82,7 @@
 	Certificate                   Certificate
 	PrivAppAllowlist              android.OptionalPath
 	OverriddenManifestPackageName *string
+	ApkCertsFile                  android.Path
 }
 
 var AppInfoProvider = blueprint.NewProvider[*AppInfo]()
diff --git a/java/app_set.go b/java/app_set.go
index 2e9d314..6a2c678 100644
--- a/java/app_set.go
+++ b/java/app_set.go
@@ -193,9 +193,10 @@
 	)
 
 	android.SetProvider(ctx, AppInfoProvider, &AppInfo{
-		AppSet:     true,
-		Privileged: as.Privileged(),
-		OutputFile: as.OutputFile(),
+		AppSet:       true,
+		Privileged:   as.Privileged(),
+		OutputFile:   as.OutputFile(),
+		ApkCertsFile: as.apkcertsFile,
 	})
 }
 
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 783b488..ff8908b 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -84,8 +84,14 @@
 			// METADATA file of packages
 			"METADATA",
 		},
-		// .mk files for product/board configuration.
-		IncludeSuffixes: []string{".mk"},
+		IncludeSuffixes: []string{
+			// .mk files for product/board configuration.
+			".mk",
+			// otatools cert files
+			".pk8",
+			".pem",
+			".avbpubkey",
+		},
 	}
 	dumpDir := config.FileListDir()
 	f, err = finder.New(cacheParams, filesystem, logger.New(ioutil.Discard),
@@ -118,6 +124,18 @@
 	return entries.DirNames, matches
 }
 
+func findOtaToolsCertFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
+	matches := []string{}
+	for _, foundName := range entries.FileNames {
+		if strings.HasSuffix(foundName, ".pk8") ||
+			strings.HasSuffix(foundName, ".pem") ||
+			strings.HasSuffix(foundName, ".avbpubkey") {
+			matches = append(matches, foundName)
+		}
+	}
+	return entries.DirNames, matches
+}
+
 // FindSources searches for source files known to <f> and writes them to the filesystem for
 // use later.
 func FindSources(ctx Context, config Config, f *finder.Finder) {
@@ -184,6 +202,17 @@
 		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
 	}
 
+	// Recursively look for all otatools cert files.
+	otatools_cert_files := f.FindMatching("build/make/target/product/security", findOtaToolsCertFiles)
+	otatools_cert_files = append(otatools_cert_files, f.FindMatching("device", findOtaToolsCertFiles)...)
+	otatools_cert_files = append(otatools_cert_files, f.FindMatching("external/avb/test/data", findOtaToolsCertFiles)...)
+	otatools_cert_files = append(otatools_cert_files, f.FindMatching("packages/modules", findOtaToolsCertFiles)...)
+	otatools_cert_files = append(otatools_cert_files, f.FindMatching("vendor", findOtaToolsCertFiles)...)
+	err = dumpListToFile(ctx, config, otatools_cert_files, filepath.Join(dumpDir, "OtaToolsCertFiles.list"))
+	if err != nil {
+		ctx.Fatalf("Could not find otatools cert files: %v", err)
+	}
+
 	// Recursively look for all Android.bp files
 	androidBps := f.FindNamedAt(".", "Android.bp")
 	if len(androidBps) == 0 {