Merge "Add RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY build flag." into main
diff --git a/android/arch_test.go b/android/arch_test.go
index 57c9010..7914884 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -560,15 +560,7 @@
 				prepareForArchTest,
 				// Test specific preparer
 				OptionalFixturePreparer(tt.preparer),
-				// Prepare for native bridge test
-				FixtureModifyConfig(func(config Config) {
-					config.Targets[Android] = []Target{
-						{Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
-						{Android, Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", "", false},
-						{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled, "x86_64", "arm64", false},
-						{Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled, "x86", "arm", false},
-					}
-				}),
+				PrepareForNativeBridgeEnabled,
 				FixtureWithRootAndroidBp(bp),
 			).RunTest(t)
 
diff --git a/android/config.go b/android/config.go
index cb604a6..b811c55 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1519,6 +1519,13 @@
 	return "64"
 }
 
+func (c *deviceConfig) RecoveryPath() string {
+	if c.config.productVariables.RecoveryPath != nil {
+		return *c.config.productVariables.RecoveryPath
+	}
+	return "recovery"
+}
+
 func (c *deviceConfig) VendorPath() string {
 	if c.config.productVariables.VendorPath != nil {
 		return *c.config.productVariables.VendorPath
@@ -1614,6 +1621,10 @@
 	return proptools.Bool(c.config.productVariables.BuildingUserdataImage)
 }
 
+func (c *deviceConfig) BuildingRecoveryImage() bool {
+	return proptools.Bool(c.config.productVariables.BuildingRecoveryImage)
+}
+
 func (c *deviceConfig) BtConfigIncludeDir() string {
 	return String(c.config.productVariables.BtConfigIncludeDir)
 }
diff --git a/android/hooks.go b/android/hooks.go
index 9f4e5b6..f8022d0 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -59,6 +59,16 @@
 	})
 }
 
+func AddLoadHookWithPriority(m blueprint.Module, hook func(LoadHookContext), priority int) {
+	blueprint.AddLoadHookWithPriority(m, func(ctx blueprint.LoadHookContext) {
+		actx := &loadHookContext{
+			earlyModuleContext: m.(Module).base().earlyModuleContextFactory(ctx),
+			bp:                 ctx,
+		}
+		hook(actx)
+	}, priority)
+}
+
 type loadHookContext struct {
 	earlyModuleContext
 	bp     blueprint.LoadHookContext
diff --git a/android/module.go b/android/module.go
index 67dab4f..d703c19 100644
--- a/android/module.go
+++ b/android/module.go
@@ -338,6 +338,7 @@
 		}
 		Android struct {
 			Compile_multilib *string
+			Enabled          *bool
 		}
 	}
 
@@ -1387,6 +1388,8 @@
 		partition = "ramdisk"
 	} else if m.InstallInVendorRamdisk() {
 		partition = "vendor_ramdisk"
+	} else if m.InstallInRecovery() {
+		partition = "recovery"
 	}
 	return partition
 }
@@ -1866,6 +1869,7 @@
 	// The Target of artifacts that this module variant is responsible for creating.
 	CompileTarget           Target
 	SkipAndroidMkProcessing bool
+	BaseModuleName          string
 }
 
 var CommonModuleInfoKey = blueprint.NewProvider[CommonModuleInfo]()
@@ -2134,6 +2138,7 @@
 		ReplacedByPrebuilt:      m.commonProperties.ReplacedByPrebuilt,
 		CompileTarget:           m.commonProperties.CompileTarget,
 		SkipAndroidMkProcessing: shouldSkipAndroidMkProcessing(ctx, m),
+		BaseModuleName:          m.BaseModuleName(),
 	}
 	if m.commonProperties.ForcedDisabled {
 		commonData.Enabled = false
diff --git a/android/neverallow.go b/android/neverallow.go
index 7f7ffa7..fdcbe1c 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -245,6 +245,11 @@
 			Without("name", "librecovery_ui_ext").
 			With("install_in_root", "true").
 			NotModuleType("prebuilt_root").
+			NotModuleType("prebuilt_vendor").
+			NotModuleType("prebuilt_sbin").
+			NotModuleType("prebuilt_system").
+			NotModuleType("prebuilt_first_stage_ramdisk").
+			NotModuleType("prebuilt_res").
 			Because("install_in_root is only for init_first_stage or librecovery_ui_ext."),
 	}
 }
@@ -282,7 +287,7 @@
 }
 
 func createLimitDirgroupRule() []Rule {
-	reason := "dirgroup module and dir_srcs property of genrule is allowed only to Trusty build rule."
+	reason := "dirgroup module and dir_srcs / keep_gendir property of genrule is allowed only to Trusty build rule."
 	return []Rule{
 		NeverAllow().
 			ModuleType("dirgroup").
@@ -297,6 +302,13 @@
 			Without("name", "trusty-x86_64.lk.elf.gen").
 			Without("name", "trusty-x86_64-test.lk.elf.gen").
 			WithMatcher("dir_srcs", isSetMatcherInstance).Because(reason),
+		NeverAllow().
+			ModuleType("genrule").
+			Without("name", "trusty-arm64.lk.elf.gen").
+			Without("name", "trusty-arm64-virt-test-debug.lk.elf.gen").
+			Without("name", "trusty-x86_64.lk.elf.gen").
+			Without("name", "trusty-x86_64-test.lk.elf.gen").
+			With("keep_gendir", "true").Because(reason),
 	}
 }
 
@@ -333,7 +345,6 @@
 			"prebuilt_priv_app",
 			"prebuilt_rfs",
 			"prebuilt_framework",
-			"prebuilt_res",
 			"prebuilt_wlc_upt",
 			"prebuilt_odm",
 			"prebuilt_vendor_dlkm",
@@ -341,6 +352,10 @@
 			"prebuilt_tvservice",
 			"prebuilt_optee",
 			"prebuilt_tvconfig",
+			"prebuilt_vendor",
+			"prebuilt_sbin",
+			"prebuilt_system",
+			"prebuilt_first_stage_ramdisk",
 		).
 		DefinedInBpFile().
 		Because("module type not allowed to be defined in bp file")
@@ -705,6 +720,9 @@
 }
 
 func (r *rule) appliesToModuleType(moduleType string) bool {
+	// Remove prefix for auto-generated modules
+	moduleType = strings.TrimSuffix(moduleType, "__loadHookModule")
+	moduleType = strings.TrimSuffix(moduleType, "__bottomUpMutatorModule")
 	return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
 }
 
diff --git a/android/packaging.go b/android/packaging.go
index dcd8844..d96cccd 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -18,7 +18,6 @@
 	"fmt"
 	"path/filepath"
 	"sort"
-	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/gobtools"
@@ -594,10 +593,6 @@
 	}
 
 	seenDir := make(map[string]bool)
-	preparerPath := PathForModuleOut(ctx, "preparer.sh")
-	cmd := builder.Command().Tool(preparerPath)
-	var sb strings.Builder
-	sb.WriteString("set -e\n")
 
 	dirs := make([]WritablePath, 0, len(dirsToSpecs))
 	for dir, _ := range dirsToSpecs {
@@ -616,22 +611,19 @@
 			entries = append(entries, ps.relPathInPackage)
 			if _, ok := seenDir[destDir]; !ok {
 				seenDir[destDir] = true
-				sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
+				builder.Command().Textf("mkdir -p %s", destDir)
 			}
 			if ps.symlinkTarget == "" {
-				cmd.Implicit(ps.srcPath)
-				sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
+				builder.Command().Text("cp").Input(ps.srcPath).Text(destPath)
 			} else {
-				sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
+				builder.Command().Textf("ln -sf %s %s", ps.symlinkTarget, destPath)
 			}
 			if ps.executable {
-				sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+				builder.Command().Textf("chmod a+x %s", destPath)
 			}
 		}
 	}
 
-	WriteExecutableFileRuleVerbatim(ctx, preparerPath, sb.String())
-
 	return entries
 }
 
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 83f8b99..db56c3f 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -63,6 +63,7 @@
 	missingDeps      []string
 	args             map[string]string
 	nsjail           bool
+	nsjailKeepGendir bool
 	nsjailBasePath   WritablePath
 	nsjailImplicits  Paths
 }
@@ -208,6 +209,18 @@
 	return r
 }
 
