Merge "Unify handling of compat and normal libs in class loader contexts."
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",