[ApiModel] Allow higher api type references without inlining

Bug: 207601948
Change-Id: I557feef557b15f77fb043254393f41642e3fb8c6
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 900e79c..25429fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -822,21 +822,4 @@
     return checksumSupplier;
   }
 
-  public ComputedApiLevel getApiReferenceLevel(
-      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
-    // The api level of a class is the max level of it's members, super class and interfaces.
-    return getMembersApiReferenceLevel(
-        apiLevelCompute.computeApiLevelForDefinition(
-            allImmediateSupertypes(), apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
-  }
-
-  public ComputedApiLevel getMembersApiReferenceLevel(ComputedApiLevel memberLevel) {
-    for (DexEncodedMember<?, ?> member : members()) {
-      memberLevel = memberLevel.max(member.getApiLevel());
-      if (memberLevel.isUnknownApiLevel()) {
-        return memberLevel;
-      }
-    }
-    return memberLevel;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
index 1f953f4..6a083f0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDifferentApiReferenceLevel.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.graph.AppView;
@@ -37,6 +39,6 @@
   @Override
   public ComputedApiLevel getMergeKey(DexProgramClass clazz) {
     assert enableApiCallerIdentification;
-    return clazz.getApiReferenceLevel(appView, apiLevelCompute);
+    return getApiReferenceLevelForMerging(appView, apiLevelCompute, clazz);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 227cbf7..a5b539b 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -220,14 +220,6 @@
   }
 
   private void setMaxApiReferenceLevel(DexReference reference) {
-    if (reference.isDexMember()) {
-      maxApiReferenceLevel =
-          maxApiReferenceLevel.max(
-              apiLevelCompute.computeApiLevelForDefinition(
-                  reference.asDexMember(),
-                  appView.dexItemFactory(),
-                  apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
-    }
     maxApiReferenceLevel =
         maxApiReferenceLevel.max(
             apiLevelCompute.computeApiLevelForLibraryReference(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9396f50..9822a54 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -531,8 +532,10 @@
     // Only merge if api reference level of source class is equal to target class. The check is
     // somewhat expensive.
     if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
-      ComputedApiLevel sourceApiLevel = sourceClass.getApiReferenceLevel(appView, apiLevelCompute);
-      ComputedApiLevel targetApiLevel = targetClass.getApiReferenceLevel(appView, apiLevelCompute);
+      ComputedApiLevel sourceApiLevel =
+          getApiReferenceLevelForMerging(appView, apiLevelCompute, sourceClass);
+      ComputedApiLevel targetApiLevel =
+          getApiReferenceLevelForMerging(appView, apiLevelCompute, targetClass);
       if (!sourceApiLevel.equals(targetApiLevel)) {
         if (Log.ENABLED) {
           AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 73cf09f..089ccce 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -6,6 +6,9 @@
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 
@@ -19,12 +22,38 @@
     if (caller.getHolderType() == inlinee.getHolderType()) {
       return true;
     }
+    // For inlining we only measure if the code has invokes into the library.
     return caller
         .getDefinition()
-        .getApiLevel()
+        .getApiLevelForCode()
         .isGreaterThanOrEqualTo(inlinee.getDefinition().getApiLevelForCode());
   }
 
+  public static ComputedApiLevel getApiReferenceLevelForMerging(
+      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
+    // The api level of a class is the max level of it's members, super class and interfaces.
+    return getMembersApiReferenceLevelForMerging(
+        clazz,
+        apiLevelCompute.computeApiLevelForDefinition(
+            clazz.allImmediateSupertypes(), apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+  }
+
+  private static ComputedApiLevel getMembersApiReferenceLevelForMerging(
+      DexProgramClass clazz, ComputedApiLevel memberLevel) {
+    // Based on b/138781768#comment57 there is almost no penalty for having an unknown reference
+    // as long as we are not invoking or accessing a field on it. Therefore we can disregard static
+    // types of fields and only consider method code api levels.
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.hasCode()) {
+        memberLevel = memberLevel.max(method.getApiLevelForCode());
+      }
+      if (memberLevel.isUnknownApiLevel()) {
+        return memberLevel;
+      }
+    }
+    return memberLevel;
+  }
+
   public static boolean isApiSafeForMemberRebinding(
       LibraryMethod method,
       AndroidApiLevelCompute androidApiLevelCompute,
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index 7c4226c..c41c5e2 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -44,14 +44,7 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              if (parameters.isDexRuntime()
-                  && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                inspector.assertClassesMerged(A.class, B.class);
-              } else {
-                inspector.assertNoClassesMerged();
-              }
-            })
+            inspector -> inspector.assertClassesMerged(A.class, B.class))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::disableCheckAllApiReferencesAreNotUnknown)
         .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index 149c93e..302cca5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -53,9 +53,9 @@
                   if (Reference.methodFromMethod(readApiField).equals(method)
                       || Reference.methodFromMethod(setApiField).equals(method)) {
                     if (parameters.isCfRuntime()) {
-                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                      assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
-                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                      assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
                 }))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index d3d9572..c4308ed 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -53,9 +53,9 @@
                   if (Reference.methodFromMethod(readApi).equals(method)
                       || Reference.methodFromMethod(setApi).equals(method)) {
                     if (parameters.isCfRuntime()) {
-                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                      assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
-                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                      assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
                 }))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
index 01e9dc7..ef4737d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
@@ -51,8 +51,9 @@
         .enableInliningAnnotations()
         .addAndroidBuildVersion()
         .compile()
-        // TODO(b/207601948): We should only inline from api level M.
-        .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+        .inspect(
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.M))
         .addRunClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLinesIf(