Add api reference level to method optimization info

Bug: 188388130
Bug: 188498051
Change-Id: I7abb04ded8b2a78e113f9bb140a356c868cb343d
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 7c26ce2..1cd35e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -56,6 +56,7 @@
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
@@ -1455,10 +1456,7 @@
 
   public synchronized UpdatableMethodOptimizationInfo getMutableOptimizationInfo() {
     checkIfObsolete();
-    if (optimizationInfo == DefaultMethodOptimizationInfo.DEFAULT_INSTANCE) {
-      optimizationInfo = optimizationInfo.mutableCopy();
-    }
-    return (UpdatableMethodOptimizationInfo) optimizationInfo;
+    return optimizationInfo.asUpdatableMethodOptimizationInfo();
   }
 
   public void setOptimizationInfo(UpdatableMethodOptimizationInfo info) {
@@ -1466,6 +1464,11 @@
     optimizationInfo = info;
   }
 
+  public void setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo info) {
+    checkIfObsolete();
+    optimizationInfo = info;
+  }
+
   public synchronized void abandonCallSiteOptimizationInfo() {
     checkIfObsolete();
     callSiteOptimizationInfo = CallSiteOptimizationInfo.abandoned();
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 7d95e87..646c9a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,10 +29,12 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -2498,6 +2500,22 @@
     return createMethod(holder, proto, createString(name));
   }
 
