Refactor containers

Implementation details:
- Add documentations
- Convert container determiniation logic to function pointers, in order
  to make addition/deletion of containers more scalable

Test: m nothing
Bug: 338660802
Change-Id: I4f7a9a027e00584bb895ce8559f621bae1e985f6
diff --git a/android/container.go b/android/container.go
index c4fdd9c..05897dd 100644
--- a/android/container.go
+++ b/android/container.go
@@ -21,12 +21,22 @@
 	"github.com/google/blueprint"
 )
 
+// ----------------------------------------------------------------------------
+// Start of the definitions of exception functions and the lookup table.
+//
+// Functions cannot be used as a value passed in providers, because functions are not
+// hashable. As a workaround, the [exceptionHandleFuncLabel] enum values are passed using providers,
+// and the corresponding functions are called from [exceptionHandleFunctionsTable] map.
+// ----------------------------------------------------------------------------
+
+type exceptionHandleFunc func(ModuleContext, Module, Module) bool
+
 type StubsAvailableModule interface {
 	IsStubsModule() bool
 }
 
 // Returns true if the dependency module is a stubs module
-var depIsStubsModule = func(_ ModuleContext, _, dep Module) bool {
+var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
 	if stubsModule, ok := dep.(StubsAvailableModule); ok {
 		return stubsModule.IsStubsModule()
 	}
@@ -41,13 +51,76 @@
 	checkStubs exceptionHandleFuncLabel = iota
 )
 
-// Functions cannot be used as a value passed in providers, because functions are not
-// hashable. As a workaround, the exceptionHandleFunc enum values are passed using providers,
-// and the corresponding functions are called from this map.
-var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]func(ModuleContext, Module, Module) bool{
+var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]exceptionHandleFunc{
 	checkStubs: depIsStubsModule,
 }
 
+// ----------------------------------------------------------------------------
+// Start of the definitions of container determination functions.
+//
+// Similar to the above section, below defines the functions used to determine
+// the container of each modules.
+// ----------------------------------------------------------------------------
+
+type containerBoundaryFunc func(mctx ModuleContext) bool
+
+var vendorContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
+	m, ok := mctx.Module().(ImageInterface)
+	return mctx.Module().InstallInVendor() || (ok && m.VendorVariantNeeded(mctx))
+}
+
+var systemContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
+	module := mctx.Module()
+
+	return !module.InstallInTestcases() &&
+		!module.InstallInData() &&
+		!module.InstallInRamdisk() &&
+		!module.InstallInVendorRamdisk() &&
+		!module.InstallInDebugRamdisk() &&
+		!module.InstallInRecovery() &&
+		!module.InstallInVendor() &&
+		!module.InstallInOdm() &&
+		!module.InstallInProduct() &&
+		determineModuleKind(module.base(), mctx.blueprintBaseModuleContext()) == platformModule
+}
+
+var productContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
+	m, ok := mctx.Module().(ImageInterface)
+	return mctx.Module().InstallInProduct() || (ok && m.ProductVariantNeeded(mctx))
+}
+
+var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
+	_, ok := ModuleProvider(mctx, AllApexInfoProvider)
+	return ok
+}
+
+var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
+	props := mctx.Module().GetProperties()
+	for _, prop := range props {
+		val := reflect.ValueOf(prop).Elem()
+		if val.Kind() == reflect.Struct {
+			testSuites := val.FieldByName("Test_suites")
+			if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// Map of [*container] to the [containerBoundaryFunc]
+var containerBoundaryFunctionsTable = map[*container]containerBoundaryFunc{
+	VendorContainer:  vendorContainerBoundaryFunc,
+	SystemContainer:  systemContainerBoundaryFunc,
+	ProductContainer: productContainerBoundaryFunc,
+	ApexContainer:    apexContainerBoundaryFunc,
+	CtsContainer:     ctsContainerBoundaryFunc,
+}
+
+// ----------------------------------------------------------------------------
+// End of the definitions of container determination functions.
+// ----------------------------------------------------------------------------
+
 type InstallableModule interface {
 	EnforceApiContainerChecks() bool
 }
@@ -77,6 +150,7 @@
 		name:       VendorVariation,
 		restricted: nil,
 	}
+
 	SystemContainer = &container{
 		name: "system",
 		restricted: []restriction{
@@ -90,6 +164,7 @@
 			},
 		},
 	}
