Merge "More partial compile optimizations" into main
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index 9a9e568..b068398 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -47,7 +47,7 @@
 		// are from RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS.
 		ReleaseConfigValues []AconfigReleaseConfigValue
 
-		// Container(system/vendor/apex) that this module belongs to
+		// Container(system/system_ext/vendor/apex) that this module belongs to
 		Container string
 
 		// The flags will only be repackaged if this prop is true.
@@ -88,13 +88,6 @@
 		ctx.PropertyErrorf("container", "missing container property")
 	}
 
-	// treating system_ext as system partition as we are combining them as one container
-	// TODO remove this logic once we start enforcing that system_ext cannot be specified as
-	// container in the container field.
-	if module.properties.Container == "system_ext" {
-		module.properties.Container = "system"
-	}
-
 	// Add a dependency on the aconfig_value_sets defined in
 	// RELEASE_ACONFIG_VALUE_SETS, and add any aconfig_values that
 	// match our package.
diff --git a/aconfig/codegen/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
index 8854369..b9455f7 100644
--- a/aconfig/codegen/java_aconfig_library_test.go
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -260,7 +260,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations_bar",
 				package: "com.example.package.bar",
-				container: "vendor",
+				container: "system_ext",
 				srcs: ["bar.aconfig"],
 			}
 
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index 205b855..7185a89 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -238,8 +238,7 @@
 	} else if base.ProductSpecific() {
 		container = "product"
 	} else if base.SystemExtSpecific() {
-		// system_ext and system partitions should be treated as one container
-		container = "system"
+		container = "system_ext"
 	}
 
 	return container
@@ -255,8 +254,7 @@
 	} else if commonInfo.ProductSpecific {
 		container = "product"
 	} else if commonInfo.SystemExtSpecific {
-		// system_ext and system partitions should be treated as one container
-		container = "system"
+		container = "system_ext"
 	}
 
 	return container
diff --git a/android/module.go b/android/module.go
index 405573c..a56fea3 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1654,38 +1654,10 @@
 
 }
 
+// generateModuleTarget generates phony targets so that you can do `m <module-name>`.
+// It will be run on every variant of the module, so it relies on the fact that phony targets
+// are deduped to merge all the deps from different variants together.
 func (m *ModuleBase) generateModuleTarget(ctx *moduleContext) {
-	var allInstalledFiles InstallPaths
-	var allCheckbuildTargets Paths
-	var alloutputFiles Paths
-	ctx.VisitAllModuleVariantProxies(func(module ModuleProxy) {
-		var checkbuildTarget Path
-		var uncheckedModule bool
-		var skipAndroidMkProcessing bool
-		if EqualModules(m.module, module) {
-			allInstalledFiles = append(allInstalledFiles, ctx.installFiles...)
-			checkbuildTarget = ctx.checkbuildTarget
-			uncheckedModule = ctx.uncheckedModule
-			skipAndroidMkProcessing = shouldSkipAndroidMkProcessing(ctx, m)
-		} else {
-			info := OtherModuleProviderOrDefault(ctx, module, InstallFilesProvider)
-			allInstalledFiles = append(allInstalledFiles, info.InstallFiles...)
-			checkbuildTarget = info.CheckbuildTarget
-			uncheckedModule = info.UncheckedModule
-			skipAndroidMkProcessing = OtherModuleProviderOrDefault(ctx, module, CommonModuleInfoKey).SkipAndroidMkProcessing
-		}
-		if outputFiles, err := outputFilesForModule(ctx, module, ""); err == nil {
-			alloutputFiles = append(alloutputFiles, outputFiles...)
-		}
-		// A module's -checkbuild phony targets should
-		// not be created if the module is not exported to make.
-		// Those could depend on the build target and fail to compile
-		// for the current build target.
-		if (!ctx.Config().KatiEnabled() || !skipAndroidMkProcessing) && !uncheckedModule && checkbuildTarget != nil {
-			allCheckbuildTargets = append(allCheckbuildTargets, checkbuildTarget)
-		}
-	})
-
 	var namespacePrefix string
 	nameSpace := ctx.Namespace().Path
 	if nameSpace != "." {
@@ -1693,25 +1665,30 @@
 	}
 
 	var deps Paths
-	var info FinalModuleBuildTargetsInfo
+	var info ModuleBuildTargetsInfo
 
-	if len(allInstalledFiles) > 0 {
+	if len(ctx.installFiles) > 0 {
 		name := namespacePrefix + ctx.ModuleName() + "-install"
-		ctx.Phony(name, allInstalledFiles.Paths()...)
+		installFiles := ctx.installFiles.Paths()
+		ctx.Phony(name, installFiles...)
 		info.InstallTarget = PathForPhony(ctx, name)
-		deps = append(deps, info.InstallTarget)
+		deps = append(deps, installFiles...)
 	}
 
-	if len(allCheckbuildTargets) > 0 {
+	// A module's -checkbuild phony targets should
+	// not be created if the module is not exported to make.
+	// Those could depend on the build target and fail to compile
+	// for the current build target.
+	if (!ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(ctx, m)) && !ctx.uncheckedModule && ctx.checkbuildTarget != nil {
 		name := namespacePrefix + ctx.ModuleName() + "-checkbuild"
-		ctx.Phony(name, allCheckbuildTargets...)
-		deps = append(deps, PathForPhony(ctx, name))
+		ctx.Phony(name, ctx.checkbuildTarget)
+		deps = append(deps, ctx.checkbuildTarget)
 	}
 
-	if len(alloutputFiles) > 0 {
+	if outputFiles, err := outputFilesForModule(ctx, ctx.Module(), ""); err == nil && len(outputFiles) > 0 {
 		name := namespacePrefix + ctx.ModuleName() + "-outputs"
-		ctx.Phony(name, alloutputFiles...)
-		deps = append(deps, PathForPhony(ctx, name))
+		ctx.Phony(name, outputFiles...)
+		deps = append(deps, outputFiles...)
 	}
 
 	if len(deps) > 0 {
@@ -1734,7 +1711,7 @@
 		}
 
 		info.BlueprintDir = ctx.ModuleDir()
-		SetProvider(ctx, FinalModuleBuildTargetsProvider, info)
+		SetProvider(ctx, ModuleBuildTargetsProvider, info)
 	}
 }
 
@@ -1876,15 +1853,15 @@
 
 var SourceFilesInfoProvider = blueprint.NewProvider[SourceFilesInfo]()
 
-// FinalModuleBuildTargetsInfo is used by buildTargetSingleton to create checkbuild and
-// per-directory build targets. Only set on the final variant of each module
-type FinalModuleBuildTargetsInfo struct {
+// ModuleBuildTargetsInfo is used by buildTargetSingleton to create checkbuild and
+// per-directory build targets.
+type ModuleBuildTargetsInfo struct {
 	InstallTarget    WritablePath
 	CheckbuildTarget WritablePath
 	BlueprintDir     string
 }
 
-var FinalModuleBuildTargetsProvider = blueprint.NewProvider[FinalModuleBuildTargetsInfo]()
+var ModuleBuildTargetsProvider = blueprint.NewProvider[ModuleBuildTargetsInfo]()
 
 type CommonModuleInfo struct {
 	Enabled bool
@@ -1938,6 +1915,7 @@
 	Dists                                        []Dist
 	ExportedToMake                               bool
 	Team                                         string
+	PartitionTag                                 string
 }
 
 type ApiLevelOrPlatform struct {
@@ -2151,14 +2129,19 @@
 	}
 
 	if sourceFileProducer, ok := m.module.(SourceFileProducer); ok {
+		srcs := sourceFileProducer.Srcs()
+		for _, src := range srcs {
+			if src == nil {
+				ctx.ModuleErrorf("SourceFileProducer cannot return nil srcs")
+				return
+			}
+		}
 		SetProvider(ctx, SourceFilesInfoProvider, SourceFilesInfo{Srcs: sourceFileProducer.Srcs()})
 	}
 
-	if ctx.IsFinalModule(m.module) {
-		m.generateModuleTarget(ctx)
-		if ctx.Failed() {
-			return
-		}
+	m.generateModuleTarget(ctx)
+	if ctx.Failed() {
+		return
 	}
 
 	ctx.TransitiveInstallFiles = depset.New[InstallPath](depset.TOPOLOGICAL, ctx.installFiles, dependencyInstallFiles)
@@ -2186,6 +2169,7 @@
 			} else {
 				hostRequired = m.baseProperties.Host_required
 			}
+			hostRequired = append(hostRequired, moduleInfoJSON.ExtraHostRequired...)
 
 			var data []string
 			for _, d := range ctx.testData {
@@ -2299,6 +2283,7 @@
 		Dists:                                        m.Dists(),
 		ExportedToMake:                               m.ExportedToMake(),
 		Team:                                         m.Team(),
+		PartitionTag:                                 m.PartitionTag(ctx.DeviceConfig()),
 	}
 	if mm, ok := m.module.(interface {
 		MinSdkVersion(ctx EarlyModuleContext) ApiLevel
@@ -3098,7 +3083,7 @@
 	modulesInDir := make(map[string]Paths)
 
 	ctx.VisitAllModuleProxies(func(module ModuleProxy) {
-		info := OtherModuleProviderOrDefault(ctx, module, FinalModuleBuildTargetsProvider)
+		info := OtherModuleProviderOrDefault(ctx, module, ModuleBuildTargetsProvider)
 
 		if info.CheckbuildTarget != nil {
 			checkbuildDeps = append(checkbuildDeps, info.CheckbuildTarget)
diff --git a/android/module_context.go b/android/module_context.go
index fb62e67..0a23a74 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -456,6 +456,11 @@
 }
 
 func (m *moduleContext) Phony(name string, deps ...Path) {
+	for _, dep := range deps {
+		if dep == nil {
+			panic("Phony dep cannot be nil")
+		}
+	}
 	m.phonies[name] = append(m.phonies[name], deps...)
 }
 
@@ -748,6 +753,9 @@
 	m.packageFile(fullInstallPath, srcPath, executable, m.requiresFullInstall())
 
 	if checkbuild {
+		if srcPath == nil {
+			panic("srcPath cannot be nil")
+		}
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 	}
 
@@ -879,6 +887,11 @@
 
 // CheckbuildFile specifies the output files that should be built by checkbuild.
 func (m *moduleContext) CheckbuildFile(srcPaths ...Path) {
+	for _, srcPath := range srcPaths {
+		if srcPath == nil {
+			panic("CheckbuildFile() files cannot be nil")
+		}
+	}
 	m.checkbuildFiles = append(m.checkbuildFiles, srcPaths...)
 }
 
diff --git a/android/module_info_json.go b/android/module_info_json.go
index bb309ff..50c961a 100644
--- a/android/module_info_json.go
+++ b/android/module_info_json.go
@@ -40,10 +40,12 @@
 	StaticDependencies  []string `json:"static_dependencies,omitempty"`   // $(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES))
 	DataDependencies    []string `json:"data_dependencies,omitempty"`     // $(sort $(ALL_MODULES.$(m).TEST_DATA_BINS))
 
-	CompatibilitySuites []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
-	AutoTestConfig      []string `json:"auto_test_config,omitempty"`     // $(ALL_MODULES.$(m).auto_test_config)
-	TestConfig          []string `json:"test_config,omitempty"`          // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
-	ExtraRequired       []string `json:"-"`
+	CompatibilitySuites  []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
+	AutoTestConfig       []string `json:"auto_test_config,omitempty"`     // $(ALL_MODULES.$(m).auto_test_config)
+	TestConfig           []string `json:"test_config,omitempty"`          // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
+	TestModuleConfigBase string   `json:"test_module_config_base,omitempty"`
+	ExtraRequired        []string `json:"-"`
+	ExtraHostRequired    []string `json:"-"`
 
 	SupportedVariantsOverride []string `json:"-"`
 	Disabled                  bool     `json:"-"`
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 27a68fb..d31fc4f 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -551,6 +551,9 @@
 }
 
 func (s *sourceModule) Srcs() Paths {
+	if s.src == nil {
+		return nil
+	}
 	return Paths{s.src}
 }
 
diff --git a/android/rule_builder.go b/android/rule_builder.go
index db56c3f..ea6aaa4 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -1187,7 +1187,11 @@
 // Textf adds the specified formatted text to the command line.  The text should not contain input or output paths or
 // the rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand {
-	return c.Text(fmt.Sprintf(format, a...))
+	if c.buf.Len() > 0 {
+		c.buf.WriteByte(' ')
+	}
+	fmt.Fprintf(&c.buf, format, a...)
+	return c
 }
 
 // Flag adds the specified raw text to the command line.  The text should not contain input or output paths or the
diff --git a/android/test_suites_test.go b/android/test_suites_test.go
index dda9329..03aa424 100644
--- a/android/test_suites_test.go
+++ b/android/test_suites_test.go
@@ -108,7 +108,8 @@
 
 func (f *fake_module) GenerateAndroidBuildActions(ctx ModuleContext) {
 	for _, output := range f.props.Outputs {
-		ctx.InstallFile(pathForTestCases(ctx), output, nil)
+		f := PathForModuleOut(ctx, output)
+		ctx.InstallFile(pathForTestCases(ctx), output, f)
 	}
 
 	SetProvider(ctx, TestSuiteInfoProvider, TestSuiteInfo{
diff --git a/apex/apex.go b/apex/apex.go
index f700768..196f389 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -18,6 +18,7 @@
 
 import (
 	"fmt"
+	"path"
 	"path/filepath"
 	"regexp"
 	"slices"
@@ -2258,6 +2259,7 @@
 	a.enforcePartitionTagOnApexSystemServerJar(ctx)
 
 	a.verifyNativeImplementationLibs(ctx)
+	a.enforceNoVintfInUpdatable(ctx)
 
 	android.SetProvider(ctx, android.ApexBundleDepsDataProvider, android.ApexBundleDepsData{
 		FlatListPath: a.FlatListPath(),
@@ -2918,3 +2920,16 @@
 		}
 	}
 }
+
+// TODO(b/399527905) libvintf is not forward compatible.
+func (a *apexBundle) enforceNoVintfInUpdatable(ctx android.ModuleContext) {
+	if !a.Updatable() {
+		return
+	}
+	for _, fi := range a.filesInfo {
+		if match, _ := path.Match("etc/vintf/*", fi.path()); match {
+			ctx.ModuleErrorf("VINTF fragment (%s) is not supported in updatable APEX.", fi.path())
+			break
+		}
+	}
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 9eaf814..36a6974 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11667,7 +11667,7 @@
 
 	// Arguments passed to aconfig to retrieve the state of the flags defined in the
 	// textproto files
-	aconfigFlagArgs := m.Output("released-flagged-apis-exportable.txt").Args["flags_path"]
+	aconfigFlagArgs := m.Output("released-flags-exportable.pb").Args["flags_path"]
 
 	// "bar-lib" is a static_lib of "foo" and is passed to metalava as classpath. Thus the
 	// cache file provided by the associated aconfig_declarations module "bar" should be passed
@@ -12216,3 +12216,30 @@
 	// Ensure that vintf fragment file is being installed
 	ensureContains(t, cmd, "/etc/vintf/my_vintf_fragment.xml ")
 }
+
+func TestNoVintfFragmentInUpdatableApex(t *testing.T) {
+	t.Parallel()
+	testApexError(t, `VINTF fragment .* is not supported in updatable APEX`, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			key: "myapex.key",
+			binaries: [ "mybin" ],
+			updatable: true,
+			min_sdk_version: "29",
+		}
+
+		cc_binary {
+			name: "mybin",
+			srcs: ["mybin.cpp"],
+			vintf_fragment_modules: ["my_vintf_fragment.xml"],
+			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
+		}
+
+		vintf_fragment {
+			name: "my_vintf_fragment.xml",
+			src: "my_vintf_fragment.xml",
+		}
+	`)
+}
diff --git a/cc/afdo.go b/cc/afdo.go
index 8d8341e..1233e33 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -105,6 +105,9 @@
 		// The flags are prepended to allow overriding.
 		profileUseFlag := fmt.Sprintf(afdoFlagsFormat, fdoProfilePath)
 		flags.Local.CFlags = append([]string{profileUseFlag}, flags.Local.CFlags...)
+		// Salvage stale profile by fuzzy matching and use the remapped location for sample profile query.
+		flags.Local.CFlags = append([]string{"-mllvm", "--salvage-stale-profile=true"}, flags.Local.CFlags...)
+		flags.Local.CFlags = append([]string{"-mllvm", "--salvage-stale-profile-max-callsites=2000"}, flags.Local.CFlags...)
 		flags.Local.LdFlags = append([]string{profileUseFlag, "-Wl,-mllvm,-no-warn-sample-unused=true"}, flags.Local.LdFlags...)
 
 		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 2c06924..7240ea5 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2689,7 +2689,7 @@
 	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
 
 	cflags := []string{"-Werror", "-std=candcpp"}
-	cstd := []string{"-std=gnu17", "-std=conly"}
+	cstd := []string{"-std=gnu23", "-std=conly"}
 	cppstd := []string{"-std=gnu++20", "-std=cpp", "-fno-rtti"}
 
 	lastNDKFlags := []string{
diff --git a/cc/config/global.go b/cc/config/global.go
index 7bea124..5011acd 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -375,7 +375,7 @@
 		"-w",
 	}
 
-	CStdVersion               = "gnu17"
+	CStdVersion               = "gnu23"
 	CppStdVersion             = "gnu++20"
 	ExperimentalCStdVersion   = "gnu2x"
 	ExperimentalCppStdVersion = "gnu++2b"
diff --git a/cc/sanitize.go b/cc/sanitize.go
index b704ef4..db99a53 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -79,7 +79,7 @@
 
 	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
 		"-fno-sanitize-recover=integer,undefined"}
-	memtagStackCommonFlags = []string{"-Xclang -target-feature -Xclang +mte"}
+	memtagStackCommonFlags = []string{"-march=armv8-a+memtag"}
 	memtagStackLlvmFlags   = []string{"-dom-tree-reachability-max-bbs-to-explore=128"}
 
 	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp
index ea3f52b..dbc1922 100644
--- a/dexpreopt/Android.bp
+++ b/dexpreopt/Android.bp
@@ -9,6 +9,7 @@
         "class_loader_context.go",
         "config.go",
         "dexpreopt.go",
+        "dexpreopt_tools_zip.go",
         "system_server_zip.go",
         "testing.go",
     ],
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 655ee9b..f3c2b2c 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -717,17 +717,8 @@
 	} else if global.EnableUffdGc == "false" {
 		android.WriteFileRuleVerbatim(ctx, uffdGcFlag, "")
 	} else if global.EnableUffdGc == "default" {
-		// Generated by `build/make/core/Makefile`.
+		// Generated by build/make/core/Makefile, or the android_device module in soong-only builds.
 		kernelVersionFile := android.PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt")
-		if !ctx.Config().KatiEnabled() {
-			// In soong-only mode, we need to generate the kernel_version_for_uffd_gc.txt with kernel version
-			kernelVersion := android.String(ctx.Config().ProductVariables().BoardKernelVersion)
-			if kernelVersion == "" {
-				// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=5382;drc=66783fca85911af9da48d9b4f35a61b3873023e9
-				panic("BOARD_KERNEL_VERSION is not set. Build team will convert more stuff from Make to Soong to support this scenario.")
-			}
-			android.WriteFileRule(ctx, kernelVersionFile, kernelVersion)
-		}
 
 		// Determine the UFFD GC flag by the kernel version file.
 		rule := android.NewRuleBuilder(pctx, ctx)
diff --git a/dexpreopt/dexpreopt_tools_zip.go b/dexpreopt/dexpreopt_tools_zip.go
new file mode 100644
index 0000000..c7cf128
--- /dev/null
+++ b/dexpreopt/dexpreopt_tools_zip.go
@@ -0,0 +1,62 @@
+package dexpreopt
+
+import "android/soong/android"
+
+func init() {
+	android.InitRegistrationContext.RegisterSingletonType("dexpreopt_tools_zip_singleton", dexpreoptToolsZipSingletonFactory)
+}
+
+func dexpreoptToolsZipSingletonFactory() android.Singleton {
+	return &dexpreoptToolsZipSingleton{}
+}
+
+type dexpreoptToolsZipSingleton struct{}
+
+func (s *dexpreoptToolsZipSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// The mac build doesn't build dex2oat, so create the zip file only if the build OS is linux.
+	if !ctx.Config().BuildOS.Linux() {
+		return
+	}
+	global := GetGlobalConfig(ctx)
+	if global.DisablePreopt {
+		return
+	}
+	config := GetCachedGlobalSoongConfig(ctx)
+	if config == nil {
+		return
+	}
+
+	deps := android.Paths{
+		ctx.Config().HostToolPath(ctx, "dexpreopt_gen"),
+		ctx.Config().HostToolPath(ctx, "dexdump"),
+		ctx.Config().HostToolPath(ctx, "oatdump"),
+		config.Profman,
+		config.Dex2oat,
+		config.Aapt,
+		config.SoongZip,
+		config.Zip2zip,
+		config.ManifestCheck,
+		config.ConstructContext,
+		config.UffdGcFlag,
+	}
+
+	out := android.PathForOutput(ctx, "dexpreopt_tools.zip")
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	cmd := builder.Command().BuiltTool("soong_zip").
+		Flag("-d").
+		FlagWithOutput("-o ", out).
+		Flag("-j")
+
+	for _, dep := range deps {
+		cmd.FlagWithInput("-f ", dep)
+	}
+
+	// This reads through a symlink to include the file it points to. This isn't great for
+	// build reproducibility, will need to be revisited later.
+	cmd.Textf("-f $(realpath %s)", config.Dex2oat)
+
+	builder.Build("dexpreopt_tools_zip", "building dexpreopt_tools.zip")
+
+	ctx.DistForGoal("droidcore", out)
+}
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
index b4173d7..20a1953 100644
--- a/filesystem/aconfig_files.go
+++ b/filesystem/aconfig_files.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"strconv"
+	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -31,20 +32,13 @@
 		Command:     `$aconfig create-storage --container $container --file $fileType --out $out --cache $in --version $version`,
 		CommandDeps: []string{"$aconfig"},
 	}, "container", "fileType", "version")
+
+	subPartitionsInPartition = map[string][]string{
+		"system": {"system_ext", "product", "vendor"},
+		"vendor": {"odm"},
+	}
 )
 
-type installedAconfigFlagsInfo struct {
-	aconfigFiles android.Paths
-}
-
-var installedAconfigFlagsProvider = blueprint.NewProvider[installedAconfigFlagsInfo]()
-
-type importAconfigDepDag struct {
-	blueprint.BaseDependencyTag
-}
-
-var importAconfigDependencyTag = interPartitionDepTag{}
-
 func (f *filesystem) buildAconfigFlagsFiles(
 	ctx android.ModuleContext,
 	builder *android.RuleBuilder,
@@ -52,88 +46,97 @@
 	dir android.OutputPath,
 	fullInstallPaths *[]FullInstallPathInfo,
 ) {
-	var caches []android.Path
-	for _, ps := range specs {
-		caches = append(caches, ps.GetAconfigPaths()...)
-	}
-
-	ctx.VisitDirectDepsWithTag(importAconfigDependencyTag, func(m android.Module) {
-		info, ok := android.OtherModuleProvider(ctx, m, installedAconfigFlagsProvider)
-		if !ok {
-			ctx.ModuleErrorf("expected dependency %s to have an installedAconfigFlagsProvider", m.Name())
-			return
-		}
-		caches = append(caches, info.aconfigFiles...)
-	})
-	caches = android.SortedUniquePaths(caches)
-
-	android.SetProvider(ctx, installedAconfigFlagsProvider, installedAconfigFlagsInfo{
-		aconfigFiles: caches,
-	})
-
 	if !proptools.Bool(f.properties.Gen_aconfig_flags_pb) {
 		return
 	}
 
-	container := f.PartitionType()
+	partition := f.PartitionType()
+	subPartitionsFound := map[string]bool{}
+	fullInstallPath := android.PathForModuleInPartitionInstall(ctx, partition)
 
-	aconfigFlagsPb := android.PathForModuleOut(ctx, "aconfig", "aconfig_flags.pb")
-	aconfigFlagsPbBuilder := android.NewRuleBuilder(pctx, ctx)
-	cmd := aconfigFlagsPbBuilder.Command().
-		BuiltTool("aconfig").
-		Text(" dump-cache --dedup --format protobuf --out").
-		Output(aconfigFlagsPb).
-		Textf("--filter container:%s+state:ENABLED", container).
-		Textf("--filter container:%s+permission:READ_WRITE", container)
-	for _, cache := range caches {
-		cmd.FlagWithInput("--cache ", cache)
-	}
-	aconfigFlagsPbBuilder.Build("aconfig_flags_pb", "build aconfig_flags.pb")
-
-	installAconfigFlagsPath := dir.Join(ctx, "etc", "aconfig_flags.pb")
-	builder.Command().Text("mkdir -p ").Text(dir.Join(ctx, "etc").String())
-	builder.Command().Text("cp").Input(aconfigFlagsPb).Text(installAconfigFlagsPath.String())
-	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
-		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc/aconfig_flags.pb"),
-		SourcePath:      aconfigFlagsPb,
-	})
-	f.appendToEntry(ctx, installAconfigFlagsPath)
-
-	// To enable fingerprint, we need to have v2 storage files. The default version is 1.
-	storageFilesVersion := 1
-	if ctx.Config().ReleaseFingerprintAconfigPackages() {
-		storageFilesVersion = 2
+	for _, subPartition := range subPartitionsInPartition[partition] {
+		subPartitionsFound[subPartition] = false
 	}
 
-	installAconfigStorageDir := dir.Join(ctx, "etc", "aconfig")
-	builder.Command().Text("mkdir -p").Text(installAconfigStorageDir.String())
+	var caches []android.Path
+	for _, ps := range specs {
+		caches = append(caches, ps.GetAconfigPaths()...)
+		for subPartition, found := range subPartitionsFound {
+			if !found && strings.HasPrefix(ps.RelPathInPackage(), subPartition+"/") {
+				subPartitionsFound[subPartition] = true
+				break
+			}
+		}
+	}
+	caches = android.SortedUniquePaths(caches)
 
-	generatePartitionAconfigStorageFile := func(fileType, fileName string) {
-		outPath := android.PathForModuleOut(ctx, "aconfig", fileName)
-		installPath := installAconfigStorageDir.Join(ctx, fileName)
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   aconfigCreateStorage,
-			Input:  aconfigFlagsPb,
-			Output: outPath,
-			Args: map[string]string{
-				"container": container,
-				"fileType":  fileType,
-				"version":   strconv.Itoa(storageFilesVersion),
-			},
-		})
-		builder.Command().
-			Text("cp").Input(outPath).Text(installPath.String())
+	buildAconfigFlagsFiles := func(container string, dir android.OutputPath, fullInstallPath android.InstallPath) {
+		aconfigFlagsPb := android.PathForModuleOut(ctx, "aconfig", container, "aconfig_flags.pb")
+		aconfigFlagsPbBuilder := android.NewRuleBuilder(pctx, ctx)
+		cmd := aconfigFlagsPbBuilder.Command().
+			BuiltTool("aconfig").
+			Text(" dump-cache --dedup --format protobuf --out").
+			Output(aconfigFlagsPb).
+			Textf("--filter container:%s+state:ENABLED", container).
+			Textf("--filter container:%s+permission:READ_WRITE", container)
+		for _, cache := range caches {
+			cmd.FlagWithInput("--cache ", cache)
+		}
+		aconfigFlagsPbBuilder.Build(container+"_aconfig_flags_pb", "build aconfig_flags.pb")
+
+		installEtcDir := dir.Join(ctx, "etc")
+		installAconfigFlagsPath := installEtcDir.Join(ctx, "aconfig_flags.pb")
+		builder.Command().Text("mkdir -p ").Text(installEtcDir.String())
+		builder.Command().Text("cp").Input(aconfigFlagsPb).Text(installAconfigFlagsPath.String())
 		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
-			SourcePath:      outPath,
-			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc/aconfig", fileName),
+			FullInstallPath: fullInstallPath.Join(ctx, "etc/aconfig_flags.pb"),
+			SourcePath:      aconfigFlagsPb,
 		})
-		f.appendToEntry(ctx, installPath)
+		f.appendToEntry(ctx, installAconfigFlagsPath)
+
+		// To enable fingerprint, we need to have v2 storage files. The default version is 1.
+		storageFilesVersion := 1
+		if ctx.Config().ReleaseFingerprintAconfigPackages() {
+			storageFilesVersion = 2
+		}
+
+		installAconfigStorageDir := installEtcDir.Join(ctx, "aconfig")
+		builder.Command().Text("mkdir -p").Text(installAconfigStorageDir.String())
+
+		generatePartitionAconfigStorageFile := func(fileType, fileName string) {
+			outPath := android.PathForModuleOut(ctx, "aconfig", container, fileName)
+			installPath := installAconfigStorageDir.Join(ctx, fileName)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   aconfigCreateStorage,
+				Input:  aconfigFlagsPb,
+				Output: outPath,
+				Args: map[string]string{
+					"container": container,
+					"fileType":  fileType,
+					"version":   strconv.Itoa(storageFilesVersion),
+				},
+			})
+			builder.Command().
+				Text("cp").Input(outPath).Text(installPath.String())
+			*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+				SourcePath:      outPath,
+				FullInstallPath: fullInstallPath.Join(ctx, "etc/aconfig", fileName),
+			})
+			f.appendToEntry(ctx, installPath)
+		}
+
+		if ctx.Config().ReleaseCreateAconfigStorageFile() {
+			generatePartitionAconfigStorageFile("package_map", "package.map")
+			generatePartitionAconfigStorageFile("flag_map", "flag.map")
+			generatePartitionAconfigStorageFile("flag_val", "flag.val")
+			generatePartitionAconfigStorageFile("flag_info", "flag.info")
+		}
 	}
 
-	if ctx.Config().ReleaseCreateAconfigStorageFile() {
-		generatePartitionAconfigStorageFile("package_map", "package.map")
-		generatePartitionAconfigStorageFile("flag_map", "flag.map")
-		generatePartitionAconfigStorageFile("flag_val", "flag.val")
-		generatePartitionAconfigStorageFile("flag_info", "flag.info")
+	buildAconfigFlagsFiles(partition, dir, fullInstallPath)
+	for _, subPartition := range android.SortedKeys(subPartitionsFound) {
+		if subPartitionsFound[subPartition] {
+			buildAconfigFlagsFiles(subPartition, dir.Join(ctx, subPartition), fullInstallPath.Join(ctx, subPartition))
+		}
 	}
 }
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index c0f0676..3d306e3 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -88,6 +88,13 @@
 	Ramdisk_node_list      *string `android:"path"`
 	Releasetools_extension *string `android:"path"`
 	FastbootInfo           *string `android:"path"`
+
+	// The kernel version in the build. Will be verified against the actual kernel.
+	// If not provided, will attempt to extract it from the loose kernel or the kernel inside
+	// the boot image. The version is later used to decide whether or not to enable uffd_gc
+	// when dexpreopting apps. So setting this doesn't really do anything except enforce that the
+	// actual kernel version is as specified here.
+	Kernel_version *string
 }
 
 type androidDevice struct {
@@ -99,9 +106,14 @@
 
 	allImagesZip android.Path
 
-	proguardDictZip     android.Path
-	proguardDictMapping android.Path
-	proguardUsageZip    android.Path
+	proguardDictZip             android.Path
+	proguardDictMapping         android.Path
+	proguardUsageZip            android.Path
+	kernelConfig                android.Path
+	kernelVersion               android.Path
+	miscInfo                    android.Path
+	rootDirForFsConfig          string
+	rootDirForFsConfigTimestamp android.Path
 }
 
 func AndroidDeviceFactory() android.Module {
@@ -176,6 +188,8 @@
 
 	allInstalledModules := a.allInstalledModules(ctx)
 
+	a.kernelConfig, a.kernelVersion = a.extractKernelVersionAndConfigs(ctx)
+	a.miscInfo = a.addMiscInfo(ctx)
 	a.buildTargetFilesZip(ctx, allInstalledModules)
 	a.buildProguardZips(ctx, allInstalledModules)
 
@@ -323,6 +337,10 @@
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardDictZip, namePrefix+insertBeforeExtension(a.proguardDictZip.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardDictMapping, namePrefix+insertBeforeExtension(a.proguardDictMapping.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardUsageZip, namePrefix+insertBeforeExtension(a.proguardUsageZip.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
+
+		if a.deviceProps.Android_info != nil {
+			ctx.DistForGoal("droidcore-unbundled", android.PathForModuleSrc(ctx, *a.deviceProps.Android_info))
+		}
 	}
 }
 
@@ -460,6 +478,17 @@
 		if toCopy.destSubdir == "SYSTEM" {
 			// Create the ROOT partition in target_files.zip
 			builder.Command().Textf("rsync --links --exclude=system/* %s/ -r %s/ROOT", toCopy.fsInfo.RootDir, targetFilesDir.String())
+			// Add a duplicate rule to assemble the ROOT/ directory in separate intermediates.
+			// The output timestamp will be an input to a separate fs_config call.
+			a.rootDirForFsConfig = android.PathForModuleOut(ctx, "root_dir_for_fs_config").String()
+			rootDirBuilder := android.NewRuleBuilder(pctx, ctx)
+			rootDirForFsConfigTimestamp := android.PathForModuleOut(ctx, "root_dir_for_fs_config.timestamp")
+			rootDirBuilder.Command().Textf("rsync --links --exclude=system/* %s/ -r %s", toCopy.fsInfo.RootDir, a.rootDirForFsConfig).
+				Implicit(toCopy.fsInfo.Output).
+				Text("&& touch").
+				Output(rootDirForFsConfigTimestamp)
+			rootDirBuilder.Build("assemble_root_dir_for_fs_config", "Assemble ROOT/ for fs_config")
+			a.rootDirForFsConfigTimestamp = rootDirForFsConfigTimestamp
 		}
 	}
 	// Copy cmdline, kernel etc. files of boot images
@@ -598,11 +627,11 @@
 		}
 		if partition == "system" {
 			// Create root_filesystem_config from the assembled ROOT/ intermediates directory
-			a.generateFilesystemConfigForTargetFiles(ctx, builder, targetFilesDir.String(), targetFilesDir.String()+"/ROOT", "root_filesystem_config.txt")
+			a.generateFilesystemConfigForTargetFiles(ctx, builder, a.rootDirForFsConfigTimestamp, targetFilesDir.String(), a.rootDirForFsConfig, "root_filesystem_config.txt")
 		}
 		if partition == "vendor_ramdisk" {
 			// Create vendor_boot_filesystem_config from the assembled VENDOR_BOOT/RAMDISK intermediates directory
-			a.generateFilesystemConfigForTargetFiles(ctx, builder, targetFilesDir.String(), targetFilesDir.String()+"/VENDOR_BOOT/RAMDISK", "vendor_boot_filesystem_config.txt")
+			a.generateFilesystemConfigForTargetFiles(ctx, builder, nil, targetFilesDir.String(), targetFilesDir.String()+"/VENDOR_BOOT/RAMDISK", "vendor_boot_filesystem_config.txt")
 		}
 	}
 	// Copy ramdisk_node_list
@@ -629,6 +658,20 @@
 		builder.Command().Textf("cp").Input(fastbootInfo).Textf(" %s/META/fastboot-info.txt", targetFilesDir.String())
 	}
 
+	// kernel_configs.txt and kernel_version.txt
+	if a.kernelConfig != nil {
+		builder.Command().Textf("cp").Input(a.kernelConfig).Textf(" %s/META/", targetFilesDir.String())
+	}
+	if a.kernelVersion != nil {
+		builder.Command().Textf("cp").Input(a.kernelVersion).Textf(" %s/META/", targetFilesDir.String())
+	}
+	// misc_info.txt
+	if a.miscInfo != nil {
+		builder.Command().Textf("cp").Input(a.miscInfo).Textf(" %s/META/", targetFilesDir.String())
+	}
+	// apex_info.pb, care_map.pb, vbmeta_digest.txt
+	a.addImgToTargetFiles(ctx, builder, targetFilesDir.String())
+
 	if a.partitionProps.Super_partition_name != nil {
 		superPartition := ctx.GetDirectDepProxyWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
 		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
@@ -642,17 +685,71 @@
 
 }
 
+// A partial implementation of make's $PRODUCT_OUT/misc_info.txt
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=5894?q=misc_info.txt%20f:build%2Fmake%2Fcore%2FMakefile&ss=android%2Fplatform%2Fsuperproject%2Fmain
+// This file is subsequently used by add_img_to_target_files to create additioanl metadata files like apex_info.pb
+// TODO (b/399788119): Complete the migration of misc_info.txt
+func (a *androidDevice) addMiscInfo(ctx android.ModuleContext) android.Path {
+	builder := android.NewRuleBuilder(pctx, ctx)
+	miscInfo := android.PathForModuleOut(ctx, "misc_info.txt")
+	builder.Command().
+		Textf("rm -f %s", miscInfo).
+		Textf("&& echo recovery_api_version=%s >> %s", ctx.Config().VendorConfig("recovery").String("recovery_api_version"), miscInfo).
+		Textf("&& echo fstab_version=%s >> %s", ctx.Config().VendorConfig("recovery").String("recovery_fstab_version"), miscInfo).
+		ImplicitOutput(miscInfo)
+
+	if a.partitionProps.Recovery_partition_name == nil {
+		builder.Command().Textf("echo no_recovery=true >> %s", miscInfo)
+	}
+	fsInfos := a.getFsInfos(ctx)
+	for _, partition := range android.SortedKeys(fsInfos) {
+		if fsInfos[partition].UseAvb {
+			builder.Command().Textf("echo 'avb_%s_hashtree_enable=true' >> %s", partition, miscInfo)
+		}
+	}
+	if len(a.partitionProps.Vbmeta_partitions) > 0 {
+		builder.Command().
+			Textf("echo avb_enable=true >> %s", miscInfo).
+			Textf("&& echo avb_building_vbmeta_image=true >> %s", miscInfo).
+			Textf("&& echo avb_avbtool=avbtool >> %s", miscInfo)
+	}
+	if a.partitionProps.Boot_partition_name != nil {
+		builder.Command().Textf("echo boot_images=boot.img >> %s", miscInfo)
+	}
+
+	builder.Build("misc_info", "Building misc_info")
+
+	return miscInfo
+}
+
+// addImgToTargetFiles invokes `add_img_to_target_files` and creates the following files in META/
+// - apex_info.pb
+// - care_map.pb
+// - vbmeta_digest.txt
+func (a *androidDevice) addImgToTargetFiles(ctx android.ModuleContext, builder *android.RuleBuilder, targetFilesDir string) {
+	mkbootimg := ctx.Config().HostToolPath(ctx, "mkbootimg")
+	builder.Command().
+		Textf("PATH=%s:$PATH", ctx.Config().HostToolDir()).
+		Textf("MKBOOTIMG=%s", mkbootimg).
+		Implicit(mkbootimg).
+		BuiltTool("add_img_to_target_files").
+		Flag("-a -v -p").
+		Flag(ctx.Config().HostToolDir()).
+		Text(targetFilesDir)
+}
+
 type ApexKeyPathInfo struct {
 	ApexKeyPath android.Path
 }
 
 var ApexKeyPathInfoProvider = blueprint.NewProvider[ApexKeyPathInfo]()
 
-func (a *androidDevice) generateFilesystemConfigForTargetFiles(ctx android.ModuleContext, builder *android.RuleBuilder, targetFilesDir, stagingDir, filename string) {
+func (a *androidDevice) generateFilesystemConfigForTargetFiles(ctx android.ModuleContext, builder *android.RuleBuilder, stagingDirTimestamp android.Path, targetFilesDir, stagingDir, filename string) {
 	fsConfigOut := android.PathForModuleOut(ctx, filename)
 	ctx.Build(pctx, android.BuildParams{
-		Rule:   fsConfigRule,
-		Output: fsConfigOut,
+		Rule:     fsConfigRule,
+		Implicit: stagingDirTimestamp,
+		Output:   fsConfigOut,
 		Args: map[string]string{
 			"rootDir": stagingDir,
 			"prefix":  "",
@@ -698,3 +795,72 @@
 		}
 	}
 }
+
+func (a *androidDevice) getKernel(ctx android.ModuleContext) android.Path {
+	if a.partitionProps.Boot_partition_name != nil {
+		bootImg := ctx.GetDirectDepProxyWithTag(proptools.String(a.partitionProps.Boot_partition_name), filesystemDepTag)
+		bootImgInfo, _ := android.OtherModuleProvider(ctx, bootImg, BootimgInfoProvider)
+		return bootImgInfo.Kernel
+	}
+	return nil
+}
+
+// Gets the kernel version and configs from the actual kernel file itself. Roughly equivalent to
+// this make code: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=5443;drc=c0b66fc59de069e06ce0ffd703d4d21613be30c6
+// However, it is a simplified version of that make code. Differences include:
+//   - Not handling BOARD_KERNEL_CONFIG_FILE because BOARD_KERNEL_CONFIG_FILE was never used.
+//   - Not unpacking the bootimage, as we should be able to just always export the kernel directly
+//     in the BootimgInfo. We don't currently support prebuilt boot images, but even if we add that
+//     in the future, it can be done in a prebuilt_bootimage module type that still exports the same
+//     BootimgInfo.
+//   - We don't print a warning and output '<unknown-kernel>' to kernel_version_for_uffd_gc.txt
+//     because we expect the kernel to always be present. If it's not, we will get an error that
+//     kernel_version_for_uffd_gc.txt doesn't exist. This may require later tweaking to the
+//     dexpreopt rules so that they don't attempt to access that file in builds that don't have
+//     a kernel.
+func (a *androidDevice) extractKernelVersionAndConfigs(ctx android.ModuleContext) (android.Path, android.Path) {
+	kernel := a.getKernel(ctx)
+	// If there's no kernel, don't create kernel version / kernel config files. Reverse dependencies
+	// on those files have to account for this, for example by disabling dexpreopt in unbundled
+	// builds.
+	if kernel == nil {
+		return nil, nil
+	}
+
+	lz4tool := ctx.Config().HostToolPath(ctx, "lz4")
+
+	extractedVersionFile := android.PathForModuleOut(ctx, "kernel_version.txt")
+	extractedConfigsFile := android.PathForModuleOut(ctx, "kernel_configs.txt")
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().BuiltTool("extract_kernel").
+		Flag("--tools lz4:"+lz4tool.String()).Implicit(lz4tool).
+		FlagWithInput("--input ", kernel).
+		FlagWithOutput("--output-release ", extractedVersionFile).
+		FlagWithOutput("--output-configs ", extractedConfigsFile)
+
+	if specifiedVersion := proptools.String(a.deviceProps.Kernel_version); specifiedVersion != "" {
+		specifiedVersionFile := android.PathForModuleOut(ctx, "specified_kernel_version.txt")
+		android.WriteFileRuleVerbatim(ctx, specifiedVersionFile, specifiedVersion)
+		builder.Command().Text("diff -q").
+			Input(specifiedVersionFile).
+			Input(extractedVersionFile).
+			Textf(`|| (echo "Specified kernel version '$(cat %s)' does not match actual kernel version '$(cat %s)'"; exit 1)`, specifiedVersionFile, extractedVersionFile)
+	}
+
+	builder.Build("extract_kernel_info", "Extract kernel version and configs")
+
+	if proptools.Bool(a.deviceProps.Main_device) && !ctx.Config().KatiEnabled() {
+		if ctx.Config().EnableUffdGc() == "default" {
+			kernelVersionFile := android.PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.CpIfChanged,
+				Input:  extractedVersionFile,
+				Output: kernelVersionFile,
+			})
+		}
+
+		ctx.DistForGoal("droid_targets", extractedVersionFile)
+	}
+
+	return extractedVersionFile, extractedConfigsFile
+}
diff --git a/filesystem/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index c1e03cb..327a41f 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -210,6 +210,9 @@
 
 // Implements android.SourceFileProducer
 func (a *avbAddHashFooter) Srcs() android.Paths {
+	if a.output == nil {
+		return nil
+	}
 	return append(android.Paths{}, a.output)
 }
 
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 7257868..35fdd00 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -208,11 +208,6 @@
 	// Install aconfig_flags.pb file for the modules installed in this partition.
 	Gen_aconfig_flags_pb *bool
 
-	// List of names of other filesystem partitions to import their aconfig flags from.
-	// This is used for the system partition to import system_ext's aconfig flags, as currently
-	// those are considered one "container": aosp/3261300
-	Import_aconfig_flags_from []string
-
 	Fsverity fsverityProperties
 
 	// If this property is set to true, the filesystem will call ctx.UncheckedModule(), causing
@@ -359,9 +354,6 @@
 	if f.properties.Android_filesystem_deps.System_ext != nil {
 		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System_ext))
 	}
-	for _, partition := range f.properties.Import_aconfig_flags_from {
-		ctx.AddDependency(ctx.Module(), importAconfigDependencyTag, partition)
-	}
 	for _, partition := range f.properties.Include_files_of {
 		ctx.AddDependency(ctx.Module(), interPartitionInstallDependencyTag, partition)
 	}
@@ -444,6 +436,8 @@
 	FilesystemConfig android.Path
 
 	Owners []InstalledModuleInfo
+
+	UseAvb bool
 }
 
 // FullInstallPathInfo contains information about the "full install" paths of all the files
@@ -688,6 +682,7 @@
 		SelinuxFc:          f.selinuxFc,
 		FilesystemConfig:   f.generateFilesystemConfig(ctx, rootDir, rebasedDir),
 		Owners:             f.gatherOwners(specs),
+		UseAvb:             proptools.Bool(f.properties.Use_avb),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index d286c66..f00e491 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -425,6 +425,7 @@
 		Ab_ota_postinstall_config: ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.AbOtaPostInstallConfig,
 		Ramdisk_node_list:         proptools.StringPtr(":ramdisk_node_list"),
 		Android_info:              proptools.StringPtr(":" + generatedModuleName(ctx.Config(), "android_info.prop{.txt}")),
+		Kernel_version:            ctx.Config().ProductVariables().BoardKernelVersion,
 	}
 
 	if bootloader, ok := f.createBootloaderFilegroup(ctx); ok {
@@ -492,10 +493,6 @@
 		fsProps.Base_dir = proptools.StringPtr("system")
 		fsProps.Dirs = proptools.NewSimpleConfigurable(commonPartitionDirs)
 		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
-
-		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
-			fsProps.Import_aconfig_flags_from = []string{systemExtName}
-		}
 		fsProps.Stem = proptools.StringPtr("system.img")
 	case "system_ext":
 		if partitionVars.ProductFsverityGenerateMetadata {
diff --git a/java/app.go b/java/app.go
index c2aa8a4..fe5eec3 100644
--- a/java/app.go
+++ b/java/app.go
@@ -611,7 +611,7 @@
 	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		switch tag {
-		case staticLibTag:
+		case staticLibTag, rroDepTag:
 			if flagPackages, ok := android.OtherModuleProvider(ctx, dep, FlagsPackagesProvider); ok {
 				aconfigTextFilePaths = append(aconfigTextFilePaths, flagPackages.AconfigTextFiles...)
 			}
diff --git a/java/builder.go b/java/builder.go
index ade9784..dff0032 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -310,7 +310,7 @@
 
 	gatherReleasedFlaggedApisRule = pctx.AndroidStaticRule("gatherReleasedFlaggedApisRule",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}' ` +
+			Command: `${aconfig} dump-cache --dedup --format=protobuf ` +
 				`--out ${out} ` +
 				`${flags_path} ` +
 				`${filter_args} `,
@@ -320,8 +320,8 @@
 
 	generateMetalavaRevertAnnotationsRule = pctx.AndroidStaticRule("generateMetalavaRevertAnnotationsRule",
 		blueprint.RuleParams{
-			Command:     `${keep-flagged-apis} ${in} > ${out}`,
-			CommandDeps: []string{"${keep-flagged-apis}"},
+			Command:     `${aconfig-to-metalava-flags} ${in} > ${out}`,
+			CommandDeps: []string{"${aconfig-to-metalava-flags}"},
 		})
 
 	generateApiXMLRule = pctx.AndroidStaticRule("generateApiXMLRule",
@@ -339,7 +339,7 @@
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("ravenizer", "ravenizer")
 	pctx.HostBinToolVariable("apimapper", "apimapper")
-	pctx.HostBinToolVariable("keep-flagged-apis", "keep-flagged-apis")
+	pctx.HostBinToolVariable("aconfig-to-metalava-flags", "aconfig-to-metalava-flags")
 }
 
 type javaBuilderFlags struct {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 150129f..c215925 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -821,19 +821,17 @@
 		}
 	}
 
-	if len(aconfigFlagsPaths) == 0 {
-		// This argument should not be added for "everything" stubs
-		cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
-		return
-	}
+	// If aconfigFlagsPaths is empty then it is still important to generate the
+	// Metalava flags config file, albeit with an empty set of flags, so that all
+	// flagged APIs will be reverted.
 
-	releasedFlaggedApisFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flagged-apis-%s.txt", stubsType.String()))
-	revertAnnotationsFile := android.PathForModuleOut(ctx, fmt.Sprintf("revert-annotations-%s.txt", stubsType.String()))
+	releasedFlagsFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flags-%s.pb", stubsType.String()))
+	metalavaFlagsConfigFile := android.PathForModuleOut(ctx, fmt.Sprintf("flags-config-%s.xml", stubsType.String()))
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        gatherReleasedFlaggedApisRule,
 		Inputs:      aconfigFlagsPaths,
-		Output:      releasedFlaggedApisFile,
+		Output:      releasedFlagsFile,
 		Description: fmt.Sprintf("%s gather aconfig flags", stubsType),
 		Args: map[string]string{
 			"flags_path":  android.JoinPathsWithPrefix(aconfigFlagsPaths, "--cache "),
@@ -843,12 +841,12 @@
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        generateMetalavaRevertAnnotationsRule,
-		Input:       releasedFlaggedApisFile,
-		Output:      revertAnnotationsFile,
-		Description: fmt.Sprintf("%s revert annotations", stubsType),
+		Input:       releasedFlagsFile,
+		Output:      metalavaFlagsConfigFile,
+		Description: fmt.Sprintf("%s metalava flags config", stubsType),
 	})
 
-	cmd.FlagWithInput("@", revertAnnotationsFile)
+	cmd.FlagWithInput("--config-file ", metalavaFlagsConfigFile)
 }
 
 func (d *Droidstubs) commonMetalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index dfdf877..1f9d223 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -410,12 +410,12 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
-		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+		strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
 
 	// revert-annotations.txt passed to exportable stubs generation metalava command
 	manifest := m.Output("metalava_exportable.sbox.textproto")
 	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
 
 	android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
 		strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
@@ -460,7 +460,7 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 
-	rule := m.Output("released-flagged-apis-exportable.txt")
+	rule := m.Output("released-flags-exportable.pb")
 	exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
 	android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
 }
diff --git a/java/java_test.go b/java/java_test.go
index a6290a6..6df81e5 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2912,12 +2912,12 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
-		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+		strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
 
 	// revert-annotations.txt passed to exportable stubs generation metalava command
 	manifest := m.Output("metalava.sbox.textproto")
 	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
 }
 
 func TestTestOnly(t *testing.T) {
diff --git a/java/rro.go b/java/rro.go
index f7f85f0..42d42b8 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -406,10 +406,11 @@
 
 	a.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
-			sdkContext:      a,
-			extraLinkFlags:  aaptLinkFlags,
-			rroDirs:         &rroDirs,
-			manifestForAapt: genManifest,
+			sdkContext:       a,
+			extraLinkFlags:   aaptLinkFlags,
+			rroDirs:          &rroDirs,
+			manifestForAapt:  genManifest,
+			aconfigTextFiles: getAconfigFilePaths(ctx),
 		},
 	)
 
@@ -426,6 +427,11 @@
 	// Install the signed apk
 	installDir := android.PathForModuleInstall(ctx, "overlay")
 	ctx.InstallFile(installDir, signed.Base(), signed)
+
+	android.SetProvider(ctx, RuntimeResourceOverlayInfoProvider, RuntimeResourceOverlayInfo{
+		OutputFile:  signed,
+		Certificate: a.certificate,
+	})
 }
 
 func (a *AutogenRuntimeResourceOverlay) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
diff --git a/java/sdk.go b/java/sdk.go
index ab1c653..73262da 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -382,7 +382,7 @@
 
 	rule.Build("api_fingerprint", "generate api_fingerprint.txt")
 
-	if ctx.Config().BuildOS == android.Linux {
+	if ctx.Config().BuildOS.Linux() {
 		ctx.DistForGoals([]string{"sdk", "droidcore"}, out)
 	}
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 94163a5..c0e13d5 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -287,9 +287,13 @@
     ],
 }
 
-sh_binary_host {
-    name: "keep-flagged-apis",
-    src: "keep-flagged-apis.sh",
+python_binary_host {
+    name: "aconfig-to-metalava-flags",
+    main: "aconfig-to-metalava-flags.py",
+    srcs: ["aconfig-to-metalava-flags.py"],
+    libs: [
+        "libaconfig_python_proto",
+    ],
 }
 
 python_binary_host {
diff --git a/scripts/aconfig-to-metalava-flags.py b/scripts/aconfig-to-metalava-flags.py
new file mode 100644
index 0000000..efa85ec
--- /dev/null
+++ b/scripts/aconfig-to-metalava-flags.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2025 The Android Open Source Project
+#
+# 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.
+
+"""Converts a set of aconfig protobuf flags to a Metalava config file."""
+
+# Formatted using `pyformat -i scripts/aconfig-to-metalava-flags.py`
+
+import argparse
+import sys
+import xml.etree.ElementTree as ET
+
+from protos import aconfig_pb2
+
+_READ_ONLY = aconfig_pb2.flag_permission.READ_ONLY
+_ENABLED = aconfig_pb2.flag_state.ENABLED
+_DISABLED = aconfig_pb2.flag_state.DISABLED
+
+# The namespace of the Metalava config file.
+CONFIG_NS = 'http://www.google.com/tools/metalava/config'
+
+
+def config_name(tag: str):
+  """Create a QName in the config namespace.
+
+  :param:tag the name of the entity in the config namespace.
+  """
+  return f'{{{CONFIG_NS}}}{tag}'
+
+
+def main():
+  """Program entry point."""
+  args_parser = argparse.ArgumentParser(
+      description='Generate Metalava flags config from aconfig protobuf',
+  )
+  args_parser.add_argument(
+      'input',
+      help='The path to the aconfig protobuf file',
+  )
+  args = args_parser.parse_args(sys.argv[1:])
+
+  # Read the parsed_flags from the protobuf file.
+  with open(args.input, 'rb') as f:
+    parsed_flags = aconfig_pb2.parsed_flags.FromString(f.read())
+
+  # Create the structure of the XML config file.
+  config = ET.Element(config_name('config'))
+  api_flags = ET.SubElement(config, config_name('api-flags'))
+  # Create an <api-flag> element for each parsed_flag.
+  for flag in parsed_flags.parsed_flag:
+    if flag.permission == _READ_ONLY:
+      # Ignore any read only disabled flags as Metalava assumes that as the
+      # default when an <api-flags/> element is provided so this reduces the
+      # size of the file.
+      if flag.state == _DISABLED:
+        continue
+      mutability = 'immutable'
+    else:
+      mutability = 'mutable'
+    if flag.state == _ENABLED:
+      status = 'enabled'
+    else:
+      status = 'disabled'
+    attributes = {
+        'package': flag.package,
+        'name': flag.name,
+        'mutability': mutability,
+        'status': status,
+    }
+    # Convert the attribute names into qualified names in, what will become, the
+    # default namespace for the XML file. This is needed to ensure that the
+    # attribute will be written in the XML file without a prefix, e.g.
+    # `name="flag_name"`. Without it, a namespace prefix, e.g. `ns1`, will be
+    # synthesized for the attribute when writing the XML file, i.e. it
+    # will be written as `ns1:name="flag_name"`. Strictly speaking, that is
+    # unnecessary as the "Namespaces in XML 1.0 (Third Edition)" specification
+    # says that unprefixed attribute names have no namespace.
+    qualified_attributes = {config_name(k): v for (k, v) in attributes.items()}
+    ET.SubElement(api_flags, config_name('api-flag'), qualified_attributes)
+
+  # Create a tree and add white space so it will pretty print when written out.
+  tree = ET.ElementTree(config)
+  ET.indent(tree)
+
+  # Write the tree using the config namespace as the default.
+  tree.write(sys.stdout, encoding='unicode', default_namespace=CONFIG_NS)
+  sys.stdout.close()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/keep-flagged-apis.sh b/scripts/keep-flagged-apis.sh
deleted file mode 100755
index 48efb7a..0000000
--- a/scripts/keep-flagged-apis.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash -e
-#
-# 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.
-
-# Convert a list of flags in the input file to a list of metalava options
-# that will keep the APIs for those flags will hiding all other flagged
-# APIs.
-
-FLAGS="$1"
-
-FLAGGED="android.annotation.FlaggedApi"
-
-# Convert the list of feature flags in the input file to Metalava options
-# of the form `--revert-annotation !android.annotation.FlaggedApi("<flag>")`
-# to prevent the annotated APIs from being hidden, i.e. include the annotated
-# APIs in the SDK snapshots.
-while read -r line; do
-  # Escape and quote the key for sed
-  escaped_line=$(echo "$line" | sed "s/'/\\\'/g; s/ /\\ /g")
-
-  echo "--revert-annotation '!$FLAGGED(\"$escaped_line\")'"
-done < "$FLAGS"
-
-# Revert all flagged APIs, unless listed above.
-echo "--revert-annotation $FLAGGED"
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 988352c..6dd48eb 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -162,6 +162,22 @@
 	m.validateBase(ctx, &testModuleConfigTag, "android_test", false)
 	m.generateManifestAndConfig(ctx)
 
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{m.provider.MkAppClass}
+	if m.provider.MkAppClass != "NATIVE_TESTS" {
+		moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	}
+	if m.provider.IsUnitTest {
+		moduleInfoJSON.IsUnitTest = "true"
+	}
+	moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, m.tradefedProperties.Test_suites...)
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	moduleInfoJSON.ExtraRequired = append(moduleInfoJSON.ExtraRequired, m.provider.RequiredModuleNames...)
+	moduleInfoJSON.ExtraRequired = append(moduleInfoJSON.ExtraRequired, *m.Base)
+	moduleInfoJSON.ExtraHostRequired = append(moduleInfoJSON.ExtraRequired, m.provider.HostRequiredModuleNames...)
+	moduleInfoJSON.TestConfig = []string{m.testConfig.String()}
+	moduleInfoJSON.AutoTestConfig = []string{"true"}
+	moduleInfoJSON.TestModuleConfigBase = proptools.String(m.Base)
 }
 
 // Ensure at least one test_suite is listed.  Ideally it should be general-tests