[ApiModel] Amend missing definitions from api-versions to database

Change-Id: I50e7da97177c7ce08a915df79e00b1602026fe15
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
index 03727d7..546b40b 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.androidapi;
 
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -14,6 +15,15 @@
 
   static void visitAdditionalKnownApiReferences(
       DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+    addStringBuilderAndBufferMethods(factory, apiLevelConsumer);
+    addConcurrentKeySetViewMethods(factory, apiLevelConsumer);
+    addNfcMethods(factory, apiLevelConsumer);
+    addWebkitCookieSyncManagerMethods(factory, apiLevelConsumer);
+    addChronoTimeMethods(factory, apiLevelConsumer);
+  }
+
+  private static void addStringBuilderAndBufferMethods(
+      DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
     // StringBuilder and StringBuffer lack api definitions for the exact same methods in
     // api-versions.xml. See b/216587554 for related error.
     for (DexType type : new DexType[] {factory.stringBuilderType, factory.stringBufferType}) {
@@ -100,4 +110,199 @@
           AndroidApiLevel.B);
     }
   }
+
+  private static void addConcurrentKeySetViewMethods(
+      DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+    // KeysetView.getMap was also added in N (24).
+    apiLevelConsumer.accept(
+        factory.createMethod(
+            factory.concurrentHashMapKeySetViewType,
+            factory.createProto(factory.concurrentHashMapType),
+            "getMap"),
+        AndroidApiLevel.N);
+  }
+
+  private static void addNfcMethods(
+      DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+    String[] nfcClasses =
+        new String[] {
+          "Landroid/nfc/tech/Ndef;",
+          "Landroid/nfc/tech/NfcA;",
+          "Landroid/nfc/tech/NfcB;",
+          "Landroid/nfc/tech/NfcBarcode;",
+          "Landroid/nfc/tech/NfcF;",
+          "Landroid/nfc/tech/NdefFormatable;",
+          "Landroid/nfc/tech/IsoDep;",
+          "Landroid/nfc/tech/MifareClassic;",
+          "Landroid/nfc/tech/MifareUltralight;",
+          "Landroid/nfc/tech/NfcV;"
+        };
+    DexType tagType = factory.createType("Landroid/nfc/Tag;");
+    // Seems like all methods are available from api level G_MR1 but we choose K since some of these
+    // classes are introduced at 17.
+    for (String nfcClass : nfcClasses) {
+      DexType nfcClassType = factory.createType(nfcClass);
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              nfcClassType, factory.createProto(factory.booleanType), "isConnected"),
+          AndroidApiLevel.K);
+      apiLevelConsumer.accept(
+          factory.createMethod(nfcClassType, factory.createProto(tagType), "getTag"),
+          AndroidApiLevel.K);
+      apiLevelConsumer.accept(
+          factory.createMethod(nfcClassType, factory.createProto(factory.voidType), "close"),
+          AndroidApiLevel.K);
+      apiLevelConsumer.accept(
+          factory.createMethod(nfcClassType, factory.createProto(factory.voidType), "connect"),
+          AndroidApiLevel.K);
+    }
+  }
+
+  private static void addWebkitCookieSyncManagerMethods(
+      DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+    // All of these are added in android.jar from at least 14.
+    DexType cookieSyncManager = factory.createType("Landroid/webkit/CookieSyncManager;");
+    DexProto voidProto = factory.createProto(factory.voidType);
+    for (String methodName : new String[] {"sync", "resetSync", "startSync", "stopSync", "run"}) {
+      apiLevelConsumer.accept(
+          factory.createMethod(cookieSyncManager, voidProto, methodName), AndroidApiLevel.I);
+    }
+  }
+
+  private static void addChronoTimeMethods(
+      DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
+    DexType valueRangeType = factory.createType("Ljava/time/temporal/ValueRange;");
+    DexType chronoLocalDateType = factory.createType("Ljava/time/chrono/ChronoLocalDate;");
+    DexType temporalType = factory.createType("Ljava/time/temporal/Temporal;");
+    DexType temporalFieldType = factory.createType("Ljava/time/temporal/TemporalField;");
+    DexType temporalUnitType = factory.createType("Ljava/time/temporal/TemporalUnit;");
+    DexType temporalAmountType = factory.createType("Ljava/time/temporal/TemporalAmount;");
+    DexType temporalAdjusterType = factory.createType("Ljava/time/temporal/TemporalAdjuster;");
+
+    // All of these classes was added in 26.
+    String[] timeClasses =
+        new String[] {
+          "Ljava/time/chrono/JapaneseDate;",
+          "Ljava/time/chrono/MinguoDate;",
+          "Ljava/time/chrono/HijrahDate;",
+          "Ljava/time/chrono/ThaiBuddhistDate;"
+        };
+    for (String timeClass : timeClasses) {
+      DexType timeType = factory.createType(timeClass);
+      // int lengthOfMonth()
+      apiLevelConsumer.accept(
+          factory.createMethod(timeType, factory.createProto(factory.intType), "lengthOfMonth"),
+          AndroidApiLevel.O);
+      // int lengthOfYear()
+      apiLevelConsumer.accept(
+          factory.createMethod(timeType, factory.createProto(factory.intType), "lengthOfYear"),
+          AndroidApiLevel.O);
+      // boolean isSupported(java.time.temporal.TemporalField)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType, factory.createProto(factory.booleanType, temporalFieldType), "isSupported"),
+          AndroidApiLevel.O);
+      // java.time.temporal.ValueRange range(java.time.temporal.TemporalField)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType, factory.createProto(valueRangeType, temporalFieldType), "range"),
+          AndroidApiLevel.O);
+      // long getLong(java.time.temporal.TemporalField)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType, factory.createProto(factory.longType, temporalFieldType), "getLong"),
+          AndroidApiLevel.O);
+      // java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType,
+              factory.createProto(
+                  factory.createType("Ljava/time/chrono/ChronoLocalDateTime;"),
+                  factory.createType("Ljava/time/LocalTime;")),
+              "atTime"),
+          AndroidApiLevel.O);
+      // java.time.chrono.ChronoPeriod
+      // java.time.chrono.JapaneseDate.until(java.time.chrono.ChronoLocalDate)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType,
+              factory.createProto(
+                  factory.createType("Ljava/time/chrono/ChronoPeriod;"), chronoLocalDateType),
+              "until"),
+          AndroidApiLevel.O);
+      // long toEpochDay()
+      apiLevelConsumer.accept(
+          factory.createMethod(timeType, factory.createProto(factory.longType), "toEpochDay"),
+          AndroidApiLevel.O);
+      // long until(java.time.temporal.Temporal, java.time.temporal.TemporalUnit)
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType,
+              factory.createProto(factory.longType, temporalType, temporalUnitType),
+              "until"),
+          AndroidApiLevel.O);
+
+      // java.time.chrono.Era getEra()
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType,
+              factory.createProto(factory.createType("Ljava/time/chrono/Era;")),
+              "getEra"),
+          AndroidApiLevel.O);
+      // java.time.chrono.Chronology getChronology()
+      apiLevelConsumer.accept(
+          factory.createMethod(
+              timeType,
+              factory.createProto(factory.createType("Ljava/time/chrono/Chronology;")),
+              "getChronology"),
+          AndroidApiLevel.O);
+      DexType[] returnTypesForModificationMethods =
+          new DexType[] {chronoLocalDateType, temporalType};
+      for (DexType returnType : returnTypesForModificationMethods) {
+        // [returnType] minus(long, java.time.temporal.TemporalUnit)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType,
+                factory.createProto(returnType, factory.longType, temporalUnitType),
+                "minus"),
+            AndroidApiLevel.O);
+        // [returnType] minus(java.time.temporal.TemporalAmount)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType, factory.createProto(returnType, temporalAmountType), "minus"),
+            AndroidApiLevel.O);
+        // [returnType] plus(long, java.time.temporal.TemporalUnit)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType,
+                factory.createProto(returnType, factory.longType, temporalUnitType),
+                "plus"),
+            AndroidApiLevel.O);
+        // [returnType] plus(java.time.temporal.TemporalAmount)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType, factory.createProto(returnType, temporalAmountType), "plus"),
+            AndroidApiLevel.O);
+        // [returnType] with(java.time.temporal.TemporalField, long)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType,
+                factory.createProto(returnType, temporalFieldType, factory.longType),
+                "with"),
+            AndroidApiLevel.O);
+        // [returnType] with(java.time.temporal.TemporalAdjuster)
+        apiLevelConsumer.accept(
+            factory.createMethod(
+                timeType, factory.createProto(returnType, temporalAdjusterType), "with"),
+            AndroidApiLevel.O);
+      }
+    }
+    // boolean java.time.chrono.HijrahDate.isLeapYear()
+    apiLevelConsumer.accept(
+        factory.createMethod(
+            factory.createType("Ljava/time/chrono/HijrahDate;"),
+            factory.createProto(factory.booleanType),
+            "isLeapYear"),
+        AndroidApiLevel.O);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e0e16a6..9cdb7f2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -305,6 +305,10 @@
   public final DexString streamDescriptor = createString("Ljava/util/stream/Stream;");
   public final DexString arraysDescriptor = createString("Ljava/util/Arrays;");
   public final DexString threadLocalDescriptor = createString("Ljava/lang/ThreadLocal;");
+  public final DexString concurrentHashMapDescriptor =
+      createString("Ljava/util/concurrent/ConcurrentHashMap;");
+  public final DexString concurrentHaspMapKeySetViewDescriptor =
+      createString("Ljava/util/concurrent/ConcurrentHashMap$KeySetView;");
 
   public final DexString throwableDescriptor = createString(throwableDescriptorString);
   public final DexString illegalAccessErrorDescriptor =
@@ -478,6 +482,10 @@
   public final DexType optionalLongType = createStaticallyKnownType(optionalLongDescriptor);
   public final DexType streamType = createStaticallyKnownType(streamDescriptor);
   public final DexType threadLocalType = createStaticallyKnownType(threadLocalDescriptor);
+  public final DexType concurrentHashMapType =
+      createStaticallyKnownType(concurrentHashMapDescriptor);
+  public final DexType concurrentHashMapKeySetViewType =
+      createStaticallyKnownType(concurrentHaspMapKeySetViewDescriptor);
 
   public final DexType bufferType = createStaticallyKnownType(bufferDescriptor);
   public final DexType byteBufferType = createStaticallyKnownType(byteBufferDescriptor);
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 b8d3f1f..4bdba09 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -87,10 +87,9 @@
     for (ParsedApiClass apiClass : apiClasses) {
       Map<DexMethod, AndroidApiLevel> methodsForApiClass = new HashMap<>();
       apiClass.visitMethodReferences(
-          (apiLevel, methods) -> {
-            methods.forEach(
-                method -> methodsForApiClass.put(factory.createMethod(method), apiLevel));
-          });
+          (apiLevel, methods) ->
+              methods.forEach(
+                  method -> methodsForApiClass.put(factory.createMethod(method), apiLevel)));
       covariantMethodsInJar.visitCovariantMethodsForHolder(
           apiClass.getClassReference(),
           methodReferenceWithApiLevel -> {
@@ -105,9 +104,8 @@
           });
       Map<DexField, AndroidApiLevel> fieldsForApiClass = new HashMap<>();
       apiClass.visitFieldReferences(
-          (apiLevel, fields) -> {
-            fields.forEach(field -> fieldsForApiClass.put(factory.createField(field), apiLevel));
-          });
+          (apiLevel, fields) ->
+              fields.forEach(field -> fieldsForApiClass.put(factory.createField(field), apiLevel)));
       methodMap.put(apiClass.getClassReference(), methodsForApiClass);
       fieldMap.put(apiClass.getClassReference(), fieldsForApiClass);
       lookupMap.put(apiClass.getClassReference(), apiClass);
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 285d96a..9ce5fd6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -13,21 +13,29 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute.DefaultAndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
 import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
