Collect container informations

This change introduces a method to collect the information of what
containers (i.e. api domain, partition, or any custom defined boundaries
of interest) the module belongs to. The method is called in
`ModuleBase.GenerateBuildActions`.

Each container objects defines the following:
- name of the container
- list of "restrictions", which are the containers that a module that
  belongs to this container is not allowed to depend on. Each
  "restrictions" also defines custom rules which allow bypassing the
  restricted dependency. Each rules are an enum that are mapped to a
  function, given that functions are not hashable and thus cannot be set
  as a value in a provider.

Note that this change is a no-op, as the container information is only
collected for modules that implement the "InstallableModule" interface,
which is not implemented by any other module types in this change. This
will be utilized in the follow-up changes.

Test: m nothing --no-skip-soong-tests
Bug: 338660802
Change-Id: I9d16dfec0dcf06da464aa49ee7b23f46f1da236a
diff --git a/android/Android.bp b/android/Android.bp
index ce8c9b0..3b17be4 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -41,6 +41,7 @@
         "build_prop.go",
         "buildinfo_prop.go",
         "config.go",
+        "container.go",
         "test_config.go",
         "configurable_properties.go",
         "configured_jars.go",
diff --git a/android/container.go b/android/container.go
new file mode 100644
index 0000000..c4fdd9c
--- /dev/null
+++ b/android/container.go
@@ -0,0 +1,233 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"reflect"
+	"slices"
+
+	"github.com/google/blueprint"
+)
+
+type StubsAvailableModule interface {
+	IsStubsModule() bool
+}
+
+// Returns true if the dependency module is a stubs module
+var depIsStubsModule = func(_ ModuleContext, _, dep Module) bool {
+	if stubsModule, ok := dep.(StubsAvailableModule); ok {
+		return stubsModule.IsStubsModule()
+	}
+	return false
+}
+
+// Labels of exception functions, which are used to determine special dependencies that allow
+// otherwise restricted inter-container dependencies
+type exceptionHandleFuncLabel int
+
+const (
+	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{
+	checkStubs: depIsStubsModule,
+}
+
+type InstallableModule interface {
+	EnforceApiContainerChecks() bool
+}
+
+type restriction struct {
+	// container of the dependency
+	dependency *container
+
+	// Error message to be emitted to the user when the dependency meets this restriction
+	errorMessage string
+
+	// List of labels of allowed exception functions that allows bypassing this restriction.
+	// If any of the functions mapped to each labels returns true, this dependency would be
+	// considered allowed and an error will not be thrown.
+	allowedExceptions []exceptionHandleFuncLabel
+}
+type container struct {
+	// The name of the container i.e. partition, api domain
+	name string
+
+	// Map of dependency restricted containers.
+	restricted []restriction
+}
+
+var (
+	VendorContainer = &container{
+		name:       VendorVariation,
+		restricted: nil,
+	}
+	SystemContainer = &container{
+		name: "system",
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the system partition other than HALs is " +
+					"not allowed to depend on the vendor partition module, in order to support " +
+					"independent development/update cycles and to support the Generic System " +
+					"Image. Try depending on HALs, VNDK or AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ProductContainer = &container{
+		name: ProductVariation,
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the product partition is not allowed to " +
+					"depend on the vendor partition module, as this may lead to security " +
+					"vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ApexContainer = initializeApexContainer()
+	CtsContainer  = &container{
+		name: "cts",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "CTS module should not depend on the modules belonging to the " +
+					"system partition, including \"framework\". Depending on the system " +
+					"partition may lead to disclosure of implementation details and regression " +
+					"due to API changes across platform versions. Try depending on the stubs instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+)
+
+func initializeApexContainer() *container {
+	apexContainer := &container{
+		name: "apex",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+					"modules belonging to the system partition. Either statically depend on the " +
+					"module or convert the depending module to java_sdk_library and depend on " +
+					"the stubs.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+
+	apexContainer.restricted = append(apexContainer.restricted, restriction{
+		dependency: apexContainer,
+		errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+			"modules belonging to other Apex(es). Either include the depending " +
+			"module in the Apex or convert the depending module to java_sdk_library " +
+			"and depend on its stubs.",
+		allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+	})
+
+	return apexContainer
+}
+
+type ContainersInfo struct {
+	belongingContainers []*container
+
+	belongingApexes []ApexInfo
+}
+
+func (c *ContainersInfo) BelongingContainers() []*container {
+	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 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)
+	}
+
+	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 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{
+		belongingContainers: containers,
+		belongingApexes:     belongingApexes,
+	}
+}
+
+func setContainerInfo(ctx ModuleContext) {
+	if _, ok := ctx.Module().(InstallableModule); ok {
+		containersInfo := generateContainerInfo(ctx)
+		SetProvider(ctx, ContainersInfoProvider, containersInfo)
+	}
+}
diff --git a/android/image.go b/android/image.go
index c278dcd..0f03107 100644
--- a/android/image.go
+++ b/android/image.go
@@ -22,7 +22,7 @@
 	// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
 	VendorVariantNeeded(ctx BaseModuleContext) bool
 
-	// ProductVariantNeeded should return true if the module needs a product variant (unstalled on the product image).
+	// ProductVariantNeeded should return true if the module needs a product variant (installed on the product image).
 	ProductVariantNeeded(ctx BaseModuleContext) bool
 
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
diff --git a/android/module.go b/android/module.go
index 2dc63d6..f4a188f 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1801,6 +1801,8 @@
 		variables:         make(map[string]string),
 	}
 
+	setContainerInfo(ctx)
+
 	m.licenseMetadataFile = PathForModuleOut(ctx, "meta_lic")
 
 	dependencyInstallFiles, dependencyPackagingSpecs := m.computeInstallDeps(ctx)