Unify handling of compat and normal libs in class loader contexts.

Also, add tests for compatibility libraries in class loader context.

This CL separates special-case handling of compatibility libraries into
a "fixup" step that is done after class loader context is constructed by
Soong. This allows to handle compatibility libraries and normal
libraries uniformly, which should enable further simplification of class
loader context representation (in subsequent CLs).

Currently the only "fixup" step is removal of libraries from conditional
class loader context if they already are in unconditional context. This
check cannot be done at the time when the libraries are added to
conditional context, because the full uncoditional context is not yet
known at that time. Previously construction of unconditional context was
delayed, now it is no longer delayed and handled in the same way as
unconditional context, and the "fixup" does the filtering.

Test: lunch aosp_cf_x86_phone-userdebug && m
Bug: 132357300
Change-Id: Ie71e9fb2d3d529b5317cd84e09ab3c853017c349
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 7d8fbbc..2052847 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -100,20 +100,30 @@
 	ConstructContext android.Path
 }
 
-// These libs are added as optional dependencies (<uses-library> with android:required set to false).
-// This is because they haven't existed prior to certain SDK version, but classes in them were in
-// bootclasspath jars, etc. So making them hard dependencies (android:required=true) would prevent
-// apps from being installed to such legacy devices.
-var OptionalCompatUsesLibs = []string{
-	"org.apache.http.legacy",
-	"android.test.base",
-	"android.test.mock",
-}
+// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
+// app manifest is less than the specified version. This is needed because these libraries haven't
+// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
+// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
+// so that if this library is missing this in not a build or run-time error.
+var OrgApacheHttpLegacy = "org.apache.http.legacy"
+var AndroidTestBase = "android.test.base"
+var AndroidTestMock = "android.test.mock"
+var AndroidHidlBase = "android.hidl.base-V1.0-java"
+var AndroidHidlManager = "android.hidl.manager-V1.0-java"
 
