[SDK Index] Show recommended versions

... in policy and outadated issues. This change also adds a remark
saying that the recommended versions are not reviwed by Google and tells
users to carefully evaluate any third party libraries before integrating
them into their apps.

Bug: 336826464
Test: GooglePlaySdkIndexTest
Change-Id: I06b258d5823e4d694781a3cc746dd16d252819b7
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GooglePlaySdkIndex.kt b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GooglePlaySdkIndex.kt
index 14c18d9..39147a7 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GooglePlaySdkIndex.kt
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/GooglePlaySdkIndex.kt
@@ -383,8 +383,9 @@
     artifactId: String,
     versionString: String,
   ): List<String> {
+    val recommendedVersions = getPolicyRecommendedVersions(groupId, artifactId, versionString)
     return getPolicyLabels(getLabels(groupId, artifactId, versionString)).map { label ->
-      "[Prevents app release in Google Play Console] $groupId:$artifactId version $versionString has $label issues that will block publishing of your app to Play Console"
+      "[Prevents app release in Google Play Console] $groupId:$artifactId version $versionString has $label issues that will block publishing of your app to Play Console$recommendedVersions"
     }
   }
 
@@ -394,8 +395,9 @@
     artifactId: String,
     versionString: String,
   ): List<String> {
+    val recommendedVersions = getPolicyRecommendedVersions(groupId, artifactId, versionString)
     return getPolicyLabels(getLabels(groupId, artifactId, versionString)).map { label ->
-      "$groupId:$artifactId version $versionString has $label issues that will block publishing of your app to Play Console in the future"
+      "$groupId:$artifactId version $versionString has $label issues that will block publishing of your app to Play Console in the future$recommendedVersions"
     }
   }
 
@@ -416,12 +418,20 @@
   }
 
   /** Generate a message for a library that has blocking outdated issues */
-  fun generateBlockingOutdatedMessage(groupId: String, artifactId: String, versionString: String) =
-    "[Prevents app release in Google Play Console] $groupId:$artifactId version $versionString has been reported as outdated by its author and will block publishing of your app to Play Console"
+  fun generateBlockingOutdatedMessage(
+    groupId: String,
+    artifactId: String,
+    versionString: String,
+  ): String {
+    val recommendedVersions = getOutdatedRecommendedVersions(groupId, artifactId, versionString)
+    return "[Prevents app release in Google Play Console] $groupId:$artifactId version $versionString has been reported as outdated by its author and will block publishing of your app to Play Console$recommendedVersions"
+  }
 
   /** Generate a message for a library that has non-blocking outdated issues */
-  fun generateOutdatedMessage(groupId: String, artifactId: String, versionString: String) =
-    "$groupId:$artifactId version $versionString has been reported as outdated by its author"
+  fun generateOutdatedMessage(groupId: String, artifactId: String, versionString: String): String {
+    val recommendedVersions = getOutdatedRecommendedVersions(groupId, artifactId, versionString)
+    return "$groupId:$artifactId version $versionString has been reported as outdated by its author$recommendedVersions"
+  }
 
   /** Generate a message for a library that has blocking issues */
   fun generateBlockingGenericIssueMessage(
@@ -527,4 +537,41 @@
     if (message.isNullOrBlank()) return ""
     return ". Note: $message"
   }
+
+  private fun getOutdatedRecommendedVersions(
+    groupId: String,
+    artifactId: String,
+    versionString: String,
+  ): String {
+    val labels = getLabels(groupId, artifactId, versionString) ?: return ""
+    val outdatedIssue = labels.outdatedIssueInfo ?: return ""
+    return generateRecommendedList(outdatedIssue.recommendedVersionsList)
+  }
+
+  private fun getPolicyRecommendedVersions(
+    groupId: String,
+    artifactId: String,
+    versionString: String,
+  ): String {
+    val labels = getLabels(groupId, artifactId, versionString) ?: return ""
+    val policyIssue = labels.policyIssuesInfo ?: return ""
+    return generateRecommendedList(policyIssue.recommendedVersionsList)
+  }
+
+  private fun generateRecommendedList(listOfVersions: List<LibraryVersionRange?>?): String {
+    val ranges =
+      (listOfVersions ?: return "").filterNotNull().joinToString("\n") { range ->
+        if (range.upperBound.isNullOrBlank()) {
+          "  - ${range.lowerBound} or higher"
+        } else if (range.upperBound != range.lowerBound) {
+          "  - From ${range.lowerBound} to ${range.upperBound}"
+        } else {
+          "  - ${range.lowerBound}"
+        }
+      }
+    if (ranges.isEmpty()) return ""
+    return ".\nThe library author recommends using versions:\n$ranges\n" +
+      "These versions have not been reviewed by Google Play. They could contain vulnerabilities or policy violations. " +
+      "Carefully evaluate any third-party SDKs before integrating them into your app."
+  }
 }
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GooglePlaySdkIndexTest.kt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GooglePlaySdkIndexTest.kt
index 2095407..a33e067 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GooglePlaySdkIndexTest.kt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/GooglePlaySdkIndexTest.kt
@@ -294,6 +294,19 @@
                             .addViolatedSdkPolicies(
                               LibraryVersionLabels.PolicyIssuesInfo.SdkPolicy.SDK_POLICY_MALWARE
                             )