+// By default, nsjail rules truncate outputDir and baseDir before running commands, similar to Sbox
+// rules which always run commands in a fresh sandbox. Calling NsjailKeepGendir keeps outputDir and
+// baseDir as-is, leaving previous artifacts. This is useful when the rules support incremental
+// builds.
+func (r *RuleBuilder) NsjailKeepGendir() *RuleBuilder {
+	if !r.nsjail {
+		panic("NsjailKeepGendir() must be called after Nsjail()")
+	}
+	r.nsjailKeepGendir = true
+	return r
+}
+
 // SandboxTools enables tool sandboxing for the rule by copying any referenced tools into the
 // sandbox.
 func (r *RuleBuilder) SandboxTools() *RuleBuilder {
@@ -555,8 +568,17 @@
 	if r.nsjail {
 		var nsjailCmd strings.Builder
 		nsjailPath := r.ctx.Config().PrebuiltBuildTool(r.ctx, "nsjail")
+		if !r.nsjailKeepGendir {
+			nsjailCmd.WriteString("rm -rf ")
+			nsjailCmd.WriteString(r.nsjailBasePath.String())
+			nsjailCmd.WriteRune(' ')
+			nsjailCmd.WriteString(r.outDir.String())
+			nsjailCmd.WriteString(" && ")
+		}
 		nsjailCmd.WriteString("mkdir -p ")
 		nsjailCmd.WriteString(r.nsjailBasePath.String())
+		nsjailCmd.WriteRune(' ')
+		nsjailCmd.WriteString(r.outDir.String())
 		nsjailCmd.WriteString(" && ")
 		nsjailCmd.WriteString(nsjailPath.String())
 		nsjailCmd.WriteRune(' ')
@@ -853,6 +875,18 @@
 		pool = localPool
 	}
 
+	// If the command length is getting close to linux's maximum, dump it to a file, which allows
+	// for longer commands.
+	if len(commandString) > 100000 {
+		hasher := sha256.New()
+		hasher.Write([]byte(output.String()))
+		script := PathForOutput(r.ctx, "rule_builder_scripts", fmt.Sprintf("%x.sh", hasher.Sum(nil)))
+		commandString = "set -eu\n\n" + commandString + "\n"
+		WriteExecutableFileRuleVerbatim(r.ctx, script, commandString)
+		inputs = append(inputs, script)
+		commandString = script.String()
+	}
+
 	commandString = proptools.NinjaEscape(commandString)
 
 	args_vars := make([]string, len(r.args))
diff --git a/android/testing.go b/android/testing.go
index f243e81..765839f 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -190,6 +190,26 @@
 	})
 }
 
+// PrepareForNativeBridgeEnabled sets configuration with targets including:
+// - X86_64 (primary)
+// - X86 (secondary)
+// - Arm64 on X86_64 (native bridge)
+// - Arm on X86 (native bridge)
+var PrepareForNativeBridgeEnabled = FixtureModifyConfig(
+	func(config Config) {
+		config.Targets[Android] = []Target{
+			{Os: Android, Arch: Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}},
+				NativeBridge: NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
+			{Os: Android, Arch: Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
+				NativeBridge: NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
+			{Os: Android, Arch: Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}},
+				NativeBridge: NativeBridgeEnabled, NativeBridgeHostArchName: "x86_64", NativeBridgeRelativePath: "arm64"},
+			{Os: Android, Arch: Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
+				NativeBridge: NativeBridgeEnabled, NativeBridgeHostArchName: "x86", NativeBridgeRelativePath: "arm"},
+		}
+	},
+)
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
diff --git a/android/variable.go b/android/variable.go
index 25d99da..baa2646 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -351,6 +351,8 @@
 	OemPath               *string `json:",omitempty"`
 	UserdataPath          *string `json:",omitempty"`
 	BuildingUserdataImage *bool   `json:",omitempty"`
+	RecoveryPath          *string `json:",omitempty"`
+	BuildingRecoveryImage *bool   `json:",omitempty"`
 
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
@@ -665,6 +667,10 @@
 	VendorRamdiskKernelBlocklistFile string   `json:",omitempty"`
 	VendorRamdiskKernelLoadModules   []string `json:",omitempty"`
 	VendorRamdiskKernelOptionsFile   string   `json:",omitempty"`
+
+	ProductFsverityGenerateMetadata bool `json:",omitempty"`
+
+	TargetScreenDensity string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/apex/apex.go b/apex/apex.go
index 72a0455..f934134 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1850,7 +1850,7 @@
 // as the apex.
 func (a *apexBundle) enforcePartitionTagOnApexSystemServerJar(ctx android.ModuleContext) {
 	global := dexpreopt.GetGlobalConfig(ctx)
-	ctx.VisitDirectDepsWithTag(sscpfTag, func(child android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(sscpfTag, func(child android.ModuleProxy) {
 		info, ok := android.OtherModuleProvider(ctx, child, java.LibraryNameToPartitionInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("Could not find partition info of apex system server jars.")
@@ -2309,7 +2309,7 @@
 // Apexes built from source retrieve this information by visiting `bootclasspath_fragments`
 // Used by dex_bootjars to generate the boot image
 func (a *apexBundle) provideApexExportsInfo(ctx android.ModuleContext) {
-	ctx.VisitDirectDepsWithTag(bcpfTag, func(child android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(bcpfTag, func(child android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, child, java.BootclasspathFragmentApexContentInfoProvider); ok {
 			exports := android.ApexExportsInfo{
 				ApexName:                      a.ApexVariationName(),
@@ -2340,7 +2340,7 @@
 	}
 	if a.Updatable() {
 		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
-		mctx.VisitDirectDeps(func(module android.Module) {
+		mctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 			if appInfo, ok := android.OtherModuleProvider(mctx, module, java.AppInfoProvider); ok {
 				// ignore android_test_app
 				if !appInfo.TestHelperApp && !appInfo.Updatable {
@@ -2647,16 +2647,12 @@
 func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
 	// Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs,
 	// java's checkLinkType guarantees correct usage for transitive deps
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		switch tag {
 		case javaLibTag, androidAppTag:
-			if m, ok := module.(interface {
-				CheckStableSdkVersion(ctx android.BaseModuleContext) error
-			}); ok {
-				if err := m.CheckStableSdkVersion(ctx); err != nil {
-					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
-				}
+			if err := java.CheckStableSdkVersion(ctx, module); err != nil {
+				ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
 			}
 		}
 	})
@@ -2889,7 +2885,7 @@
 	}
 
 	var appEmbeddedJNILibs android.Paths
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		if !checkApexTag(tag) {
 			return
diff --git a/apex/apex_test.go b/apex/apex_test.go
index ed6284d..282cd1d 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -84,26 +84,6 @@
 	return files.AddToFixture()
 }
 
-// withNativeBridgeTargets sets configuration with targets including:
-// - X86_64 (primary)
-// - X86 (secondary)
-// - Arm64 on X86_64 (native bridge)
-// - Arm on X86 (native bridge)
-var withNativeBridgeEnabled = android.FixtureModifyConfig(
-	func(config android.Config) {
-		config.Targets[android.Android] = []android.Target{
-			{Os: android.Android, Arch: android.Arch{ArchType: android.X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}},
-				NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
-				NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}},
-				NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86_64", NativeBridgeRelativePath: "arm64"},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
-				NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86", NativeBridgeRelativePath: "arm"},
-		}
-	},
-)
-
 func withManifestPackageNameOverrides(specs []string) android.FixturePreparer {
 	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 		variables.ManifestPackageNameOverrides = specs
@@ -3198,7 +3178,7 @@
 				},
 			},
 		}
-	`, withNativeBridgeEnabled)
+	`, android.PrepareForNativeBridgeEnabled)
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"bin/foo/bar/mybin",
 		"bin/foo/bar/mybin64",
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index b0b5da9..2bcbde1 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -81,6 +81,10 @@
 	ctx.RegisterModuleType("prebuilt_tvservice", PrebuiltTvServiceFactory)
 	ctx.RegisterModuleType("prebuilt_optee", PrebuiltOpteeFactory)
 	ctx.RegisterModuleType("prebuilt_tvconfig", PrebuiltTvConfigFactory)
+	ctx.RegisterModuleType("prebuilt_vendor", PrebuiltVendorFactory)
+	ctx.RegisterModuleType("prebuilt_sbin", PrebuiltSbinFactory)
+	ctx.RegisterModuleType("prebuilt_system", PrebuiltSystemFactory)
+	ctx.RegisterModuleType("prebuilt_first_stage_ramdisk", PrebuiltFirstStageRamdiskFactory)
 
 	ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
 
@@ -573,6 +577,7 @@
 	p.installDirBase = dirBase
 	p.AddProperties(&p.properties)
 	p.AddProperties(&p.subdirProperties)
+	p.AddProperties(&p.rootProperties)
 }
 
 func InitPrebuiltRootModule(p *PrebuiltEtc) {
@@ -972,3 +977,43 @@
 	android.InitDefaultableModule(module)
 	return module
 }
+
+// prebuilt_vendor installs files in <partition>/vendor directory.
+func PrebuiltVendorFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "vendor")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_sbin installs files in <partition>/sbin directory.
+func PrebuiltSbinFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "sbin")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_system installs files in <partition>/system directory.
+func PrebuiltSystemFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "system")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_first_stage_ramdisk installs files in <partition>/first_stage_ramdisk directory.
+func PrebuiltFirstStageRamdiskFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "first_stage_ramdisk")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
index 7de404f..c80ae03 100644
--- a/filesystem/aconfig_files.go
+++ b/filesystem/aconfig_files.go
@@ -16,7 +16,6 @@
 
 import (
 	"android/soong/android"
-	"strings"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -26,57 +25,34 @@
 		return
 	}
 
-	aconfigFlagsBuilderPath := android.PathForModuleOut(ctx, "aconfig_flags_builder.sh")
-	aconfigToolPath := ctx.Config().HostToolPath(ctx, "aconfig")
-	cmd := builder.Command().Tool(aconfigFlagsBuilderPath).Implicit(aconfigToolPath)
-
-	var caches []string
+	var caches []android.Path
 	for _, ps := range specs {
-		cmd.Implicits(ps.GetAconfigPaths())
-		caches = append(caches, ps.GetAconfigPaths().Strings()...)
+		caches = append(caches, ps.GetAconfigPaths()...)
 	}
-	caches = android.SortedUniqueStrings(caches)
-
-	var sbCaches strings.Builder
-	for _, cache := range caches {
-		sbCaches.WriteString("  --cache ")
-		sbCaches.WriteString(cache)
-		sbCaches.WriteString(" \\\n")
-	}
-	sbCaches.WriteRune('\n')
-
-	var sb strings.Builder
-	sb.WriteString("set -e\n")
+	caches = android.SortedUniquePaths(caches)
 
 	installAconfigFlagsPath := dir.Join(ctx, "etc", "aconfig_flags.pb")
-	sb.WriteString(aconfigToolPath.String())
-	sb.WriteString(" dump-cache --dedup --format protobuf --out ")
-	sb.WriteString(installAconfigFlagsPath.String())
-	sb.WriteString(" --filter container:")
-	sb.WriteString(f.PartitionType())
-	sb.WriteString(" \\\n")
-	sb.WriteString(sbCaches.String())
-	cmd.ImplicitOutput(installAconfigFlagsPath)
+	cmd := builder.Command().
+		BuiltTool("aconfig").
+		Text(" dump-cache --dedup --format protobuf --out").
+		Output(installAconfigFlagsPath).
+		Textf("--filter container:%s", f.PartitionType())
+	for _, cache := range caches {
+		cmd.FlagWithInput("--cache ", cache)
+	}
 	f.appendToEntry(ctx, installAconfigFlagsPath)
 
 	installAconfigStorageDir := dir.Join(ctx, "etc", "aconfig")
-	sb.WriteString("mkdir -p ")
-	sb.WriteString(installAconfigStorageDir.String())
-	sb.WriteRune('\n')
+	builder.Command().Text("mkdir -p").Text(installAconfigStorageDir.String())
 
 	generatePartitionAconfigStorageFile := func(fileType, fileName string) {
 		outputPath := installAconfigStorageDir.Join(ctx, fileName)
-		sb.WriteString(aconfigToolPath.String())
-		sb.WriteString(" create-storage --container ")
-		sb.WriteString(f.PartitionType())
-		sb.WriteString(" --file ")
-		sb.WriteString(fileType)
-		sb.WriteString(" --out ")
-		sb.WriteString(outputPath.String())
-		sb.WriteString(" --cache ")
-		sb.WriteString(installAconfigFlagsPath.String())
-		sb.WriteRune('\n')
-		cmd.ImplicitOutput(outputPath)
+		builder.Command().
+			BuiltTool("aconfig").
+			FlagWithArg("create-storage --container ", f.PartitionType()).
+			FlagWithArg("--file ", fileType).
+			FlagWithOutput("--out ", outputPath).
+			FlagWithArg("--cache ", installAconfigFlagsPath.String())
 		f.appendToEntry(ctx, outputPath)
 	}
 
@@ -86,6 +62,4 @@
 		generatePartitionAconfigStorageFile("flag_val", "flag.val")
 		generatePartitionAconfigStorageFile("flag_info", "flag.info")
 	}
-
-	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 5b217ae..fbc8089 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -25,6 +25,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/java"
 	"android/soong/linkerconfig"
 
 	"github.com/google/blueprint"
@@ -33,6 +34,7 @@
 
 func init() {
 	registerBuildComponents(android.InitRegistrationContext)
+	registerMutators(android.InitRegistrationContext)
 }
 
 func registerBuildComponents(ctx android.RegistrationContext) {
@@ -45,6 +47,12 @@
 	ctx.RegisterModuleType("avb_gen_vbmeta_image_defaults", avbGenVbmetaImageDefaultsFactory)
 }
 
+func registerMutators(ctx android.RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("add_autogenerated_rro_deps", addAutogeneratedRroDeps)
+	})
+}
+
 type filesystem struct {
 	android.ModuleBase
 	android.PackagingBase
@@ -170,6 +178,14 @@
 	// Path to the dev nodes description file. This is only needed for building the ramdisk
 	// partition and should not be explicitly specified.
 	Dev_nodes_description_file *string `android:"path" blueprint:"mutated"`
+
+	// Additional dependencies used for building android products
+	Android_filesystem_deps AndroidFilesystemDeps
+}
+
+type AndroidFilesystemDeps struct {
+	System     *string
+	System_ext *string
 }
 
 // Additional properties required to generate erofs FS partitions.
@@ -235,6 +251,12 @@
 	depTag
 }
 
