Implement InstallableModule for Java modules

This change enables the container information to be collected for Java
modules.

Test: m nothing --no-skip-soong-tests
Bug: 338660802
Change-Id: I01bf99fa274275a608601ad6248d577ae8f6dffc
diff --git a/apex/Android.bp b/apex/Android.bp
index abae9e2..17fdfc3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,7 @@
         "apex_test.go",
         "bootclasspath_fragment_test.go",
         "classpath_element_test.go",
+        "container_test.go",
         "dexpreopt_bootjars_test.go",
         "platform_bootclasspath_test.go",
         "systemserver_classpath_fragment_test.go",
diff --git a/apex/container_test.go b/apex/container_test.go
new file mode 100644
index 0000000..3931174
--- /dev/null
+++ b/apex/container_test.go
@@ -0,0 +1,329 @@
+// 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 apex
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+				"baz",
+			],
+			libs: [
+				"bar",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "baz",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "baz",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, c.variant)
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestNonUpdatableApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+			],
+			libs: [
+				"bar",
+			],
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, c.variant)
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestUpdatableAndNonUpdatableApexesIdenticalMinSdkVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/myapex_non_updatable-file_contexts": nil,
+			"system/sepolicy/apex/myapex_updatable-file_contexts":     nil,
+		}),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex_non_updatable",
+			key: "myapex_non_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: false,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_non_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		apex {
+			name: "myapex_updatable",
+			key: "myapex_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex_non_updatable",
+				"myapex_updatable",
+			],
+			min_sdk_version: "30",
+			sdk_version: "current",
+		}
+	`)
+
+	fooApexVariant := result.ModuleForTests("foo", "android_common_apex30")
+	containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), fooApexVariant.Module(), android.ContainersInfoProvider)
+	belongingContainers := containers.BelongingContainers()
+	checkContainerMatch(t, "foo", "system", true, android.InList(android.SystemContainer, belongingContainers))
+	checkContainerMatch(t, "foo", "apex", true, android.InList(android.ApexContainer, belongingContainers))
+}
diff --git a/java/Android.bp b/java/Android.bp
index 54b36ab..a941754 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -87,6 +87,7 @@
         "app_set_test.go",
         "app_test.go",
         "code_metadata_test.go",
+        "container_test.go",
         "bootclasspath_fragment_test.go",
         "device_host_converter_test.go",
         "dex_test.go",
diff --git a/java/base.go b/java/base.go
index ee8df3e..ad79e98 100644
--- a/java/base.go
+++ b/java/base.go
@@ -552,6 +552,18 @@
 	aconfigCacheFiles android.Paths
 }
 
+var _ android.InstallableModule = (*Module)(nil)
+
+// To satisfy the InstallableModule interface
+func (j *Module) EnforceApiContainerChecks() bool {
+	return true
+}
+
+// Overrides android.ModuleBase.InstallInProduct()
+func (j *Module) InstallInProduct() bool {
+	return j.ProductSpecific()
+}
+
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
 	sdkVersion := j.SdkVersion(ctx)
 	if sdkVersion.Stable() {
diff --git a/java/container_test.go b/java/container_test.go
new file mode 100644
index 0000000..3441855
--- /dev/null
+++ b/java/container_test.go
@@ -0,0 +1,129 @@
+// 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 java
+
+import (
+	"android/soong/android"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestJavaContainersModuleProperties(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["A.java"],
+		}
+		java_library {
+			name: "foo_vendor",
+			srcs: ["A.java"],
+			vendor: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_soc_specific",
+			srcs: ["A.java"],
+			soc_specific: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_product_specific",
+			srcs: ["A.java"],
+			product_specific: true,
+			sdk_version: "current",
+		}
+		java_test {
+			name: "foo_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"cts",
+			],
+		}
+		java_test {
+			name: "foo_non_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"general-tests",
+			],
+		}
+	`)
+
+	testcases := []struct {
+		moduleName         string
+		isSystemContainer  bool
+		isVendorContainer  bool
+		isProductContainer bool
+		isCts              bool
+	}{
+		{
+			moduleName:         "foo",
+			isSystemContainer:  true,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_vendor",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_soc_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_product_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: true,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              true,
+		},
+		{
+			moduleName:         "foo_non_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(c.moduleName, "android_common")
+		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
+		belongingContainers := containers.BelongingContainers()
+		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "vendor", c.isVendorContainer, android.InList(android.VendorContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "product", c.isProductContainer, android.InList(android.ProductContainer, belongingContainers))
+	}
+}