Merge changes from topic "system_other_img_soong_only" into main

* changes:
  Install odex/vdex files into soong-built system_other
  Build system_other image in soong-only builds
diff --git a/android/variable.go b/android/variable.go
index 08bcedf..14094e2 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -623,6 +623,8 @@
 	VendorDlkmSecurityPatch string `json:",omitempty"`
 	OdmDlkmSecurityPatch    string `json:",omitempty"`
 
+	BuildingSystemOtherImage bool `json:",omitempty"`
+
 	// Boot image stuff
 	BuildingRamdiskImage              bool     `json:",omitempty"`
 	ProductBuildBootImage             bool     `json:",omitempty"`
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 986b72e..cb76df2 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -28,6 +28,7 @@
         "raw_binary.go",
         "super_image.go",
         "system_image.go",
+        "system_other.go",
         "vbmeta.go",
         "testing.go",
     ],
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index ad19cc6..822ba43 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -378,6 +378,13 @@
 	// Name of the module that produced this FilesystemInfo origionally. (though it may be
 	// re-exported by super images or boot images)
 	ModuleName string
+	// The property file generated by this module and passed to build_image.
+	// It's exported here so that system_other can reuse system's property file.
+	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]()
@@ -484,9 +491,11 @@
 
 	var mapFile android.Path
 	var outputHermetic android.Path
