[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(