+type interPartitionDepTag struct {
+	blueprint.BaseDependencyTag
+}
+
+var interPartitionDependencyTag = interPartitionDepTag{}
+
 var _ android.ExcludeFromVisibilityEnforcementTag = (*depTagWithVisibilityEnforcementBypass)(nil)
 
 func (t depTagWithVisibilityEnforcementBypass) ExcludeFromVisibilityEnforcement() {}
@@ -257,6 +279,12 @@
 	} else {
 		f.AddDeps(ctx, dependencyTag)
 	}
+	if f.properties.Android_filesystem_deps.System != nil {
+		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System))
+	}
+	if f.properties.Android_filesystem_deps.System_ext != nil {
+		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System_ext))
+	}
 }
 
 type fsType int
@@ -649,11 +677,10 @@
 	switch fst {
 	case erofsType:
 		// Add erofs properties
-		if compressor := f.properties.Erofs.Compressor; compressor != nil {
-			addStr("erofs_default_compressor", proptools.String(compressor))
-		}
-		if compressHints := f.properties.Erofs.Compress_hints; compressHints != nil {
-			addPath("erofs_default_compress_hints", android.PathForModuleSrc(ctx, *compressHints))
+		addStr("erofs_default_compressor", proptools.StringDefault(f.properties.Erofs.Compressor, "lz4hc,9"))
+		if f.properties.Erofs.Compress_hints != nil {
+			src := android.PathForModuleSrc(ctx, *f.properties.Erofs.Compress_hints)
+			addPath("erofs_default_compress_hints", src)
 		}
 		if proptools.BoolDefault(f.properties.Erofs.Sparse, true) {
 			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2292;bpv=1;bpt=0;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b
@@ -762,6 +789,7 @@
 	"system_dlkm",
 	"ramdisk",
 	"vendor_ramdisk",
+	"recovery",
 }
 
 func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
@@ -1004,3 +1032,30 @@
 		image, maxSize)
 	cmd.Implicit(image)
 }
+
+// addAutogeneratedRroDeps walks the transitive closure of vendor and product partitions.
+// It visits apps installed in system and system_ext partitions, and adds the autogenerated
+// RRO modules to its own deps.
+func addAutogeneratedRroDeps(ctx android.BottomUpMutatorContext) {
+	f, ok := ctx.Module().(*filesystem)
+	if !ok {
+		return
+	}
+	thisPartition := f.PartitionType()
+	if thisPartition != "vendor" && thisPartition != "product" {
+		return
+	}
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if parent.Name() == f.Name() && depTag != interPartitionDependencyTag {
+			return false // This is a module listed in deps of vendor/product filesystem
+		}
+		if vendorOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "vendor"); ctx.OtherModuleExists(vendorOverlay) && thisPartition == "vendor" {
+			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, vendorOverlay)
+		}
+		if productOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "product"); ctx.OtherModuleExists(productOverlay) && thisPartition == "product" {
+			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, productOverlay)
+		}
+		return true
+	})
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 746e4de..72a5211 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"os"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -182,10 +183,14 @@
 	module := result.ModuleForTests("myfilesystem", "android_common")
 	output := module.Output("out/soong/.intermediates/myfilesystem/android_common/root/system/etc/linker.config.pb")
 
+	fullCommand := output.RuleParams.Command
+	startIndex := strings.Index(fullCommand, "conv_linker_config")
+	linkerConfigCommand := fullCommand[startIndex:]
+
 	android.AssertStringDoesContain(t, "linker.config.pb should have libfoo",
-		output.RuleParams.Command, "libfoo.so")
+		linkerConfigCommand, "libfoo.so")
 	android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
-		output.RuleParams.Command, "libbar.so")
+		linkerConfigCommand, "libbar.so")
 }
 
 func registerComponent(ctx android.RegistrationContext) {
diff --git a/filesystem/fsverity_metadata.go b/filesystem/fsverity_metadata.go
index 3119f2f..ef46067 100644
--- a/filesystem/fsverity_metadata.go
+++ b/filesystem/fsverity_metadata.go
@@ -66,62 +66,44 @@
 		return
 	}
 
-	fsverityBuilderPath := android.PathForModuleOut(ctx, "fsverity_builder.sh")
-	metadataGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_metadata_generator")
 	fsverityPath := ctx.Config().HostToolPath(ctx, "fsverity")
 
-	cmd := builder.Command().Tool(fsverityBuilderPath)
-
 	// STEP 1: generate .fsv_meta
 	var sb strings.Builder
 	sb.WriteString("set -e\n")