@@ -114,6 +122,53 @@
   }
 
   @Test
+  public void testAmendedClassesToApiDatabase() throws Exception {
+    Path androidJar = ToolHelper.getAndroidJar(API_LEVEL);
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierarchy(AndroidApp.builder().addLibraryFile(androidJar).build());
+    AndroidApiLevelCompute androidApiLevelCompute = DefaultAndroidApiLevelCompute.create(appView);
+    assertTrue(androidApiLevelCompute.isEnabled());
+    ensureAllPublicMethodsAreMapped(appView, androidApiLevelCompute);
+  }
+
+  private static void ensureAllPublicMethodsAreMapped(
+      AppView<AppInfoWithClassHierarchy> appView, AndroidApiLevelCompute apiLevelCompute) {
+    Set<String> notModeledTypes = new HashSet<>();
+    notModeledTypes.add("androidx.annotation.RecentlyNullable");
+    notModeledTypes.add("androidx.annotation.RecentlyNonNull");
+    notModeledTypes.add("android.annotation.Nullable");
+    notModeledTypes.add("android.annotation.NonNull");
+    DexItemFactory factory = appView.dexItemFactory();
+    for (DexLibraryClass clazz : appView.app().asDirect().libraryClasses()) {
+      if (notModeledTypes.contains(clazz.getClassReference().getTypeName())) {
+        continue;
+      }
+      assertTrue(
+          apiLevelCompute
+              .computeApiLevelForLibraryReference(clazz.getReference())
+              .isKnownApiLevel());
+      clazz.forEachClassField(
+          field -> {
+            if (field.getAccessFlags().isPublic() && !field.toSourceString().contains("this$0")) {
+              assertTrue(
+                  apiLevelCompute
+                      .computeApiLevelForLibraryReference(field.getReference())
+                      .isKnownApiLevel());
+            }
+          });
+      clazz.forEachClassMethod(
+          method -> {
+            if (method.getAccessFlags().isPublic()) {
+              assertTrue(
+                  apiLevelCompute
+                      .computeApiLevelForLibraryReference(method.getReference())
+                      .isKnownApiLevel());
+            }
+          });
+    }
+  }
+
+  @Test
   public void testCanLookUpAllParsedApiClassesAndMembers() throws Exception {
     List<ParsedApiClass> parsedApiClasses =
         AndroidApiVersionsXmlParser.getParsedApiClasses(