Install odex/vdex files into soong-built system_other

This makes system_other almost identical to the make-built one,
but there's still a diff in just one file:
system_other/system/priv-app/CredentialManager/oat/x86_64/CredentialManager.art

Bug: 390269431
Test: m --soong-only
Change-Id: I440097cead56a20d0268f4e766ac1be8fe11b34b
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index e485e4f..822ba43 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -383,6 +383,8 @@
 	BuildImagePropFile android.Path
 	// Paths to all the tools referenced inside of the build image property file.
 	BuildImagePropFileDeps android.Paths
+	// Packaging specs to be installed on the system_other image, for the initial boot's dexpreopt.
+	SpecsForSystemOther map[string]android.PackagingSpec
 }
 
 var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
@@ -524,6 +526,7 @@
 		ModuleName:             ctx.ModuleName(),
 		BuildImagePropFile:     buildImagePropFile,
 		BuildImagePropFileDeps: buildImagePropFileDeps,
+		SpecsForSystemOther:    f.systemOtherFiles(ctx),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
@@ -1070,8 +1073,21 @@
 // Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
 func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
-	specs := f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
-	return specs
+	return f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
+}
+
+// Dexpreopt files are installed to system_other. Collect the packaingSpecs for the dexpreopt files
+// from this partition to export to the system_other partition later.
+func (f *filesystem) systemOtherFiles(ctx android.ModuleContext) map[string]android.PackagingSpec {
+	filter := func(spec android.PackagingSpec) bool {
+		// For some reason system_other packaging specs don't set the partition field.
+		return strings.HasPrefix(spec.RelPathInPackage(), "system_other/")
+	}
+	modifier := func(spec *android.PackagingSpec) {
+		spec.SetRelPathInPackage(strings.TrimPrefix(spec.RelPathInPackage(), "system_other/"))
+		spec.SetPartition("system_other")
+	}
+	return f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, filter, modifier)
 }
 
 func sha1sum(values []string) string {
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
index cad4263..28fe1ce 100644
--- a/filesystem/system_other.go
+++ b/filesystem/system_other.go
@@ -49,7 +49,7 @@
 func SystemOtherImageFactory() android.Module {
 	module := &systemOtherImage{}
 	module.AddProperties(&module.properties)
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 	return module
 }
@@ -89,14 +89,29 @@
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Textf("rm -rf %s && mkdir -p %s", stagingDir, stagingDir)
 
+	specs := make(map[string]android.PackagingSpec)
 	for _, otherPartition := range m.properties.Preinstall_dexpreopt_files_from {
 		dexModule := ctx.GetDirectDepProxyWithTag(otherPartition, dexpreoptDependencyTag)
-		_, ok := android.OtherModuleProvider(ctx, dexModule, FilesystemProvider)
+		fsInfo, ok := android.OtherModuleProvider(ctx, dexModule, FilesystemProvider)
 		if !ok {
 			ctx.PropertyErrorf("preinstall_dexpreopt_files_from", "Expected module %q to provide FilesystemProvider", otherPartition)
 			return
 		}
-		// TODO(b/390269431): Install dex files to the staging dir
+		// Merge all the packaging specs into 1 map
+		for k := range fsInfo.SpecsForSystemOther {
+			if _, ok := specs[k]; ok {
+				ctx.ModuleErrorf("Packaging spec %s given by two different partitions", k)
+				continue
+			}
+			specs[k] = fsInfo.SpecsForSystemOther[k]
+		}
+	}
+
+	// TOOD: CopySpecsToDir only exists on PackagingBase, but doesn't use any fields from it. Clean this up.
+	(&android.PackagingBase{}).CopySpecsToDir(ctx, builder, specs, stagingDir)
+
+	if len(m.properties.Preinstall_dexpreopt_files_from) > 0 {
+		builder.Command().Textf("touch %s", filepath.Join(stagingDir.String(), "system-other-odex-marker"))
 	}
 
 	// Most of the time, if build_image were to call a host tool, it accepts the path to the
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 6166074..44ae953 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -128,6 +128,10 @@
 			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, partitionType)
 		}
 	}
+	finalSoongGeneratedPartitionNames := make([]string, 0, len(finalSoongGeneratedPartitions))
+	for _, partitionType := range finalSoongGeneratedPartitions {
+		finalSoongGeneratedPartitionNames = append(finalSoongGeneratedPartitionNames, generatedModuleNameForPartition(ctx.Config(), partitionType))
+	}
 	// Create android_info.prop
 	f.createAndroidInfo(ctx)
 
@@ -163,7 +167,8 @@
 		ctx.CreateModule(
 			filesystem.SystemOtherImageFactory,
 			&filesystem.SystemOtherImageProperties{
-				System_image: &systemModule,
+				System_image:                    &systemModule,
+				Preinstall_dexpreopt_files_from: finalSoongGeneratedPartitionNames,
 			},
 			&struct {
 				Name *string