+	var buildImagePropFile android.Path
+	var buildImagePropFileDeps android.Paths
 	switch f.fsType(ctx) {
 	case ext4Type, erofsType, f2fsType:
-		f.output, outputHermetic = f.buildImageUsingBuildImage(ctx, builder, rootDir, rebasedDir)
+		f.output, outputHermetic, buildImagePropFile, buildImagePropFileDeps = f.buildImageUsingBuildImage(ctx, builder, rootDir, rebasedDir)
 		mapFile = f.getMapFile(ctx)
 	case compressedCpioType:
 		f.output = f.buildCpioImage(ctx, builder, rootDir, true)
@@ -508,17 +517,16 @@
 	android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
 
 	fsInfo := FilesystemInfo{
-		Output:       f.output,
-		FileListFile: fileListFile,
-		RootDir:      rootDir,
-		RebasedDir:   rebasedDir,
-		ModuleName:   ctx.ModuleName(),
-	}
-	if mapFile != nil {
-		fsInfo.MapFile = mapFile
-	}
-	if outputHermetic != nil {
-		fsInfo.OutputHermetic = outputHermetic
+		Output:                 f.output,
+		OutputHermetic:         outputHermetic,
+		FileListFile:           fileListFile,
+		RootDir:                rootDir,
+		RebasedDir:             rebasedDir,
+		MapFile:                mapFile,
+		ModuleName:             ctx.ModuleName(),
+		BuildImagePropFile:     buildImagePropFile,
+		BuildImagePropFileDeps: buildImagePropFileDeps,
+		SpecsForSystemOther:    f.systemOtherFiles(ctx),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
@@ -670,7 +678,7 @@
 	builder *android.RuleBuilder,
 	rootDir android.OutputPath,
 	rebasedDir android.OutputPath,
-) (android.Path, android.Path) {
+) (android.Path, android.Path, android.Path, android.Paths) {
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
 	// While such concept is not implement this will do.
@@ -721,7 +729,7 @@
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	return output, outputHermetic
+	return output, outputHermetic, propFile, toolDeps
 }
 
 func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
@@ -1065,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/super_image.go b/filesystem/super_image.go
index 4419a2f..5332462 100644
--- a/filesystem/super_image.go
+++ b/filesystem/super_image.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"strconv"
 	"strings"
 
@@ -55,6 +56,9 @@
 	Sparse *bool
 	// information about how partitions within the super partition are grouped together
 	Partition_groups []PartitionGroupsInfo
+	// Name of the system_other partition filesystem module. This module will be installed to
+	// the "b" slot of the system partition in a/b partition builds.
+	System_other_partition *string
 	// whether dynamic partitions is used
 	Use_dynamic_partitions *bool
 	Virtual_ab             struct {
@@ -127,6 +131,12 @@
 
 var subImageDepTag superImageDepTagType
 
+type systemOtherDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var systemOtherDepTag systemOtherDepTagType
+
 func (s *superImage) DepsMutator(ctx android.BottomUpMutatorContext) {
 	addDependencyIfDefined := func(dep *string) {
 		if dep != nil {
@@ -143,6 +153,9 @@
 	addDependencyIfDefined(s.partitionProps.Vendor_dlkm_partition)
 	addDependencyIfDefined(s.partitionProps.Odm_partition)
 	addDependencyIfDefined(s.partitionProps.Odm_dlkm_partition)
+	if s.properties.System_other_partition != nil {
+		ctx.AddDependency(ctx.Module(), systemOtherDepTag, *s.properties.System_other_partition)
+	}
 }
 
 func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -299,6 +312,20 @@
 		}
 	}
 
+	if s.properties.System_other_partition != nil {
+		if !slices.Contains(partitionList, "system") {
+			ctx.PropertyErrorf("system_other_partition", "Must have a system partition to use a system_other partition")
+		}
+		systemOther := ctx.GetDirectDepProxyWithTag(*s.properties.System_other_partition, systemOtherDepTag)
+		systemOtherFiles := android.OutputFilesForModule(ctx, systemOther, "")
+		if len(systemOtherFiles) != 1 {
+			ctx.PropertyErrorf("system_other_partition", "Expected 1 output file from module %q", *&s.properties.System_other_partition)
+		} else {
+			addStr("system_other_image", systemOtherFiles[0].String())
+			deps = append(deps, systemOtherFiles[0])
+		}
+	}
+
 	// Delay the error message until execution time because on aosp-main-future-without-vendor,
 	// BUILDING_VENDOR_IMAGE is false so we don't get the vendor image, but it's still listed in
 	// BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST.
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
new file mode 100644
index 0000000..28fe1ce
--- /dev/null
+++ b/filesystem/system_other.go
@@ -0,0 +1,137 @@
+// 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 filesystem
+
+import (
+	"android/soong/android"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type SystemOtherImageProperties struct {
+	// The system_other image always requires a reference to the system image. The system_other
+	// partition gets built into the system partition's "b" slot in a/b partition builds. Thus, it
+	// copies most of its configuration from the system image, such as filesystem type, avb signing
+	// info, etc. Including it here does not automatically mean that it will pick up the system
+	// image's dexpropt files, it must also be listed in Preinstall_dexpreopt_files_from for that.
+	System_image *string
+
+	// This system_other partition will include all the dexpreopt files from the apps on these
+	// partitions.
+	Preinstall_dexpreopt_files_from []string
+}
+
+type systemOtherImage struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	properties SystemOtherImageProperties
+}
+
+// The system_other image is the default contents of the "b" slot of the system image.
+// It contains the dexpreopt files of all the apps on the device, for a faster first boot.
+// Afterwards, at runtime, it will be used as a regular b slot for OTA updates, and the initial
+// dexpreopt files will be deleted.
+func SystemOtherImageFactory() android.Module {
+	module := &systemOtherImage{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+type systemImageDeptag struct {
+	blueprint.BaseDependencyTag
+}
+
+var systemImageDependencyTag = systemImageDeptag{}
+
+type dexpreoptDeptag struct {
+	blueprint.BaseDependencyTag
+}
+
+var dexpreoptDependencyTag = dexpreoptDeptag{}
+
+func (m *systemOtherImage) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if proptools.String(m.properties.System_image) == "" {
+		ctx.ModuleErrorf("system_image property must be set")
+		return
+	}
+	ctx.AddDependency(ctx.Module(), systemImageDependencyTag, *m.properties.System_image)
+	ctx.AddDependency(ctx.Module(), dexpreoptDependencyTag, m.properties.Preinstall_dexpreopt_files_from...)
+}
+
+func (m *systemOtherImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	systemImage := ctx.GetDirectDepProxyWithTag(*m.properties.System_image, systemImageDependencyTag)
+	systemInfo, ok := android.OtherModuleProvider(ctx, systemImage, FilesystemProvider)
+	if !ok {
+		ctx.PropertyErrorf("system_image", "Expected system_image module to provide FilesystemProvider")
+		return
+	}
+
+	output := android.PathForModuleOut(ctx, "system_other.img")
+	stagingDir := android.PathForModuleOut(ctx, "staging_dir")
+
+	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)
+		fsInfo, ok := android.OtherModuleProvider(ctx, dexModule, FilesystemProvider)
+		if !ok {
+			ctx.PropertyErrorf("preinstall_dexpreopt_files_from", "Expected module %q to provide FilesystemProvider", otherPartition)
+			return
+		}
+		// 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
+	// host tool in a field in the prop file. However, it doesn't have that option for fec, which
+	// it expects to just be on the PATH. Add fec to the PATH.
+	fec := ctx.Config().HostToolPath(ctx, "fec")
+	pathToolDirs := []string{filepath.Dir(fec.String())}
+
+	builder.Command().
+		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
+		BuiltTool("build_image").
+		Text(stagingDir.String()). // input directory
+		Input(systemInfo.BuildImagePropFile).
+		Implicits(systemInfo.BuildImagePropFileDeps).
+		Implicit(fec).
+		Output(output).
+		Text(stagingDir.String())
+
+	builder.Build("build_system_other", "build system other")
+
+	ctx.SetOutputFiles(android.Paths{output}, "")
+	ctx.CheckbuildFile(output)
+}
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 7f5a068..d2f00cd 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)
 
@@ -156,6 +160,24 @@
 		}
 	}
 