+
 	ProductContainer = &container{
 		name: ProductVariation,
 		restricted: []restriction{
@@ -102,8 +177,10 @@
 			},
 		},
 	}
+
 	ApexContainer = initializeApexContainer()
-	CtsContainer  = &container{
+
+	CtsContainer = &container{
 		name: "cts",
 		restricted: []restriction{
 			{
@@ -116,6 +193,14 @@
 			},
 		},
 	}
+
+	allContainers = []*container{
+		VendorContainer,
+		SystemContainer,
+		ProductContainer,
+		ApexContainer,
+		CtsContainer,
+	}
 )
 
 func initializeApexContainer() *container {
@@ -155,68 +240,38 @@
 	return c.belongingContainers
 }
 
-var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
-
-// Determines if the module can be installed in the system partition or not.
-// Logic is identical to that of modulePartition(...) defined in paths.go
-func installInSystemPartition(ctx ModuleContext) bool {
-	module := ctx.Module()
-	return !module.InstallInTestcases() &&
-		!module.InstallInData() &&
-		!module.InstallInRamdisk() &&
-		!module.InstallInVendorRamdisk() &&
-		!module.InstallInDebugRamdisk() &&
-		!module.InstallInRecovery() &&
-		!module.InstallInVendor() &&
-		!module.InstallInOdm() &&
-		!module.InstallInProduct() &&
-		determineModuleKind(module.base(), ctx.blueprintBaseModuleContext()) == platformModule
+func (c *ContainersInfo) ApexNames() (ret []string) {
+	for _, apex := range c.belongingApexes {
+		ret = append(ret, apex.InApexModules...)
+	}
+	slices.Sort(ret)
+	return ret
 }
 
-func generateContainerInfo(ctx ModuleContext) ContainersInfo {
-	inSystem := installInSystemPartition(ctx)
-	inProduct := ctx.Module().InstallInProduct()
-	inVendor := ctx.Module().InstallInVendor()
-	inCts := false
-	inApex := false
-
-	if m, ok := ctx.Module().(ImageInterface); ok {
-		inProduct = inProduct || m.ProductVariantNeeded(ctx)
-		inVendor = inVendor || m.VendorVariantNeeded(ctx)
+// Returns true if any of the apex the module belongs to is updatable.
+func (c *ContainersInfo) UpdatableApex() bool {
+	for _, apex := range c.belongingApexes {
+		if apex.Updatable {
+			return true
+		}
 	}
+	return false
+}
 
-	props := ctx.Module().GetProperties()
-	for _, prop := range props {
-		val := reflect.ValueOf(prop).Elem()
-		if val.Kind() == reflect.Struct {
-			testSuites := val.FieldByName("Test_suites")
-			if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
-				inCts = true
-			}
+var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
+
+func generateContainerInfo(ctx ModuleContext) ContainersInfo {
+	var containers []*container
+
+	for _, cnt := range allContainers {
+		if containerBoundaryFunctionsTable[cnt](ctx) {
+			containers = append(containers, cnt)
 		}
 	}
 
 	var belongingApexes []ApexInfo
 	if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
 		belongingApexes = apexInfo.ApexInfos
-		inApex = true
-	}
-
-	containers := []*container{}
-	if inSystem {
-		containers = append(containers, SystemContainer)
-	}
-	if inProduct {
-		containers = append(containers, ProductContainer)
-	}
-	if inVendor {
-		containers = append(containers, VendorContainer)
-	}
-	if inCts {
-		containers = append(containers, CtsContainer)
-	}
-	if inApex {
-		containers = append(containers, ApexContainer)
 	}
 
 	return ContainersInfo{