+                            .addRecommendedVersions(
+                              LibraryVersionRange.newBuilder()
+                                .setLowerBound("7.1.9")
+                                .setUpperBound("7.1.9")
+                            )
+                            .addRecommendedVersions(
+                              LibraryVersionRange.newBuilder()
+                                .setLowerBound("7.2.1")
+                                .setUpperBound("7.3.0")
+                            )
+                            .addRecommendedVersions(
+                              LibraryVersionRange.newBuilder().setLowerBound("8.0.0")
+                            )
                         )
                         .setSeverity(LibraryVersionLabels.Severity.BLOCKING_SEVERITY)
                     )
@@ -347,8 +360,8 @@
                   LibraryIdentifier.newBuilder()
                     .setMavenId(
                       LibraryIdentifier.MavenIdentifier.newBuilder()
-                        .setGroupId("no.url")
-                        .setArtifactId("no.url")
+                        .setGroupId("no.url.group")
+                        .setArtifactId("no.url.artifact")
                         .build()
                     )
                 )
@@ -394,6 +407,11 @@
                                 .setLowerBound("1.0.1")
                                 .setUpperBound("1.0.2")
                             )
+                            .addRecommendedVersions(
+                              LibraryVersionRange.newBuilder()
+                                .setLowerBound("1.0.4")
+                                .setUpperBound("1.0.4")
+                            )
                             // An open range
                             .addRecommendedVersions(
                               LibraryVersionRange.newBuilder().setLowerBound("2.0.0")
@@ -593,6 +611,21 @@
   }
 
   @Test
+  fun `multiple policy types issue message with recommended versions`() {
+    verifyPolicyMessages(
+      "7.1.8",
+      listOf("User Data policy", "Malware policy"),
+      recommendedVersions =
+        ".\nThe library author recommends using versions:\n" +
+          "  - 7.1.9\n" +
+          "  - From 7.2.1 to 7.3.0\n" +
+          "  - 8.0.0 or higher\n" +
+          "These versions have not been reviewed by Google Play. They could contain vulnerabilities or policy violations. " +
+          "Carefully evaluate any third-party SDKs before integrating them into your app.",
+    )
+  }
+
+  @Test
   fun `unknown policy type issue message`() {
     verifyPolicyMessages("7.1.10", listOf("Permissions policy", "policy"))
   }
@@ -638,6 +671,20 @@
     assertThat(index.generateCriticalMessage("log4j", "log4j", "1.2.13")).isEqualTo(expectedMessage)
   }
 
+  @Test
+  fun `Outdated issue with recommended versions`() {
+    val expectedMessage =
+      "no.url.group:no.url.artifact version 1.0.0 has been reported as outdated by its author.\n" +
+        "The library author recommends using versions:\n" +
+        "  - From 1.0.1 to 1.0.2\n" +
+        "  - 1.0.4\n" +
+        "  - 2.0.0 or higher\n" +
+        "These versions have not been reviewed by Google Play. They could contain vulnerabilities or policy violations. " +
+        "Carefully evaluate any third-party SDKs before integrating them into your app."
+    assertThat(index.generateOutdatedMessage("no.url.group", "no.url.artifact", "1.0.0"))
+      .isEqualTo(expectedMessage)
+  }
+
   private fun countOutdatedIssues(): Int {
     var result = 0
     for (sdk in proto.sdksList) {
@@ -702,11 +749,15 @@
     return result
   }
 
-  private fun verifyPolicyMessages(version: String, policyTypes: List<String>) {
+  private fun verifyPolicyMessages(
+    version: String,
+    policyTypes: List<String>,
+    recommendedVersions: String = "",
+  ) {
     index.showPolicyIssues = true
     val expectedBlockingMessages =
       policyTypes.map { policyType ->
-        "[Prevents app release in Google Play Console] com.example.ads.third.party:example version $version has $policyType issues that will block publishing of your app to Play Console"
+        "[Prevents app release in Google Play Console] com.example.ads.third.party:example version $version has $policyType issues that will block publishing of your app to Play Console$recommendedVersions"
       }
     assertThat(
         index.generateBlockingPolicyMessages("com.example.ads.third.party", "example", version)
@@ -714,7 +765,7 @@
       .isEqualTo(expectedBlockingMessages)
     val expectedNonBlockingMessages =
       policyTypes.map { policyType ->
-        "com.example.ads.third.party:example version $version has $policyType issues that will block publishing of your app to Play Console in the future"
+        "com.example.ads.third.party:example version $version has $policyType issues that will block publishing of your app to Play Console in the future$recommendedVersions"
       }
     assertThat(index.generatePolicyMessages("com.example.ads.third.party", "example", version))
       .isEqualTo(expectedNonBlockingMessages)