+  public DexMethod createMethod(MethodReference methodReference) {
+    DexString[] formals = new DexString[methodReference.getFormalTypes().size()];
+    ListUtils.forEachWithIndex(
+        methodReference.getFormalTypes(),
+        (formal, index) -> {
+          formals[index] = createString(formal.getDescriptor());
+        });
+    return createMethod(
+        createString(methodReference.getHolderClass().getDescriptor()),
+        createString(methodReference.getMethodName()),
+        methodReference.getReturnType() == null
+            ? voidDescriptor
+            : createString(methodReference.getReturnType().getDescriptor()),
+        formals);
+  }
+
   public DexMethodHandle createMethodHandle(
       MethodHandleType type,
       DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 17758fa..f8aae86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -11,13 +11,16 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
 import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerUseRegistryFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.ListIterator;
+import java.util.Map;
 
 public class ProtoEnqueuerUseRegistry extends DefaultEnqueuerUseRegistry {
 
@@ -28,8 +31,9 @@
   public ProtoEnqueuerUseRegistry(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProgramMethod currentMethod,
-      Enqueuer enqueuer) {
-    super(appView, currentMethod, enqueuer);
+      Enqueuer enqueuer,
+      Map<DexReference, AndroidApiLevel> apiLevelReferenceMap) {
+    super(appView, currentMethod, enqueuer, apiLevelReferenceMap);
     this.references = appView.protoShrinker().references;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 8e66601..ececbe3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo.isApiSafeForInlining;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 
@@ -33,6 +34,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
@@ -121,11 +123,22 @@
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
-    if (singleTargetMethod.getOptimizationInfo().neverInline()) {
+    MethodOptimizationInfo targetOptimizationInfo = singleTargetMethod.getOptimizationInfo();
+    if (targetOptimizationInfo.neverInline()) {
       whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return false;
     }
 
+    // Do not inline if the inlinee is greater than the api caller level.
+    MethodOptimizationInfo callerOptimizationInfo = method.getDefinition().getOptimizationInfo();
+    // TODO(b/188498051): We should not force inline lower api method calls.
+    if (reason != Reason.FORCE
+        && isApiSafeForInlining(callerOptimizationInfo, targetOptimizationInfo, appView.options())
+            .isPossiblyFalse()) {
+      whyAreYouNotInliningReporter.reportInlineeHigherApiCall();
+      return false;
+    }
+
     // We don't inline into constructors when producing class files since this can mess up
     // the stackmap, see b/136250031
     if (method.getDefinition().isInstanceInitializer()
@@ -138,7 +151,7 @@
     if (method.getDefinition() == singleTargetMethod) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
-      assert !singleTargetMethod.getOptimizationInfo().forceInline();
+      assert !targetOptimizationInfo.forceInline();
       whyAreYouNotInliningReporter.reportRecursiveMethod();
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index d49189f..2b7ef1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableSet;
 import java.util.BitSet;
 import java.util.Set;
@@ -38,8 +39,9 @@
   static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
   static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
   static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
+  static AndroidApiLevel UNKNOWN_API_REFERENCE_LEVEL = null;
 
-  private DefaultMethodOptimizationInfo() {}
+  protected DefaultMethodOptimizationInfo() {}
 
   public static DefaultMethodOptimizationInfo getInstance() {
     return DEFAULT_INSTANCE;
@@ -57,7 +59,7 @@
 
   @Override
   public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
-    return null;
+    return mutableCopy();
   }
 
   @Override
@@ -192,6 +194,16 @@
   }
 
   @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    return UNKNOWN_API_REFERENCE_LEVEL;
+  }
+
+  @Override
+  public boolean hasApiReferenceLevel() {
+    return false;
+  }
+
+  @Override
   public UpdatableMethodOptimizationInfo mutableCopy() {
     return new UpdatableMethodOptimizationInfo();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
new file mode 100644
index 0000000..fc61ba6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class DefaultMethodOptimizationWithMinApiInfo extends DefaultMethodOptimizationInfo {
+
+  private static final DefaultMethodOptimizationWithMinApiInfo DEFAULT_MIN_API_INSTANCE =
+      new DefaultMethodOptimizationWithMinApiInfo();
+
+  public static DefaultMethodOptimizationWithMinApiInfo getInstance() {
+    return DEFAULT_MIN_API_INSTANCE;
+  }
+
+  @Override
+  public boolean hasApiReferenceLevel() {
+    return true;
+  }
+
+  @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    return minApi;
+  }
+
+  @Override
+  public UpdatableMethodOptimizationInfo mutableCopy() {
+    UpdatableMethodOptimizationInfo updatableMethodOptimizationInfo = super.mutableCopy();
+    // Use null to specify that the min api is set to minApi.
+    updatableMethodOptimizationInfo.setApiReferenceLevel(null);
+    return updatableMethodOptimizationInfo;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index bdc35f3..c006e6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import static com.android.tools.r8.utils.OptionalBool.UNKNOWN;
+
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -13,6 +15,9 @@
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import java.util.BitSet;
 import java.util.Set;
 
@@ -95,5 +100,20 @@
 
   public abstract boolean returnValueHasBeenPropagated();
 
+  public abstract AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
+
+  public abstract boolean hasApiReferenceLevel();
+
   public abstract UpdatableMethodOptimizationInfo mutableCopy();
+
+  public static OptionalBool isApiSafeForInlining(
+      MethodOptimizationInfo caller, MethodOptimizationInfo inlinee, InternalOptions options) {
+    if (!caller.hasApiReferenceLevel() || !inlinee.hasApiReferenceLevel()) {
+      return UNKNOWN;
+    }
+    return OptionalBool.of(
+        caller
+            .getApiReferenceLevel(options.minApiLevel)
+            .isGreaterThanOrEqualTo(inlinee.getApiReferenceLevel(options.minApiLevel)));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 0d2e43b..85e2576 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -22,8 +22,10 @@
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.BitSet;
+import java.util.Optional;
 import java.util.Set;
 
 public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
@@ -66,6 +68,8 @@
   private SimpleInliningConstraint simpleInliningConstraint =
       NeverSimpleInliningConstraint.getInstance();
 
+  private Optional<AndroidApiLevel> apiReferenceLevel = null;
+
   // To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
   // merged into a flag int field. The various static final FLAG fields indicate which bit is
   // used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
@@ -146,6 +150,7 @@
     nonNullParamOrThrow = template.nonNullParamOrThrow;
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
     classInlinerConstraint = template.classInlinerConstraint;
+    apiReferenceLevel = template.apiReferenceLevel;
   }
 
   public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
@@ -495,6 +500,23 @@
   }
 
   @Override
+  public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+    assert hasApiReferenceLevel();
+    return apiReferenceLevel.orElse(minApi);
+  }
+
+  @SuppressWarnings("OptionalAssignedToNull")
+  @Override
+  public boolean hasApiReferenceLevel() {
+    return apiReferenceLevel != null;
+  }
+
+  public UpdatableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+    this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
+    return this;
+  }
+
+  @Override
   public UpdatableMethodOptimizationInfo mutableCopy() {
     return new UpdatableMethodOptimizationInfo(this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index e398dce..218a8fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -58,6 +58,9 @@
   public void reportInlineeNotSimple() {}
 
   @Override
+  public void reportInlineeHigherApiCall() {}
+
+  @Override
   public void reportInlineeRefersToClassesNotInMainDex() {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 81c6a35..8063674 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -69,6 +69,8 @@
 
   public abstract void reportInlineeNotSimple();
 
+  public abstract void reportInlineeHigherApiCall();
+
   public abstract void reportInlineeRefersToClassesNotInMainDex();
 
   public abstract void reportInliningAcrossFeatureSplit();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 257cc15..f9418b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -120,6 +120,11 @@
   }
 
   @Override
+  public void reportInlineeHigherApiCall() {
+    print("inlinee having a higher api call than caller context.");
+  }
+
+  @Override
   public void reportInlineeRefersToClassesNotInMainDex() {
     print(
         "inlining could increase the main dex size "
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 f8302d7..cb49580 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -13,25 +13,33 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.ListIterator;
+import java.util.Map;
 
 public class DefaultEnqueuerUseRegistry extends UseRegistry {
 
   protected final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProgramMethod context;
   protected final Enqueuer enqueuer;
+  private final Map<DexReference, AndroidApiLevel> apiReferenceMapping;
+  private AndroidApiLevel maxApiReferenceLevel;
 
   public DefaultEnqueuerUseRegistry(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProgramMethod context,
-      Enqueuer enqueuer) {
+      Enqueuer enqueuer,
+      Map<DexReference, AndroidApiLevel> apiReferenceMapping) {
     super(appView.dexItemFactory());
     this.appView = appView;
     this.context = context;
     this.enqueuer = enqueuer;
+    this.apiReferenceMapping = apiReferenceMapping;
+    this.maxApiReferenceLevel = appView.options().minApiLevel;
   }
 
   public ProgramMethod getContext() {
@@ -53,26 +61,31 @@
 
   @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeVirtual(invokedMethod, context);
   }
 
   @Override
   public void registerInvokeDirect(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeDirect(invokedMethod, context);
   }
 
   @Override
   public void registerInvokeStatic(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeStatic(invokedMethod, context);
   }
 
   @Override
   public void registerInvokeInterface(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeInterface(invokedMethod, context);
   }
 
   @Override
   public void registerInvokeSuper(DexMethod invokedMethod) {
+    setMaxApiReferenceLevel(invokedMethod);
     enqueuer.traceInvokeSuper(invokedMethod, context);
   }
 
@@ -158,4 +171,14 @@
     super.registerCallSite(callSite);
     enqueuer.traceCallSite(callSite, context);
   }
+
+  private void setMaxApiReferenceLevel(DexMethod invokedMethod) {
+    this.maxApiReferenceLevel =
+        maxApiReferenceLevel.max(
+            apiReferenceMapping.getOrDefault(invokedMethod, maxApiReferenceLevel));
+  }
+
+  public AndroidApiLevel getMaxApiReferenceLevel() {
+    return maxApiReferenceLevel;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f6d9e6e..6d9bc15 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -102,6 +102,8 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -120,6 +122,7 @@
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -254,6 +257,8 @@
 
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
+  private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap;
+
   /**
    * Tracks the dependency between a method and the super-method it calls, if any. Used to make
    * super methods become live when they become reachable from a live sub-method.
@@ -463,6 +468,17 @@
     } else {
       desugaredLibraryWrapperAnalysis = null;
     }
+    referenceToApiLevelMap = new IdentityHashMap<>();
+    if (options.apiModelingOptions().enableApiCallerIdentification) {
+      options
+          .apiModelingOptions()
+          .methodApiMapping
+          .forEach(
+              (methodReference, apiLevel) -> {
+                referenceToApiLevelMap.put(
+                    options.dexItemFactory().createMethod(methodReference), apiLevel);
+              });
+    }
   }
 
   private AppInfoWithClassHierarchy appInfo() {
@@ -3895,7 +3911,22 @@
   }
 
   void traceCode(ProgramMethod method) {
-    method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+    DefaultEnqueuerUseRegistry registry =
+        useRegistryFactory.create(appView, method, this, referenceToApiLevelMap);
+    method.registerCodeReferences(registry);
+    DexEncodedMethod methodDefinition = method.getDefinition();
+    AndroidApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel();
+    assert maxApiReferenceLevel.isGreaterThanOrEqualTo(options.minApiLevel);
+    // To not have mutable update information for all methods that all has min api level we
+    // swap the default optimization info for one with that marks the api level to be min api.
+    if (methodDefinition.getOptimizationInfo() == DefaultMethodOptimizationInfo.getInstance()
+        && maxApiReferenceLevel == options.minApiLevel) {
+      methodDefinition.setMinApiOptimizationInfo(
+          DefaultMethodOptimizationWithMinApiInfo.getInstance());
+      return;
+    }
+    methodDefinition.setOptimizationInfo(
+        methodDefinition.getMutableOptimizationInfo().setApiReferenceLevel(maxApiReferenceLevel));
   }
 
   private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
index c15f8d7..6bd8473 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
@@ -6,13 +6,16 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Map;
 
 public interface EnqueuerUseRegistryFactory {
 
-  UseRegistry create(
+  DefaultEnqueuerUseRegistry create(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProgramMethod currentMethod,
-      Enqueuer enqueuer);
+      Enqueuer enqueuer,
+      Map<DexReference, AndroidApiLevel> apiLevelReferenceMap);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index de01d36..0263e28 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -348,9 +348,6 @@
   // Contain the contents of the build properties file from the compiler command.
   public DumpOptions dumpOptions;
 
-  // A mapping from methods to the api-level introducing them.
-  public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
-
   // Hidden marker for classes.dex
   private boolean hasMarker = false;
   private Marker marker;
@@ -675,6 +672,7 @@
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
   private final KotlinOptimizationOptions kotlinOptimizationOptions =
       new KotlinOptimizationOptions();
+  private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
   private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
   public final TestingOptions testing = new TestingOptions();
 
@@ -707,6 +705,10 @@
     return kotlinOptimizationOptions;
   }
 
+  public ApiModelTestingOptions apiModelingOptions() {
+    return apiModelTestingOptions;
+  }
+
   public DesugarSpecificOptions desugarSpecificOptions() {
     return desugarSpecificOptions;
   }
@@ -1290,6 +1292,14 @@
     }
   }
 
+  public static class ApiModelTestingOptions {
+
+    // A mapping from methods to the api-level introducing them.
+    public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+
+    public boolean enableApiCallerIdentification = false;
+  }
+
   public static class ProtoShrinkingOptions {
 
     public boolean enableGeneratedExtensionRegistryShrinking = false;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
similarity index 60%
copy from src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java
copy to src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 7130631..4e5c39d 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -21,23 +20,23 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelStaticTest extends TestBase {
+public class ApiModelNoInliningOfHigherApiLevelInterfaceTest extends TestBase {
 
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
   }
 
-  public ApiModelingNoInliningOfHigherApiLevelStaticTest(TestParameters parameters) {
+  public ApiModelNoInliningOfHigherApiLevelInterfaceTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
   public void testR8() throws Exception {
     Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
-    Method apiCaller = ApiCaller.class.getDeclaredMethod("callStaticMethod");
+    Method apiCaller = ApiCaller.class.getDeclaredMethod("callInterfaceMethod", Api.class);
     Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class, A.class, ApiCaller.class)
@@ -48,41 +47,27 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
-        .compile()
-        .inspect(
-            inspector -> {
-              if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                verifyThat(parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                    .accept(inspector);
-              } else {
-                // TODO(b/188388130): Should only inline on minApi >= 22.
-                assertThrows(
-                    AssertionError.class,
-                    () ->
-                        verifyThat(parameters, apiCaller)
-                            .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                            .accept(inspector));
-              }
-            })
-        .addRunClasspathClasses(Api.class)
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(
-            "A::noApiCall", "ApiCaller::callStaticMethod", "Api::apiLevel22");
+        .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::callInterfaceMethod")
+        .inspect(
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1));
   }
 
-  public static class Api {
+  public interface Api {
 
-    public static void apiLevel22() {
-      System.out.println("Api::apiLevel22");
-    }
+    void apiLevel22();
   }
 
   @NoHorizontalClassMerging
   public static class ApiCaller {
-    public static void callStaticMethod() {
-      System.out.println("ApiCaller::callStaticMethod");
-      Api.apiLevel22();
+
+    public static void callInterfaceMethod(Api api) {
+      System.out.println("ApiCaller::callInterfaceMethod");
+      if (api != null) {
+        api.apiLevel22();
+      }
     }
   }
 
@@ -92,7 +77,7 @@
     @NeverInline
     public static void noApiCall() {
       System.out.println("A::noApiCall");
-      ApiCaller.callStaticMethod();
+      ApiCaller.callInterfaceMethod(null);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelIntoLowerDirectTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelIntoLowerDirectTest.java
rename to src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
index 174dcf7..048874d 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelIntoLowerDirectTest.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
@@ -20,16 +20,16 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelIntoLowerDirectTest extends TestBase {
+public class ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest extends TestBase {
 
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ApiModelingNoInliningOfHigherApiLevelIntoLowerDirectTest(TestParameters parameters) {
+  public ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -45,6 +45,7 @@
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForMethod(apiLevel21, AndroidApiLevel.L))
         .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A::apiLevel21", "B::apiLevel22")
         .inspect(verifyThat(parameters, apiLevel22).inlinedInto(apiLevel21));
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java
rename to src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
index 7130631..dfb8628 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelStaticTest.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -21,16 +20,16 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelStaticTest extends TestBase {
+public class ApiModelNoInliningOfHigherApiLevelStaticTest extends TestBase {
 
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ApiModelingNoInliningOfHigherApiLevelStaticTest(TestParameters parameters) {
+  public ApiModelNoInliningOfHigherApiLevelStaticTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -48,23 +47,11 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
         .inspect(
-            inspector -> {
-              if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                verifyThat(parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                    .accept(inspector);
-              } else {
-                // TODO(b/188388130): Should only inline on minApi >= 22.
-                assertThrows(
-                    AssertionError.class,
-                    () ->
-                        verifyThat(parameters, apiCaller)
-                            .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                            .accept(inspector));
-              }
-            })
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
similarity index 74%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelSuperTest.java
rename to src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
index 7f96dbc9..415a163 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelSuperTest.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -6,11 +6,11 @@
 
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -22,7 +22,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelSuperTest extends TestBase {
+public class ApiModelNoInliningOfHigherApiLevelSuperTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -31,7 +31,7 @@
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public ApiModelingNoInliningOfHigherApiLevelSuperTest(TestParameters parameters) {
+  public ApiModelNoInliningOfHigherApiLevelSuperTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -47,30 +47,19 @@
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
         .inspect(
-            inspector -> {
-              if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                verifyThat(parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                    .accept(inspector);
-              } else {
-                // TODO(b/188388130): Should only inline on minApi >= 22.
-                assertThrows(
-                    AssertionError.class,
-                    () ->
-                        verifyThat(parameters, apiCaller)
-                            .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                            .accept(inspector));
-              }
-            })
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::apiLevel22", "Api::apiLevel22");
   }
 
-  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
   public static class Api {
 
     void apiLevel22() {
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
similarity index 70%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelVirtualTest.java
rename to src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
index fdf184e..31af9e3 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -4,11 +4,10 @@
 
 package com.android.tools.r8.apimodeling;
 
+import static com.android.tools.r8.apimodeling.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
 
-import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -22,7 +21,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelVirtualTest extends TestBase {
+public class ApiModelNoInliningOfHigherApiLevelVirtualTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -31,7 +30,7 @@
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public ApiModelingNoInliningOfHigherApiLevelVirtualTest(TestParameters parameters) {
+  public ApiModelNoInliningOfHigherApiLevelVirtualTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -48,25 +47,12 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        .enableNeverClassInliningAnnotations()
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .compile()
         .inspect(
-            inspector -> {
-              if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-                verifyThat(parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                    .accept(inspector);
-              } else {
-                // TODO(b/188388130): Should only inline on minApi >= 22.
-                assertThrows(
-                    AssertionError.class,
-                    () ->
-                        verifyThat(parameters, apiCaller)
-                            .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)
-                            .accept(inspector));
-              }
-            })
+            verifyThat(parameters, apiCaller)
+                .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
         .addRunClasspathClasses(Api.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
@@ -81,9 +67,9 @@
   }
 
   @NoHorizontalClassMerging
-  @NeverClassInline
   public static class ApiCaller {
-    public void callVirtualMethod() {
+
+    public static void callVirtualMethod() {
       System.out.println("ApiCaller::callVirtualMethod");
       new Api().apiLevel22();
     }
@@ -95,7 +81,7 @@
     @NeverInline
     public static void noApiCall() {
       System.out.println("A::noApiCall");
-      new ApiCaller().callVirtualMethod();
+      callVirtualMethod();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelInterfaceTest.java
deleted file mode 100644
index e56d36e..0000000
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingNoInliningOfHigherApiLevelInterfaceTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.apimodeling;
-
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.lang.reflect.Method;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class ApiModelingNoInliningOfHigherApiLevelInterfaceTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
-  }
-
-  public ApiModelingNoInliningOfHigherApiLevelInterfaceTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
-    Method apiCaller = ApiCaller.class.getDeclaredMethod("callInterfaceMethod", Api.class);
-    Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
-    R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(Main.class, A.class, ApiCaller.class)
-            .addLibraryClasses(Api.class)
-            .addDefaultRuntimeLibrary(parameters)
-            .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(Main.class)
-            .enableInliningAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::callInterfaceMethod");
-    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
-      runResult.inspect(
-          verifyThat(parameters, apiCaller)
-              .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1));
-    } else {
-      // TODO(b/188388130): Should only inline on minApi >= 22.
-      assertThrows(
-          AssertionError.class,
-          () ->
-              runResult.inspect(
-                  verifyThat(parameters, apiCaller)
-                      .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1)));
-    }
-  }
-
-  public interface Api {
-
-    void apiLevel22();
-  }
-
-  @NoHorizontalClassMerging
-  public static class ApiCaller {
-
-    public static void callInterfaceMethod(Api api) {
-      System.out.println("ApiCaller::callInterfaceMethod");
-      if (api != null) {
-        api.apiLevel22();
-      }
-    }
-  }
-
-  @NoHorizontalClassMerging
-  public static class A {
-
-    @NeverInline
-    public static void noApiCall() {
-      System.out.println("A::noApiCall");
-      ApiCaller.callInterfaceMethod(null);
-    }
-  }
-
-  public static class Main {
-
-    public static void main(String[] args) {
-      A.noApiCall();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
index d22c4bb..5b1a2f7 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
@@ -26,11 +26,21 @@
     return compilerBuilder -> {
       compilerBuilder.addOptionsModification(
           options -> {
-            options.methodApiMapping.put(Reference.methodFromMethod(method), apiLevel);
+            options
+                .apiModelingOptions()
+                .methodApiMapping
+                .put(Reference.methodFromMethod(method), apiLevel);
           });
     };
   }
 
+  static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+    compilerBuilder.addOptionsModification(
+        options -> {
+          options.apiModelingOptions().enableApiCallerIdentification = true;
+        });
+  }
+
   static ApiModelingMethodVerificationHelper verifyThat(TestParameters parameters, Method method) {
     return new ApiModelingMethodVerificationHelper(parameters, method);
   }
@@ -47,7 +57,7 @@
 
     protected ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel(
         Method method, AndroidApiLevel apiLevel) {
-      return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
+      return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
           ? inlinedInto(method)
           : notInlinedInto(method);
     }