Merge "Make the order of cases stable when creating new configurables. Otherwise the properties hash values will change randomly." into main
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/module.go b/android/module.go
index 87377cc..ecd0f23 100644
--- a/android/module.go
+++ b/android/module.go
@@ -22,6 +22,7 @@
 	"reflect"
 	"slices"
 	"sort"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -2699,6 +2700,13 @@
 					return proptools.ConfigurableValueString(v)
 				case "bool":
 					return proptools.ConfigurableValueBool(v == "true")
+				case "int":
+					i, err := strconv.ParseInt(v, 10, 64)
+					if err != nil {
+						ctx.OtherModulePropertyErrorf(m, property, "integer soong_config_variable was not an int: %q", v)
+						return proptools.ConfigurableValueUndefined()
+					}
+					return proptools.ConfigurableValueInt(i)
 				case "string_list":
 					return proptools.ConfigurableValueStringList(strings.Split(v, " "))
 				default:
diff --git a/android/neverallow.go b/android/neverallow.go
index e693f2d..98b443e 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -311,9 +311,9 @@
 		"trusty_tee_package",
 		// Trusty vm target names
 		"trusty_desktop_vm_arm64.bin",
-		"trusty_desktop_vm_x86_64.elf",
+		"trusty_desktop_vm_x86_64.bin",
 		"trusty_desktop_test_vm_arm64.bin",
-		"trusty_desktop_test_vm_x86_64.elf",
+		"trusty_desktop_test_vm_x86_64.bin",
 		"trusty_test_vm_arm64.bin",
 		"trusty_test_vm_x86_64.elf",
 		"trusty_test_vm_os_arm64.bin",
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/android/selects_test.go b/android/selects_test.go
index 7f20a3d..8e469f8 100644
--- a/android/selects_test.go
+++ b/android/selects_test.go
@@ -666,6 +666,81 @@
 			},
 		},
 		{
+			name: "Select on integer soong config variable",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					34: "34",
+					default: "other",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "34",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "int",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("34"),
+			},
+		},
+		{
+			name: "Select on integer soong config variable default",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					34: "34",
+					default: "other",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "5",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "int",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("other"),
+			},
+		},
+		{
+			name: "Assign to integer property",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_int64: select(soong_config_variable("my_namespace", "my_variable"), {
+					any @ val: val,
+					default: "other",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "5",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "int",
+				},
+			},
+			provider: selectsTestProvider{
+				my_int64: proptools.Int64Ptr(5),
+			},
+		},
+		{
 			name: "Mismatched condition types",
 			bp: `
 			my_module_type {
@@ -1132,6 +1207,7 @@
 type selectsTestProvider struct {
 	my_bool                        *bool
 	my_string                      *string
+	my_int64                       *int64
 	my_string_list                 *[]string
 	my_paths                       *[]string
 	replacing_string_list          *[]string
@@ -1181,6 +1257,7 @@
 
 type selectsMockModuleProperties struct {
 	My_bool                        proptools.Configurable[bool]
+	My_int64                       proptools.Configurable[int64]
 	My_string                      proptools.Configurable[string]
 	My_string_list                 proptools.Configurable[[]string]
 	My_paths                       proptools.Configurable[[]string] `android:"path"`
@@ -1213,6 +1290,7 @@
 	SetProvider(ctx, selectsTestProviderKey, selectsTestProvider{
 		my_bool:                        optionalToPtr(p.properties.My_bool.Get(ctx)),
 		my_string:                      optionalToPtr(p.properties.My_string.Get(ctx)),
+		my_int64:                       optionalToPtr(p.properties.My_int64.Get(ctx)),
 		my_string_list:                 optionalToPtr(p.properties.My_string_list.Get(ctx)),
 		my_paths:                       optionalToPtr(p.properties.My_paths.Get(ctx)),
 		replacing_string_list:          optionalToPtr(p.properties.Replacing_string_list.Get(ctx)),
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/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index b0f8cb7..dd98bca 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -314,6 +314,9 @@
 				}
 			}
 		}
+		if flagDeclaration.Namespace == nil {
+			return fmt.Errorf("Flag declaration %s has no namespace.", path)
+		}
 
 		m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration)
 		name := *flagDeclaration.Name
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/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index 327a41f..c776012 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -70,7 +70,7 @@
 	Props []avbProp
 
 	// The index used to prevent rollback of the image on device.
-	Rollback_index *int64
+	Rollback_index proptools.Configurable[int64] `android:"replace_instead_of_append"`
 
 	// Include descriptors from images
 	Include_descriptors_from_images []string `android:"path,arch_variant"`
@@ -134,8 +134,9 @@
 		addAvbProp(ctx, cmd, prop)
 	}
 
-	if a.properties.Rollback_index != nil {
-		rollbackIndex := proptools.Int(a.properties.Rollback_index)
+	rollbackIndex := a.properties.Rollback_index.Get(ctx)
+	if rollbackIndex.IsPresent() {
+		rollbackIndex := rollbackIndex.Get()
 		if rollbackIndex < 0 {
 			ctx.PropertyErrorf("rollback_index", "Rollback index must be non-negative")
 		}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index f8faa49..e86ebf4 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -455,6 +455,14 @@
 	HasFsverity bool
 
 	PropFileForMiscInfo android.Path
+
+	// Additional avb and partition size information.
+	// `system_other` will use this information of `system` dep for misc_info.txt processing.
+	PartitionSize    *int64
+	UseAvb           bool
+	AvbAlgorithm     string
+	AvbHashAlgorithm string
+	AvbKey           android.Path
 }
 
 // FullInstallPathInfo contains information about the "full install" paths of all the files
@@ -711,6 +719,15 @@
 		Owners:              f.gatherOwners(specs),
 		HasFsverity:         f.properties.Fsverity.Inputs.GetOrDefault(ctx, nil) != nil,
 		PropFileForMiscInfo: propFileForMiscInfo,
+		PartitionSize:       f.properties.Partition_size,
+	}
+	if proptools.Bool(f.properties.Use_avb) {
+		fsInfo.UseAvb = true
+		fsInfo.AvbAlgorithm = proptools.StringDefault(f.properties.Avb_algorithm, "SHA256_RSA4096")
+		fsInfo.AvbHashAlgorithm = proptools.StringDefault(f.properties.Avb_hash_algorithm, "sha256")
+		if f.properties.Avb_private_key != nil {
+			fsInfo.AvbKey = android.PathForModuleSrc(ctx, *f.properties.Avb_private_key)
+		}
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
index cbfd78b..32a6cc7 100644
--- a/filesystem/system_other.go
+++ b/filesystem/system_other.go
@@ -16,8 +16,11 @@
 
 import (
 	"android/soong/android"
+	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
+	"time"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -117,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")
@@ -172,16 +178,21 @@
 	builder.Build("build_system_other_hermetic", "build system other")
 
 	fsInfo := FilesystemInfo{
-		Output:           output,
-		OutputHermetic:   outputHermetic,
-		RootDir:          stagingDir,
-		FilesystemConfig: m.generateFilesystemConfig(ctx, stagingDir, stagingDirTimestamp),
+		Output:              output,
+		OutputHermetic:      outputHermetic,
+		RootDir:             stagingDir,
+		FilesystemConfig:    m.generateFilesystemConfig(ctx, stagingDir, stagingDirTimestamp),
+		PropFileForMiscInfo: m.buildPropFileForMiscInfo(ctx),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
 
 	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 {
@@ -204,3 +215,52 @@
 		Textf(" && echo use_fixed_timestamp=true >> %s", propFilePinnedTimestamp)
 	return propFilePinnedTimestamp
 }
+
+func (f *systemOtherImage) 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("building_system_other_image", "true")
+
+	systemImage := ctx.GetDirectDepProxyWithTag(*f.properties.System_image, systemImageDependencyTag)
+	systemInfo, ok := android.OtherModuleProvider(ctx, systemImage, FilesystemProvider)
+	if !ok {
+		ctx.PropertyErrorf("system_image", "Expected system_image module to provide FilesystemProvider")
+		return nil
+	}
+	if systemInfo.PartitionSize == nil {
+		addStr("system_other_disable_sparse", "true")
+	}
+	if systemInfo.UseAvb {
+		addStr("avb_system_other_hashtree_enable", "true")
+		addStr("avb_system_other_algorithm", systemInfo.AvbAlgorithm)
+		footerArgs := fmt.Sprintf("--hash_algorithm %s", systemInfo.AvbHashAlgorithm)
+		if rollbackIndex, err := f.avbRollbackIndex(ctx); err == nil {
+			footerArgs += fmt.Sprintf(" --rollback_index %d", rollbackIndex)
+		} else {
+			ctx.ModuleErrorf("Could not determine rollback_index %s\n", err)
+		}
+		addStr("avb_system_other_add_hashtree_footer_args", footerArgs)
+		if systemInfo.AvbKey != nil {
+			addStr("avb_system_other_key_path", systemInfo.AvbKey.String())
+		}
+	}
+
+	sort.Strings(lines)
+
+	propFile := android.PathForModuleOut(ctx, "prop_file")
+	android.WriteFileRule(ctx, propFile, strings.Join(lines, "\n"))
+	return propFile
+}
+
+// Use the default: PlatformSecurityPatch
+// TODO: Get this value from vbmeta_system
+func (f *systemOtherImage) avbRollbackIndex(ctx android.ModuleContext) (int64, error) {
+	t, err := time.Parse(time.DateOnly, ctx.Config().PlatformSecurityPatch())
+	if err != nil {
+		return -1, err
+	}
+	return t.Unix(), err
+}
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/genrule/Android.bp b/genrule/Android.bp
index 49df480..b82f2a9 100644
--- a/genrule/Android.bp
+++ b/genrule/Android.bp
@@ -14,7 +14,6 @@
         "soong-shared",
     ],
     srcs: [
-        "allowlists.go",
         "genrule.go",
         "locations.go",
     ],
diff --git a/genrule/allowlists.go b/genrule/allowlists.go
deleted file mode 100644
index 45a7f72..0000000
--- a/genrule/allowlists.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2023 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 genrule
-
-var (
-	SandboxingDenyModuleList = []string{
-		// go/keep-sorted start
-		// go/keep-sorted end
-	}
-)
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 710ec95..a7c09e7 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -969,30 +969,9 @@
 	return module
 }
 
-var sandboxingAllowlistKey = android.NewOnceKey("genruleSandboxingAllowlistKey")
-
-type sandboxingAllowlistSets struct {
-	sandboxingDenyModuleSet map[string]bool
-}
-
-func getSandboxingAllowlistSets(ctx android.PathContext) *sandboxingAllowlistSets {
-	return ctx.Config().Once(sandboxingAllowlistKey, func() interface{} {
-		sandboxingDenyModuleSet := map[string]bool{}
-
-		android.AddToStringSet(sandboxingDenyModuleSet, SandboxingDenyModuleList)
-		return &sandboxingAllowlistSets{
-			sandboxingDenyModuleSet: sandboxingDenyModuleSet,
-		}
-	}).(*sandboxingAllowlistSets)
-}
-
 func getSandboxedRuleBuilder(ctx android.ModuleContext, r *android.RuleBuilder) *android.RuleBuilder {
 	if !ctx.DeviceConfig().GenruleSandboxing() {
 		return r.SandboxTools()
 	}
-	sandboxingAllowlistSets := getSandboxingAllowlistSets(ctx)
-	if sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] {
-		return r.SandboxTools()
-	}
 	return r.SandboxInputs()
 }
diff --git a/java/app.go b/java/app.go
index 553c658..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]()
@@ -691,7 +692,7 @@
 	}
 
 	// Use non final ids if we are doing optimized shrinking and are using R8.
-	nonFinalIds := a.dexProperties.optimizedResourceShrinkingEnabled(ctx) && a.dexer.effectiveOptimizeEnabled()
+	nonFinalIds := a.dexProperties.optimizedResourceShrinkingEnabled(ctx) && a.dexer.effectiveOptimizeEnabled(ctx)
 
 	aconfigTextFilePaths := getAconfigFilePaths(ctx)
 
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/java/base.go b/java/base.go
index 1a12075..8aa0109 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1816,7 +1816,7 @@
 				classesJar:    outputFile,
 				jarName:       jarName,
 			}
-			if j.GetProfileGuided(ctx) && j.optimizeOrObfuscateEnabled() && !j.EnableProfileRewriting(ctx) {
+			if j.GetProfileGuided(ctx) && j.optimizeOrObfuscateEnabled(ctx) && !j.EnableProfileRewriting(ctx) {
 				ctx.PropertyErrorf("enable_profile_rewriting",
 					"Enable_profile_rewriting must be true when profile_guided dexpreopt and R8 optimization/obfuscation is turned on. The attached profile should be sourced from an unoptimized/unobfuscated APK.",
 				)
diff --git a/java/dex.go b/java/dex.go
index ed9c82b..dd64675 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -161,8 +161,8 @@
 	providesTransitiveHeaderJarsForR8
 }
 
-func (d *dexer) effectiveOptimizeEnabled() bool {
-	return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault)
+func (d *dexer) effectiveOptimizeEnabled(ctx android.EarlyModuleContext) bool {
+	return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault && !ctx.Config().Eng())
 }
 
 func (d *DexProperties) resourceShrinkingEnabled(ctx android.ModuleContext) bool {
@@ -173,8 +173,8 @@
 	return d.resourceShrinkingEnabled(ctx) && BoolDefault(d.Optimize.Optimized_shrink_resources, ctx.Config().UseOptimizedResourceShrinkingByDefault())
 }
 
-func (d *dexer) optimizeOrObfuscateEnabled() bool {
-	return d.effectiveOptimizeEnabled() && (proptools.Bool(d.dexProperties.Optimize.Optimize) || proptools.Bool(d.dexProperties.Optimize.Obfuscate))
+func (d *dexer) optimizeOrObfuscateEnabled(ctx android.EarlyModuleContext) bool {
+	return d.effectiveOptimizeEnabled(ctx) && (proptools.Bool(d.dexProperties.Optimize.Optimize) || proptools.Bool(d.dexProperties.Optimize.Obfuscate))
 }
 
 var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8",
@@ -353,7 +353,7 @@
 		flags = append(flags, "--release")
 	} else if ctx.Config().Eng() {
 		flags = append(flags, "--debug")
-	} else if !d.effectiveOptimizeEnabled() && d.dexProperties.Optimize.EnabledByDefault {
+	} else if !d.effectiveOptimizeEnabled(ctx) && d.dexProperties.Optimize.EnabledByDefault {
 		// D8 uses --debug by default, whereas R8 uses --release by default.
 		// For targets that default to R8 usage (e.g., apps), but override this default, we still
 		// want D8 to run in release mode, preserving semantics as much as possible between the two.
@@ -627,7 +627,7 @@
 		mergeZipsFlags = "-stripFile META-INF/*.kotlin_module -stripFile **/*.kotlin_builtins"
 	}
 
-	useR8 := d.effectiveOptimizeEnabled()
+	useR8 := d.effectiveOptimizeEnabled(ctx)
 	useD8 := !useR8 || ctx.Config().PartialCompileFlags().Use_d8
 	rbeR8 := ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8")
 	rbeD8 := ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8")
diff --git a/java/dex_test.go b/java/dex_test.go
index e94864b..8c1e5f7 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -797,12 +797,14 @@
 		},
 		{
 			name:          "app_eng",
+			useD8:         true,
 			isEng:         true,
 			expectedFlags: "--debug",
 		},
 		{
 			name:    "app_release_eng",
 			isEng:   true,
+			useD8:   true,
 			dxFlags: "--release",
 			// Eng mode does *not* override explicit dxflags.
 			expectedFlags: "--release",
diff --git a/java/java.go b/java/java.go
index dd9f852..07e38a1 100644
--- a/java/java.go
+++ b/java/java.go
@@ -670,12 +670,12 @@
 		ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
 		ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
 		ctx.AddVariationDependencies(nil, sdkLibTag, sdkDep.classpath...)
-		if d.effectiveOptimizeEnabled() && sdkDep.hasStandardLibs() {
+		if d.effectiveOptimizeEnabled(ctx) && sdkDep.hasStandardLibs() {
 			ctx.AddVariationDependencies(nil, proguardRaiseTag,
 				config.LegacyCorePlatformBootclasspathLibraries...,
 			)
 		}
-		if d.effectiveOptimizeEnabled() && sdkDep.hasFrameworkLibs() {
+		if d.effectiveOptimizeEnabled(ctx) && sdkDep.hasFrameworkLibs() {
 			ctx.AddVariationDependencies(nil, proguardRaiseTag, config.FrameworkLibraries...)
 		}
 	}
diff --git a/tradefed_modules/Android.bp b/tradefed_modules/Android.bp
index a765a05..37bae39 100644
--- a/tradefed_modules/Android.bp
+++ b/tradefed_modules/Android.bp
@@ -14,11 +14,9 @@
     ],
     srcs: [
         "test_module_config.go",
-        "test_suite.go",
     ],
     testSrcs: [
         "test_module_config_test.go",
-        "test_suite_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/tradefed_modules/test_suite.go b/tradefed_modules/test_suite.go
deleted file mode 100644
index 8b7babf..0000000
--- a/tradefed_modules/test_suite.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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 tradefed_modules
-
-import (
-	"encoding/json"
-	"path"
-	"path/filepath"
-
-	"android/soong/android"
-	"android/soong/tradefed"
-	"github.com/google/blueprint"
-)
-
-const testSuiteModuleType = "test_suite"
-
-type testSuiteTag struct {
-	blueprint.BaseDependencyTag
-}
-
-type testSuiteManifest struct {
-	Name  string   `json:"name"`
-	Files []string `json:"files"`
-}
-
-func init() {
-	RegisterTestSuiteBuildComponents(android.InitRegistrationContext)
-}
-
-func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType(testSuiteModuleType, TestSuiteFactory)
-}
-
-var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers(
-	android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
-)
-
-type testSuiteProperties struct {
-	Description string
-	Tests       []string `android:"path,arch_variant"`
-}
-
-type testSuiteModule struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	testSuiteProperties
-}
-
-func (t *testSuiteModule) DepsMutator(ctx android.BottomUpMutatorContext) {
-	for _, test := range t.Tests {
-		if ctx.OtherModuleDependencyVariantExists(ctx.Config().BuildOSCommonTarget.Variations(), test) {
-			// Host tests.
-			ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), testSuiteTag{}, test)
-		} else {
-			// Target tests.
-			ctx.AddDependency(ctx.Module(), testSuiteTag{}, test)
-		}
-	}
-}
-
-func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	suiteName := ctx.ModuleName()
-	modulesByName := make(map[string]android.Module)
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		// Recurse into test_suite dependencies.
-		if ctx.OtherModuleType(child) == testSuiteModuleType {
-			ctx.Phony(suiteName, android.PathForPhony(ctx, child.Name()))
-			return true
-		}
-
-		// Only write out top level test suite dependencies here.
-		if _, ok := ctx.OtherModuleDependencyTag(child).(testSuiteTag); !ok {
-			return false
-		}
-
-		if !child.InstallInTestcases() {
-			ctx.ModuleErrorf("test_suite only supports modules installed in testcases. %q is not installed in testcases.", child.Name())
-			return false
-		}
-
-		modulesByName[child.Name()] = child
-		return false
-	})
-
-	var files []string
-	for name, module := range modulesByName {
-		// Get the test provider data from the child.
-		tp, ok := android.OtherModuleProvider(ctx, module, tradefed.BaseTestProviderKey)
-		if !ok {
-			// TODO: Consider printing out a list of all module types.
-			ctx.ModuleErrorf("%q is not a test module.", name)
-			continue
-		}
-
-		files = append(files, packageModuleFiles(ctx, suiteName, module, tp)...)
-		ctx.Phony(suiteName, android.PathForPhony(ctx, name))
-	}
-
-	manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json")
-	b, err := json.Marshal(testSuiteManifest{Name: suiteName, Files: android.SortedUniqueStrings(files)})
-	if err != nil {
-		ctx.ModuleErrorf("Failed to marshal manifest: %v", err)
-		return
-	}
-	android.WriteFileRule(ctx, manifestPath, string(b))
-
-	ctx.Phony(suiteName, manifestPath)
-}
-
-func TestSuiteFactory() android.Module {
-	module := &testSuiteModule{}
-	module.AddProperties(&module.testSuiteProperties)
-
-	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-
-	return module
-}
-
-func packageModuleFiles(ctx android.ModuleContext, suiteName string, module android.Module, tp tradefed.BaseTestProviderData) []string {
-
-	hostOrTarget := "target"
-	if tp.IsHost {
-		hostOrTarget = "host"
-	}
-
-	// suiteRoot at out/soong/packaging/<suiteName>.
-	suiteRoot := android.PathForSuiteInstall(ctx, suiteName)
-
-	var installed android.InstallPaths
-	// Install links to installed files from the module.
-	if installFilesInfo, ok := android.OtherModuleProvider(ctx, module, android.InstallFilesProvider); ok {
-		for _, f := range installFilesInfo.InstallFiles {
-			// rel is anything under .../<partition>, normally under .../testcases.
-			rel := android.Rel(ctx, f.PartitionDir(), f.String())
-
-			// Install the file under <suiteRoot>/<host|target>/<partition>.
-			installDir := suiteRoot.Join(ctx, hostOrTarget, f.Partition(), path.Dir(rel))
-			linkTo, err := filepath.Rel(installDir.String(), f.String())
-			if err != nil {
-				ctx.ModuleErrorf("Failed to get relative path from %s to %s: %v", installDir.String(), f.String(), err)
-				continue
-			}
-			installed = append(installed, ctx.InstallAbsoluteSymlink(installDir, path.Base(rel), linkTo))
-		}
-	}
-
-	// Install config file.
-	if tp.TestConfig != nil {
-		moduleRoot := suiteRoot.Join(ctx, hostOrTarget, "testcases", module.Name())
-		installed = append(installed, ctx.InstallFile(moduleRoot, module.Name()+".config", tp.TestConfig))
-	}
-
-	// Add to phony and manifest, manifestpaths are relative to suiteRoot.
-	var manifestEntries []string
-	for _, f := range installed {
-		manifestEntries = append(manifestEntries, android.Rel(ctx, suiteRoot.String(), f.String()))
-		ctx.Phony(suiteName, f)
-	}
-	return manifestEntries
-}
diff --git a/tradefed_modules/test_suite_test.go b/tradefed_modules/test_suite_test.go
deleted file mode 100644
index 3e1472c..0000000
--- a/tradefed_modules/test_suite_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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 tradefed_modules
-
-import (
-	"android/soong/android"
-	"android/soong/java"
-	"encoding/json"
-	"slices"
-	"testing"
-)
-
-func TestTestSuites(t *testing.T) {
-	t.Parallel()
-	ctx := android.GroupFixturePreparers(
-		java.PrepareForTestWithJavaDefaultModules,
-		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
-	).RunTestWithBp(t, `
-		android_test {
-			name: "TestModule1",
-			sdk_version: "current",
-		}
-
-		android_test {
-			name: "TestModule2",
-			sdk_version: "current",
-		}
-
-		test_suite {
-			name: "my-suite",
-			description: "a test suite",
-			tests: [
-				"TestModule1",
-				"TestModule2",
-			]
-		}
-	`)
-	manifestPath := ctx.ModuleForTests(t, "my-suite", "android_common").Output("out/soong/test_suites/my-suite/my-suite.json")
-	var actual testSuiteManifest
-	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
-		t.Errorf("failed to unmarshal manifest: %v", err)
-	}
-	slices.Sort(actual.Files)
-
-	expected := testSuiteManifest{
-		Name: "my-suite",
-		Files: []string{
-			"target/testcases/TestModule1/TestModule1.config",
-			"target/testcases/TestModule1/arm64/TestModule1.apk",
-			"target/testcases/TestModule2/TestModule2.config",
-			"target/testcases/TestModule2/arm64/TestModule2.apk",
-		},
-	}
-
-	android.AssertDeepEquals(t, "manifests differ", expected, actual)
-}
-
-func TestTestSuitesWithNested(t *testing.T) {
-	t.Parallel()
-	ctx := android.GroupFixturePreparers(
-		java.PrepareForTestWithJavaDefaultModules,
-		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
-	).RunTestWithBp(t, `
-		android_test {
-			name: "TestModule1",
-			sdk_version: "current",
-		}
-
-		android_test {
-			name: "TestModule2",
-			sdk_version: "current",
-		}
-
-		android_test {
-			name: "TestModule3",
-			sdk_version: "current",
-		}
-
-		test_suite {
-			name: "my-child-suite",
-			description: "a child test suite",
-			tests: [
-				"TestModule1",
-				"TestModule2",
-			]
-		}
-
-		test_suite {
-			name: "my-all-tests-suite",
-			description: "a parent test suite",
-			tests: [
-				"TestModule1",
-				"TestModule3",
-				"my-child-suite",
-			]
-		}
-	`)
-	manifestPath := ctx.ModuleForTests(t, "my-all-tests-suite", "android_common").Output("out/soong/test_suites/my-all-tests-suite/my-all-tests-suite.json")
-	var actual testSuiteManifest
-	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
-		t.Errorf("failed to unmarshal manifest: %v", err)
-	}
-	slices.Sort(actual.Files)
-
-	expected := testSuiteManifest{
-		Name: "my-all-tests-suite",
-		Files: []string{
-			"target/testcases/TestModule1/TestModule1.config",
-			"target/testcases/TestModule1/arm64/TestModule1.apk",
-			"target/testcases/TestModule2/TestModule2.config",
-			"target/testcases/TestModule2/arm64/TestModule2.apk",
-			"target/testcases/TestModule3/TestModule3.config",
-			"target/testcases/TestModule3/arm64/TestModule3.apk",
-		},
-	}
-
-	android.AssertDeepEquals(t, "manifests differ", expected, actual)
-}
-
-func TestTestSuitesNotInstalledInTestcases(t *testing.T) {
-	t.Parallel()
-	android.GroupFixturePreparers(
-		java.PrepareForTestWithJavaDefaultModules,
-		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
-	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{
-		`"SomeHostTest" is not installed in testcases`,
-	})).RunTestWithBp(t, `
-			java_test_host {
-				name: "SomeHostTest",
-				srcs: ["a.java"],
-			}
-			test_suite {
-				name: "my-suite",
-				description: "a test suite",
-				tests: [
-					"SomeHostTest",
-				]
-			}
-	`)
-}
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 {