Avoid hiddenapi ignoring prebuilt with missing dex implementation jar

Previously, when a prebuilt was preferred but did not provide a
suitable boot dex jar both the source and the prebuilt were silently
ignored which meant that the "hiddenapi list" command was not given a
complete set of boot dex jars. That could either lead to incorrect
hiddenapi flags being set or the "hiddenapi list" command failing if it
could not find a class. Debugging the cause of either of those cases
can be very time consuming so this change fails early and makes the
cause very explicit.

Bug: 181267622
Test: m nothing
Change-Id: I6763ddb9ba90ed2e501d0cf7984f6655237e905d
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index da2c48f..208ced7 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -148,7 +148,18 @@
 		primary = configurationName == ctx.ModuleName()
 
 		// A source module that has been replaced by a prebuilt can never be the primary module.
-		primary = primary && !module.IsReplacedByPrebuilt()
+		if module.IsReplacedByPrebuilt() {
+			ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
+				if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
+					primary = false
+				} else {
+					ctx.ModuleErrorf(
+						"hiddenapi has determined that the source module %q should be ignored as it has been"+
+							" replaced by the prebuilt module %q but unfortunately it does not provide a"+
+							" suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
+				}
+			})
+		}
 	}
 	h.primary = primary
 }
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index c0f0e38..fb63820 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -126,6 +126,29 @@
 `, indexParams)
 }
 
+func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
+	config := testConfigWithBootJars(`
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			compile_dex: true,
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["a.jar"],
+			prefer: true,
+		}
+	`, []string{"platform:foo"}, nil)
+
+	ctx := testContextWithHiddenAPI(config)
+
+	runWithErrors(t, ctx, config,
+		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been"+
+			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a"+
+			" suitable boot dex jar")
+}
+
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
 	ctx, _ := testHiddenAPIBootJars(t, `
 		java_import {
diff --git a/java/java_test.go b/java/java_test.go
index 8407f24..bb51ebc 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -114,20 +114,26 @@
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
+	runWithErrors(t, ctx, config, pattern)
+
+	return ctx, config
+}
+
+func runWithErrors(t *testing.T, ctx *android.TestContext, config android.Config, pattern string) {
 	ctx.Register()
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return ctx, config
+		return
 	}
 	_, errs = ctx.PrepareBuildActions(config)
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return ctx, config
+		return
 	}
 
 	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
-	return ctx, config
+	return
 }
 
 func testJavaWithFS(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {