[ApiModel] Generate covariant library methods in database

Bug: b/232891189
Change-Id: I32ef515e16467c5327840a05a777618aabf98d09
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index db640a2..cf867dd 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -21,13 +21,16 @@
 import com.android.tools.r8.apimodel.JavaSourceCodePrinter.ParameterizedType;
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.EntryUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -40,10 +43,12 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -81,7 +86,9 @@
   public void testCanFindAnnotatedMethodsInJar() throws Exception {
     CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
     // These assertions are here to ensure we produce a sane result.
-    assertEquals(51, covariantMethodsInJar.methodReferenceMap.size());
+    assertEquals(9, covariantMethodsInJar.methodReferenceMap.keySet().size());
+    assertEquals(
+        51, covariantMethodsInJar.methodReferenceMap.values().stream().mapToLong(List::size).sum());
   }
 
   @Test
@@ -91,11 +98,9 @@
 
   public static String generateCode() throws Exception {
     CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
-    Map<MethodReference, List<MethodReference>> methodReferenceMap =
-        covariantMethodsInJar.methodReferenceMap;
-    List<Entry<MethodReference, List<MethodReference>>> entries =
-        new ArrayList<>(methodReferenceMap.entrySet());
-    entries.sort(Entry.comparingByKey(MethodReferenceUtils.getMethodReferenceComparator()));
+    List<Entry<ClassReference, List<MethodReferenceWithApiLevel>>> entries =
+        new ArrayList<>(covariantMethodsInJar.methodReferenceMap.entrySet());
+    entries.sort(Entry.comparingByKey(ClassReferenceUtils.getClassReferenceComparator()));
     JavaSourceCodePrinter printer =
         JavaSourceCodePrinter.builder()
             .setHeader(
@@ -119,10 +124,16 @@
                 methodPrinter ->
                     entries.forEach(
                         EntryUtils.accept(
-                            (ignored, covariations) ->
-                                covariations.forEach(
-                                    covariant ->
-                                        registerCovariantMethod(methodPrinter, covariant)))))
+                            (ignored, covariations) -> {
+                              covariations.sort(
+                                  Comparator.comparing(
+                                      MethodReferenceWithApiLevel::getMethodReference,
+                                      MethodReferenceUtils.getMethodReferenceComparator()));
+                              covariations.forEach(
+                                  covariant ->
+                                      registerCovariantMethod(
+                                          methodPrinter, covariant.methodReference));
+                            })))
             .toString();
     Path tempFile = Files.createTempFile("output-", ".java");
     Files.write(tempFile, javaSourceCode.getBytes(StandardCharsets.UTF_8));
@@ -175,15 +186,15 @@
   }
 
   public static class CovariantMethodsInJarResult {
-    private final Map<MethodReference, List<MethodReference>> methodReferenceMap;
+    private final Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap;
 
     private CovariantMethodsInJarResult(
-        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+        Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap) {
       this.methodReferenceMap = methodReferenceMap;
     }
 
     public static CovariantMethodsInJarResult create() throws Exception {
-      Map<MethodReference, List<MethodReference>> methodReferenceMap = new HashMap<>();
+      Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap = new HashMap<>();
       CodeInspector inspector = new CodeInspector(PATH_TO_CORE_JAR);
       DexItemFactory factory = inspector.getFactory();
       for (FoundClassSubject clazz : inspector.allClasses()) {
@@ -196,6 +207,7 @@
                           isCovariantReturnTypeAnnotation(annotation.annotation, factory));
               if (!covariantAnnotations.isEmpty()) {
                 MethodReference methodReference = method.asMethodReference();
+                ClassReference holder = clazz.getOriginalReference();
                 for (DexAnnotation covariantAnnotation : covariantAnnotations) {
                   if (covariantAnnotation.annotation.type
                       == factory.annotationCovariantReturnType) {
@@ -214,17 +226,55 @@
     private static void createCovariantMethodReference(
         MethodReference methodReference,
         DexAnnotation covariantAnnotation,
-        Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+        Map<ClassReference, List<MethodReferenceWithApiLevel>> methodReferenceMap) {
       DexValueType newReturnType =
           covariantAnnotation.annotation.getElement(0).getValue().asDexValueType();
+      DexAnnotationElement element = covariantAnnotation.annotation.getElement(1);
+      assert element.name.toString().equals("presentAfter");
+      AndroidApiLevel apiLevel =
+          AndroidApiLevel.getAndroidApiLevel(element.getValue().asDexValueInt().value);
       methodReferenceMap
-          .computeIfAbsent(methodReference, ignoreKey(ArrayList::new))
+          .computeIfAbsent(methodReference.getHolderClass(), ignoreKey(ArrayList::new))
           .add(
-              Reference.method(
-                  methodReference.getHolderClass(),
-                  methodReference.getMethodName(),
-                  methodReference.getFormalTypes(),
-                  newReturnType.value.asClassReference()));
+              new MethodReferenceWithApiLevel(
+                  Reference.method(
+                      methodReference.getHolderClass(),
+                      methodReference.getMethodName(),
+                      methodReference.getFormalTypes(),
+                      newReturnType.value.asClassReference()),
+                  apiLevel));
+    }
+
+    public void visitCovariantMethodsForHolder(
+        ClassReference reference, Consumer<MethodReferenceWithApiLevel> consumer) {
+      List<MethodReferenceWithApiLevel> methodReferences = methodReferenceMap.get(reference);
+      if (methodReferences != null) {
+        methodReferences.stream()
+            .sorted(
+                Comparator.comparing(
+                    MethodReferenceWithApiLevel::getMethodReference,
+                    MethodReferenceUtils.getMethodReferenceComparator()))
+            .forEach(consumer);
+      }
+    }
+  }
+
+  public static class MethodReferenceWithApiLevel {
+
+    private final MethodReference methodReference;
+    private final AndroidApiLevel apiLevel;
+
+    private MethodReferenceWithApiLevel(MethodReference methodReference, AndroidApiLevel apiLevel) {
+      this.methodReference = methodReference;
+      this.apiLevel = apiLevel;
+    }
+
+    public MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    public AndroidApiLevel getApiLevel() {
+      return apiLevel;
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 708858e..423850e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.androidapi.AndroidApiDataAccess;
+import com.android.tools.r8.androidapi.GenerateCovariantReturnTypeMethodsTest.CovariantMethodsInJarResult;
 import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -81,6 +82,8 @@
         computeAppViewWithClassHierarchy(AndroidApp.builder().addLibraryFile(androidJar).build());
     DexItemFactory factory = appView.dexItemFactory();
 
+    CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+
     for (ParsedApiClass apiClass : apiClasses) {
       Map<DexMethod, AndroidApiLevel> methodsForApiClass = new HashMap<>();
       apiClass.visitMethodReferences(
@@ -88,6 +91,18 @@
             methods.forEach(
                 method -> methodsForApiClass.put(factory.createMethod(method), apiLevel));
           });
+      covariantMethodsInJar.visitCovariantMethodsForHolder(
+          apiClass.getClassReference(),
+          methodReferenceWithApiLevel -> {
+            DexMethod method =
+                factory.createMethod(methodReferenceWithApiLevel.getMethodReference());
+            if (!methodsForApiClass.containsKey(method)) {
+              apiClass.amendCovariantMethod(
+                  methodReferenceWithApiLevel.getMethodReference(),
+                  methodReferenceWithApiLevel.getApiLevel());
+              methodsForApiClass.put(method, methodReferenceWithApiLevel.getApiLevel());
+            }
+          });
       Map<DexField, AndroidApiLevel> fieldsForApiClass = new HashMap<>();
       apiClass.visitFieldReferences(
           (apiLevel, fields) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 53f5fda..7de78a2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -6,6 +6,7 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -110,7 +111,7 @@
   @Test
   public void testDatabaseGenerationUpToDate() throws Exception {
     GenerateDatabaseResourceFilesResult result = generateResourcesFiles();
-    TestBase.filesAreEqual(result.apiLevels, API_DATABASE);
+    assertTrue(TestBase.filesAreEqual(result.apiLevels, API_DATABASE));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 4a3cfa2..8e8f4ee 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -247,6 +247,10 @@
       interfaces.forEach(consumer);
     }
 
+    public void amendCovariantMethod(MethodReference methodReference, AndroidApiLevel apiLevel) {
+      register(methodReference, apiLevel);
+    }
+
     public boolean isInterface() {
       return isInterface;
     }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
index ddba615..524147f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
@@ -5,12 +5,13 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.lang.reflect.Method;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentHashMap.KeySetView;
@@ -45,8 +46,11 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/232891189): Should be api level 28.
-                    assertNull(apiLevel);
+                    assertEquals(
+                        parameters.isCfRuntime()
+                            ? AndroidApiLevel.P
+                            : parameters.getApiLevel().max(AndroidApiLevel.P),
+                        apiLevel);
                   }
                 }))
         .compile();
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index ee68444..3a106aa 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-72bdd118be45bbd99762b5dfea457c90052e0f22
\ No newline at end of file
+da5a2b797563b19461f62db48bf1e45e2f86752f
\ No newline at end of file