+	var systemOtherImageName string
+	if buildingSystemOtherImage(partitionVars) {
+		systemModule := generatedModuleNameForPartition(ctx.Config(), "system")
+		systemOtherImageName = generatedModuleNameForPartition(ctx.Config(), "system_other")
+		ctx.CreateModule(
+			filesystem.SystemOtherImageFactory,
+			&filesystem.SystemOtherImageProperties{
+				System_image:                    &systemModule,
+				Preinstall_dexpreopt_files_from: finalSoongGeneratedPartitionNames,
+			},
+			&struct {
+				Name *string
+			}{
+				Name: proptools.StringPtr(systemOtherImageName),
+			},
+		)
+	}
+
 	for _, x := range createVbmetaPartitions(ctx, finalSoongGeneratedPartitions) {
 		f.properties.Vbmeta_module_names = append(f.properties.Vbmeta_module_names, x.moduleName)
 		f.properties.Vbmeta_partition_names = append(f.properties.Vbmeta_partition_names, x.partitionName)
@@ -163,7 +185,7 @@
 
 	var superImageSubpartitions []string
 	if buildingSuperImage(partitionVars) {
-		superImageSubpartitions = createSuperImage(ctx, finalSoongGeneratedPartitions, partitionVars)
+		superImageSubpartitions = createSuperImage(ctx, finalSoongGeneratedPartitions, partitionVars, systemOtherImageName)
 		f.properties.Super_image = ":" + generatedModuleNameForPartition(ctx.Config(), "super")
 	}
 
@@ -183,6 +205,12 @@
 	return generatedModuleName(cfg, fmt.Sprintf("%s_image", partitionType))
 }
 
+func buildingSystemOtherImage(partitionVars android.PartitionVariables) bool {
+	// TODO: Recreate this logic from make instead of just depending on the final result variable:
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=429;drc=15a0df840e7093f65518003ab80cf24a3d9e8e6a
+	return partitionVars.BuildingSystemOtherImage
+}
+
 func (f *filesystemCreator) createBootloaderFilegroup(ctx android.LoadHookContext) (string, bool) {
 	bootloaderPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.PrebuiltBootloader
 	if len(bootloaderPath) == 0 {
diff --git a/fsgen/super_img.go b/fsgen/super_img.go
index 5c23868..e353688 100644
--- a/fsgen/super_img.go
+++ b/fsgen/super_img.go
@@ -27,7 +27,12 @@
 	return partitionVars.ProductBuildSuperPartition
 }
 
-func createSuperImage(ctx android.LoadHookContext, partitions []string, partitionVars android.PartitionVariables) []string {
+func createSuperImage(
+	ctx android.LoadHookContext,
+	partitions []string,
+	partitionVars android.PartitionVariables,
+	systemOtherImageName string,
+) []string {
 	baseProps := &struct {
 		Name *string
 	}{
@@ -79,6 +84,10 @@
 	}
 	superImageProps.Partition_groups = partitionGroupsInfo
 
+	if systemOtherImageName != "" {
+		superImageProps.System_other_partition = proptools.StringPtr(systemOtherImageName)
+	}
+
 	var superImageSubpartitions []string
 	partitionNameProps := &filesystem.SuperImagePartitionNameProperties{}
 	if android.InList("system", partitions) {