-var CompatUsesLibs = []string{
-	"android.hidl.base-V1.0-java",
-	"android.hidl.manager-V1.0-java",
+var OptionalCompatUsesLibs28 = []string{
+	OrgApacheHttpLegacy,
 }
+var OptionalCompatUsesLibs30 = []string{
+	AndroidTestBase,
+	AndroidTestMock,
+}
+var CompatUsesLibs29 = []string{
+	AndroidHidlBase,
+	AndroidHidlManager,
+}
+var OptionalCompatUsesLibs = append(android.CopyOf(OptionalCompatUsesLibs28), OptionalCompatUsesLibs30...)
+var CompatUsesLibs = android.CopyOf(CompatUsesLibs29)
 
 const UnknownInstallLibraryPath = "error"
 
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 814b75d..903677f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -195,6 +195,9 @@
 }
 
 type classLoaderContext struct {
+	// Library names
+	Names []string
+
 	// The class loader context using paths in the build.
 	Host android.Paths
 
@@ -209,7 +212,7 @@
 // targetSdkVersion in the manifest or APK is less than that API version.
 type classLoaderContextMap map[int]*classLoaderContext
 
-const anySdkVersion int = 9999 // should go last in class loader context
+const AnySdkVersion int = 9999 // should go last in class loader context
 
 func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
 	if _, ok := m[sdkVer]; !ok {
@@ -218,14 +221,19 @@
 	return m[sdkVer]
 }
 
+func (clc *classLoaderContext) addLib(lib string, hostPath android.Path, targetPath string) {
+	clc.Names = append(clc.Names, lib)
+	clc.Host = append(clc.Host, hostPath)
+	clc.Target = append(clc.Target, targetPath)
+}
+
 func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, module *ModuleConfig, libs ...string) bool {
 	clc := m.getValue(sdkVer)
 	for _, lib := range libs {
 		if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath {
-			clc.Host = append(clc.Host, p.Host)
-			clc.Target = append(clc.Target, p.Device)
+			clc.addLib(lib, p.Host, p.Device)
 		} else {
-			if sdkVer == anySdkVersion {
+			if sdkVer == AnySdkVersion {
 				// Fail the build if dexpreopt doesn't know paths to one of the <uses-library>
 				// dependencies. In the future we may need to relax this and just disable dexpreopt.
 				android.ReportPathErrorf(ctx, "dexpreopt cannot find path for <uses-library> '%s'", lib)
@@ -239,11 +247,17 @@
 	return true
 }
 
+func (m classLoaderContextMap) usesLibs() []string {
+	if clc, ok := m[AnySdkVersion]; ok {
+		return clc.Names
+	}
+	return nil
+}
+
 func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
 	clc := m.getValue(sdkVer)
 	for _, lib := range libs {
-		clc.Host = append(clc.Host, SystemServerDexJarHostPath(ctx, lib))
-		clc.Target = append(clc.Target, filepath.Join("/system/framework", lib+".jar"))
+		clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar"))
 	}
 }
 
@@ -261,7 +275,7 @@
 //    as the runtime classpath won't match and the dexpreopted code will be discarded. Therefore in
 //    such cases the function returns nil, which disables dexpreopt.
 //
-// 2. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
+// 3. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
 //    the unsafe &-classpath workaround that means empty class loader context and absence of runtime
 //    check that the class loader context provided by the PackageManager agrees with the stored
 //    class loader context recorded in the .odex file.
@@ -273,21 +287,19 @@
 	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
 		// System server jars should be dexpreopted together: class loader context of each jar
 		// should include all preceding jars on the system server classpath.
-		classLoaderContexts.addSystemServerLibs(anySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
+		classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
 
 	} else if module.EnforceUsesLibraries {
 		// Unconditional class loader context.
 		usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
-		if !classLoaderContexts.addLibs(ctx, anySdkVersion, module, usesLibs...) {
+		if !classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...) {
 			return nil
 		}
 
 		// Conditional class loader context for API version < 28.
 		const httpLegacy = "org.apache.http.legacy"
-		if !contains(usesLibs, httpLegacy) {
-			if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) {
-				return nil
-			}
+		if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) {
+			return nil
 		}
 
 		// Conditional class loader context for API version < 29.
@@ -301,10 +313,8 @@
 
 		// Conditional class loader context for API version < 30.
 		const testBase = "android.test.base"
-		if !contains(usesLibs, testBase) {
-			if !classLoaderContexts.addLibs(ctx, 30, module, testBase) {
-				return nil
-			}
+		if !classLoaderContexts.addLibs(ctx, 30, module, testBase) {
+			return nil
 		}
 
 	} else {
@@ -314,9 +324,32 @@
 		// to the &.
 	}
 
+	fixConditionalClassLoaderContext(classLoaderContexts)
+
 	return &classLoaderContexts
 }
 
+// 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.
+func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
+	usesLibs := clcMap.usesLibs()
+
+	for sdkVer, clc := range clcMap {
+		if sdkVer == AnySdkVersion {
+			continue
+		}
+		clcMap[sdkVer] = &classLoaderContext{}
+		for i, lib := range clc.Names {
+			if android.InList(lib, usesLibs) {
+				// skip compatibility libraries that are already included in unconditional context
+			} else {
+				clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i])
+			}
+		}
+	}
+}
+
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
 	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap,
 	profile android.WritablePath, appImage bool, generateDM bool) {
@@ -363,7 +396,7 @@
 
 		checkSystemServerOrder(ctx, jarIndex)
 
-		clc := classLoaderContexts[anySdkVersion]
+		clc := classLoaderContexts[AnySdkVersion]
 		rule.Command().
 			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clc.Host.Strings(), ":") + "]").
 			Implicits(clc.Host).