-	cmd.Implicit(metadataGeneratorPath).Implicit(fsverityPath)
 	for _, spec := range matchedSpecs {
 		// srcPath is copied by CopySpecsToDir()
 		srcPath := rebasedDir.Join(ctx, spec.RelPathInPackage())
 		destPath := rebasedDir.Join(ctx, spec.RelPathInPackage()+".fsv_meta")
-		sb.WriteString(metadataGeneratorPath.String())
-		sb.WriteString(" --fsverity-path ")
-		sb.WriteString(fsverityPath.String())
-		sb.WriteString(" --signature none --hash-alg sha256 --output ")
-		sb.WriteString(destPath.String())
-		sb.WriteRune(' ')
-		sb.WriteString(srcPath.String())
-		sb.WriteRune('\n')
+		builder.Command().
+			BuiltTool("fsverity_metadata_generator").
+			FlagWithInput("--fsverity-path ", fsverityPath).
+			FlagWithArg("--signature ", "none").
+			FlagWithArg("--hash-alg ", "sha256").
+			FlagWithArg("--output ", destPath.String()).
+			Text(srcPath.String())
 		f.appendToEntry(ctx, destPath)
 	}
 
 	// STEP 2: generate signed BuildManifest.apk
 	// STEP 2-1: generate build_manifest.pb
-	assetsPath := android.PathForModuleOut(ctx, "fsverity_manifest/assets")
-	manifestPbPath := assetsPath.Join(ctx, "build_manifest.pb")
-	manifestGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_manifest_generator")
-	cmd.Implicit(manifestGeneratorPath)
-	sb.WriteString("rm -rf ")
-	sb.WriteString(assetsPath.String())
-	sb.WriteString(" && mkdir -p ")
-	sb.WriteString(assetsPath.String())
-	sb.WriteRune('\n')
-	sb.WriteString(manifestGeneratorPath.String())
-	sb.WriteString(" --fsverity-path ")
-	sb.WriteString(fsverityPath.String())
-	sb.WriteString(" --base-dir ")
-	sb.WriteString(rootDir.String())
-	sb.WriteString(" --output ")
-	sb.WriteString(manifestPbPath.String())
-	sb.WriteRune(' ')
-	f.appendToEntry(ctx, manifestPbPath)
-
 	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
 	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath, matchedSpecs, rebasedDir)
-	sb.WriteRune('@')
-	sb.WriteString(manifestGeneratorListPath.String())
-	sb.WriteRune('\n')
-	cmd.Implicit(manifestGeneratorListPath)
+	assetsPath := android.PathForModuleOut(ctx, "fsverity_manifest/assets")
+	manifestPbPath := assetsPath.Join(ctx, "build_manifest.pb")
+	builder.Command().Text("rm -rf " + assetsPath.String())
+	builder.Command().Text("mkdir -p " + assetsPath.String())
+	builder.Command().
+		BuiltTool("fsverity_manifest_generator").
+		FlagWithInput("--fsverity-path ", fsverityPath).
+		FlagWithArg("--base-dir ", rootDir.String()).
+		FlagWithArg("--output ", manifestPbPath.String()).
+		FlagWithInput("@", manifestGeneratorListPath)
+
+	f.appendToEntry(ctx, manifestPbPath)
 	f.appendToEntry(ctx, manifestGeneratorListPath)
 
 	// STEP 2-2: generate BuildManifest.apk (unsigned)
-	aapt2Path := ctx.Config().HostToolPath(ctx, "aapt2")
 	apkNameSuffix := ""
 	if f.PartitionType() == "system_ext" {
 		//https://source.corp.google.com/h/googleplex-android/platform/build/+/e392d2b486c2d4187b20a72b1c67cc737ecbcca5:core/Makefile;l=3410;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0
@@ -131,55 +113,38 @@
 	idsigPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix))
 	manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml")
 	libs := android.PathsForModuleSrc(ctx, f.properties.Fsverity.Libs)
-	cmd.Implicit(aapt2Path)
-	cmd.Implicit(manifestTemplatePath)
-	cmd.Implicits(libs)
-	cmd.ImplicitOutput(apkPath)
 
-	sb.WriteString(aapt2Path.String())
-	sb.WriteString(" link -o ")
-	sb.WriteString(apkPath.String())
-	sb.WriteString(" -A ")
-	sb.WriteString(assetsPath.String())
-	for _, lib := range libs {
-		sb.WriteString(" -I ")
-		sb.WriteString(lib.String())
-	}
 	minSdkVersion := ctx.Config().PlatformSdkCodename()
 	if minSdkVersion == "REL" {
 		minSdkVersion = ctx.Config().PlatformSdkVersion().String()
 	}
-	sb.WriteString(" --min-sdk-version ")
-	sb.WriteString(minSdkVersion)
-	sb.WriteString(" --version-code ")
-	sb.WriteString(ctx.Config().PlatformSdkVersion().String())
-	sb.WriteString(" --version-name ")
-	sb.WriteString(ctx.Config().AppsDefaultVersionName())
-	sb.WriteString(" --manifest ")
-	sb.WriteString(manifestTemplatePath.String())
-	sb.WriteString(" --rename-manifest-package com.android.security.fsverity_metadata.")
-	sb.WriteString(f.partitionName())
-	sb.WriteRune('\n')
+
+	unsignedApkCommand := builder.Command().
+		BuiltTool("aapt2").
+		Text("link").
+		FlagWithOutput("-o ", apkPath).
+		FlagWithArg("-A ", assetsPath.String())
+	for _, lib := range libs {
+		unsignedApkCommand.FlagWithInput("-I ", lib)
+	}
+	unsignedApkCommand.
+		FlagWithArg("--min-sdk-version ", minSdkVersion).
+		FlagWithArg("--version-code ", ctx.Config().PlatformSdkVersion().String()).
+		FlagWithArg("--version-name ", ctx.Config().AppsDefaultVersionName()).
+		FlagWithInput("--manifest ", manifestTemplatePath).
+		Text(" --rename-manifest-package com.android.security.fsverity_metadata." + f.partitionName())
 
 	f.appendToEntry(ctx, apkPath)
 
 	// STEP 2-3: sign BuildManifest.apk
-	apksignerPath := ctx.Config().HostToolPath(ctx, "apksigner")
 	pemPath, keyPath := ctx.Config().DefaultAppCertificate(ctx)
-	cmd.Implicit(apksignerPath)
-	cmd.Implicit(pemPath)
-	cmd.Implicit(keyPath)
-	cmd.ImplicitOutput(idsigPath)
-	sb.WriteString(apksignerPath.String())
-	sb.WriteString(" sign --in ")
-	sb.WriteString(apkPath.String())
-	sb.WriteString(" --cert ")
-	sb.WriteString(pemPath.String())
-	sb.WriteString(" --key ")
-	sb.WriteString(keyPath.String())
-	sb.WriteRune('\n')
+	builder.Command().
+		BuiltTool("apksigner").
+		Text("sign").
+		FlagWithArg("--in ", apkPath.String()).
+		FlagWithInput("--cert ", pemPath).
+		FlagWithInput("--key ", keyPath).
+		ImplicitOutput(idsigPath)
 
 	f.appendToEntry(ctx, idsigPath)
-
-	android.WriteExecutableFileRuleVerbatim(ctx, fsverityBuilderPath, sb.String())
 }
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index a022581..e8065f3 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -17,6 +17,7 @@
         "filesystem_creator.go",
         "fsgen_mutators.go",
         "prebuilt_etc_modules_gen.go",
+        "util.go",
         "vbmeta_partitions.go",
     ],
     testSrcs: [
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 8325b1e..745aeaa 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -69,6 +69,7 @@
 		avbpubkeyGenerated := createAvbpubkeyModule(ctx)
 		createFsGenState(ctx, generatedPrebuiltEtcModuleNames, avbpubkeyGenerated)
 		module.createAvbKeyFilegroups(ctx)
+		module.createMiscFilegroups(ctx)
 		module.createInternalModules(ctx)
 	})
 
@@ -108,6 +109,9 @@
 	if buildingVendorBootImage(partitionVars) {
 		generatedPartitions = append(generatedPartitions, "vendor_ramdisk")
 	}
+	if ctx.DeviceConfig().BuildingRecoveryImage() && ctx.DeviceConfig().RecoveryPath() == "recovery" {
+		generatedPartitions = append(generatedPartitions, "recovery")
+	}
 	return generatedPartitions
 }
 
@@ -205,23 +209,25 @@
 	ctx.CreateModule(filesystem.AndroidDeviceFactory, baseProps, partitionProps)
 }
 
