Merge commit '7c1e8bbef02070ca0ce6b9afd70b02d157fcf201' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 0744c8f..fb03dbb 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -83,6 +83,27 @@
       }
     },
     {
+      "api_level_below_or_equal": 30,
+      "rewrite_prefix": {
+        "j$.time.": "java.time.",
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
       "api_level_below_or_equal": 29,
       "rewrite_prefix": {
         "java.util.concurrent.Flow": "j$.util.concurrent.Flow"
@@ -91,10 +112,7 @@
     {
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
-        "j$.time.": "java.time.",
-        "java.time.": "j$.time.",
         "java.io.DesugarFile" : "j$.io.DesugarFile",
-        "java.util.Desugar": "j$.util.Desugar",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "jdk.internal.": "j$.jdk.internal.",
         "java.nio.Desugar": "j$.nio.Desugar",
@@ -117,18 +135,7 @@
         "java.lang.DesugarMath": "java.lang.Math"
       },
       "retarget_lib_member": {
-        "java.io.File#toPath": "java.io.DesugarFile",
-        "java.util.Date#toInstant": "java.util.DesugarDate",
-        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
-      },
-      "custom_conversion": {
-        "java.time.ZonedDateTime": "java.time.TimeConversions",
-        "java.time.LocalDate": "java.time.TimeConversions",
-        "java.time.Duration": "java.time.TimeConversions",
-        "java.time.ZoneId": "java.time.TimeConversions",
-        "java.time.MonthDay": "java.time.TimeConversions",
-        "java.time.Instant": "java.time.TimeConversions"
+        "java.io.File#toPath": "java.io.DesugarFile"
       }
     },
     {
@@ -211,7 +218,7 @@
       }
     },
     {
-      "api_level_below_or_equal": 25,
+      "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
index 863111d..fac2ec0 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -83,18 +83,36 @@
       }
     },
     {
+      "api_level_below_or_equal": 30,
+      "rewrite_prefix": {
+        "j$.time.": "java.time.",
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
       "api_level_below_or_equal": 29,
       "rewrite_prefix": {
         "java.util.concurrent.Flow": "j$.util.concurrent.Flow"
       }
     },
     {
-      "api_level_below_or_equal": 25,
+      "api_level_below_or_equal": 30,
       "rewrite_prefix": {
-        "j$.time.": "java.time.",
-        "java.time.": "j$.time.",
         "java.io.DesugarFile" : "j$.io.DesugarFile",
-        "java.util.Desugar": "j$.util.Desugar",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "jdk.internal.": "j$.jdk.internal.",
         "java.nio.Desugar": "j$.nio.Desugar",
@@ -117,18 +135,7 @@
         "java.lang.DesugarMath": "java.lang.Math"
       },
       "retarget_lib_member": {
-        "java.io.File#toPath": "java.io.DesugarFile",
-        "java.util.Date#toInstant": "java.util.DesugarDate",
-        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
-      },
-      "custom_conversion": {
-        "java.time.ZonedDateTime": "java.time.TimeConversions",
-        "java.time.LocalDate": "java.time.TimeConversions",
-        "java.time.Duration": "java.time.TimeConversions",
-        "java.time.ZoneId": "java.time.TimeConversions",
-        "java.time.MonthDay": "java.time.TimeConversions",
-        "java.time.Instant": "java.time.TimeConversions"
+        "java.io.File#toPath": "java.io.DesugarFile"
       }
     },
     {
@@ -208,7 +215,14 @@
   ],
   "program_flags": [
     {
-      "api_level_below_or_equal": 25,
+      "api_level_below_or_equal": 10000,
+      "rewrite_prefix": {
+        "java.net.URLDecoder": "j$.net.URLDecoder",
+        "java.net.URLEncoder": "j$.net.URLEncoder"
+      }
+    },
+    {
+      "api_level_below_or_equal": 30,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
@@ -293,6 +307,15 @@
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
+    },
+    {
+      "api_level_below_or_equal": 18,
+      "rewrite_prefix": {
+        "java.nio.charset.StandardCharsets": "j$.nio.charset.StandardCharsets"
+      },
+      "retarget_lib_member": {
+        "java.lang.Character#isBmpCodePoint": "j$.lang.DesugarCharacter"
+      }
     }
   ],
   "shrinker_config": [
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 72c0cbb..722ae3e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -104,7 +104,6 @@
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.CollectionUtils;
@@ -880,10 +879,10 @@
     for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
       clazz.forEachProgramMember(
           member -> {
-            assert member.getDefinition().getApiLevel() != AndroidApiLevel.NOT_SET
+            assert !member.getDefinition().getApiLevel().isNotSetApiLevel()
                 : "Every member should have been analyzed";
             assert appView.options().apiModelingOptions().enableApiCallerIdentification
-                    || member.getDefinition().getApiLevel() == AndroidApiLevel.UNKNOWN
+                    || member.getDefinition().getApiLevel().isUnknownApiLevel()
                 : "Every member should have level UNKNOWN";
           });
     }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 34a2d65..48d4d4f 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -4,67 +4,108 @@
 
 package com.android.tools.r8.androidapi;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
-
+import com.android.tools.r8.androidapi.ComputedApiLevel.KnownApiLevel;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
 
-public interface AndroidApiLevelCompute {
+public abstract class AndroidApiLevelCompute {
 
-  AndroidApiLevel computeApiLevelForLibraryReference(DexReference reference);
+  private final KnownApiLevel[] knownApiLevelCache;
 
-  AndroidApiLevel computeApiLevelForDefinition(Iterable<DexType> types);
-
-  default AndroidApiLevel computeApiLevelForDefinition(
-      DexMember<?, ?> reference, DexItemFactory factory) {
-    return computeApiLevelForDefinition(reference.getReferencedBaseTypes(factory));
+  public AndroidApiLevelCompute() {
+    knownApiLevelCache = new KnownApiLevel[AndroidApiLevel.LATEST.getLevel() + 1];
+    for (AndroidApiLevel value : AndroidApiLevel.values()) {
+      if (value != AndroidApiLevel.ANDROID_PLATFORM) {
+        knownApiLevelCache[value.getLevel()] = new KnownApiLevel(value);
+      }
+    }
   }
 
-  static AndroidApiLevelCompute create(AppView<?> appView) {
+  public KnownApiLevel of(AndroidApiLevel apiLevel) {
+    if (apiLevel == AndroidApiLevel.ANDROID_PLATFORM) {
+      return ComputedApiLevel.platform();
+    }
+    return knownApiLevelCache[apiLevel.getLevel()];
+  }
+
+  public abstract ComputedApiLevel computeApiLevelForLibraryReference(
+      DexReference reference, ComputedApiLevel unknownValue);
+
+  public abstract ComputedApiLevel computeApiLevelForDefinition(
+      Iterable<DexType> types, ComputedApiLevel unknownValue);
+
+  public ComputedApiLevel computeApiLevelForDefinition(
+      DexMember<?, ?> reference, DexItemFactory factory, ComputedApiLevel unknownValue) {
+    return computeApiLevelForDefinition(reference.getReferencedBaseTypes(factory), unknownValue);
+  }
+
+  public static AndroidApiLevelCompute create(AppView<?> appView) {
     return appView.options().apiModelingOptions().enableApiCallerIdentification
         ? new DefaultAndroidApiLevelCompute(appView)
         : new NoAndroidApiLevelCompute();
   }
 
-  class NoAndroidApiLevelCompute implements AndroidApiLevelCompute {
-
-    @Override
-    public AndroidApiLevel computeApiLevelForDefinition(Iterable<DexType> types) {
-      return UNKNOWN;
-    }
-
-    @Override
-    public AndroidApiLevel computeApiLevelForLibraryReference(DexReference reference) {
-      return UNKNOWN;
+  public static ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) {
+    if (options.apiModelingOptions().enableApiCallerIdentification) {
+      return options.getMinApiLevel() == AndroidApiLevel.ANDROID_PLATFORM
+          ? ComputedApiLevel.platform()
+          : new KnownApiLevel(options.getMinApiLevel());
+    } else {
+      return ComputedApiLevel.unknown();
     }
   }
 
-  class DefaultAndroidApiLevelCompute implements AndroidApiLevelCompute {
+  public ComputedApiLevel getPlatformApiLevelOrUnknown(AppView<?> appView) {
+    if (appView.options().getMinApiLevel() == AndroidApiLevel.ANDROID_PLATFORM) {
+      return ComputedApiLevel.platform();
+    }
+    return ComputedApiLevel.unknown();
+  }
 
-    private final AndroidApiReferenceLevelCache cache;
-    private final AndroidApiLevel minApiLevel;
+  public static class NoAndroidApiLevelCompute extends AndroidApiLevelCompute {
 
-    public DefaultAndroidApiLevelCompute(AppView<?> appView) {
-      this.cache = AndroidApiReferenceLevelCache.create(appView);
-      this.minApiLevel = appView.options().getMinApiLevel();
+    @Override
+    public ComputedApiLevel computeApiLevelForDefinition(
+        Iterable<DexType> types, ComputedApiLevel unknownValue) {
+      return unknownValue;
     }
 
     @Override
-    public AndroidApiLevel computeApiLevelForDefinition(Iterable<DexType> types) {
-      AndroidApiLevel computedLevel = minApiLevel;
+    public ComputedApiLevel computeApiLevelForLibraryReference(
+        DexReference reference, ComputedApiLevel unknownValue) {
+      return unknownValue;
+    }
+  }
+
+  public static class DefaultAndroidApiLevelCompute extends AndroidApiLevelCompute {
+
+    private final AndroidApiReferenceLevelCache cache;
+    private final ComputedApiLevel minApiLevel;
+
+    public DefaultAndroidApiLevelCompute(AppView<?> appView) {
+      this.cache = AndroidApiReferenceLevelCache.create(appView, this);
+      this.minApiLevel = of(appView.options().getMinApiLevel());
+    }
+
+    @Override
+    public ComputedApiLevel computeApiLevelForDefinition(
+        Iterable<DexType> types, ComputedApiLevel unknownValue) {
+      ComputedApiLevel computedLevel = minApiLevel;
       for (DexType type : types) {
-        computedLevel = cache.lookupMax(type, computedLevel);
+        computedLevel = cache.lookupMax(type, computedLevel, unknownValue);
       }
       return computedLevel;
     }
 
     @Override
-    public AndroidApiLevel computeApiLevelForLibraryReference(DexReference reference) {
-      return cache.lookup(reference);
+    public ComputedApiLevel computeApiLevelForLibraryReference(
+        DexReference reference, ComputedApiLevel unknownValue) {
+      return cache.lookup(reference, unknownValue);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
index 551085a..bedc254 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelHashingDatabaseImpl.java
@@ -4,12 +4,10 @@
 
 package com.android.tools.r8.androidapi;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
+import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -41,25 +39,23 @@
       new Int2ReferenceOpenHashMap<AndroidApiLevel>();
   private final Map<String, AndroidApiLevel> ambiguousHashesWithApiLevel = new HashMap<>();
   private final Map<DexReference, AndroidApiLevel> ambiguousCache = new IdentityHashMap<>();
-  private final DexItemFactory factory;
 
   public AndroidApiLevelHashingDatabaseImpl(
-      DexItemFactory factory, List<AndroidApiForHashingClass> predefinedApiTypeLookup) {
-    this.factory = factory;
+      List<AndroidApiForHashingClass> predefinedApiTypeLookup) {
     loadData();
     predefinedApiTypeLookup.forEach(
         apiClass -> {
           DexType type = apiClass.getType();
-          lookupNonAmbiguousCache.put(type.hashCode(), NOT_SET);
+          lookupNonAmbiguousCache.put(type.hashCode(), null);
           ambiguousCache.put(type, apiClass.getApiLevel());
           apiClass.visitMethodsWithApiLevels(
               (method, apiLevel) -> {
-                lookupNonAmbiguousCache.put(method.hashCode(), NOT_SET);
+                lookupNonAmbiguousCache.put(method.hashCode(), null);
                 ambiguousCache.put(method, apiLevel);
               });
           apiClass.visitFieldsWithApiLevels(
               (field, apiLevel) -> {
-                lookupNonAmbiguousCache.put(field.hashCode(), NOT_SET);
+                lookupNonAmbiguousCache.put(field.hashCode(), null);
                 ambiguousCache.put(field, apiLevel);
               });
         });
@@ -96,7 +92,7 @@
     for (int i = 0; i < hashIndices.length; i++) {
       byte apiLevel = apiLevels[i];
       lookupNonAmbiguousCache.put(
-          hashIndices[i], apiLevel == -1 ? NOT_SET : AndroidApiLevel.getAndroidApiLevel(apiLevel));
+          hashIndices[i], apiLevel == -1 ? null : AndroidApiLevel.getAndroidApiLevel(apiLevel));
     }
     ambiguous.forEach(this::parseAmbiguous);
   }
@@ -130,9 +126,12 @@
   }
 
   private AndroidApiLevel lookupApiLevel(DexReference reference) {
-    AndroidApiLevel result = lookupNonAmbiguousCache.getOrDefault(reference.hashCode(), UNKNOWN);
-    if (result != NOT_SET) {
-      return result;
+    // We use Android platform to track if an element is unknown since no occurrences of that api
+    // level exists in the database.
+    AndroidApiLevel result =
+        lookupNonAmbiguousCache.getOrDefault(reference.hashCode(), ANDROID_PLATFORM);
+    if (result != null) {
+      return result == ANDROID_PLATFORM ? null : result;
     }
     return ambiguousCache.computeIfAbsent(
         reference,
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 2eed210..4e17f60 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
@@ -16,85 +17,89 @@
 public class AndroidApiReferenceLevelCache {
 
   private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+  private final AndroidApiLevelCompute apiLevelCompute;
   private final AndroidApiLevelDatabase androidApiLevelDatabase;
   private final AppView<?> appView;
-
-  private AndroidApiReferenceLevelCache(AppView<?> appView) {
-    this(appView, ImmutableList.of());
-  }
+  private final DexItemFactory factory;
 
   private AndroidApiReferenceLevelCache(
-      AppView<?> appView, List<AndroidApiForHashingClass> predefinedApiTypeLookupForHashing) {
+      AppView<?> appView,
+      AndroidApiLevelCompute apiLevelCompute,
+      List<AndroidApiForHashingClass> predefinedApiTypeLookupForHashing) {
     this.appView = appView;
+    this.apiLevelCompute = apiLevelCompute;
+    factory = appView.dexItemFactory();
     androidApiLevelDatabase =
-        new AndroidApiLevelHashingDatabaseImpl(
-            appView.dexItemFactory(), predefinedApiTypeLookupForHashing);
+        new AndroidApiLevelHashingDatabaseImpl(predefinedApiTypeLookupForHashing);
     desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
   }
 
-  public static AndroidApiReferenceLevelCache create(AppView<?> appView) {
-    if (!appView.options().apiModelingOptions().enableApiCallerIdentification) {
-      // If enableApiCallerIdentification is not enabled then override lookup to always return
-      // AndroidApiLevel.B.
-      return new AndroidApiReferenceLevelCache(appView) {
-        @Override
-        public AndroidApiLevel lookup(DexReference reference) {
-          return AndroidApiLevel.B;
-        }
-      };
-    }
+  public static AndroidApiReferenceLevelCache create(
+      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+    assert appView.options().apiModelingOptions().enableApiCallerIdentification;
     ImmutableList.Builder<AndroidApiForHashingClass> builder = ImmutableList.builder();
     appView
         .options()
         .apiModelingOptions()
         .visitMockedApiLevelsForReferences(appView.dexItemFactory(), builder::add);
-    return new AndroidApiReferenceLevelCache(appView, builder.build());
+    return new AndroidApiReferenceLevelCache(appView, apiLevelCompute, builder.build());
   }
 
-  public AndroidApiLevel lookupMax(DexReference reference, AndroidApiLevel minApiLevel) {
-    return lookup(reference).max(minApiLevel);
+  public ComputedApiLevel lookupMax(
+      DexReference reference, ComputedApiLevel minApiLevel, ComputedApiLevel unknownValue) {
+    assert !minApiLevel.isNotSetApiLevel();
+    return lookup(reference, unknownValue).max(minApiLevel);
   }
 
-  public AndroidApiLevel lookup(DexReference reference) {
+  public ComputedApiLevel lookup(DexReference reference, ComputedApiLevel unknownValue) {
     DexType contextType = reference.getContextType();
     if (contextType.isArrayType()) {
-      if (reference.isDexMethod()
-          && reference.asDexMethod().match(appView.dexItemFactory().objectMembers.clone)) {
-        return appView.options().getMinApiLevel();
+      if (reference.isDexMethod() && reference.asDexMethod().match(factory.objectMembers.clone)) {
+        return appView.computedMinApiLevel();
       }
-      return lookup(contextType.toBaseType(appView.dexItemFactory()));
+      return lookup(contextType.toBaseType(factory), unknownValue);
     }
     if (contextType.isPrimitiveType() || contextType.isVoidType()) {
-      return appView.options().getMinApiLevel();
+      return appView.computedMinApiLevel();
     }
     DexClass clazz = appView.definitionFor(contextType);
     if (clazz == null) {
-      return AndroidApiLevel.UNKNOWN;
+      return unknownValue;
     }
     if (!clazz.isLibraryClass()) {
-      return appView.options().getMinApiLevel();
+      return appView.computedMinApiLevel();
     }
-    if (isReferenceToJavaLangObject(reference)) {
-      return appView.options().getMinApiLevel();
+    if (reference.getContextType() == factory.objectType) {
+      return appView.computedMinApiLevel();
     }
     if (desugaredLibraryConfiguration.isSupported(reference, appView)) {
       // If we end up desugaring the reference, the library classes is bridged by j$ which is part
       // of the program.
-      return appView.options().getMinApiLevel();
+      return appView.computedMinApiLevel();
     }
-    return reference
-        .apply(
+    if (reference.isDexMethod()
+        && !reference.asDexMethod().isInstanceInitializer(factory)
+        && factory.objectMembers.isObjectMember(reference.asDexMethod())) {
+      // If we can lookup the method it was introduced/overwritten later. Take for example
+      // a default toString that was not available before some api level. If unknown we default
+      // back to the static holder.
+      AndroidApiLevel methodApiLevel =
+          androidApiLevelDatabase.getMethodApiLevel(reference.asDexMethod());
+      if (methodApiLevel != null) {
+        return apiLevelCompute.of(methodApiLevel);
+      }
+      AndroidApiLevel typeApiLevel =
+          androidApiLevelDatabase.getTypeApiLevel(reference.getContextType());
+      // TODO(b/207452750): Investigate if we can return minApi here.
+      return typeApiLevel == null ? ComputedApiLevel.unknown() : apiLevelCompute.of(typeApiLevel);
+    }
+    AndroidApiLevel foundApiLevel =
+        reference.apply(
             androidApiLevelDatabase::getTypeApiLevel,
             androidApiLevelDatabase::getFieldApiLevel,
-            androidApiLevelDatabase::getMethodApiLevel)
-        .max(appView.options().getMinApiLevel());
-  }
-
-  private boolean isReferenceToJavaLangObject(DexReference reference) {
-    if (reference.getContextType() == appView.dexItemFactory().objectType) {
-      return true;
-    }
-    return reference.isDexMethod()
-        && appView.dexItemFactory().objectMembers.isObjectMember(reference.asDexMethod());
+            androidApiLevelDatabase::getMethodApiLevel);
+    return (foundApiLevel == null)
+        ? unknownValue
+        : apiLevelCompute.of(foundApiLevel.max(appView.options().getMinApiLevel()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
new file mode 100644
index 0000000..2f5ab5e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -0,0 +1,165 @@
+// 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.androidapi;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.structural.Equatable;
+import java.util.Objects;
+
+/**
+ * The ComputedApiLevel encodes a lattice over AndroidApiLevel with a bottom (NotSet) and a top
+ * (Unknown).
+ */
+public interface ComputedApiLevel extends Equatable<ComputedApiLevel> {
+
+  static NotSetApiLevel notSet() {
+    return NotSetApiLevel.INSTANCE;
+  }
+
+  static UnknownApiLevel unknown() {
+    return UnknownApiLevel.INSTANCE;
+  }
+
+  static KnownApiLevel platform() {
+    return KnownApiLevel.PLATFORM_INSTANCE;
+  }
+
+  default boolean isNotSetApiLevel() {
+    return false;
+  }
+
+  default boolean isUnknownApiLevel() {
+    return false;
+  }
+
+  default ComputedApiLevel max(ComputedApiLevel other) {
+    return isGreaterThanOrEqualTo(other) ? this : other;
+  }
+
+  default boolean isGreaterThanOrEqualTo(ComputedApiLevel other) {
+    assert !isNotSetApiLevel() && !other.isNotSetApiLevel()
+        : "Cannot compute relationship for not set";
+    if (isUnknownApiLevel()) {
+      return true;
+    }
+    if (other.isUnknownApiLevel()) {
+      return false;
+    }
+    assert isKnownApiLevel() && other.isKnownApiLevel();
+    return asKnownApiLevel()
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(other.asKnownApiLevel().getApiLevel());
+  }
+
+  default boolean isKnownApiLevel() {
+    return false;
+  }
+
+  default KnownApiLevel asKnownApiLevel() {
+    return null;
+  }
+
+  @Override
+  default boolean isEqualTo(ComputedApiLevel other) {
+    return this.equals(other);
+  }
+
+  class NotSetApiLevel implements ComputedApiLevel {
+
+    private static final NotSetApiLevel INSTANCE = new NotSetApiLevel();
+
+    private NotSetApiLevel() {}
+
+    @Override
+    public boolean isNotSetApiLevel() {
+      return true;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return this == obj;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  class UnknownApiLevel implements ComputedApiLevel {
+
+    private static final UnknownApiLevel INSTANCE = new UnknownApiLevel();
+
+    private UnknownApiLevel() {}
+
+    @Override
+    public boolean isUnknownApiLevel() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "UNKNOWN";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return this == obj;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+  }
+
+  class KnownApiLevel implements ComputedApiLevel {
+
+    private static final KnownApiLevel PLATFORM_INSTANCE =
+        new KnownApiLevel(AndroidApiLevel.ANDROID_PLATFORM);
+
+    private final AndroidApiLevel apiLevel;
+
+    KnownApiLevel(AndroidApiLevel apiLevel) {
+      this.apiLevel = apiLevel;
+    }
+
+    public AndroidApiLevel getApiLevel() {
+      return apiLevel;
+    }
+
+    @Override
+    public boolean isKnownApiLevel() {
+      return true;
+    }
+
+    @Override
+    public KnownApiLevel asKnownApiLevel() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return apiLevel.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof KnownApiLevel)) {
+        return false;
+      }
+      KnownApiLevel that = (KnownApiLevel) o;
+      return apiLevel == that.apiLevel;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(apiLevel);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 32e1f64..e21fbf4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.contexts.CompilationContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
@@ -116,6 +118,8 @@
 
   private final Thread mainThread = Thread.currentThread();
 
+  private final ComputedApiLevel computedMinApiLevel;
+
   private AppView(
       T appInfo,
       WholeProgramOptimizations wholeProgramOptimizations,
@@ -137,6 +141,8 @@
     this.libraryMethodSideEffectModelCollection = new LibraryMethodSideEffectModelCollection(this);
     this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
     this.protoShrinker = ProtoShrinker.create(withLiveness());
+
+    this.computedMinApiLevel = AndroidApiLevelCompute.computeInitialMinApiLevel(appInfo.options());
   }
 
   public boolean verifyMainThread() {
@@ -827,4 +833,8 @@
   public boolean checkForTesting(Supplier<Boolean> test) {
     return testing().enableTestAssertions ? test.get() : true;
   }
+
+  public ComputedApiLevel computedMinApiLevel() {
+    return computedMinApiLevel;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 56a302d..44a5132 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -21,7 +22,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -59,7 +59,7 @@
       FieldTypeSignature genericSignature,
       DexAnnotationSet annotations,
       DexValue staticValue,
-      AndroidApiLevel apiLevel,
+      ComputedApiLevel apiLevel,
       boolean deprecated,
       boolean d8R8Synthesized) {
     super(field, annotations, d8R8Synthesized, apiLevel);
@@ -103,7 +103,7 @@
   }
 
   @Override
-  public AndroidApiLevel getApiLevel() {
+  public ComputedApiLevel getApiLevel() {
     return getApiLevelForDefinition();
   }
 
@@ -365,7 +365,7 @@
     private FieldAccessFlags accessFlags;
     private FieldTypeSignature genericSignature = FieldTypeSignature.noSignature();
     private DexValue staticValue = null;
-    private AndroidApiLevel apiLevel = AndroidApiLevel.NOT_SET;
+    private ComputedApiLevel apiLevel = ComputedApiLevel.notSet();
     private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
     private boolean deprecated;
     private final boolean d8R8Synthesized;
@@ -438,7 +438,7 @@
       return this;
     }
 
-    public Builder setApiLevel(AndroidApiLevel apiLevel) {
+    public Builder setApiLevel(ComputedApiLevel apiLevel) {
       this.apiLevel = apiLevel;
       return this;
     }
@@ -474,7 +474,7 @@
       assert accessFlags != null;
       assert genericSignature != null;
       assert annotations != null;
-      assert !checkAndroidApiLevel || apiLevel != AndroidApiLevel.NOT_SET;
+      assert !checkAndroidApiLevel || !apiLevel.isNotSetApiLevel();
       DexEncodedField dexEncodedField =
           new DexEncodedField(
               field,
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index a5ea49e..7f42e49 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,11 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
-
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
 import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -23,7 +21,7 @@
   private final boolean d8R8Synthesized;
 
   /** apiLevelForDefinition describes the api level needed for knowing all types */
-  private AndroidApiLevel apiLevelForDefinition;
+  private ComputedApiLevel apiLevelForDefinition;
 
   private final R reference;
 
@@ -31,7 +29,7 @@
       R reference,
       DexAnnotationSet annotations,
       boolean d8R8Synthesized,
-      AndroidApiLevel apiLevelForDefinition) {
+      ComputedApiLevel apiLevelForDefinition) {
     super(annotations);
     this.reference = reference;
     this.d8R8Synthesized = d8R8Synthesized;
@@ -95,18 +93,18 @@
 
   public abstract MemberOptimizationInfo<?> getOptimizationInfo();
 
-  public abstract AndroidApiLevel getApiLevel();
+  public abstract ComputedApiLevel getApiLevel();
 
-  public AndroidApiLevel getApiLevelForDefinition() {
+  public ComputedApiLevel getApiLevelForDefinition() {
     return apiLevelForDefinition;
   }
 
-  public void setApiLevelForDefinition(AndroidApiLevel apiLevelForDefinition) {
+  public void setApiLevelForDefinition(ComputedApiLevel apiLevelForDefinition) {
     this.apiLevelForDefinition = apiLevelForDefinition;
   }
 
   public boolean hasComputedApiReferenceLevel() {
-    return getApiLevel() != NOT_SET;
+    return !getApiLevel().isNotSetApiLevel();
   }
 
   @Override
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 ccf7d83..a8709ec 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -11,10 +11,10 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
-import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 import static java.util.Objects.requireNonNull;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -71,7 +71,6 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -148,8 +147,8 @@
           ParameterAnnotationsList.empty(),
           null,
           false,
-          NOT_SET,
-          NOT_SET,
+          ComputedApiLevel.notSet(),
+          ComputedApiLevel.notSet(),
           null,
           CallSiteOptimizationInfo.top(),
           DefaultMethodOptimizationInfo.getInstance(),
@@ -168,7 +167,7 @@
   private CallSiteOptimizationInfo callSiteOptimizationInfo;
   private CfVersion classFileVersion;
   /** The apiLevelForCode describes the api level needed for knowing all references in the code */
-  private AndroidApiLevel apiLevelForCode;
+  private ComputedApiLevel apiLevelForCode;
 
   private KotlinMethodLevelInfo kotlinMemberInfo = getNoKotlinInfo();
   /** Generic signature information if the attribute is present in the input */
@@ -242,8 +241,8 @@
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
       boolean d8R8Synthesized,
-      AndroidApiLevel apiLevelForDefinition,
-      AndroidApiLevel apiLevelForCode,
+      ComputedApiLevel apiLevelForDefinition,
+      ComputedApiLevel apiLevelForCode,
       CfVersion classFileVersion,
       CallSiteOptimizationInfo callSiteOptimizationInfo,
       MethodOptimizationInfo optimizationInfo,
@@ -1232,7 +1231,7 @@
             .fixupOptimizationInfo(appView, prototypeChanges.createMethodOptimizationInfoFixer())
             .setGenericSignature(MethodTypeSignature.noSignature());
     DexEncodedMethod method = builder.build();
-    method.copyMetadata(this);
+    method.copyMetadata(appView, this);
     setObsolete();
     return method;
   }
@@ -1268,23 +1267,23 @@
     return optimizationInfo;
   }
 
-  public AndroidApiLevel getApiLevelForCode() {
+  public ComputedApiLevel getApiLevelForCode() {
     return apiLevelForCode;
   }
 
-  public void clearApiLevelForCode(AppView<?> appView) {
-    this.apiLevelForCode = AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView);
+  public void clearApiLevelForCode() {
+    this.apiLevelForCode = ComputedApiLevel.notSet();
   }
 
-  public void setApiLevelForCode(AndroidApiLevel apiLevel) {
+  public void setApiLevelForCode(ComputedApiLevel apiLevel) {
     assert apiLevel != null;
     this.apiLevelForCode = apiLevel;
   }
 
   @Override
-  public AndroidApiLevel getApiLevel() {
-    return (shouldNotHaveCode() ? AndroidApiLevel.B : getApiLevelForCode())
-        .max(getApiLevelForDefinition());
+  public ComputedApiLevel getApiLevel() {
+    ComputedApiLevel apiLevelForDefinition = getApiLevelForDefinition();
+    return shouldNotHaveCode() ? apiLevelForDefinition : apiLevelForDefinition.max(apiLevelForCode);
   }
 
   public synchronized MutableMethodOptimizationInfo getMutableOptimizationInfo() {
@@ -1320,12 +1319,14 @@
     this.callSiteOptimizationInfo = callSiteOptimizationInfo;
   }
 
-  public void copyMetadata(DexEncodedMethod from) {
+  public void copyMetadata(AppView<?> appView, DexEncodedMethod from) {
     checkIfObsolete();
     if (from.hasClassFileVersion()) {
       upgradeClassFileVersion(from.getClassFileVersion());
     }
-    apiLevelForCode = getApiLevelForCode().max(from.getApiLevelForCode());
+    if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+      apiLevelForCode = getApiLevelForCode().max(from.getApiLevelForCode());
+    }
   }
 
   public MethodTypeSignature getGenericSignature() {
@@ -1374,8 +1375,8 @@
     private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
     private KotlinMethodLevelInfo kotlinInfo = getNoKotlinInfo();
     private CfVersion classFileVersion = null;
-    private AndroidApiLevel apiLevelForDefinition = NOT_SET;
-    private AndroidApiLevel apiLevelForCode = NOT_SET;
+    private ComputedApiLevel apiLevelForDefinition = ComputedApiLevel.notSet();
+    private ComputedApiLevel apiLevelForCode = ComputedApiLevel.notSet();
     private final boolean d8R8Synthesized;
     private boolean deprecated = false;
 
@@ -1611,12 +1612,12 @@
       return this;
     }
 
-    public Builder setApiLevelForDefinition(AndroidApiLevel apiLevelForDefinition) {
+    public Builder setApiLevelForDefinition(ComputedApiLevel apiLevelForDefinition) {
       this.apiLevelForDefinition = apiLevelForDefinition;
       return this;
     }
 
-    public Builder setApiLevelForCode(AndroidApiLevel apiLevelForCode) {
+    public Builder setApiLevelForCode(ComputedApiLevel apiLevelForCode) {
       this.apiLevelForCode = apiLevelForCode;
       return this;
     }
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 e297bd4..900e79c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
@@ -21,7 +22,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticMarker;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -34,7 +34,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -823,26 +822,19 @@
     return checksumSupplier;
   }
 
-  public AndroidApiLevel getApiReferenceLevel(
-      AppView<?> appView,
-      BiFunction<DexReference, AndroidApiLevel, AndroidApiLevel> apiLevelLookup) {
+  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.
-    AndroidApiLevel computedApiLevel = minApiLevelIfEnabledOrUnknown(appView);
-    for (DexType superType : allImmediateSupertypes()) {
-      computedApiLevel = apiLevelLookup.apply(superType, computedApiLevel);
-      if (computedApiLevel == AndroidApiLevel.UNKNOWN) {
-        return AndroidApiLevel.UNKNOWN;
-      }
-    }
-    return computedApiLevel.max(getMembersApiReferenceLevel(appView));
+    return getMembersApiReferenceLevel(
+        apiLevelCompute.computeApiLevelForDefinition(
+            allImmediateSupertypes(), apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
   }
 
-  public AndroidApiLevel getMembersApiReferenceLevel(AppView<?> appView) {
-    AndroidApiLevel memberLevel = minApiLevelIfEnabledOrUnknown(appView);
+  public ComputedApiLevel getMembersApiReferenceLevel(ComputedApiLevel memberLevel) {
     for (DexEncodedMember<?, ?> member : members()) {
       memberLevel = memberLevel.max(member.getApiLevel());
-      if (memberLevel == AndroidApiLevel.UNKNOWN) {
-        return AndroidApiLevel.UNKNOWN;
+      if (memberLevel.isUnknownApiLevel()) {
+        return memberLevel;
       }
     }
     return memberLevel;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 844b45e..cf866d4 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 
 /** Type representing a method definition in the programs compilation unit and its holder. */
 public final class ProgramMethod extends DexClassAndMethod
@@ -95,7 +94,7 @@
       accessFlags.demoteFromStrict();
       accessFlags.demoteFromSynchronized();
       accessFlags.promoteToAbstract();
-      getDefinition().clearApiLevelForCode(appView);
+      getDefinition().clearApiLevelForCode();
       getDefinition().unsetCode();
       getSimpleFeedback().unsetOptimizationInfoForAbstractMethod(this);
     }
@@ -105,7 +104,7 @@
   public void convertToThrowNullMethod(AppView<?> appView) {
     MethodAccessFlags accessFlags = getAccessFlags();
     accessFlags.demoteFromAbstract();
-    getDefinition().setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
+    getDefinition().setApiLevelForCode(appView.computedMinApiLevel());
     getDefinition().setCode(ThrowNullCode.get(), appView);
     getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
     getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
index 7f4b9f9..45e2065 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph.analysis;
 
 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.DexClassAndMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,18 +14,17 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
-import com.android.tools.r8.utils.AndroidApiLevel;
 
 public class ApiModelAnalysis extends EnqueuerAnalysis {
 
   private final AppView<?> appView;
-  private final AndroidApiLevel minApiLevel;
   private final AndroidApiLevelCompute apiCompute;
+  private final ComputedApiLevel minApiLevel;
 
   public ApiModelAnalysis(AppView<?> appView, AndroidApiLevelCompute apiCompute) {
     this.appView = appView;
-    this.minApiLevel = appView.options().getMinApiLevel();
     this.apiCompute = apiCompute;
+    this.minApiLevel = appView.computedMinApiLevel();
   }
 
   @Override
@@ -73,7 +73,7 @@
   @Override
   public void notifyFailedMethodResolutionTarget(DexEncodedMethod method) {
     // We may not trace into failed resolution targets.
-    method.setApiLevelForCode(AndroidApiLevel.UNKNOWN);
+    method.setApiLevelForCode(ComputedApiLevel.unknown());
   }
 
   private void computeAndSetApiLevelForDefinition(DexClassAndMember<?, ?> member) {
@@ -81,6 +81,8 @@
         .getDefinition()
         .setApiLevelForDefinition(
             apiCompute.computeApiLevelForDefinition(
-                member.getReference(), appView.dexItemFactory()));
+                member.getReference(),
+                appView.dexItemFactory(),
+                apiCompute.getPlatformApiLevelOrUnknown(appView)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
new file mode 100644
index 0000000..eee1f34
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
@@ -0,0 +1,24 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerFieldAccessAnalysis {
+
+  void traceInstanceFieldRead(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+  void traceInstanceFieldWrite(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+  void traceStaticFieldRead(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+  void traceStaticFieldWrite(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
new file mode 100644
index 0000000..1243e98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -0,0 +1,122 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+/**
+ * In Dalvik it is a verification error to read and use a field of type Missing[].
+ *
+ * <p>Example:
+ *
+ * <pre>
+ *   Consumer<?>[] consumer = this.field;
+ *   acceptConsumer(consumer); // acceptConsumer(Consumer[])
+ * </pre>
+ *
+ * <p>To avoid that the compiler moves such code into other contexts (e.g., as a result of inlining
+ * or class merging), and thereby causes new classes to no longer verify on Dalvik, we soft-pin
+ * methods that reads such fields.
+ */
+public class GetArrayOfMissingTypeVerifyErrorWorkaround implements EnqueuerFieldAccessAnalysis {
+
+  private final DexItemFactory dexItemFactory;
+  private final Enqueuer enqueuer;
+  private final Set<DexType> knownToBePresentOnDalvik;
+
+  public GetArrayOfMissingTypeVerifyErrorWorkaround(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+    this.dexItemFactory = appView.dexItemFactory();
+    this.enqueuer = enqueuer;
+    this.knownToBePresentOnDalvik =
+        ImmutableSet.<DexType>builder()
+            .add(dexItemFactory.boxedBooleanType)
+            .add(dexItemFactory.boxedByteType)
+            .add(dexItemFactory.boxedCharType)
+            .add(dexItemFactory.boxedDoubleType)
+            .add(dexItemFactory.boxedFloatType)
+            .add(dexItemFactory.boxedIntType)
+            .add(dexItemFactory.boxedLongType)
+            .add(dexItemFactory.boxedShortType)
+            .add(dexItemFactory.classType)
+            .add(dexItemFactory.objectType)
+            .add(dexItemFactory.enumType)
+            .add(dexItemFactory.stringType)
+            .build();
+  }
+
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+    if (!isNoop(appView)) {
+      enqueuer.registerFieldAccessAnalysis(
+          new GetArrayOfMissingTypeVerifyErrorWorkaround(appView, enqueuer));
+    }
+  }
+
+  private static boolean isNoop(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    InternalOptions options = appView.options();
+    return options.isGeneratingDex()
+        && options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
+
+  @Override
+  public void traceInstanceFieldRead(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+    if (isUnsafeToUseFieldOnDalvik(field, context)) {
+      enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+    }
+  }
+
+  @Override
+  public void traceStaticFieldRead(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+    if (isUnsafeToUseFieldOnDalvik(field, context)) {
+      enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+    }
+  }
+
+  private boolean isUnsafeToUseFieldOnDalvik(DexField field, ProgramMethod context) {
+    DexType fieldType = field.getType();
+    if (!fieldType.isArrayType()) {
+      return false;
+    }
+    DexType baseType = fieldType.toBaseType(dexItemFactory);
+    if (!baseType.isClassType()) {
+      return false;
+    }
+    if (knownToBePresentOnDalvik.contains(baseType)) {
+      return false;
+    }
+    // TODO(b/206891715): Use the API database to determine if the given type is introduced in API
+    //  level L or later.
+    DexClass baseClass = enqueuer.definitionFor(baseType, context);
+    return baseClass != null && baseClass.isLibraryClass();
+  }
+
+  @Override
+  public void traceInstanceFieldWrite(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void traceStaticFieldWrite(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index b1055ef..c76f43f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static com.google.common.base.Predicates.not;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -125,7 +124,7 @@
         newMethodReference.withName("$r8$clinit$synthetic", dexItemFactory);
     lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
 
-    AndroidApiLevel apiReferenceLevel = classInitializerMerger.getApiReferenceLevel(appView);
+    ComputedApiLevel apiReferenceLevel = classInitializerMerger.getApiReferenceLevel(appView);
     DexEncodedMethod definition =
         DexEncodedMethod.syntheticBuilder()
             .setMethod(newMethodReference)
@@ -215,7 +214,7 @@
         DexEncodedField.syntheticBuilder()
             .setField(group.getClassIdField())
             .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
-            .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+            .setApiLevel(appView.computedMinApiLevel())
             .build();
 
     // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index fbbe362..5e76e2d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.LimitInterfaceGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoCheckDiscard;
@@ -229,6 +230,7 @@
     builder.add(
         new NoDefaultInterfaceMethodMerging(appView, mode),
         new NoDefaultInterfaceMethodCollisions(appView, mode),
+        new LimitInterfaceGroups(appView),
         new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode));
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 085276f..b1219c4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static java.lang.Integer.max;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -36,7 +36,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.CfVersionUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -101,11 +100,11 @@
     return null;
   }
 
-  public AndroidApiLevel getApiReferenceLevel(AppView<?> appView) {
+  public ComputedApiLevel getApiReferenceLevel(AppView<?> appView) {
     assert !classInitializers.isEmpty();
     return ListUtils.fold(
         classInitializers,
-        minApiLevelIfEnabledOrUnknown(appView),
+        appView.computedMinApiLevel(),
         (accApiLevel, method) -> accApiLevel.max(method.getDefinition().getApiLevel()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index 210f21d..2752544 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -18,7 +18,7 @@
   private final int maxGroupSize;
 
   public LimitClassGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxGroupSize();
+    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxClassGroupSize();
     assert maxGroupSize >= 2;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
new file mode 100644
index 0000000..adee1f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
@@ -0,0 +1,63 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class LimitInterfaceGroups extends MultiClassPolicy {
+
+  private final int maxGroupSize;
+
+  public LimitInterfaceGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxInterfaceGroupSize();
+    assert maxGroupSize >= 0;
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    if (group.isClassGroup()) {
+      return Collections.singletonList(group);
+    }
+    // Mapping from new merge groups to their size.
+    Map<MergeGroup, Integer> newGroups = new LinkedHashMap<>();
+    for (DexProgramClass clazz : group) {
+      processClass(clazz, newGroups);
+    }
+    return removeTrivialGroups(newGroups.keySet());
+  }
+
+  private void processClass(DexProgramClass clazz, Map<MergeGroup, Integer> newGroups) {
+    int increment = clazz.getMethodCollection().size();
+
+    // Find an existing group.
+    for (Entry<MergeGroup, Integer> entry : newGroups.entrySet()) {
+      MergeGroup candidateGroup = entry.getKey();
+      int candidateGroupSize = entry.getValue();
+      int newCandidateGroupSize = candidateGroupSize + increment;
+      if (newCandidateGroupSize <= maxGroupSize) {
+        candidateGroup.add(clazz);
+        entry.setValue(newCandidateGroupSize);
+        return;
+      }
+    }
+
+    // Failed to find an existing group.
+    newGroups.put(new MergeGroup(clazz), increment);
+  }
+
+  @Override
+  public String getName() {
+    return "LimitInterfaceGroups";
+  }
+}
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 3b7e1e0..1f953f4 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,21 +4,21 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache;
+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.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.utils.AndroidApiLevel;
 
-public class NoDifferentApiReferenceLevel extends MultiClassSameReferencePolicy<AndroidApiLevel> {
+public class NoDifferentApiReferenceLevel extends MultiClassSameReferencePolicy<ComputedApiLevel> {
 
-  private final AndroidApiReferenceLevelCache apiReferenceLevelCache;
+  private final AndroidApiLevelCompute apiLevelCompute;
   private final AppView<?> appView;
   // TODO(b/188388130): Remove when stabilized.
   private final boolean enableApiCallerIdentification;
 
   public NoDifferentApiReferenceLevel(AppView<?> appView) {
-    apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView);
+    apiLevelCompute = AndroidApiLevelCompute.create(appView);
     this.appView = appView;
     enableApiCallerIdentification =
         appView.options().apiModelingOptions().enableApiCallerIdentification;
@@ -35,8 +35,8 @@
   }
 
   @Override
-  public AndroidApiLevel getMergeKey(DexProgramClass clazz) {
+  public ComputedApiLevel getMergeKey(DexProgramClass clazz) {
     assert enableApiCallerIdentification;
-    return clazz.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
+    return clazz.getApiReferenceLevel(appView, apiLevelCompute);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldInfo.java
index 6704f91..1515932 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldInfo.java
@@ -119,7 +119,7 @@
    * </pre>
    */
   public boolean hasHazzerBitField(ProtoMessageInfo protoMessageInfo) {
-    return protoMessageInfo.isProto2() && type.isSingular();
+    return type.hasAuxData(protoMessageInfo.isProto2());
   }
 
   public ProgramField getHazzerBitField(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2b72d1e..37ce381 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -205,7 +205,6 @@
             .map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
             .map(options.itemFactory::createString)
             .collect(Collectors.toList());
-    AndroidApiLevelCompute apiLevelCompute = AndroidApiLevelCompute.create(appView);
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings, performs all desugaring including L8 specific desugaring.
       //
@@ -223,8 +222,7 @@
       // - nest based access desugaring,
       // - invoke-special desugaring.
       assert options.desugarState.isOn();
-      this.instructionDesugaring =
-          CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
+      this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -249,7 +247,7 @@
     this.instructionDesugaring =
         appView.enableWholeProgramOptimizations()
             ? CfInstructionDesugaringCollection.empty()
-            : CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
+            : CfInstructionDesugaringCollection.create(appView);
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -288,7 +286,8 @@
       this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
       this.serviceLoaderRewriter =
           options.enableServiceLoaderRewriting
-              ? new ServiceLoaderRewriter(appViewWithLiveness, apiLevelCompute)
+              ? new ServiceLoaderRewriter(
+                  appViewWithLiveness, AndroidApiLevelCompute.create(appView))
               : null;
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index c516da4..ed1260e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -1470,8 +1468,7 @@
               appView,
               builder ->
                   builder
-                      .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                      .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                      .disableAndroidApiLevelCheck()
                       .setProto(getProto(appView.dexItemFactory()))
                       .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                       .setCode(methodSig -> generateTemplateMethod(appView.options(), methodSig)));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
index 3f60ffe..8b8b2e3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
@@ -31,6 +31,11 @@
   }
 
   @Override
+  public void acceptEnumConversionProgramClass(DexProgramClass clazz) {
+    synthesizedClasses.add(clazz);
+  }
+
+  @Override
   public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
     synthesizedClasses.add(clazz);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 8328e9b..b34a515 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -28,19 +27,16 @@
  */
 public abstract class CfInstructionDesugaringCollection {
 
-  public static CfInstructionDesugaringCollection create(
-      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+  public static CfInstructionDesugaringCollection create(AppView<?> appView) {
     if (appView.options().desugarState.isOn()) {
-      return new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
+      return new NonEmptyCfInstructionDesugaringCollection(appView);
     }
     // TODO(b/145775365): invoke-special desugaring is mandatory, since we currently can't map
     //  invoke-special instructions that require desugaring into IR.
     if (appView.options().isGeneratingClassFiles()) {
-      return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(
-          appView, apiLevelCompute);
+      return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(appView);
     }
-    return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(
-        appView, apiLevelCompute);
+    return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(appView);
   }
 
   public static CfInstructionDesugaringCollection empty() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 28334b1..42212fd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -181,6 +181,11 @@
     }
 
     @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptAPIConversion(ProgramMethod method) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
@@ -336,6 +341,11 @@
     }
 
     @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
+
+    @Override
     public void acceptAPIConversion(ProgramMethod method) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 31b8c3c..496cef9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -108,6 +108,11 @@
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
+
+    @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
@@ -169,5 +174,10 @@
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
+
+    @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index cd17565..a014283 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -645,7 +645,7 @@
                             .setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition())
                             .setApiLevelForCode(encodedMethod.getApiLevelForCode())
                             .build();
-                    newMethod.copyMetadata(encodedMethod);
+                    newMethod.copyMetadata(appView, encodedMethod);
                     forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
                         encodedMethod.getReference(), callTarget);
 
@@ -736,7 +736,7 @@
                             .setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition())
                             .setApiLevelForCode(encodedMethod.getApiLevelForCode())
                             .build();
-                    newMethod.copyMetadata(encodedMethod);
+                    newMethod.copyMetadata(appView, encodedMethod);
                     forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
                         encodedMethod.getReference(), callTarget);
                     return newMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 66d3a56..bc5c0fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.Unreachable;
@@ -52,12 +51,9 @@
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-  private final AndroidApiLevelCompute apiLevelCompute;
 
-  NonEmptyCfInstructionDesugaringCollection(
-      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+  NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
-    this.apiLevelCompute = apiLevelCompute;
     AlwaysThrowingInstructionDesugaring alwaysThrowingInstructionDesugaring =
         appView.enableWholeProgramOptimizations()
             ? new AlwaysThrowingInstructionDesugaring(appView.withClassHierarchy())
@@ -131,24 +127,22 @@
     }
   }
 
-  static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(
-      AppView<?> appView, AndroidApiLevelCompute computeApiLevel) {
+  static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) {
     assert appView.options().desugarState.isOff();
     assert appView.options().isGeneratingClassFiles();
     NonEmptyCfInstructionDesugaringCollection desugaringCollection =
-        new NonEmptyCfInstructionDesugaringCollection(appView, computeApiLevel);
+        new NonEmptyCfInstructionDesugaringCollection(appView);
     // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled.
     //  This should be removed once we can represent invoke-special instructions in the IR.
     desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     return desugaringCollection;
   }
 
-  static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(
-      AppView<?> appView, AndroidApiLevelCompute computeApiLevel) {
+  static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(AppView<?> appView) {
     assert appView.options().desugarState.isOff();
     assert appView.options().isGeneratingDex();
     NonEmptyCfInstructionDesugaringCollection desugaringCollection =
-        new NonEmptyCfInstructionDesugaringCollection(appView, computeApiLevel);
+        new NonEmptyCfInstructionDesugaringCollection(appView);
     desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugaringCollection.desugarings.add(new InvokeToPrivateRewriter());
     return desugaringCollection;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 3f9e078..fb848df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.CACHE_CONSTANT;
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_ICCE;
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_NSME;
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 
 import com.android.tools.r8.cf.code.CfCheckCast;
@@ -56,7 +55,6 @@
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.google.common.collect.ImmutableList;
@@ -194,12 +192,12 @@
             DexEncodedField.syntheticBuilder()
                 .setField(this.initializedValueField)
                 .setAccessFlags(FieldAccessFlags.createPrivateStaticSynthetic())
-                .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+                .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedField.syntheticBuilder()
                 .setField(this.constantValueField)
                 .setAccessFlags(FieldAccessFlags.createPrivateStaticSynthetic())
-                .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+                .disableAndroidApiLevelCheck()
                 .build()));
   }
 
@@ -210,8 +208,7 @@
                 .setMethod(getConstMethod)
                 .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                 .setCode(generateGetterCode(builder))
-                .setApiLevelForDefinition(AndroidApiLevel.S)
-                .setApiLevelForCode(AndroidApiLevel.S)
+                .disableAndroidApiLevelCheck()
                 .build()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryEnumConversionSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryEnumConversionSynthesizer.java
new file mode 100644
index 0000000..c97d2ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryEnumConversionSynthesizer.java
@@ -0,0 +1,197 @@
+// 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.vivifiedTypeFor;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.EnumArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.EnumConversionCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticClasspathClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.Iterables;
+
+public class DesugaredLibraryEnumConversionSynthesizer {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  public DesugaredLibraryEnumConversionSynthesizer(AppView<?> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  private void buildEnumConvert(
+      SyntheticMethodBuilder builder,
+      DexType src,
+      DexType dest,
+      SyntheticCodeGenerator codeGenerator) {
+    builder
+        .setName(factory.convertMethodName)
+        .setProto(factory.createProto(dest, src))
+        .setAccessFlags(
+            MethodAccessFlags.fromCfAccessFlags(
+                Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, false))
+        // Will be traced by the enqueuer.
+        .disableAndroidApiLevelCheck()
+        .setCode(codeGenerator);
+  }
+
+  private void buildEnumMethodsWithCode(
+      SyntheticProgramClassBuilder builder,
+      Iterable<DexEncodedField> enumFields,
+      DexType enumType,
+      DexType convertType) {
+    DexType enumArray = factory.createArrayType(1, enumType);
+    DexType convertArray = factory.createArrayType(1, convertType);
+    builder
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(
+                    methodBuilder,
+                    enumType,
+                    convertType,
+                    codeSynthesizor ->
+                        new EnumConversionCfCodeProvider(
+                                appView,
+                                codeSynthesizor.getHolderType(),
+                                enumFields,
+                                enumType,
+                                convertType)
+                            .generateCfCode()))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(
+                    methodBuilder,
+                    convertType,
+                    enumType,
+                    codeSynthesizor ->
+                        new EnumConversionCfCodeProvider(
+                                appView,
+                                codeSynthesizor.getHolderType(),
+                                enumFields,
+                                convertType,
+                                enumType)
+                            .generateCfCode()))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(
+                    methodBuilder,
+                    enumArray,
+                    convertArray,
+                    codeSynthesizor ->
+                        new EnumArrayConversionCfCodeProvider(
+                                appView, codeSynthesizor.getHolderType(), enumType, convertType)
+                            .generateCfCode()))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(
+                    methodBuilder,
+                    convertArray,
+                    enumArray,
+                    codeSynthesizor ->
+                        new EnumArrayConversionCfCodeProvider(
+                                appView, codeSynthesizor.getHolderType(), convertType, enumType)
+                            .generateCfCode()));
+  }
+
+  private void buildEnumMethodsWithoutCode(
+      SyntheticClasspathClassBuilder builder, DexType enumType, DexType convertType) {
+    DexType enumArray = factory.createArrayType(1, enumType);
+    DexType convertArray = factory.createArrayType(1, convertType);
+    builder
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(methodBuilder, enumType, convertType, ignored -> null))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(methodBuilder, convertType, enumType, ignored -> null))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(methodBuilder, enumArray, convertArray, ignored -> null))
+        .addMethod(
+            methodBuilder ->
+                buildEnumConvert(methodBuilder, convertArray, enumArray, ignored -> null));
+  }
+
+  DexMethod ensureEnumConversionMethod(
+      DexClass clazz,
+      DexType srcType,
+      DexType destType,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+    DexClass enumConversion = ensureEnumConversionClass(clazz, eventConsumer);
+    DexMethod method =
+        factory.createMethod(
+            enumConversion.type, factory.createProto(destType, srcType), factory.convertMethodName);
+    assert enumConversion.lookupDirectMethod(method) != null;
+    return method;
+  }
+
+  DexMethod getExistingProgramEnumConversionMethod(
+      DexClass clazz, DexType srcType, DexType destType) {
+    DexProgramClass enumConversion =
+        appView
+            .getSyntheticItems()
+            .getExistingFixedClass(SyntheticKind.ENUM_CONVERSION, clazz, appView);
+    DexMethod method =
+        factory.createMethod(
+            enumConversion.type, factory.createProto(destType, srcType), factory.convertMethodName);
+    assert enumConversion.lookupProgramMethod(method) != null;
+    return method;
+  }
+
+  DexProgramClass ensureProgramEnumConversionClass(
+      DexClass context, CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+    assert eventConsumer != null;
+    assert context.isProgramClass();
+    DexType type = context.type;
+    DexType vivifiedType = vivifiedTypeFor(context.type, appView);
+    assert appView.options().isDesugaredLibraryCompilation();
+    DexProgramClass programContext = context.asProgramClass();
+    Iterable<DexEncodedField> enumFields =
+        Iterables.filter(programContext.staticFields(), DexEncodedField::isEnum);
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClass(
+            SyntheticKind.ENUM_CONVERSION,
+            programContext,
+            appView,
+            builder -> buildEnumMethodsWithCode(builder, enumFields, type, vivifiedType),
+            eventConsumer::acceptWrapperProgramClass);
+  }
+
+  private DexClass ensureEnumConversionClass(
+      DexClass context, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
+    assert eventConsumer != null;
+    if (context.isProgramClass()) {
+      return appView
+          .getSyntheticItems()
+          .getExistingFixedClass(SyntheticKind.ENUM_CONVERSION, context, appView);
+    }
+    DexType type = context.type;
+    DexType vivifiedType = vivifiedTypeFor(context.type, appView);
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClasspathClass(
+            SyntheticKind.ENUM_CONVERSION,
+            context.asClasspathOrLibraryClass(),
+            appView,
+            builder -> buildEnumMethodsWithoutCode(builder, type, vivifiedType),
+            eventConsumer::acceptWrapperClasspathClass);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
index 6aed582..14240a6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
-
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -101,10 +100,12 @@
   private final DexItemFactory factory;
   private final ConcurrentHashMap<DexType, List<DexEncodedMethod>> allImplementedMethodsCache =
       new ConcurrentHashMap<>();
+  private final DesugaredLibraryEnumConversionSynthesizer enumConverter;
 
   public DesugaredLibraryWrapperSynthesizer(AppView<?> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
+    this.enumConverter = new DesugaredLibraryEnumConversionSynthesizer(appView);
   }
 
   public boolean isSyntheticWrapper(DexType type) {
@@ -117,6 +118,9 @@
   }
 
   public boolean shouldConvert(DexType type, DexMethod method, ProgramMethod context) {
+    if (type.isArrayType()) {
+      return shouldConvert(type.toBaseType(appView.dexItemFactory()), method, context);
+    }
     if (!appView.rewritePrefix.hasRewrittenType(type, appView)) {
       return false;
     }
@@ -136,8 +140,11 @@
     if (customConversion != null) {
       return customConversion;
     }
-    assert canGenerateWrapper(type) : type;
     DexClass clazz = getValidClassToWrap(type);
+    if (clazz.isEnum()) {
+      return enumConverter.ensureEnumConversionMethod(clazz, srcType, destType, eventConsumer);
+    }
+    assert canGenerateWrapper(type) : type;
     WrapperConversions wrapperConversions = ensureWrappers(clazz, eventConsumer);
     DexMethod conversion =
         type == srcType
@@ -154,8 +161,11 @@
     if (customConversion != null) {
       return customConversion;
     }
-    WrapperConversions wrapperConversions =
-        getExistingProgramWrapperConversions(getValidClassToWrap(type));
+    DexClass clazz = getValidClassToWrap(type);
+    if (clazz.isEnum()) {
+      return enumConverter.getExistingProgramEnumConversionMethod(clazz, srcType, destType);
+    }
+    WrapperConversions wrapperConversions = getExistingProgramWrapperConversions(clazz);
     DexMethod conversion =
         type == srcType
             ? wrapperConversions.getConversion()
@@ -209,11 +219,14 @@
   }
 
   private DexClass getValidClassToWrap(DexType type) {
+    if (type.isArrayType()) {
+      return getValidClassToWrap(type.toBaseType(factory));
+    }
     DexClass dexClass = appView.definitionFor(type);
     // The dexClass should be a library class, so it cannot be null.
     assert dexClass != null;
     assert dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
-    assert !dexClass.accessFlags.isFinal();
+    assert !dexClass.accessFlags.isFinal() || dexClass.isEnum();
     return dexClass;
   }
 
@@ -581,7 +594,8 @@
         .setAccessFlags(newFlags)
         .setCode(code)
         .setApiLevelForDefinition(template.getApiLevelForDefinition())
-        .setApiLevelForCode(code == null ? NOT_SET : template.getApiLevelForCode())
+        .setApiLevelForCode(
+            code == null ? ComputedApiLevel.notSet() : template.getApiLevelForCode())
         .build();
   }
 
@@ -660,8 +674,12 @@
       // In broken set-ups we can end up having a json files containing wrappers of non desugared
       // classes. Such wrappers are not required since the class won't be rewritten.
       if (validClassToWrap.isProgramClass()) {
-        validClassesToWrap.add(validClassToWrap.asProgramClass());
-        ensureProgramWrappersWithoutVirtualMethods(validClassToWrap, eventConsumer);
+        if (validClassToWrap.isEnum()) {
+          enumConverter.ensureProgramEnumConversionClass(validClassToWrap, eventConsumer);
+        } else {
+          validClassesToWrap.add(validClassToWrap.asProgramClass());
+          ensureProgramWrappersWithoutVirtualMethods(validClassToWrap, eventConsumer);
+        }
       }
     }
     for (DexProgramClass validClassToWrap : validClassesToWrap) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java
index 212ad66..511e0d2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizerEventConsumer.java
@@ -13,11 +13,15 @@
   interface DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer {
 
     void acceptWrapperProgramClass(DexProgramClass clazz);
+
+    void acceptEnumConversionProgramClass(DexProgramClass clazz);
   }
 
   interface DesugaredLibraryClasspathWrapperSynthesizeEventConsumer {
 
     void acceptWrapperClasspathClass(DexClasspathClass clazz);
+
+    void acceptEnumConversionClasspathClass(DexClasspathClass clazz);
   }
 
   interface DesugaredLibraryAPIConverterEventConsumer
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index e1462ea..36519f8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfStackInstruction;
@@ -802,8 +800,7 @@
             .setAccessFlags(accessFlags)
             .setCode(
                 createExceptionThrowingCfCode(newMethod, accessFlags, errorType, dexItemFactory))
-            .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-            .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+            .disableAndroidApiLevelCheck()
             .build();
     addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 674493f..510331b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -1088,7 +1088,7 @@
             context.getDefinition().accessFlags.demoteFromSynthetic();
           }
 
-          context.getDefinition().copyMetadata(singleTargetMethod);
+          context.getDefinition().copyMetadata(appView, singleTargetMethod);
 
           if (inlineeMayHaveInvokeMethod && options.applyInliningToInlinee) {
             if (inlineeStack.size() + 1 > options.applyInliningToInlineeMaxDepth
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index d73ac60..7ef7216 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.Constants;
@@ -68,7 +69,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -1527,8 +1527,8 @@
                         .setProto(outline.buildProto())
                         // It is OK to set the api level to UNKNOWN since we are not interested in
                         // inlining the outlines anyway.
-                        .setApiLevelForDefinition(AndroidApiLevel.UNKNOWN)
-                        .setApiLevelForCode(AndroidApiLevel.UNKNOWN)
+                        .setApiLevelForDefinition(ComputedApiLevel.unknown())
+                        .setApiLevelForCode(ComputedApiLevel.unknown())
                         .setCode(m -> new OutlineCode(outline));
                     if (appView.options().isGeneratingClassFiles()) {
                       builder.setClassFileVersion(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 117ecfc..afb7003 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -63,6 +63,7 @@
   private final ProgramMethod method;
   private final IRCode code;
   private final int maxCapacityPerBlock;
+  private final boolean release;
 
   // Values that may require type propagation.
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -81,6 +82,7 @@
     this.method = code.context();
     this.code = code;
     this.maxCapacityPerBlock = Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
+    this.release = !appView.options().debug;
   }
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
@@ -209,19 +211,7 @@
             }
 
             if (instruction.isInstanceGet()) {
-              InstanceGet instanceGet = instruction.asInstanceGet();
-              if (instanceGet.outValue().hasLocalInfo()) {
-                continue;
-              }
-              Value object = instanceGet.object().getAliasedValue();
-              FieldAndObject fieldAndObject = new FieldAndObject(reference, object);
-              FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
-              if (replacement != null) {
-                replacement.eliminateRedundantRead(it, instanceGet);
-              } else {
-                activeState.putNonFinalInstanceField(
-                    fieldAndObject, new ExistingValue(instanceGet.value()));
-              }
+              handleInstanceGet(it, instruction.asInstanceGet(), field);
             } else if (instruction.isInstancePut()) {
               handleInstancePut(instruction.asInstancePut(), field);
             } else if (instruction.isStaticGet()) {
@@ -249,6 +239,11 @@
             // field values to change. In that case, it must be handled above.
             assert !instruction.instructionMayTriggerMethodInvocation(appView, method);
 
+            // Clear the field writes.
+            if (instruction.instructionInstanceCanThrow(appView, method)) {
+              activeState.clearMostRecentFieldWrites();
+            }
+
             // If this assertion fails for a new instruction we need to determine if that
             // instruction has side-effects that can change the value of fields. If so, it must be
             // handled above. If not, it can be safely added to the assert.
@@ -387,18 +382,62 @@
 
   private void handleInitClass(InstructionListIterator instructionIterator, InitClass initClass) {
     assert !initClass.outValue().hasAnyUsers();
+
     killNonFinalActiveFields(initClass);
+
+    // If the instruction can throw, we can't use any previous field stores for store-after-store
+    // elimination.
+    if (initClass.instructionInstanceCanThrow(appView, method)) {
+      activeState.clearMostRecentFieldWrites();
+    }
+
     DexType clazz = initClass.getClassValue();
     if (!activeState.markClassAsInitialized(clazz)) {
       instructionIterator.removeOrReplaceByDebugLocalRead();
     }
   }
 
+  private void handleInstanceGet(
+      InstructionListIterator it, InstanceGet instanceGet, DexClassAndField field) {
+    if (instanceGet.outValue().hasLocalInfo()) {
+      clearMostRecentInstanceFieldWrite(instanceGet, field);
+      return;
+    }
+
+    Value object = instanceGet.object().getAliasedValue();
+    FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
+    FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+    if (replacement != null) {
+      replacement.eliminateRedundantRead(it, instanceGet);
+      return;
+    }
+
+    activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(instanceGet.value()));
+    clearMostRecentInstanceFieldWrite(instanceGet, field);
+  }
+
+  private void clearMostRecentInstanceFieldWrite(InstanceGet instanceGet, DexClassAndField field) {
+    // If the instruction can throw, we need to clear all most-recent-writes, since subsequent field
+    // writes (if any) are not guaranteed to be executed.
+    if (instanceGet.instructionInstanceCanThrow(appView, method)) {
+      activeState.clearMostRecentFieldWrites();
+    } else {
+      activeState.clearMostRecentInstanceFieldWrite(field.getReference());
+    }
+  }
+
   private void handleInstancePut(InstancePut instancePut, DexClassAndField field) {
-    // An instance-put instruction can potentially write the given field on all objects
-    // because of aliases.
+    // An instance-put instruction can potentially write the given field on all objects because of
+    // aliases.
     activeState.removeNonFinalInstanceFields(field.getReference());
-    // ... but at least we know the field value for this particular object.
+
+    // If the instruction can throw, we can't use any previous field stores for store-after-store
+    // elimination.
+    if (instancePut.instructionInstanceCanThrow(appView, method)) {
+      activeState.clearMostRecentFieldWrites();
+    }
+
+    // Update the value of the field to allow redundant load elimination.
     Value object = instancePut.object().getAliasedValue();
     FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
     ExistingValue value = new ExistingValue(instancePut.value());
@@ -410,13 +449,16 @@
     } else {
       activeState.putNonFinalInstanceField(fieldAndObject, value);
 
-      InstancePut mostRecentInstanceFieldWrite =
-          activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
-      if (mostRecentInstanceFieldWrite != null) {
-        instructionsToRemove
-            .computeIfAbsent(
-                mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
-            .add(mostRecentInstanceFieldWrite);
+      // Record that this field is now most recently written by the current instruction.
+      if (release) {
+        InstancePut mostRecentInstanceFieldWrite =
+            activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
+        if (mostRecentInstanceFieldWrite != null) {
+          instructionsToRemove
+              .computeIfAbsent(
+                  mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+              .add(mostRecentInstanceFieldWrite);
+        }
       }
     }
   }
@@ -424,6 +466,8 @@
   private void handleStaticGet(
       InstructionListIterator instructionIterator, StaticGet staticGet, DexClassAndField field) {
     if (staticGet.outValue().hasLocalInfo()) {
+      killNonFinalActiveFields(staticGet);
+      clearMostRecentStaticFieldWrite(staticGet, field);
       return;
     }
 
@@ -435,6 +479,7 @@
 
     // A field get on a different class can cause <clinit> to run and change static field values.
     killNonFinalActiveFields(staticGet);
+    clearMostRecentStaticFieldWrite(staticGet, field);
 
     FieldValue value = new ExistingValue(staticGet.value());
     if (isFinal(field)) {
@@ -452,9 +497,26 @@
     }
   }
 
+  private void clearMostRecentStaticFieldWrite(StaticGet staticGet, DexClassAndField field) {
+    // If the instruction can throw, we need to clear all most-recent-writes, since subsequent field
+    // writes (if any) are not guaranteed to be executed.
+    if (staticGet.instructionInstanceCanThrow(appView, method)) {
+      activeState.clearMostRecentFieldWrites();
+    } else {
+      activeState.clearMostRecentStaticFieldWrite(field.getReference());
+    }
+  }
+
   private void handleStaticPut(StaticPut staticPut, DexClassAndField field) {
     // A field put on a different class can cause <clinit> to run and change static field values.
     killNonFinalActiveFields(staticPut);
+
+    // If the instruction can throw, we can't use any previous field stores for store-after-store
+    // elimination.
+    if (staticPut.instructionInstanceCanThrow(appView, method)) {
+      activeState.clearMostRecentFieldWrites();
+    }
+
     ExistingValue value = new ExistingValue(staticPut.value());
     if (isFinal(field)) {
       assert appView.checkForTesting(
@@ -463,13 +525,15 @@
     } else {
       activeState.putNonFinalStaticField(field.getReference(), value);
 
-      StaticPut mostRecentStaticFieldWrite =
-          activeState.putMostRecentStaticFieldWrite(field.getReference(), staticPut);
-      if (mostRecentStaticFieldWrite != null) {
-        instructionsToRemove
-            .computeIfAbsent(
-                mostRecentStaticFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
-            .add(mostRecentStaticFieldWrite);
+      if (release) {
+        StaticPut mostRecentStaticFieldWrite =
+            activeState.putMostRecentStaticFieldWrite(field.getReference(), staticPut);
+        if (mostRecentStaticFieldWrite != null) {
+          instructionsToRemove
+              .computeIfAbsent(
+                  mostRecentStaticFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+              .add(mostRecentStaticFieldWrite);
+        }
       }
     }
   }
@@ -704,10 +768,22 @@
       clearMostRecentStaticFieldWrites();
     }
 
+    public void clearMostRecentInstanceFieldWrite(DexField field) {
+      if (mostRecentInstanceFieldWrites != null) {
+        mostRecentInstanceFieldWrites.keySet().removeIf(key -> key.field == field);
+      }
+    }
+
     public void clearMostRecentInstanceFieldWrites() {
       mostRecentInstanceFieldWrites = null;
     }
 
+    public void clearMostRecentStaticFieldWrite(DexField field) {
+      if (mostRecentStaticFieldWrites != null) {
+        mostRecentStaticFieldWrites.remove(field);
+      }
+    }
+
     public void clearMostRecentStaticFieldWrites() {
       mostRecentStaticFieldWrites = null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index a735ad9..260a5a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -180,7 +178,8 @@
                 DexEncodedMethod addedMethod =
                     createSynthesizedMethod(service, classes, methodProcessingContext);
                 if (appView.options().isGeneratingClassFiles()) {
-                  addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
+                  addedMethod.upgradeClassFileVersion(
+                      code.context().getDefinition().getClassFileVersion());
                 }
                 return addedMethod;
               });
@@ -206,10 +205,11 @@
                     builder
                         .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                         .setProto(proto)
-                        .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
+                        .setApiLevelForDefinition(appView.computedMinApiLevel())
                         .setApiLevelForCode(
                             apiLevelCompute.computeApiLevelForDefinition(
-                                ListUtils.map(classes, clazz -> clazz.type)))
+                                ListUtils.map(classes, clazz -> clazz.type),
+                                apiLevelCompute.getPlatformApiLevelOrUnknown(appView)))
                         .setCode(
                             m ->
                                 ServiceLoaderSourceCode.generate(
@@ -248,7 +248,7 @@
     private final IRCode code;
     private final InvokeStatic serviceLoaderLoad;
 
-    private InstructionListIterator iterator;
+    private final InstructionListIterator iterator;
 
     Rewriter(IRCode code, InstructionListIterator iterator, InvokeStatic serviceLoaderLoad) {
       this.iterator = iterator;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index aaeb960..5639925 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
@@ -45,8 +43,8 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(method -> getToStringIfNotNullCodeTemplate(method, options))
                     .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
@@ -74,8 +72,8 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(
                         method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options))
                     .setProto(proto));
@@ -104,8 +102,8 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(method -> getThrowIllegalAccessErrorCodeTemplate(method, options))
                     .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
@@ -132,8 +130,8 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(
                         method -> getThrowIncompatibleClassChangeErrorCodeTemplate(method, options))
                     .setProto(proto));
@@ -162,8 +160,8 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(method -> getThrowNoSuchMethodErrorCodeTemplate(method, options))
                     .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index e392248..9997cc0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
@@ -186,8 +185,8 @@
                                   checkNotNullMethod
                                       .getDefinition()
                                       .getClassFileVersionOrElse(null))
-                              .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                              .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                              .setApiLevelForDefinition(appView.computedMinApiLevel())
+                              .setApiLevelForCode(appView.computedMinApiLevel())
                               .setCode(method -> new CheckNotZeroCode(checkNotNullMethod))
                               .setOptimizationInfo(
                                   checkNotNullMethod
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index ddf8c42..ed8958d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.cf.CfVersion;
@@ -131,8 +130,8 @@
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(codeGenerator)
                     .setClassFileVersion(CfVersion.V1_6));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 90d49ac..182b415 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
@@ -149,8 +147,8 @@
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-                    .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+                    .setApiLevelForDefinition(appView.computedMinApiLevel())
+                    .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(codeGenerator)
                     .setClassFileVersion(CfVersion.V1_6));
   }
@@ -232,7 +230,7 @@
                   dexItemFactory.createField(
                       sharedUtilityClassType, dexItemFactory.intArrayType, "$VALUES"))
               .setAccessFlags(FieldAccessFlags.createPublicStaticFinalSynthetic())
-              .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+              .setApiLevel(appView.computedMinApiLevel())
               .build();
       fieldAccessInfoCollectionModifierBuilder
           .recordFieldReadInUnknownContext(valuesField.getReference())
@@ -249,8 +247,8 @@
           .setAccessFlags(MethodAccessFlags.createForClassInitializer())
           .setCode(createClassInitializerCode(sharedUtilityClassType, valuesField))
           .setClassFileVersion(CfVersion.V1_6)
-          .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-          .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+          .setApiLevelForDefinition(appView.computedMinApiLevel())
+          .setApiLevelForCode(appView.computedMinApiLevel())
           .build();
     }
 
@@ -295,8 +293,8 @@
               .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
               .setCode(createValuesMethodCode(sharedUtilityClassType, valuesField))
               .setClassFileVersion(CfVersion.V1_6)
-              .setApiLevelForDefinition(minApiLevelIfEnabledOrUnknown(appView))
-              .setApiLevelForCode(minApiLevelIfEnabledOrUnknown(appView))
+              .setApiLevelForDefinition(appView.computedMinApiLevel())
+              .setApiLevelForCode(appView.computedMinApiLevel())
               .build();
       this.valuesMethod = valuesMethod;
       return valuesMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
index 27fa69a..5cb256c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -39,7 +38,7 @@
                       .setAccessFlags(
                           FieldAccessFlags.fromCfAccessFlags(
                               Constants.ACC_PRIVATE | Constants.ACC_FINAL))
-                      .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+                      .setApiLevel(appView.computedMinApiLevel())
                       // Will be traced by the enqueuer.
                       .disableAndroidApiLevelCheck()
                       .build());
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 6777d57..1a1cf0a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -4,12 +4,20 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
@@ -18,12 +26,16 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -31,6 +43,8 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer;
@@ -39,6 +53,7 @@
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
 
@@ -386,6 +401,159 @@
     }
   }
 
+  public static class EnumArrayConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final DexType enumType;
+    private final DexType convertedType;
+
+    public EnumArrayConversionCfCodeProvider(
+        AppView<?> appView, DexType holder, DexType enumType, DexType convertedType) {
+      super(appView, holder);
+      this.enumType = enumType;
+      this.convertedType = convertedType;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+      DexType enumTypeArray = factory.createArrayType(1, enumType);
+      DexType convertedTypeArray = factory.createArrayType(1, convertedType);
+
+      // if (arg == null) { return null; }
+      instructions.add(new CfLoad(ValueType.fromDexType(enumTypeArray), 0));
+      instructions.add(new CfConstNull());
+      CfLabel nonNull = new CfLabel();
+      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
+      instructions.add(new CfConstNull());
+      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
+      instructions.add(nonNull);
+      instructions.add(
+          new CfFrame(
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+                  .put(0, FrameType.initialized(enumTypeArray))
+                  .build(),
+              ImmutableDeque.of()));
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(enumTypeArray))
+              .put(1, FrameType.initialized(factory.intType))
+              .put(2, FrameType.initialized(convertedTypeArray))
+              .put(3, FrameType.initialized(factory.intType))
+              .build();
+
+      // int t1 = arg.length;
+      instructions.add(new CfLoad(ValueType.fromDexType(enumTypeArray), 0));
+      instructions.add(new CfArrayLength());
+      instructions.add(new CfStore(ValueType.INT, 1));
+      // ConvertedType[] t2 = new ConvertedType[t1];
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfNewArray(convertedTypeArray));
+      instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2));
+      // int t3 = 0;
+      instructions.add(new CfConstNumber(0, ValueType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      // while (t3 < t1) {
+      CfLabel returnLabel = new CfLabel();
+      CfLabel loopLabel = new CfLabel();
+      instructions.add(loopLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel));
+      // t2[t3] = convert(arg[t3]);
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.fromDexType(enumTypeArray), 0));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfArrayLoad(MemberType.OBJECT));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC,
+              factory.createMethod(
+                  getHolder(),
+                  factory.createProto(convertedType, enumType),
+                  factory.convertMethodName),
+              false));
+      instructions.add(new CfArrayStore(MemberType.OBJECT));
+      // t3 = t3 + 1; }
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfConstNumber(1, ValueType.INT));
+      instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      instructions.add(new CfGoto(loopLabel));
+      // return t2;
+      instructions.add(returnLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
+  public static class EnumConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final Iterable<DexEncodedField> enumFields;
+    private final DexType enumType;
+    private final DexType convertedType;
+
+    public EnumConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        Iterable<DexEncodedField> enumFields,
+        DexType enumType,
+        DexType convertedType) {
+      super(appView, holder);
+      this.enumFields = enumFields;
+      this.enumType = enumType;
+      this.convertedType = convertedType;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(enumType))
+              .build();
+
+      // if (arg == null) { return null; }
+      instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
+      instructions.add(new CfConstNull());
+      CfLabel nonNull = new CfLabel();
+      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
+      instructions.add(new CfConstNull());
+      instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
+      instructions.add(nonNull);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // if (arg == enumType.enumField1) { return convertedType.enumField1; }
+      Iterator<DexEncodedField> iterator = enumFields.iterator();
+      while (iterator.hasNext()) {
+        DexEncodedField enumField = iterator.next();
+        CfLabel notEqual = new CfLabel();
+        if (iterator.hasNext()) {
+          instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
+          instructions.add(
+              new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName())));
+          instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual));
+        }
+        instructions.add(
+            new CfStaticFieldRead(
+                factory.createField(convertedType, convertedType, enumField.getName())));
+        instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
+        if (iterator.hasNext()) {
+          instructions.add(notEqual);
+          instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+        }
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
   public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexField wrapperField;
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 129acae..19bbe92 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -87,7 +86,7 @@
                       .dexItemFactory()
                       .createField(clazz.type, clinitField.type, clinitField.name))
               .setAccessFlags(accessFlags)
-              .setApiLevel(minApiLevelIfEnabledOrUnknown(appView))
+              .setApiLevel(appView.computedMinApiLevel())
               .build();
       clazz.appendStaticField(encodedClinitField);
     }
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 941bcff..227cbf7 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -21,26 +22,25 @@
 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;
 
 public class DefaultEnqueuerUseRegistry extends UseRegistry<ProgramMethod> {
 
   protected final AppView<? extends AppInfoWithClassHierarchy> appView;
   protected final Enqueuer enqueuer;
-  private final AndroidApiLevelCompute computeApiLevel;
-  private AndroidApiLevel maxApiReferenceLevel;
+  private final AndroidApiLevelCompute apiLevelCompute;
+  private ComputedApiLevel maxApiReferenceLevel;
 
   public DefaultEnqueuerUseRegistry(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ProgramMethod context,
       Enqueuer enqueuer,
-      AndroidApiLevelCompute computeApiLevel) {
+      AndroidApiLevelCompute apiLevelCompute) {
     super(appView, context);
     this.appView = appView;
     this.enqueuer = enqueuer;
-    this.computeApiLevel = computeApiLevel;
-    this.maxApiReferenceLevel = appView.options().getMinApiLevel();
+    this.apiLevelCompute = apiLevelCompute;
+    maxApiReferenceLevel = appView.computedMinApiLevel();
   }
 
   public DexProgramClass getContextHolder() {
@@ -223,14 +223,18 @@
     if (reference.isDexMember()) {
       maxApiReferenceLevel =
           maxApiReferenceLevel.max(
-              computeApiLevel.computeApiLevelForDefinition(
-                  reference.asDexMember(), appView.dexItemFactory()));
+              apiLevelCompute.computeApiLevelForDefinition(
+                  reference.asDexMember(),
+                  appView.dexItemFactory(),
+                  apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
     }
     maxApiReferenceLevel =
-        maxApiReferenceLevel.max(computeApiLevel.computeApiLevelForLibraryReference(reference));
+        maxApiReferenceLevel.max(
+            apiLevelCompute.computeApiLevelForLibraryReference(
+                reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
   }
 
-  public AndroidApiLevel getMaxApiReferenceLevel() {
+  public ComputedApiLevel 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 fdd1b9f..9f30d38 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -82,8 +82,10 @@
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.graph.analysis.GetArrayOfMissingTypeVerifyErrorWorkaround;
 import com.android.tools.r8.graph.analysis.InvokeVirtualToInterfaceVerifyErrorWorkaround;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
@@ -235,11 +237,12 @@
   private final boolean forceProguardCompatibility;
   private final Mode mode;
 
-  private Set<EnqueuerAnalysis> analyses = new LinkedHashSet<>();
-  private Set<EnqueuerInvokeAnalysis> invokeAnalyses = new LinkedHashSet<>();
-  private Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = new LinkedHashSet<>();
-  private Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = new LinkedHashSet<>();
-  private Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = new LinkedHashSet<>();
+  private final Set<EnqueuerAnalysis> analyses = new LinkedHashSet<>();
+  private final Set<EnqueuerFieldAccessAnalysis> fieldAccessAnalyses = new LinkedHashSet<>();
+  private final Set<EnqueuerInvokeAnalysis> invokeAnalyses = new LinkedHashSet<>();
+  private final Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = new LinkedHashSet<>();
+  private final Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = new LinkedHashSet<>();
+  private final Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = new LinkedHashSet<>();
 
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
@@ -480,6 +483,7 @@
             : null;
 
     if (mode.isInitialOrFinalTreeShaking()) {
+      GetArrayOfMissingTypeVerifyErrorWorkaround.register(appView, this);
       InvokeVirtualToInterfaceVerifyErrorWorkaround.register(appView, this);
       if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
         registerAnalysis(new ProtoEnqueuerExtension(appView));
@@ -498,7 +502,7 @@
     liveFields = new LiveFieldsSet(graphReporter::registerField);
     apiLevelCompute = AndroidApiLevelCompute.create(appView);
     if (mode.isInitialTreeShaking()) {
-      desugaring = CfInstructionDesugaringCollection.create(appView, apiLevelCompute);
+      desugaring = CfInstructionDesugaringCollection.create(appView);
       interfaceProcessor = new InterfaceProcessor(appView);
     } else {
       desugaring = CfInstructionDesugaringCollection.empty();
@@ -538,6 +542,11 @@
     return this;
   }
 
+  public Enqueuer registerFieldAccessAnalysis(EnqueuerFieldAccessAnalysis analysis) {
+    fieldAccessAnalyses.add(analysis);
+    return this;
+  }
+
   public Enqueuer registerInvokeAnalysis(EnqueuerInvokeAnalysis analysis) {
     invokeAnalyses.add(analysis);
     return this;
@@ -1449,6 +1458,10 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    fieldAccessAnalyses.forEach(
+        analysis ->
+            analysis.traceInstanceFieldRead(fieldReference, resolutionResult, currentMethod));
+
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must trace the types from the field reference even if it does not exist.
       traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1503,6 +1516,10 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    fieldAccessAnalyses.forEach(
+        analysis ->
+            analysis.traceInstanceFieldWrite(fieldReference, resolutionResult, currentMethod));
+
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must trace the types from the field reference even if it does not exist.
       traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1555,6 +1572,9 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    fieldAccessAnalyses.forEach(
+        analysis -> analysis.traceStaticFieldRead(fieldReference, resolutionResult, currentMethod));
+
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must trace the types from the field reference even if it does not exist.
       traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1623,6 +1643,10 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    fieldAccessAnalyses.forEach(
+        analysis ->
+            analysis.traceStaticFieldWrite(fieldReference, resolutionResult, currentMethod));
+
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must trace the types from the field reference even if it does not exist.
       traceFieldReference(fieldReference, resolutionResult, currentMethod);
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 4f116de..9396f50 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -8,7 +8,8 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
-import com.android.tools.r8.androidapi.AndroidApiReferenceLevelCache;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -66,7 +67,6 @@
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -222,7 +222,7 @@
   private final MethodPoolCollection methodPoolCollection;
   private final Timing timing;
   private Collection<DexMethod> invokes;
-  private final AndroidApiReferenceLevelCache apiReferenceLevelCache;
+  private final AndroidApiLevelCompute apiLevelCompute;
 
   private final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
@@ -261,7 +261,7 @@
     this.executorService = executorService;
     this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo);
     this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory());
-    this.apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView);
+    this.apiLevelCompute = AndroidApiLevelCompute.create(appView);
     this.timing = timing;
 
     Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
@@ -531,11 +531,9 @@
     // Only merge if api reference level of source class is equal to target class. The check is
     // somewhat expensive.
     if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
-      AndroidApiLevel sourceApiLevel =
-          sourceClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
-      AndroidApiLevel targetApiLevel =
-          targetClass.getApiReferenceLevel(appView, apiReferenceLevelCache::lookupMax);
-      if (sourceApiLevel != targetApiLevel) {
+      ComputedApiLevel sourceApiLevel = sourceClass.getApiReferenceLevel(appView, apiLevelCompute);
+      ComputedApiLevel targetApiLevel = targetClass.getApiReferenceLevel(appView, apiLevelCompute);
+      if (!sourceApiLevel.equals(targetApiLevel)) {
         if (Log.ENABLED) {
           AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
         }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 56a9d20..0af4bf0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.NOT_SET;
-
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-import com.android.tools.r8.utils.AndroidApiLevel;
 
 public class SyntheticMethodBuilder {
 
@@ -39,8 +37,8 @@
   private MethodTypeSignature genericSignature = MethodTypeSignature.noSignature();
   private DexAnnotationSet annotations = DexAnnotationSet.empty();
   private ParameterAnnotationsList parameterAnnotationsList = ParameterAnnotationsList.empty();
-  private AndroidApiLevel apiLevelForDefinition = NOT_SET;
-  private AndroidApiLevel apiLevelForCode = NOT_SET;
+  private ComputedApiLevel apiLevelForDefinition = ComputedApiLevel.notSet();
+  private ComputedApiLevel apiLevelForCode = ComputedApiLevel.notSet();
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
 
   private boolean checkAndroidApiLevels = true;
@@ -109,12 +107,12 @@
     return this;
   }
 
-  public SyntheticMethodBuilder setApiLevelForDefinition(AndroidApiLevel apiLevelForDefinition) {
+  public SyntheticMethodBuilder setApiLevelForDefinition(ComputedApiLevel apiLevelForDefinition) {
     this.apiLevelForDefinition = apiLevelForDefinition;
     return this;
   }
 
-  public SyntheticMethodBuilder setApiLevelForCode(AndroidApiLevel apiLevelForCode) {
+  public SyntheticMethodBuilder setApiLevelForCode(ComputedApiLevel apiLevelForCode) {
     this.apiLevelForCode = apiLevelForCode;
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0a05c0f..41020e2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -34,6 +34,7 @@
     RETARGET_INTERFACE("RetargetInterface", 21, false, true),
     WRAPPER("$Wrapper", 22, false, true),
     VIVIFIED_WRAPPER("$VivifiedWrapper", 23, false, true),
+    ENUM_CONVERSION("$EnumConversion", 31, false, true),
     LAMBDA("Lambda", 4, false),
     INIT_TYPE_ARGUMENT("-IA", 5, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 6b82664..38934f2 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.utils.structural.Ordered;
 import java.util.Arrays;
 import java.util.List;
@@ -43,9 +42,7 @@
   R(30),
   S(31),
   Sv2(32),
-  ANDROID_PLATFORM(10000),
-  UNKNOWN(10001),
-  NOT_SET(10002);
+  ANDROID_PLATFORM(10000);
 
   // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
   // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
@@ -78,13 +75,6 @@
     return DexVersion.getDexVersion(this);
   }
 
-  public static AndroidApiLevel minApiLevelIfEnabledOrUnknown(AppView<?> appView) {
-    InternalOptions options = appView.options();
-    return options.apiModelingOptions().enableApiCallerIdentification
-        ? options.getMinApiLevel()
-        : UNKNOWN;
-  }
-
   public static List<AndroidApiLevel> getAndroidApiLevelsSorted() {
     return Arrays.asList(AndroidApiLevel.values());
   }
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 c4a7396..73cf09f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 
@@ -15,10 +16,6 @@
     if (!options.apiModelingOptions().enableApiCallerIdentification) {
       return true;
     }
-    if (options.isAndroidPlatform()) {
-      // Don't disable inlining in the Android platform based on the Api database.
-      return true;
-    }
     if (caller.getHolderType() == inlinee.getHolderType()) {
       return true;
     }
@@ -32,12 +29,13 @@
       LibraryMethod method,
       AndroidApiLevelCompute androidApiLevelCompute,
       InternalOptions options) {
-    AndroidApiLevel apiLevel =
-        androidApiLevelCompute.computeApiLevelForLibraryReference(method.getReference());
-    if (apiLevel == AndroidApiLevel.UNKNOWN) {
+    ComputedApiLevel apiLevel =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(
+            method.getReference(), ComputedApiLevel.unknown());
+    if (apiLevel.isUnknownApiLevel()) {
       return false;
     }
     assert options.apiModelingOptions().enableApiCallerIdentification;
-    return apiLevel.isLessThanOrEqualTo(options.getMinApiLevel());
+    return apiLevel.asKnownApiLevel().getApiLevel().isLessThanOrEqualTo(options.getMinApiLevel());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index a807bba..624e70e 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -77,7 +77,6 @@
       case L_MR1:
       case M:
         return DexVersion.V35;
-      case UNKNOWN:
       default:
         throw new Unreachable("Unsupported api level " + androidApiLevel);
     }
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 727f9c9..1cbbc21 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
+
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -17,6 +19,7 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.androidapi.AndroidApiForHashingClass;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
@@ -424,7 +427,7 @@
   }
 
   public boolean isAndroidPlatform() {
-    return minApiLevel == AndroidApiLevel.ANDROID_PLATFORM;
+    return minApiLevel == ANDROID_PLATFORM;
   }
 
   public boolean isDesugaredLibraryCompilation() {
@@ -564,7 +567,6 @@
 
   public void setMinApiLevel(AndroidApiLevel minApiLevel) {
     assert minApiLevel != null;
-    assert minApiLevel.isLessThan(AndroidApiLevel.UNKNOWN);
     this.minApiLevel = minApiLevel;
   }
 
@@ -1381,8 +1383,6 @@
     private boolean ignoreRuntimeTypeChecksForTesting = false;
     private boolean restrictToSynthetics = false;
 
-    public int maxGroupSize = 30;
-
     public void disable() {
       enable = false;
     }
@@ -1399,8 +1399,12 @@
       this.enable = enable;
     }
 
-    public int getMaxGroupSize() {
-      return maxGroupSize;
+    public int getMaxClassGroupSize() {
+      return 30;
+    }
+
+    public int getMaxInterfaceGroupSize() {
+      return 100;
     }
 
     public boolean isConstructorMergingEnabled() {
@@ -1468,7 +1472,7 @@
     public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
     public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
     public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>();
-    public BiConsumer<MethodReference, AndroidApiLevel> tracedMethodApiLevelCallback = null;
+    public BiConsumer<MethodReference, ComputedApiLevel> tracedMethodApiLevelCallback = null;
 
     public boolean enableApiCallerIdentification = true;
     public boolean checkAllApiReferencesAreSet = true;
@@ -1496,7 +1500,7 @@
 
                   @Override
                   public AndroidApiLevel getApiLevel() {
-                    return classApiMapping.getOrDefault(classReference, AndroidApiLevel.UNKNOWN);
+                    return classApiMapping.getOrDefault(classReference, ANDROID_PLATFORM);
                   }
 
                   @Override
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 53886e2..2d0cc12 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -867,8 +867,6 @@
       case J_MR2:
       case K_WATCH:
       case ANDROID_PLATFORM:
-      case UNKNOWN:
-      case NOT_SET:
         return false;
       default:
         return true;
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 d7db635..300eed5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -103,8 +103,7 @@
     for (int i = 0; i < integers.size(); i++) {
       indices[i] = integers.get(i);
       AndroidApiLevel androidApiLevel = apiLevelMap.get(integers.get(i));
-      apiLevel[i] =
-          (byte) (androidApiLevel == AndroidApiLevel.NOT_SET ? -1 : androidApiLevel.getLevel());
+      apiLevel[i] = (byte) (androidApiLevel == null ? -1 : androidApiLevel.getLevel());
     }
 
     try (FileOutputStream fileOutputStream = new FileOutputStream(pathToIndices.toFile());
@@ -160,7 +159,7 @@
     return ((reference, apiLevel) -> {
       AndroidApiLevel existingMethod = apiLevelMap.put(reference.hashCode(), apiLevel);
       if (existingMethod != null) {
-        apiLevelMap.put(reference.hashCode(), AndroidApiLevel.NOT_SET);
+        apiLevelMap.put(reference.hashCode(), null);
         Pair<DexReference, AndroidApiLevel> existingPair = reverseMap.get(reference.hashCode());
         addAmbiguousEntry(existingPair.getSecond(), existingPair.getFirst(), ambiguousMap);
         addAmbiguousEntry(apiLevel, reference, ambiguousMap);
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 88d53c1..9bbc751 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -126,7 +126,7 @@
         AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
     DexItemFactory factory = new DexItemFactory();
     AndroidApiLevelHashingDatabaseImpl androidApiLevelDatabase =
-        new AndroidApiLevelHashingDatabaseImpl(factory, ImmutableList.of());
+        new AndroidApiLevelHashingDatabaseImpl(ImmutableList.of());
     parsedApiClasses.forEach(
         parsedApiClass -> {
           DexType type = factory.createType(parsedApiClass.getClassReference().getDescriptor());
@@ -137,9 +137,13 @@
                   methodReferences.forEach(
                       methodReference -> {
                         DexMethod method = factory.createMethod(methodReference);
-                        androidApiLevelDatabase
-                            .getMethodApiLevel(method)
-                            .isLessThanOrEqualTo(methodApiLevel);
+                        AndroidApiLevel androidApiLevel;
+                        if (factory.objectMembers.isObjectMember(method)) {
+                          androidApiLevel = AndroidApiLevel.B;
+                        } else {
+                          androidApiLevel = androidApiLevelDatabase.getMethodApiLevel(method);
+                        }
+                        androidApiLevel.isLessThanOrEqualTo(methodApiLevel);
                       }));
           parsedApiClass.visitFieldReferences(
               (fieldApiLevel, fieldReferences) ->
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java
new file mode 100644
index 0000000..e5118ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelObjectInitTest.java
@@ -0,0 +1,126 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Constructor;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelObjectInitTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    boolean addToClassPath =
+        parameters.isDexRuntime()
+            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+    Constructor<LibraryClass> declaredConstructor =
+        LibraryClass.class.getDeclaredConstructor(int.class);
+    MethodReference shouldBeL =
+        Reference.methodFromMethod(Main.class.getDeclaredMethod("shouldBeL"));
+    MethodReference shouldBeN =
+        Reference.methodFromMethod(Main.class.getDeclaredMethod("shouldBeN"));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class)
+                // We replace java.lang.Object.toString with LibraryClass.toString
+                .transformMethodInsnInMethod(
+                    "main",
+                    (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                      if (name.equals("toString") && owner.equals(binaryName(Object.class))) {
+                        visitor.visitMethodInsn(
+                            opcode, binaryName(LibraryClass.class), name, descriptor, isInterface);
+                      } else {
+                        visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                      }
+                    })
+                .transform())
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.L))
+        .apply(setMockApiLevelForMethod(declaredConstructor, AndroidApiLevel.L))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, AndroidApiLevel.N))
+        .apply(
+            ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
+                (methodReference, apiLevel) -> {
+                  AndroidApiLevel currentLevel =
+                      parameters.isCfRuntime() ? AndroidApiLevel.B : parameters.getApiLevel();
+                  if (methodReference.equals(shouldBeL)) {
+                    Assert.assertEquals(AndroidApiLevel.L.max(currentLevel), apiLevel);
+                  }
+                  if (methodReference.equals(shouldBeN)) {
+                    Assert.assertEquals(AndroidApiLevel.N.max(currentLevel), apiLevel);
+                  }
+                }))
+        .addKeepMainRule(Main.class)
+        .addAndroidBuildVersion()
+        .enableInliningAnnotations()
+        .compile()
+        .applyIf(addToClassPath, b -> b.addRunClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLinesIf(!addToClassPath, "Old")
+        .assertSuccessWithOutputLinesIf(addToClassPath, "New");
+  }
+
+  public static class LibraryClass {
+
+    public LibraryClass() {
+      // Default constructor
+    }
+
+    public LibraryClass(int i) {
+      // Non default constructor.
+    }
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static void shouldBeL() {
+      LibraryClass libraryClass = new LibraryClass(1);
+    }
+
+    @NeverInline
+    public static void shouldBeN() {
+      if (new LibraryClass().toString().equals("FOO")) {
+        throw new RuntimeException("Unexpected toString value");
+      }
+    }
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 21) {
+        shouldBeL();
+        shouldBeN();
+        System.out.println("New");
+      } else {
+        System.out.println("Old");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
index c87f2cc..d4127e8 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
@@ -63,15 +63,22 @@
   }
 
   public static class AccessibilityNodeInfo$AccessibilityAction {
+
+    private int i;
+
+    public AccessibilityNodeInfo$AccessibilityAction(int i, CharSequence sequence) {
+      this.i = i;
+    }
+
     int describeContents() {
-      return 42;
+      return i;
     }
   }
 
   public static class Main {
 
     public static void main(String[] args) {
-      new AccessibilityNodeInfo$AccessibilityAction().describeContents();
+      new AccessibilityNodeInfo$AccessibilityAction(42, "foobar").describeContents();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 80e9aef..c8ee027 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.function.BiConsumer;
@@ -39,6 +41,20 @@
     };
   }
 
+  public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+      ThrowableConsumer<T> setMockApiLevelForMethod(
+          Constructor<?> constructor, AndroidApiLevel apiLevel) {
+    return compilerBuilder -> {
+      compilerBuilder.addOptionsModification(
+          options -> {
+            options
+                .apiModelingOptions()
+                .methodApiMapping
+                .put(Reference.methodFromMethod(constructor), apiLevel);
+          });
+    };
+  }
+
   static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
       ThrowableConsumer<T> setMockApiLevelForDefaultInstanceInitializer(
           Class<?> clazz, AndroidApiLevel apiLevel) {
@@ -103,7 +119,12 @@
     return compilerBuilder -> {
       compilerBuilder.addOptionsModification(
           options -> {
-            options.apiModelingOptions().tracedMethodApiLevelCallback = consumer;
+            options.apiModelingOptions().tracedMethodApiLevelCallback =
+                (methodReference, computedApiLevel) -> {
+                  assertTrue(computedApiLevel.isKnownApiLevel());
+                  consumer.accept(
+                      methodReference, computedApiLevel.asKnownApiLevel().getApiLevel());
+                };
           });
     };
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 67cce40..645588c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -97,8 +97,16 @@
     return parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
   }
 
+  protected boolean requiresTimeDesugaring(TestParameters parameters) {
+    return parameters.getApiLevel().getLevel()
+        < (isJDK11DesugaredLibrary() ? AndroidApiLevel.S.getLevel() : AndroidApiLevel.O.getLevel());
+  }
+
   protected boolean requiresAnyCoreLibDesugaring(TestParameters parameters) {
-    return parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel();
+    return parameters.getApiLevel().getLevel()
+        <= (isJDK11DesugaredLibrary()
+            ? AndroidApiLevel.LATEST.getLevel()
+            : AndroidApiLevel.N.getLevel());
   }
 
   protected L8TestBuilder testForL8(AndroidApiLevel apiLevel) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
index 8e1ab72..1245c7e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.LocalDate;
@@ -55,7 +54,7 @@
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Main.class);
-    if (shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.O)) {
+    if (shrinkDesugaredLibrary && requiresTimeDesugaring(parameters)) {
       runResult.assertFailureWithErrorThatMatches(
           containsString("java.lang.NoSuchMethodException"));
     } else {
@@ -80,7 +79,7 @@
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Main.class);
-    if (shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.O)) {
+    if (shrinkDesugaredLibrary && requiresTimeDesugaring(parameters)) {
       runResult.assertFailureWithErrorThatMatches(
           containsString("java.lang.NoSuchMethodException"));
     } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
index 78d3317..139103d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.LocalDate;
@@ -56,7 +55,7 @@
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Main.class);
-    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+    if (!requiresTimeDesugaring(parameters)) {
       runResult.assertFailureWithErrorThatMatches(
           containsString("java.lang.ClassNotFoundException: j$.time.LocalDate"));
     } else {
@@ -81,7 +80,7 @@
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
             .run(parameters.getRuntime(), Main.class);
-    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+    if (!requiresTimeDesugaring(parameters)) {
       runResult.assertFailureWithErrorThatMatches(
           containsString("java.lang.ClassNotFoundException: j$.time.LocalDate"));
     } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
index 2411f70..45ec5c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
@@ -36,15 +36,16 @@
   @Parameters(name = "api: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        range(AndroidApiLevel.K, AndroidApiLevel.ANDROID_PLATFORM),
+        range(AndroidApiLevel.K, AndroidApiLevel.LATEST),
         getTestParameters().withNoneRuntime().build());
   }
 
   private static List<AndroidApiLevel> range(
-      AndroidApiLevel fromIncluding, AndroidApiLevel toExcluding) {
+      AndroidApiLevel fromIncluding, AndroidApiLevel toIncluding) {
     ArrayList<AndroidApiLevel> result = new ArrayList<>();
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
-      if (apiLevel.isGreaterThanOrEqualTo(fromIncluding) && apiLevel.isLessThan(toExcluding)) {
+      if (apiLevel.isGreaterThanOrEqualTo(fromIncluding)
+          && apiLevel.isLessThanOrEqualTo(toIncluding)) {
         result.add(apiLevel);
       }
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
index 253763b..88f3ca9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.BufferedWriter;
 import java.io.File;
@@ -138,7 +137,7 @@
   public void testJ$ExtensionDesugaring() throws Exception {
     Assume.assumeFalse(parameters.isCfRuntime());
     // Above O no desugaring is required.
-    Assume.assumeTrue(parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel());
+    Assume.assumeTrue(requiresTimeDesugaring(parameters));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
 
     try {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 0e9d632..6598d6b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -96,7 +96,7 @@
     Set<String> expectedCatchGuards;
     Set<String> expectedCheckCastType;
     String expectedInstanceOfTypes;
-    if (parameters.getApiLevel().getLevel() >= 26) {
+    if (!requiresTimeDesugaring(parameters)) {
       expectedInvokeHolders =
           SetUtils.newHashSet("java.time.Clock", "java.time.LocalDate", "java.time.ZoneId");
       if (!isR8) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index 05282d5..ac25355 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -124,8 +124,7 @@
         allOf(
             markerTool(Tool.D8),
             markerIsDesugared(),
-            markerHasDesugaredLibraryIdentifier(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.O)));
+            markerHasDesugaredLibraryIdentifier(requiresAnyCoreLibDesugaring(parameters)));
     assertMarkersMatch(
         ExtractMarker.extractMarkerFromDexFile(app), ImmutableList.of(libraryMatcher, d8Matcher));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
index f5b2096..fbc2ad7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MonthTest.java
@@ -41,8 +41,7 @@
       return EXPECTED_JAVA_8_OUTPUT;
     }
     assert parameters.isDexRuntime();
-    // Assumes java.time is desugared only if any library desugaring is required, i.e., on 26.
-    if (requiresAnyCoreLibDesugaring(parameters)) {
+    if (requiresTimeDesugaring(parameters)) {
       return EXPECTED_JAVA_8_OUTPUT;
     }
     return EXPECTED_JAVA_9_OUTPUT;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AccessModeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AccessModeConversionTest.java
index 10a563d..d392dd8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AccessModeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AccessModeConversionTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.AccessMode;
 import java.nio.file.Path;
 import java.util.List;
@@ -29,7 +30,8 @@
   private final boolean shrinkDesugaredLibrary;
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
-  private static final String EXPECTED_RESULT = StringUtils.lines("WRITE", "WRITE");
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("READ", "WRITE", "READ", "WRITE", "EXECUTE");
 
   private static Path CUSTOM_LIB;
 
@@ -54,28 +56,38 @@
             .writeToZip();
   }
 
-  private void configureDesugaredLibrary(InternalOptions options) {
-    options.desugaredLibraryConfiguration =
+  private void configureDesugaredLibrary(InternalOptions options, boolean l8Compilation) {
+    DesugaredLibraryConfiguration.Builder builder =
         DesugaredLibraryConfiguration.builder(
                 options.itemFactory, options.reporter, Origin.unknown())
+            .setDesugaredLibraryIdentifier("com.tools.android:desugar_jdk_libs:9.99.99")
             .putRewritePrefix("java.nio.file.AccessMode", "j$.nio.file.AccessMode")
-            .addWrapperConversion("java.nio.file.AccessMode")
-            .build();
+            .addWrapperConversion("java.nio.file.AccessMode");
+    if (l8Compilation) {
+      builder.setLibraryCompilation();
+    }
+    options.desugaredLibraryConfiguration = builder.build();
   }
 
   @Test
   public void testD8() throws Exception {
-    Assume.assumeTrue(false);
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addLibraryFiles(getLibraryFile())
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .addOptionsModification(this::configureDesugaredLibrary)
+        .addOptionsModification(opt -> this.configureDesugaredLibrary(opt, false))
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
+            (apiLevel, keepRules, shrink) ->
+                this.buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    opt -> this.configureDesugaredLibrary(opt, true)),
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
@@ -86,7 +98,7 @@
 
   @Test
   public void testR8() throws Exception {
-    Assume.assumeTrue(false);
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
@@ -94,11 +106,16 @@
         .addKeepMainRule(Executor.class)
         .addProgramClasses(Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .addOptionsModification(this::configureDesugaredLibrary)
+        .addOptionsModification(opt -> this.configureDesugaredLibrary(opt, false))
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
-            this::buildDesugaredLibrary,
+            (apiLevel, keepRules, shrink) ->
+                this.buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    opt -> this.configureDesugaredLibrary(opt, true)),
             parameters.getApiLevel(),
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
@@ -111,7 +128,16 @@
 
     public static void main(String[] args) {
       System.out.println(CustomLibClass.get(AccessMode.READ));
-      System.out.println(CustomLibClass.get(new AccessMode[] {AccessMode.READ})[0]);
+      System.out.println(CustomLibClass.get(AccessMode.WRITE));
+      System.out.println(
+          CustomLibClass.get(
+              new AccessMode[] {AccessMode.READ, AccessMode.WRITE, AccessMode.EXECUTE})[0]);
+      System.out.println(
+          CustomLibClass.get(
+              new AccessMode[] {AccessMode.READ, AccessMode.WRITE, AccessMode.EXECUTE})[1]);
+      System.out.println(
+          CustomLibClass.get(
+              new AccessMode[] {AccessMode.READ, AccessMode.WRITE, AccessMode.EXECUTE})[2]);
     }
   }
 
@@ -121,11 +147,11 @@
   static class CustomLibClass {
 
     public static AccessMode get(AccessMode mode) {
-      return AccessMode.WRITE;
+      return mode;
     }
 
     public static AccessMode[] get(AccessMode[] modes) {
-      return new AccessMode[] {AccessMode.WRITE};
+      return modes;
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
index a045372..01f9b80 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
@@ -60,14 +59,14 @@
         inspector.allClasses().stream()
             .filter(c -> c.getOriginalName().contains("Conversions"))
             .collect(Collectors.toList());
-    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+    if (requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
       assertEquals(5, conversionsClasses.size());
       assertTrue(inspector.clazz("j$.util.OptionalConversions").isPresent());
       assertTrue(inspector.clazz("j$.time.TimeConversions").isPresent());
       assertTrue(inspector.clazz("j$.util.LongSummaryStatisticsConversions").isPresent());
       assertTrue(inspector.clazz("j$.util.IntSummaryStatisticsConversions").isPresent());
       assertTrue(inspector.clazz("j$.util.DoubleSummaryStatisticsConversions").isPresent());
-    } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O)) {
+    } else if (requiresTimeDesugaring(parameters)) {
       assertEquals(1, conversionsClasses.size());
       assertTrue(inspector.clazz("j$.time.TimeConversions").isPresent());
     } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index 6e9d7ca..0874b57 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -48,7 +48,7 @@
     }
     Path desugaredLibJDK11Undesugared = Paths.get("build/libs/desugar_jdk_libs_11_undesugared.jar");
     if (Files.exists(desugaredLibJDK11Undesugared)) {
-        return desugaredLibJDK11Undesugared;
+      return desugaredLibJDK11Undesugared;
     }
     return generateUndesugaredJar(desugaredLibJDK11Undesugared);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DurationJDK11Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DurationJDK11Test.java
index d95f9c8..e16f9e1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DurationJDK11Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DurationJDK11Test.java
@@ -44,10 +44,6 @@
   @Test
   public void testD8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    Assume.assumeFalse(
-        "TODO(b/206068300)",
-        parameters.getApiLevel().getLevel() >= AndroidApiLevel.O.getLevel()
-            && parameters.getApiLevel().getLevel() < AndroidApiLevel.S.getLevel());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8(parameters.getBackend())
         .addLibraryFiles(getLibraryFile())
@@ -67,10 +63,6 @@
   @Test
   public void testR8() throws Exception {
     Assume.assumeTrue(isJDK11DesugaredLibrary());
-    Assume.assumeFalse(
-        "TODO(b/206068300)",
-        parameters.getApiLevel().getLevel() >= AndroidApiLevel.O.getLevel()
-            && parameters.getApiLevel().getLevel() < AndroidApiLevel.S.getLevel());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addLibraryFiles(getLibraryFile())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index 1928523..9d4ad7e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -234,7 +234,7 @@
     for (String issue : formattingProblem) {
       D8TestRunResult result =
           compileResult.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, issue);
-      if (requiresAnyCoreLibDesugaring(parameters)) {
+      if (requiresTimeDesugaring(parameters)) {
         // Fails due to formatting differences in desugared library.
         assertTrue(result.getStdOut().contains("for style NARROW"));
       } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index 27e9882..b58b4e1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -118,14 +118,14 @@
                 false)
             .run(parameters.getRuntime(), PKG + ".MainKt")
             .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
-    if (requiresAnyCoreLibDesugaring(parameters)) {
+    if (requiresTimeDesugaring(parameters)) {
       d8TestRunResult.inspect(this::inspectRewrittenMetadata);
     }
   }
 
   @Test
   public void testTimeR8() throws Exception {
-    boolean desugarLibrary = parameters.isDexRuntime() && requiresAnyCoreLibDesugaring(parameters);
+    boolean desugarLibrary = parameters.isDexRuntime() && requiresTimeDesugaring(parameters);
     final R8FullTestBuilder testBuilder =
         testForR8(parameters.getBackend())
             .addLibraryFiles(getLibraryFile())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/FieldReadBeforeOtherwiseRedundantStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/FieldReadBeforeOtherwiseRedundantStoreTest.java
new file mode 100644
index 0000000..6f8b296
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/FieldReadBeforeOtherwiseRedundantStoreTest.java
@@ -0,0 +1,70 @@
+// 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.redundantfieldloadelimination;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldReadBeforeOtherwiseRedundantStoreTest extends TestBase {
+
+  @Parameter(0)
+  public CompilationMode mode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, mode: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        CompilationMode.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8()
+        .addInnerClasses(FieldReadBeforeOtherwiseRedundantStoreTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .setMode(mode)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess();
+  }
+
+  static class Main {
+
+    public static int field = 0;
+
+    public int testLoop() {
+      int iterations = 50;
+      for (int i = 0; i < iterations; i++) {
+        int a = field;
+        field = a + i;
+        int b = field;
+        field = b + 2 * i;
+      }
+      assertIntEquals(field, 3675);
+      return field;
+    }
+
+    public static void main(String[] args) {
+      Main obj = new Main();
+      obj.testLoop();
+    }
+
+    public static void assertIntEquals(int expected, int result) {
+      if (expected != result) {
+        throw new Error("Expected: " + expected + ", found: " + result);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
index cad2c50..af7854c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
@@ -1,3 +1,7 @@
+// 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.redundantfieldloadelimination;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
new file mode 100644
index 0000000..4c3f07d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest.java
@@ -0,0 +1,61 @@
+// 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.redundantfieldloadelimination;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ThrowingInstructionBeforeOtherwiseRedundantInstanceStoreTest extends TestBase {
+
+  @Parameter(0)
+  public CompilationMode mode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, mode: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        CompilationMode.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1");
+  }
+
+  static class Main {
+
+    int sum = 0;
+
+    public static void main(String[] args) {
+      Main main = new Main();
+      try {
+        main.test(new int[] {1});
+        throw new RuntimeException();
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println(main.sum);
+      }
+    }
+
+    void test(int[] array) {
+      sum += array[0];
+      sum += array[1];
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
new file mode 100644
index 0000000..60a0b34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest.java
@@ -0,0 +1,60 @@
+// 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.redundantfieldloadelimination;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ThrowingInstructionBeforeOtherwiseRedundantStaticStoreTest extends TestBase {
+
+  @Parameter(0)
+  public CompilationMode mode;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, mode: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        CompilationMode.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1");
+  }
+
+  static class Main {
+
+    static int sum = 0;
+
+    public static void main(String[] args) {
+      try {
+        test(new int[] {1});
+        throw new RuntimeException();
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println(sum);
+      }
+    }
+
+    static void test(int[] array) {
+      sum += array[0];
+      sum += array[1];
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index c1da822..89f57c8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -73,7 +73,8 @@
                           .allMatch(
                               mergeGroup ->
                                   mergeGroup.size()
-                                      <= defaultHorizontalClassMergerOptions.getMaxGroupSize()));
+                                      <= defaultHorizontalClassMergerOptions
+                                          .getMaxClassGroupSize()));
                 })
             .allowDiagnosticWarningMessages()
             .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index 86ccd24..51bd0cf 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -189,7 +189,7 @@
                       new DataResourceConsumerForTesting(options.dataResourceConsumer);
                   options.dataResourceConsumer = dataResourceConsumer;
                 })
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), OtherTestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java b/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java
new file mode 100644
index 0000000..aba5532
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java
@@ -0,0 +1,115 @@
+// 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.workaround;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayFieldGetWithMissingBaseTypeTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .applyIf(
+            parameters.isDexRuntime(),
+            testBuilder -> testBuilder.addLibraryFiles(ToolHelper.getMostRecentAndroidJar()))
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Utils.class)
+        .addKeepRules(
+            "-keep class " + Main.class.getTypeName() + " { void notUsedDuringLaunch(); }")
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .applyIf(
+                        parameters.isDexRuntime()
+                            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L),
+                        i ->
+                            i.assertIsCompleteMergeGroup(
+                                UsedDuringLaunch.class, NotUsedDuringLaunch.class))
+                    .assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> o.apiModelingOptions().disableApiCallerIdentification())
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new UsedDuringLaunch().usedDuringLaunch();
+    }
+
+    // @Keep
+    static void notUsedDuringLaunch() {
+      Consumer<?> emptyConsumer = Utils.getEmptyConsumer();
+      new UsedDuringLaunch().onlyUsedOnHighApiLevels(emptyConsumer);
+      NotUsedDuringLaunch.f = new Consumer<?>[] {emptyConsumer};
+      new NotUsedDuringLaunch().illegalUseOfConsumerArrayOnDalvik();
+    }
+  }
+
+  @NeverClassInline
+  static class UsedDuringLaunch {
+
+    @NeverInline
+    void usedDuringLaunch() {
+      System.out.println("Hello world!");
+    }
+
+    @NeverInline
+    void onlyUsedOnHighApiLevels(Consumer<?> c) {
+      System.out.println(c);
+    }
+  }
+
+  @NeverClassInline
+  static class NotUsedDuringLaunch {
+
+    static Consumer<?>[] f;
+
+    @NeverInline
+    void illegalUseOfConsumerArrayOnDalvik() {
+      Utils.accept(f);
+    }
+  }
+
+  // @Keep
+  static class Utils {
+
+    // @Keep
+    static void accept(Consumer<?>[] array) {
+      System.out.println(array.length);
+    }
+
+    // @Keep
+    public static Consumer<?> getEmptyConsumer() {
+      return ignore -> {};
+    }
+  }
+}