Add support for excluding libraries from class loader contexts

A number of tests in the cts/tests/signature/api-check check for the
accessibility of classes from the android.test.base,
android.test.runner and android.test.mock libraries. Some tests expect
to find the classes other do not. Unfortunately, the tests use
libraries, specifically compatibility-device-util-axt, that depend on
the android.test... libraries which causes Soong to implicitly add
<uses-library> entries to the manifest so that they will be accessible
at runtime. That causes the tests that do not expect to find the
classes to fail.

Bug: 209607558
Test: m nothing
Change-Id: I54c194ab23d5a70df790ece3fe98f2b3d6a1c1f6
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 658e8e2..36513b6 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -210,6 +210,34 @@
 	Subcontexts []*ClassLoaderContext
 }
 
+// excludeLibs excludes the libraries from this ClassLoaderContext.
+//
+// This treats the supplied context as being immutable (as it may come from a dependency). So, it
+// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within this context then this will return a deep copy of this without those libraries.
+//
+// If this ClassLoaderContext matches one of the libraries to exclude then this returns (nil, true)
+// to indicate that this context should be excluded from the containing list.
+//
+// If any of this ClassLoaderContext's Subcontexts reference the excluded libraries then this
+// returns a pointer to a copy of this without the excluded libraries and true to indicate that this
+// was copied.
+//
+// Otherwise, this returns a pointer to this and false to indicate that this was not copied.
+func (c *ClassLoaderContext) excludeLibs(excludedLibs []string) (*ClassLoaderContext, bool) {
+	if android.InList(c.Name, excludedLibs) {
+		return nil, true
+	}
+
+	if excludedList, modified := excludeLibsFromCLCList(c.Subcontexts, excludedLibs); modified {
+		clcCopy := *c
+		clcCopy.Subcontexts = excludedList
+		return &clcCopy, true
+	}
+
+	return c, false
+}
+
 // ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
 // AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
 //
@@ -408,6 +436,67 @@
 	return string(bytes)
 }
 
+// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list.
+//
+// This treats the supplied list as being immutable (as it may come from a dependency). So, it
+// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within the contexts in the list then this will return a deep copy of the list without those
+// libraries.
+//
+// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a
+// copy of this list without the excluded libraries and true to indicate that this was copied.
+//
+// Otherwise, this returns the list and false to indicate that this was not copied.
+func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) {
+	modifiedList := false
+	copiedList := make([]*ClassLoaderContext, 0, len(clcList))
+	for _, clc := range clcList {
+		resultClc, modifiedClc := clc.excludeLibs(excludedLibs)
+		if resultClc != nil {
+			copiedList = append(copiedList, resultClc)
+		}
+		modifiedList = modifiedList || modifiedClc
+	}
+
+	if modifiedList {
+		return copiedList, true
+	} else {
+		return clcList, false
+	}
+}
+
+// ExcludeLibs excludes the libraries from the ClassLoaderContextMap.
+//
+// If the list o libraries is empty then this returns the ClassLoaderContextMap.
+//
+// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So,
+// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within the contexts in the map then this will return a deep copy of the map without those
+// libraries.
+//
+// Otherwise, this returns the map unchanged.
+func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap {
+	if len(excludedLibs) == 0 {
+		return clcMap
+	}
+
+	excludedClcMap := make(ClassLoaderContextMap)
+	modifiedMap := false
+	for sdkVersion, clcList := range clcMap {
+		excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs)
+		if len(excludedList) != 0 {
+			excludedClcMap[sdkVersion] = excludedList
+		}
+		modifiedMap = modifiedMap || modifiedList
+	}
+
+	if modifiedMap {
+		return excludedClcMap
+	} else {
+		return clcMap
+	}
+}
+
 // Now that the full unconditional context is known, reconstruct conditional context.
 // Apply filters for individual libraries, mirroring what the PackageManager does when it
 // constructs class loader context on device.
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 4a3d390..5d3a9d9 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -284,6 +284,111 @@
 	})
 }
 
+func TestCLCMExcludeLibs(t *testing.T) {
+	ctx := testContext()
+	const optional = false
+	const implicit = true
+
+	excludeLibs := func(t *testing.T, m ClassLoaderContextMap, excluded_libs ...string) ClassLoaderContextMap {
+		// Dump the CLCM before creating a new copy that excludes a specific set of libraries.
+		before := m.Dump()
+
+		// Create a new CLCM that excludes some libraries.
+		c := m.ExcludeLibs(excluded_libs)
+
+		// Make sure that the original CLCM was not changed.
+		after := m.Dump()
+		android.AssertStringEquals(t, "input CLCM modified", before, after)
+
+		return c
+	}
+
+	t.Run("exclude nothing", func(t *testing.T) {
+		m := make(ClassLoaderContextMap)
+		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+
+		a := excludeLibs(t, m)
+
+		android.AssertStringEquals(t, "output CLCM ", `{
+  "28": [
+    {
+      "Name": "a",
+      "Optional": false,
+      "Implicit": true,
+      "Host": "out/soong/a.jar",
+      "Device": "/system/a.jar",
+      "Subcontexts": []
+    }
+  ]
+}`, a.Dump())
+	})
+
+	t.Run("one item from list", func(t *testing.T) {
+		m := make(ClassLoaderContextMap)
+		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+		m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+
+		a := excludeLibs(t, m, "a")
+
+		expected := `{
+  "28": [
+    {
+      "Name": "b",
+      "Optional": false,
+      "Implicit": true,
+      "Host": "out/soong/b.jar",
+      "Device": "/system/b.jar",
+      "Subcontexts": []
+    }
+  ]
+}`
+		android.AssertStringEquals(t, "output CLCM ", expected, a.Dump())
+	})
+
+	t.Run("all items from a list", func(t *testing.T) {
+		m := make(ClassLoaderContextMap)
+		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+		m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+
+		a := excludeLibs(t, m, "a", "b")
+
+		android.AssertStringEquals(t, "output CLCM ", `{}`, a.Dump())
+	})
+
+	t.Run("items from a subcontext", func(t *testing.T) {
+		s := make(ClassLoaderContextMap)
+		s.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+		s.AddContext(ctx, AnySdkVersion, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+
+		m := make(ClassLoaderContextMap)
+		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), s)
+
+		a := excludeLibs(t, m, "b")
+
+		android.AssertStringEquals(t, "output CLCM ", `{
+  "28": [
+    {
+      "Name": "a",
+      "Optional": false,
+      "Implicit": true,
+      "Host": "out/soong/a.jar",
+      "Device": "/system/a.jar",
+      "Subcontexts": [
+        {
+          "Name": "c",
+          "Optional": false,
+          "Implicit": true,
+          "Host": "out/soong/c.jar",
+          "Device": "/system/c.jar",
+          "Subcontexts": []
+        }
+      ]
+    }
+  ]
+}`, a.Dump())
+	})
+}
+
 func checkError(t *testing.T, have error, want string) {
 	if have == nil {
 		t.Errorf("\nwant error: '%s'\nhave: none", want)
diff --git a/java/aar.go b/java/aar.go
index 4687424..51aad8d 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -267,11 +267,15 @@
 	})
 
 func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkContext,
-	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, excludedLibs []string,
+	extraLinkFlags ...string) {
 
 	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
 		aaptLibs(ctx, sdkContext, classLoaderContexts)
 
+	// Exclude any libraries from the supplied list.
+	classLoaderContexts = classLoaderContexts.ExcludeLibs(excludedLibs)
+
 	// App manifest file
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
@@ -530,7 +534,7 @@
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
-	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, nil)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
diff --git a/java/app.go b/java/app.go
index 9f2f99a..e4432ff 100755
--- a/java/app.go
+++ b/java/app.go
@@ -425,7 +425,8 @@
 
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts,
+		a.usesLibraryProperties.Exclude_uses_libs, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -1211,6 +1212,23 @@
 	// libraries, because SDK ones are automatically picked up by Soong. The <uses-library> name
 	// normally is the same as the module name, but there are exceptions.
 	Provides_uses_lib *string
+
+	// A list of shared library names to exclude from the classpath of the APK. Adding a library here
+	// will prevent it from being used when precompiling the APK and prevent it from being implicitly
+	// added to the APK's manifest's <uses-library> elements.
+	//
+	// Care must be taken when using this as it could result in runtime errors if the APK actually
+	// uses classes provided by the library and which are not provided in any other way.
+	//
+	// This is primarily intended for use by various CTS tests that check the runtime handling of the
+	// android.test.base shared library (and related libraries) but which depend on some common
+	// libraries that depend on the android.test.base library. Without this those tests will end up
+	// with a <uses-library android:name="android.test.base"/> in their manifest which would either
+	// render the tests worthless (as they would be testing the wrong behavior), or would break the
+	// test altogether by providing access to classes that the tests were not expecting. Those tests
+	// provide the android.test.base statically and use jarjar to rename them so they do not collide
+	// with the classes provided by the android.test.base library.
+	Exclude_uses_libs []string
 }
 
 // usesLibrary provides properties and helper functions for AndroidApp and AndroidAppImport to verify that the
diff --git a/java/rro.go b/java/rro.go
index 0b4d091..be84aff 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -139,7 +139,7 @@
 		aaptLinkFlags = append(aaptLinkFlags,
 			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
 	}
-	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
+	r.aapt.buildActions(ctx, r, nil, nil, aaptLinkFlags...)
 
 	// Sign the built package
 	_, certificates := collectAppDeps(ctx, r, false, false)