-func partitionSpecificFsProps(fsProps *filesystem.FilesystemProperties, partitionVars android.PartitionVariables, partitionType string) {
+func partitionSpecificFsProps(ctx android.EarlyModuleContext, fsProps *filesystem.FilesystemProperties, partitionVars android.PartitionVariables, partitionType string) {
 	switch partitionType {
 	case "system":
 		fsProps.Build_logtags = proptools.BoolPtr(true)
 		// https://source.corp.google.com/h/googleplex-android/platform/build//639d79f5012a6542ab1f733b0697db45761ab0f3:core/packaging/flags.mk;l=21;drc=5ba8a8b77507f93aa48cc61c5ba3f31a4d0cbf37;bpv=1;bpt=0
 		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
 		// Identical to that of the aosp_shared_system_image
-		fsProps.Fsverity.Inputs = []string{
-			"etc/boot-image.prof",
-			"etc/dirty-image-objects",
-			"etc/preloaded-classes",
-			"etc/classpaths/*.pb",
-			"framework/*",
-			"framework/*/*",     // framework/{arch}
-			"framework/oat/*/*", // framework/oat/{arch}
+		if partitionVars.ProductFsverityGenerateMetadata {
+			fsProps.Fsverity.Inputs = []string{
+				"etc/boot-image.prof",
+				"etc/dirty-image-objects",
+				"etc/preloaded-classes",
+				"etc/classpaths/*.pb",
+				"framework/*",
+				"framework/*/*",     // framework/{arch}
+				"framework/oat/*/*", // framework/oat/{arch}
+			}
+			fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
 		}
-		fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
 		// Most of the symlinks and directories listed here originate from create_root_structure.mk,
 		// but the handwritten generic system image also recreates them:
 		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/target/product/generic/Android.bp;l=33;drc=db08311f1b6ef6cb0a4fbcc6263b89849360ce04
@@ -348,14 +354,20 @@
 			"product",
 		})
 	case "system_ext":
-		fsProps.Fsverity.Inputs = []string{
-			"framework/*",
-			"framework/*/*",     // framework/{arch}
-			"framework/oat/*/*", // framework/oat/{arch}
+		if partitionVars.ProductFsverityGenerateMetadata {
+			fsProps.Fsverity.Inputs = []string{
+				"framework/*",
+				"framework/*/*",     // framework/{arch}
+				"framework/oat/*/*", // framework/oat/{arch}
+			}
+			fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
 		}
-		fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
 	case "product":
 		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
+		fsProps.Android_filesystem_deps.System = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system"))
+		if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system_ext"))
+		}
 	case "vendor":
 		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
 		fsProps.Symlinks = []filesystem.SymlinkDefinition{
@@ -368,6 +380,10 @@
 				Name:   proptools.StringPtr("vendor/lib/modules"),
 			},
 		}
+		fsProps.Android_filesystem_deps.System = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system"))
+		if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system_ext"))
+		}
 	case "odm":
 		fsProps.Symlinks = []filesystem.SymlinkDefinition{
 			filesystem.SymlinkDefinition{
@@ -399,6 +415,22 @@
 				"first_stage_ramdisk/sys",
 			})
 		}
+	case "recovery":
+		// Following https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2826;drc=ad7cfb56010cb22c3aa0e70cf71c804352553526
+		fsProps.Dirs = android.NewSimpleConfigurable([]string{
+			"sdcard",
+			"tmp",
+		})
+		fsProps.Symlinks = []filesystem.SymlinkDefinition{
+			{
+				Target: proptools.StringPtr("/system/bin/init"),
+				Name:   proptools.StringPtr("init"),
+			},
+			{
+				Target: proptools.StringPtr("prop.default"),
+				Name:   proptools.StringPtr("default.prop"),
+			},
+		}
 	}
 }
 
@@ -491,6 +523,29 @@
 	}
 }
 
+// Creates filegroups for miscellaneous other files
+func (f *filesystemCreator) createMiscFilegroups(ctx android.LoadHookContext) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	if partitionVars.BoardErofsCompressorHints != "" {
+		dir := filepath.Dir(partitionVars.BoardErofsCompressorHints)
+		base := filepath.Base(partitionVars.BoardErofsCompressorHints)
+		ctx.CreateModuleInDirectory(
+			android.FileGroupFactory,
+			dir,
+			&struct {
+				Name       *string
+				Srcs       []string
+				Visibility []string
+			}{
+				Name:       proptools.StringPtr("soong_generated_board_erofs_compress_hints_filegroup"),
+				Srcs:       []string{base},
+				Visibility: []string{"//visibility:public"},
+			},
+		)
+	}
+}
+
 // createPrebuiltKernelModules creates `prebuilt_kernel_modules`. These modules will be added to deps of the
 // autogenerated *_dlkm filsystem modules. Each _dlkm partition should have a single prebuilt_kernel_modules dependency.
 // This ensures that the depmod artifacts (modules.* installed in /lib/modules/) are generated with a complete view.
@@ -690,6 +745,15 @@
 		return nil, false
 	}
 
+	if *fsProps.Type == "erofs" {
+		if partitionVars.BoardErofsCompressor != "" {
+			fsProps.Erofs.Compressor = proptools.StringPtr(partitionVars.BoardErofsCompressor)
+		}
+		if partitionVars.BoardErofsCompressorHints != "" {
+			fsProps.Erofs.Compress_hints = proptools.StringPtr(":soong_generated_board_erofs_compress_hints_filegroup")
+		}
+	}
+
 	// Don't build this module on checkbuilds, the soong-built partitions are still in-progress
 	// and sometimes don't build.
 	fsProps.Unchecked_module = proptools.BoolPtr(true)
@@ -711,7 +775,7 @@
 
 	fsProps.Is_auto_generated = proptools.BoolPtr(true)
 
-	partitionSpecificFsProps(fsProps, partitionVars, partitionType)
+	partitionSpecificFsProps(ctx, fsProps, partitionVars, partitionType)
 
 	// system_image properties that are not set:
 	// - filesystemProperties.Avb_hash_algorithm
diff --git a/fsgen/filesystem_creator_test.go b/fsgen/filesystem_creator_test.go
index fe4a403..5657608 100644
--- a/fsgen/filesystem_creator_test.go
+++ b/fsgen/filesystem_creator_test.go
@@ -168,13 +168,8 @@
 						BoardFileSystemType: "ext4",
 					},
 				}
-			config.Targets[android.Android] = []android.Target{
-				{Os: android.Android, Arch: android.Arch{ArchType: android.X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}}, NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: "", HostCross: false},
-				{Os: android.Android, Arch: android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}}, NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: "", HostCross: false},
-				{Os: android.Android, Arch: android.Arch{ArchType: android.Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86_64", NativeBridgeRelativePath: "arm64", HostCross: false},
-				{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86", NativeBridgeRelativePath: "arm", HostCross: false},
-			}
 		}),