@@ -391,14 +424,15 @@
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`)
 		for _, ver := range android.SortedIntKeys(classLoaderContexts) {
-			clc := classLoaderContexts.getValue(ver)
-			verString := fmt.Sprintf("%d", ver)
-			if ver == anySdkVersion {
-				verString = "any" // a special keyword that means any SDK version
+			if clc := classLoaderContexts.getValue(ver); len(clc.Host) > 0 {
+				verString := fmt.Sprintf("%d", ver)
+				if ver == AnySdkVersion {
+					verString = "any" // a special keyword that means any SDK version
+				}
+				cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")).
+					Implicits(clc.Host).
+					Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":"))
 			}
-			cmd.Textf(`--host-classpath-for-sdk %s %s`, verString, strings.Join(clc.Host.Strings(), ":")).
-				Implicits(clc.Host).
-				Textf(`--target-classpath-for-sdk %s %s`, verString, strings.Join(clc.Target, ":"))
 		}
 		cmd.Text(`)"`)
 	} else {
diff --git a/java/app_test.go b/java/app_test.go
index cec8a62..98945da 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2766,7 +2766,7 @@
 			name: "prebuilt",
 			apk: "prebuilts/apk/app.apk",
 			certificate: "platform",
-			uses_libs: ["foo"],
+			uses_libs: ["foo", "android.test.runner"],
 			optional_uses_libs: [
 				"bar",
 				"baz",
@@ -2804,7 +2804,7 @@
 
 	cmd = prebuilt.Rule("verify_uses_libraries").RuleParams.Command
 
-	if w := `uses_library_names="foo"`; !strings.Contains(cmd, w) {
+	if w := `uses_library_names="foo android.test.runner"`; !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
@@ -2812,20 +2812,48 @@
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
-	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
+	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs.
 	cmd = app.Rule("dexpreopt").RuleParams.Command
 	w := `--target-classpath-for-sdk any` +
 		` /system/framework/foo.jar` +
 		`:/system/framework/quuz.jar` +
 		`:/system/framework/qux.jar` +
 		`:/system/framework/runtime-library.jar` +
-		`:/system/framework/bar.jar`
+		`:/system/framework/bar.jar `
 	if !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 
+	// Test conditional context for target SDK version 28.
+	if w := `--target-classpath-for-sdk 28` +
+		` /system/framework/org.apache.http.legacy.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 29.
+	if w := `--target-classpath-for-sdk 29` +
+		` /system/framework/android.hidl.base-V1.0-java.jar` +
+		`:/system/framework/android.hidl.manager-V1.0-java.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 30.
+	if w := `--target-classpath-for-sdk 30` +
+		` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
 	cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
-	if w := `--target-classpath-for-sdk any /system/framework/foo.jar:/system/framework/bar.jar`; !strings.Contains(cmd, w) {
+	if w := `--target-classpath-for-sdk any` +
+		` /system/framework/foo.jar` +
+		`:/system/framework/android.test.runner.jar` +
+		`:/system/framework/bar.jar `; !strings.Contains(cmd, w) {
+		t.Errorf("wanted %q in %q", w, cmd)
+	}
+
+	// Test conditional context for target SDK version 30.
+	if w := `--target-classpath-for-sdk 30` +
+		` /system/framework/android.test.base.jar `; !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
 }
diff --git a/java/testing.go b/java/testing.go
index 461fd3f..ab13121 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -22,6 +22,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	"android/soong/python"
 
 	"github.com/google/blueprint"
@@ -152,6 +153,24 @@
 		`, extra)
 	}
 
+	// For class loader context and <uses-library> tests.
+	dexpreoptModules := []string{"android.test.runner"}
+	dexpreoptModules = append(dexpreoptModules, dexpreopt.CompatUsesLibs...)
+	dexpreoptModules = append(dexpreoptModules, dexpreopt.OptionalCompatUsesLibs...)
+
+	for _, extra := range dexpreoptModules {
+		bp += fmt.Sprintf(`
+			java_library {
+				name: "%s",
+				srcs: ["a.java"],
+				sdk_version: "none",
+				system_modules: "stable-core-platform-api-stubs-system-modules",
+				compile_dex: true,
+				installable: true,
+			}
+		`, extra)
+	}
+
 	bp += `
 		java_library {
 			name: "framework",
@@ -166,48 +185,7 @@
 		android_app {
 			name: "framework-res",
 			sdk_version: "core_platform",
-		}
-
-		java_library {
-			name: "android.hidl.base-V1.0-java",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "android.hidl.manager-V1.0-java",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "org.apache.http.legacy",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-
-		java_library {
-			name: "android.test.base",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-  
-		java_library {
-			name: "android.test.mock",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "stable-core-platform-api-stubs-system-modules",
-			installable: true,
-		}
-	`
+		}`
 
 	systemModules := []string{
 		"core-current-stubs-system-modules",