+		android.PrepareForNativeBridgeEnabled,
 		android.FixtureMergeMockFs(android.MockFS{
 			"external/avb/test/data/testkey_rsa4096.pem": nil,
 			"build/soong/fsgen/Android.bp": []byte(`
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index 0cc643e..f0a54db 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -150,6 +150,7 @@
 				},
 				"ramdisk":        {},
 				"vendor_ramdisk": {},
+				"recovery":       {},
 			},
 			fsDepsMutex:                     sync.Mutex{},
 			moduleToInstallationProps:       map[string]installationProperties{},
@@ -161,6 +162,9 @@
 			(*fsGenState.fsDeps["product"])["system_other_avbpubkey"] = defaultDepCandidateProps(ctx.Config())
 		}
 
+		// Add common resources `prebuilt_res` module as dep of recovery partition
+		(*fsGenState.fsDeps["recovery"])[fmt.Sprintf("recovery-resources-common-%s", getDpi(ctx))] = defaultDepCandidateProps(ctx.Config())
+
 		return &fsGenState
 	}).(*FsGenState)
 }
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
index efbc462..e028b1d 100644
--- a/fsgen/prebuilt_etc_modules_gen.go
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -36,6 +36,7 @@
 	system_ext map[string][]srcBaseFileInstallBaseFileTuple
 	product    map[string][]srcBaseFileInstallBaseFileTuple
 	vendor     map[string][]srcBaseFileInstallBaseFileTuple
+	recovery   map[string][]srcBaseFileInstallBaseFileTuple
 }
 
 func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
@@ -44,6 +45,7 @@
 		system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
 		product:    map[string][]srcBaseFileInstallBaseFileTuple{},
 		vendor:     map[string][]srcBaseFileInstallBaseFileTuple{},
+		recovery:   map[string][]srcBaseFileInstallBaseFileTuple{},
 	}
 }
 
@@ -73,6 +75,8 @@
 				srcMap = srcGroup.product
 			case "vendor":
 				srcMap = srcGroup.vendor
+			case "recovery":
+				srcMap = srcGroup.recovery
 			}
 			if srcMap != nil {
 				srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
@@ -128,6 +132,7 @@
 	// System is intentionally added at the last to consider the scenarios where
 	// non-system partitions are installed as part of the system partition
 	partitionToInstallPathList := []partitionToInstallPath{
+		{name: "recovery", installPath: "recovery/root"},
 		{name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
 		{name: "product", installPath: ctx.DeviceConfig().ProductPath()},
 		{name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
@@ -155,6 +160,8 @@
 	Soc_specific        *bool
 	Product_specific    *bool
 	System_ext_specific *bool
+	Recovery            *bool
+	Ramdisk             *bool
 
 	Srcs []string
 	Dsts []string
@@ -174,42 +181,52 @@
 	Relative_install_path *string
 }
 
+// Split install_in_root to a separate struct as it is part of rootProperties instead of
+// properties
+type prebuiltInstallInRootProperties struct {
+	Install_in_root *bool
+}
+
 var (
 	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
-		"":                etc.PrebuiltRootFactory,
-		"avb":             etc.PrebuiltAvbFactory,
-		"bin":             etc.PrebuiltBinaryFactory,
-		"bt_firmware":     etc.PrebuiltBtFirmwareFactory,
-		"cacerts":         etc.PrebuiltEtcCaCertsFactory,
-		"dsp":             etc.PrebuiltDSPFactory,
-		"etc":             etc.PrebuiltEtcFactory,
-		"etc/dsp":         etc.PrebuiltDSPFactory,
-		"etc/firmware":    etc.PrebuiltFirmwareFactory,
-		"firmware":        etc.PrebuiltFirmwareFactory,
-		"fonts":           etc.PrebuiltFontFactory,
-		"framework":       etc.PrebuiltFrameworkFactory,
-		"lib":             etc.PrebuiltRenderScriptBitcodeFactory,
-		"lib64":           etc.PrebuiltRenderScriptBitcodeFactory,
-		"lib/rfsa":        etc.PrebuiltRFSAFactory,
-		"media":           etc.PrebuiltMediaFactory,
-		"odm":             etc.PrebuiltOdmFactory,
-		"optee":           etc.PrebuiltOpteeFactory,
-		"overlay":         etc.PrebuiltOverlayFactory,
-		"priv-app":        etc.PrebuiltPrivAppFactory,
-		"res":             etc.PrebuiltResFactory,
-		"rfs":             etc.PrebuiltRfsFactory,
-		"tts":             etc.PrebuiltVoicepackFactory,
-		"tvconfig":        etc.PrebuiltTvConfigFactory,
-		"tvservice":       etc.PrebuiltTvServiceFactory,
-		"usr/share":       etc.PrebuiltUserShareFactory,
-		"usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
-		"usr/keylayout":   etc.PrebuiltUserKeyLayoutFactory,
-		"usr/keychars":    etc.PrebuiltUserKeyCharsFactory,
-		"usr/srec":        etc.PrebuiltUserSrecFactory,
-		"usr/idc":         etc.PrebuiltUserIdcFactory,
-		"vendor_dlkm":     etc.PrebuiltVendorDlkmFactory,
-		"wallpaper":       etc.PrebuiltWallpaperFactory,
-		"wlc_upt":         etc.PrebuiltWlcUptFactory,
+		"":                    etc.PrebuiltRootFactory,
+		"avb":                 etc.PrebuiltAvbFactory,
+		"bin":                 etc.PrebuiltBinaryFactory,
+		"bt_firmware":         etc.PrebuiltBtFirmwareFactory,
+		"cacerts":             etc.PrebuiltEtcCaCertsFactory,
+		"dsp":                 etc.PrebuiltDSPFactory,
+		"etc":                 etc.PrebuiltEtcFactory,
+		"etc/dsp":             etc.PrebuiltDSPFactory,
+		"etc/firmware":        etc.PrebuiltFirmwareFactory,
+		"firmware":            etc.PrebuiltFirmwareFactory,
+		"first_stage_ramdisk": etc.PrebuiltFirstStageRamdiskFactory,
+		"fonts":               etc.PrebuiltFontFactory,
+		"framework":           etc.PrebuiltFrameworkFactory,
+		"lib":                 etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib64":               etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib/rfsa":            etc.PrebuiltRFSAFactory,
+		"media":               etc.PrebuiltMediaFactory,
+		"odm":                 etc.PrebuiltOdmFactory,
+		"optee":               etc.PrebuiltOpteeFactory,
+		"overlay":             etc.PrebuiltOverlayFactory,
+		"priv-app":            etc.PrebuiltPrivAppFactory,
+		"sbin":                etc.PrebuiltSbinFactory,
+		"system":              etc.PrebuiltSystemFactory,
+		"res":                 etc.PrebuiltResFactory,
+		"rfs":                 etc.PrebuiltRfsFactory,
+		"tts":                 etc.PrebuiltVoicepackFactory,
+		"tvconfig":            etc.PrebuiltTvConfigFactory,
+		"tvservice":           etc.PrebuiltTvServiceFactory,
+		"usr/share":           etc.PrebuiltUserShareFactory,
+		"usr/hyphen-data":     etc.PrebuiltUserHyphenDataFactory,
+		"usr/keylayout":       etc.PrebuiltUserKeyLayoutFactory,
+		"usr/keychars":        etc.PrebuiltUserKeyCharsFactory,
+		"usr/srec":            etc.PrebuiltUserSrecFactory,
+		"usr/idc":             etc.PrebuiltUserIdcFactory,
+		"vendor":              etc.PrebuiltVendorFactory,
+		"vendor_dlkm":         etc.PrebuiltVendorDlkmFactory,
+		"wallpaper":           etc.PrebuiltWallpaperFactory,
+		"wlc_upt":             etc.PrebuiltWlcUptFactory,
 	}
 )
 
@@ -242,7 +259,7 @@
 	return ret, maxLen
 }
 
-func prebuiltEtcModuleProps(moduleName, partition string) prebuiltModuleProperties {
+func prebuiltEtcModuleProps(ctx android.LoadHookContext, moduleName, partition, destDir string) prebuiltModuleProperties {
 	moduleProps := prebuiltModuleProperties{}
 	moduleProps.Name = proptools.StringPtr(moduleName)
 
@@ -254,6 +271,13 @@
 		moduleProps.Product_specific = proptools.BoolPtr(true)
 	case "vendor":
 		moduleProps.Soc_specific = proptools.BoolPtr(true)
+	case "recovery":
+		// To match the logic in modulePartition() in android/paths.go
+		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() && strings.HasPrefix(destDir, "first_stage_ramdisk") {
+			moduleProps.Ramdisk = proptools.BoolPtr(true)
+		} else {
+			moduleProps.Recovery = proptools.BoolPtr(true)
+		}
 	}
 
 	moduleProps.No_full_install = proptools.BoolPtr(true)
@@ -287,7 +311,7 @@
 		}
 
 		moduleName := generatedPrebuiltEtcModuleName(partition, srcDir, destDir, fileIndex)
-		moduleProps := prebuiltEtcModuleProps(moduleName, partition)
+		moduleProps := prebuiltEtcModuleProps(ctx, moduleName, partition, destDir)
 		modulePropsPtr := &moduleProps
 		propsList := []interface{}{modulePropsPtr}
 
@@ -301,6 +325,16 @@
 			installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
 		}
 
+		// Recovery partition-installed modules are installed to `recovery/root/system` by
+		// default (See modulePartition() in android/paths.go). If the destination file
+		// directory is not `recovery/root/system/...`, it should set install_in_root to true
+		// to prevent being installed in `recovery/root/system`.
+		if partition == "recovery" && !strings.HasPrefix(destDir, "system") {
+			propsList = append(propsList, &prebuiltInstallInRootProperties{
+				Install_in_root: proptools.BoolPtr(true),
+			})
+		}
+
 		// Set appropriate srcs, dsts, and releative_install_path based on
 		// the source and install file names
 		if allCopyFileNamesUnchanged {
@@ -346,6 +380,7 @@
 		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
 		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
 		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "recovery", srcDir, groupedSource.recovery)...)
 	}
 
 	return ret
diff --git a/fsgen/util.go b/fsgen/util.go
new file mode 100644
index 0000000..9ab3ad8
--- /dev/null
+++ b/fsgen/util.go
@@ -0,0 +1,60 @@
+// Copyright (C) 2024 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.
+
+package fsgen
+
+import (
+	"android/soong/android"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+// Returns the appropriate dpi for recovery common resources selection. Replicates the logic in
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2536;drc=a6af369e71ded123734523ea640b97b70a557cb9
+func getDpi(ctx android.LoadHookContext) string {
+	recoveryDensity := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.TargetScreenDensity
+	if len(recoveryDensity) == 0 {
+		aaptPreferredConfig := ctx.Config().ProductAAPTPreferredConfig()
+		if len(aaptPreferredConfig) > 0 {
+			recoveryDensity = aaptPreferredConfig
+		} else {
+			recoveryDensity = "mdpi"
+		}
+	}
+	if !android.InList(recoveryDensity, []string{"xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi"}) {
+		recoveryDensity = strings.TrimSuffix(recoveryDensity, "dpi")
+		dpiInt, err := strconv.ParseInt(recoveryDensity, 10, 64)
+		if err != nil {
+			panic(fmt.Sprintf("Error in parsing recoveryDensity: %s", err.Error()))
+		}
+		if dpiInt >= 560 {
+			recoveryDensity = "xxxhdpi"
+		} else if dpiInt >= 400 {
+			recoveryDensity = "xxhdpi"
+		} else if dpiInt >= 280 {
+			recoveryDensity = "xhdpi"
+		} else if dpiInt >= 200 {
+			recoveryDensity = "hdpi"
+		} else {
+			recoveryDensity = "mdpi"
+		}
+	}
+
+	if p := android.ExistentPathForSource(ctx, fmt.Sprintf("bootable/recovery/res-%s", recoveryDensity)); !p.Valid() {
+		recoveryDensity = "xhdpi"
+	}
+
+	return recoveryDensity
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 9d2dbc7..ac62b8d 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -230,8 +230,9 @@
 	shards int
 
 	// For nsjail tasks
-	useNsjail bool
-	dirSrcs   android.DirectoryPaths
+	useNsjail  bool
+	dirSrcs    android.DirectoryPaths
+	keepGendir bool
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -487,6 +488,9 @@
 		name := "generator"
 		if task.useNsjail {
 			rule = android.NewRuleBuilder(pctx, ctx).Nsjail(task.genDir, android.PathForModuleOut(ctx, "nsjail_build_sandbox"))
+			if task.keepGendir {
+				rule.NsjailKeepGendir()
+			}
 		} else {
 			manifestName := "genrule.sbox.textproto"
 			if task.shards > 0 {
@@ -897,17 +901,24 @@
 			return nil
 		}
 
+		keepGendir := Bool(properties.Keep_gendir)
+		if keepGendir && !useNsjail {
+			ctx.PropertyErrorf("keep_gendir", "can't use keep_gendir if use_nsjail is false")
+			return nil
+		}
+
 		outs := make(android.WritablePaths, len(properties.Out))
 		for i, out := range properties.Out {
 			outs[i] = android.PathForModuleGen(ctx, out)
 		}
 		return []generateTask{{
-			in:        srcFiles,
-			out:       outs,
-			genDir:    android.PathForModuleGen(ctx),
-			cmd:       rawCommand,
-			useNsjail: useNsjail,
-			dirSrcs:   dirSrcs,
+			in:         srcFiles,
+			out:        outs,
+			genDir:     android.PathForModuleGen(ctx),
+			cmd:        rawCommand,
+			useNsjail:  useNsjail,
+			dirSrcs:    dirSrcs,
+			keepGendir: keepGendir,
 		}}
 	}
 
@@ -928,6 +939,10 @@
 	// dir_srcs is limited only to Trusty build.
 	Dir_srcs []string `android:"path"`
 
+	// If set to true, $(genDir) is not truncated. Useful when this genrule can be incrementally
+	// built. Can be set only when use_nsjail is true.
+	Keep_gendir *bool
+
 	// names of the output files that will be generated
 	Out []string `android:"arch_variant"`
 }
diff --git a/java/aar.go b/java/aar.go
index e0e642e..1e5c95a 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -588,14 +588,16 @@
 		}
 	}
 
-	for _, dir := range overlayDirs {
-		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
-			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
-	}
-
 	var compiledRro, compiledRroOverlay android.Paths
 	if opts.rroDirs != nil {
 		compiledRro, compiledRroOverlay = a.compileResInDir(ctx, *opts.rroDirs, compileFlags, opts.aconfigTextFiles)
+	} else {
+		// RRO enforcement is done based on module name. Compile the overlayDirs only if rroDirs is nil.
+		// This ensures that the autogenerated RROs do not compile the overlay dirs twice.
+		for _, dir := range overlayDirs {
+			compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
+				compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
+		}
 	}
 
 	var splitPackages android.WritablePaths
diff --git a/java/androidmk.go b/java/androidmk.go
index 2ad30b1..b6bab53 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -307,15 +307,11 @@
 			Disabled: true,
 		}}
 	}
-	var required []string
-	if proptools.Bool(app.appProperties.Generate_product_characteristics_rro) {
-		required = []string{app.productCharacteristicsRROPackageName()}
-	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "APPS",
 		OutputFile: android.OptionalPathForPath(app.outputFile),
 		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
-		Required:   required,
+		Required:   app.requiredModuleNames,
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				// App module names can be overridden.
@@ -350,31 +346,6 @@
 					entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true)
 				}
 
-				filterRRO := func(filter overlayType) android.Paths {
-					var paths android.Paths
-					seen := make(map[android.Path]bool)
-					for _, d := range app.rroDirsDepSet.ToList() {
-						if d.overlayType == filter {
-							if seen[d.path] {
-								continue
-							}
-							seen[d.path] = true
-							paths = append(paths, d.path)
-						}
-					}
-					// Reverse the order, Soong stores rroDirs in aapt2 order (low to high priority), but Make
-					// expects it in LOCAL_RESOURCE_DIRS order (high to low priority).
-					return android.ReversePaths(paths)
-				}
-				deviceRRODirs := filterRRO(device)
-				if len(deviceRRODirs) > 0 {
-					entries.AddStrings("LOCAL_SOONG_DEVICE_RRO_DIRS", deviceRRODirs.Strings()...)
-				}
-				productRRODirs := filterRRO(product)
-				if len(productRRODirs) > 0 {
-					entries.AddStrings("LOCAL_SOONG_PRODUCT_RRO_DIRS", productRRODirs.Strings()...)
-				}
-
 				entries.SetBoolIfTrue("LOCAL_EXPORT_PACKAGE_RESOURCES", Bool(app.appProperties.Export_package_resources))
 
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", app.manifestPath)
@@ -437,7 +408,7 @@
 		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_CERTIFICATE", "presigned") // The apk will be signed by soong
+				entries.SetString("LOCAL_CERTIFICATE", "PRESIGNED") // The apk will be signed by soong
 			},
 		},
 	}}
diff --git a/java/app.go b/java/app.go
index 832a083..8739d1c 100644
--- a/java/app.go
+++ b/java/app.go
@@ -223,6 +223,8 @@
 	javaApiUsedByOutputFile android.ModuleOutPath
 
 	privAppAllowlist android.OptionalPath
+
+	requiredModuleNames []string
 }
 
 func (a *AndroidApp) IsInstallable() bool {
@@ -421,6 +423,24 @@
 		TestHelperApp:   false,
 		EmbeddedJNILibs: embeddedJniLibs,
 	})
+
+	a.requiredModuleNames = a.getRequiredModuleNames(ctx)
+}
+
+func (a *AndroidApp) getRequiredModuleNames(ctx android.ModuleContext) []string {
+	var required []string
+	if proptools.Bool(a.appProperties.Generate_product_characteristics_rro) {
+		required = []string{a.productCharacteristicsRROPackageName()}
+	}
+	// Install the vendor overlay variant if this app is installed.
+	if len(filterRRO(a.rroDirsDepSet, device)) > 0 {
+		required = append(required, AutogeneratedRroModuleName(ctx, ctx.Module().Name(), "vendor"))
+	}
+	// Install the product overlay variant if this app is installed.
+	if len(filterRRO(a.rroDirsDepSet, product)) > 0 {
+		required = append(required, AutogeneratedRroModuleName(ctx, ctx.Module().Name(), "product"))
+	}
+	return required
 }
 
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
@@ -1083,10 +1103,11 @@
 	}
 	jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
 		checkNativeSdkVersion, func(parent, child android.Module) bool {
+			apkInApex := ctx.Module().(android.ApexModule).NotInPlatform()
 			childLinkable, _ := child.(cc.LinkableInterface)
 			parentLinkable, _ := parent.(cc.LinkableInterface)
 			useStubsOfDep := childLinkable.IsStubs()
-			if parent.(android.ApexModule).NotInPlatform() && parentLinkable != nil {
+			if apkInApex && parentLinkable != nil {
 				// APK-in-APEX
 				// If the parent is a linkable interface, use stubs if the dependency edge crosses an apex boundary.
 				useStubsOfDep = useStubsOfDep || (childLinkable.HasStubsVariants() && cc.ShouldUseStubForApex(ctx, parent, child))
@@ -1387,11 +1408,82 @@
 			}
 		}
 		ctx.CreateModule(RuntimeResourceOverlayFactory, &rroProperties)
+
+	})
+
+	module.SetDefaultableHook(func(ctx android.DefaultableHookContext) {
+		createInternalRuntimeOverlays(ctx, module.ModuleBase)
 	})
 
 	return module
 }
 
+func AutogeneratedRroModuleName(ctx android.EarlyModuleContext, moduleName, partition string) string {
+	return fmt.Sprintf("%s__%s__auto_generated_rro_%s", moduleName, ctx.Config().DeviceProduct(), partition)
+}
+
+type createModuleContext interface {
+	android.EarlyModuleContext
+	CreateModule(android.ModuleFactory, ...interface{}) android.Module
+}
+
+func createInternalRuntimeOverlays(ctx createModuleContext, a android.ModuleBase) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	// vendor
+	vendorOverlayProps := struct {
+		Name                *string
+		Base                *string
+		Vendor              *bool
+		Product_specific    *bool
+		System_ext_specific *bool
+		Manifest            *string
+		Sdk_version         *string
+		Compile_multilib    *string
+		Enabled             proptools.Configurable[bool]
+	}{
+		Name:                proptools.StringPtr(AutogeneratedRroModuleName(ctx, a.Name(), "vendor")),
+		Base:                proptools.StringPtr(a.Name()),
+		Vendor:              proptools.BoolPtr(true),
+		Product_specific:    proptools.BoolPtr(false),
+		System_ext_specific: proptools.BoolPtr(false),
+		Manifest:            proptools.StringPtr(":" + a.Name() + "{.manifest.xml}"),
+		Sdk_version:         proptools.StringPtr("current"),
+		Compile_multilib:    proptools.StringPtr("first"),
+		Enabled:             a.EnabledProperty().Clone(),
+	}
+	ctx.CreateModule(AutogenRuntimeResourceOverlayFactory, &vendorOverlayProps)
+
+	// product
+	productOverlayProps := struct {
+		Name                *string
+		Base                *string
+		Vendor              *bool
+		Proprietary         *bool
+		Soc_specific        *bool
+		Product_specific    *bool
+		System_ext_specific *bool
+		Manifest            *string
+		Sdk_version         *string
+		Compile_multilib    *string
+		Enabled             proptools.Configurable[bool]
+	}{
+		Name:                proptools.StringPtr(AutogeneratedRroModuleName(ctx, a.Name(), "product")),
+		Base:                proptools.StringPtr(a.Name()),
+		Vendor:              proptools.BoolPtr(false),
+		Proprietary:         proptools.BoolPtr(false),
+		Soc_specific:        proptools.BoolPtr(false),
+		Product_specific:    proptools.BoolPtr(true),
+		System_ext_specific: proptools.BoolPtr(false),
+		Manifest:            proptools.StringPtr(":" + a.Name() + "{.manifest.xml}"),
+		Sdk_version:         proptools.StringPtr("current"),
+		Compile_multilib:    proptools.StringPtr("first"),
+		Enabled:             a.EnabledProperty().Clone(),
+	}
+	ctx.CreateModule(AutogenRuntimeResourceOverlayFactory, &productOverlayProps)
+}
+
 // A dictionary of values to be overridden in the manifest.
 type Manifest_values struct {
 	// Overrides the value of package_name in the manifest
@@ -1702,6 +1794,10 @@
 
 	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	android.InitOverrideModule(m)
+	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
+		createInternalRuntimeOverlays(ctx, m.ModuleBase)
+	})
+
 	return m
 }
 
diff --git a/java/app_test.go b/java/app_test.go
index 3d83ea1..61b718d 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4727,3 +4727,74 @@
 		"out/soong/.intermediates/foo/android_common/aapt2/res/values_strings.(test.package.flag1).arsc.flat",
 	)
 }
+
+func TestAutogeneratedStaticRro(t *testing.T) {
+	t.Parallel()
+	bp := `
+android_app {
+	name: "foo",
+	srcs: ["foo.java"],
+	platform_apis: true,
+}
+override_android_app {
+	name: "override_foo",
+	base: "foo",
+}
+`
+	testCases := []struct {
+		desc               string
+		preparer           android.FixturePreparer
+		overlayApkExpected bool
+	}{
+		{
+			desc:               "No DEVICE_PACKAGE_OVERLAYS, no overlay .apk file",
+			overlayApkExpected: false,
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS exists, but the directory is empty",
+			overlayApkExpected: false,
+			preparer: android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+			}),
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS exists, directory is non-empty, but does not contain a matching resource dir",
+			overlayApkExpected: false,
+			preparer: android.GroupFixturePreparers(
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+				}),
+				android.MockFS{
+					"res/foo.xml": nil,
+					"device/company/test_product/different_res/foo.xml": nil, // different dir
+				}.AddToFixture(),
+			),
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS and the directory contain a matching resource dir",
+			overlayApkExpected: true,
+			preparer: android.GroupFixturePreparers(
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+				}),
+				android.MockFS{
+					"res/foo.xml": nil,
+					"device/company/test_product/res/foo.xml": nil,
+				}.AddToFixture(),
+			),
+		},
+	}
+	for _, tc := range testCases {
+		result := android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceRROTargets = []string{"*"}
+			}),
+			android.OptionalFixturePreparer(tc.preparer),
+		).RunTestWithBp(t, bp)
+		vendorOverlayApk := result.ModuleForTests("foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("foo__test_product__auto_generated_rro_vendor.apk")
+		android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, vendorOverlayApk.Rule != nil)
+		overrideVendorOverlayApk := result.ModuleForTests("override_foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("override_foo__test_product__auto_generated_rro_vendor.apk")
+		android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, overrideVendorOverlayApk.Rule != nil)
+	}
+}
diff --git a/java/base.go b/java/base.go
index 3bf2e23..c0ac4ab 100644
--- a/java/base.go
+++ b/java/base.go
@@ -612,21 +612,24 @@
 	return proptools.Bool(j.properties.Is_stubs_module)
 }
 
-func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
-	sdkVersion := j.SdkVersion(ctx)
-	if sdkVersion.Stable() {
-		return nil
-	}
-	if sdkVersion.Kind == android.SdkCorePlatform {
-		if useLegacyCorePlatformApi(ctx, j.BaseModuleName()) {
-			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
-		} else {
-			// Treat stable core platform as stable.
+func CheckStableSdkVersion(ctx android.BaseModuleContext, module android.ModuleProxy) error {
+	if info, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+		if info.SdkVersion.Stable() {
 			return nil
 		}
-	} else {
-		return fmt.Errorf("non stable SDK %v", sdkVersion)
+		if info.SdkVersion.Kind == android.SdkCorePlatform {
+			if useLegacyCorePlatformApi(ctx, android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).BaseModuleName) {
+				return fmt.Errorf("non stable SDK %v - uses legacy core platform", info.SdkVersion)
+			} else {
+				// Treat stable core platform as stable.
+				return nil
+			}
+		} else {
+			return fmt.Errorf("non stable SDK %v", info.SdkVersion)
+		}
 	}
+
+	return nil
 }
 
 // checkSdkVersions enforces restrictions around SDK dependencies.
@@ -1300,6 +1303,7 @@
 			ExportedPluginDisableTurbine:        j.exportedDisableTurbine,
 			StubsLinkType:                       j.stubsLinkType,
 			AconfigIntermediateCacheOutputPaths: deps.aconfigProtoFiles,
+			SdkVersion:                          j.SdkVersion(ctx),
 		})
 
 		j.outputFile = j.headerJarFile
@@ -1929,6 +1933,7 @@
 		JacocoReportClassesFile:             j.jacocoReportClassesFile,
 		StubsLinkType:                       j.stubsLinkType,
 		AconfigIntermediateCacheOutputPaths: j.aconfigCacheFiles,
+		SdkVersion:                          j.SdkVersion(ctx),
 	})
 
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
diff --git a/java/java.go b/java/java.go
index 260d336..ee112c1 100644
--- a/java/java.go
+++ b/java/java.go
@@ -326,6 +326,8 @@
 	// AconfigIntermediateCacheOutputPaths is a path to the cache files collected from the
 	// java_aconfig_library modules that are statically linked to this module.
 	AconfigIntermediateCacheOutputPaths android.Paths
+
+	SdkVersion android.SdkSpec
 }
 
 var JavaInfoProvider = blueprint.NewProvider[*JavaInfo]()
diff --git a/java/rro_test.go b/java/rro_test.go
index 4d79130..4d58bb4 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -255,103 +255,6 @@
 	}
 }
 
-func TestEnforceRRO_propagatesToDependencies(t *testing.T) {
-	testCases := []struct {
-		name              string
-		enforceRROTargets []string
-		rroDirs           map[string][]string
-	}{
-		{
-			name:              "no RRO",
-			enforceRROTargets: nil,
-			rroDirs: map[string][]string{
-				"foo": nil,
-				"bar": nil,
-			},
-		},
-		{
-			name:              "enforce RRO on all",
-			enforceRROTargets: []string{"*"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": {"product/vendor/blah/overlay/lib2/res"},
-			},
-		},
-		{
-			name:              "enforce RRO on foo",
-			enforceRROTargets: []string{"foo"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": nil,
-			},
-		},
-	}
-
-	productResourceOverlays := []string{
-		"product/vendor/blah/overlay",
-	}
-
-	fs := android.MockFS{
-		"lib2/res/values/strings.xml":                             nil,
-		"product/vendor/blah/overlay/lib2/res/values/strings.xml": nil,
-	}
-
-	bp := `
-			android_app {
-				name: "foo",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_app {
-				name: "bar",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_library {
-				name: "lib",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib2"],
-			}
-
-			android_library {
-				name: "lib2",
-				sdk_version: "current",
-				resource_dirs: ["lib2/res"],
-			}
-		`
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			result := android.GroupFixturePreparers(
-				PrepareForTestWithJavaDefaultModules,
-				fs.AddToFixture(),
-				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-					variables.ProductResourceOverlays = productResourceOverlays
-					if testCase.enforceRROTargets != nil {
-						variables.EnforceRROTargets = testCase.enforceRROTargets
-					}
-				}),
-			).RunTestWithBp(t, bp)
-
-			modules := []string{"foo", "bar"}
-			for _, moduleName := range modules {
-				module := result.ModuleForTests(moduleName, "android_common")
-				mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0]
-				actualRRODirs := mkEntries.EntryMap["LOCAL_SOONG_PRODUCT_RRO_DIRS"]
-				if !reflect.DeepEqual(actualRRODirs, testCase.rroDirs[moduleName]) {
-					t.Errorf("exected %s LOCAL_SOONG_PRODUCT_RRO_DIRS entry: %v\ngot:%q",
-						moduleName, testCase.rroDirs[moduleName], actualRRODirs)
-				}
-			}
-		})
-	}
-}
-
 func TestRuntimeResourceOverlayPartition(t *testing.T) {
 	bp := `
 		runtime_resource_overlay {