[ApiModel] Amend the synthetic marker annotation with api-level

Fixes: b/268596049
Change-Id: Id6e5a12ca1888745741ff399f0c62f2f9e769b19
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index aff4bff..e24a22c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 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.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.synthesis.SyntheticItems;
 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.Pair;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -56,6 +59,9 @@
   public final int visibility;
   public final DexEncodedAnnotation annotation;
 
+  private static final int UNKNOWN_API_LEVEL = -1;
+  private static final int NOT_SET_API_LEVEL = -2;
+
   private static void specify(StructuralSpecification<DexAnnotation, ?> spec) {
     spec.withItem(a -> a.annotation).withInt(a -> a.visibility);
   }
@@ -469,26 +475,37 @@
   }
 
   public static DexAnnotation createAnnotationSynthesizedClass(
-      SyntheticKind kind, DexItemFactory dexItemFactory) {
+      SyntheticKind kind, DexItemFactory dexItemFactory, ComputedApiLevel computedApiLevel) {
     DexString versionHash =
         dexItemFactory.createString(dexItemFactory.getSyntheticNaming().getVersionHash());
     DexAnnotationElement kindElement =
         new DexAnnotationElement(dexItemFactory.kindString, DexValueInt.create(kind.getId()));
     DexAnnotationElement versionHashElement =
         new DexAnnotationElement(dexItemFactory.versionHashString, new DexValueString(versionHash));
-    DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement, versionHashElement};
+    int apiLevel = getApiLevelForSerialization(computedApiLevel);
+    DexAnnotationElement apiLevelElement =
+        new DexAnnotationElement(dexItemFactory.apiLevelString, DexValueInt.create(apiLevel));
+    DexAnnotationElement[] elements =
+        new DexAnnotationElement[] {apiLevelElement, kindElement, versionHashElement};
     return new DexAnnotation(
         VISIBILITY_BUILD,
         new DexEncodedAnnotation(dexItemFactory.annotationSynthesizedClass, elements));
   }
 
   public static boolean hasSynthesizedClassAnnotation(
-      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
-    return getSynthesizedClassAnnotationInfo(annotations, factory, synthetics) != null;
+      DexAnnotationSet annotations,
+      DexItemFactory factory,
+      SyntheticItems synthetics,
+      AndroidApiLevelCompute apiLevelCompute) {
+    return getSynthesizedClassAnnotationInfo(annotations, factory, synthetics, apiLevelCompute)
+        != null;
   }
 
-  public static SyntheticKind getSynthesizedClassAnnotationInfo(
-      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
+  public static SynthesizedAnnotationClassInfo getSynthesizedClassAnnotationInfo(
+      DexAnnotationSet annotations,
+      DexItemFactory factory,
+      SyntheticItems synthetics,
+      AndroidApiLevelCompute apiLevelCompute) {
     if (annotations.size() != 1) {
       return null;
     }
@@ -497,12 +514,13 @@
       return null;
     }
     int length = annotation.annotation.elements.length;
-    if (length != 2) {
+    if (length != 3) {
       return null;
     }
     assert factory.kindString.isLessThan(factory.versionHashString);
-    DexAnnotationElement kindElement = annotation.annotation.elements[0];
-    DexAnnotationElement versionHashElement = annotation.annotation.elements[1];
+    DexAnnotationElement apiLevelElement = annotation.annotation.elements[0];
+    DexAnnotationElement kindElement = annotation.annotation.elements[1];
+    DexAnnotationElement versionHashElement = annotation.annotation.elements[2];
     if (kindElement.name != factory.kindString) {
       return null;
     }
@@ -515,14 +533,43 @@
     if (!versionHashElement.value.isDexValueString()) {
       return null;
     }
+    if (apiLevelElement.name != factory.apiLevelString || !apiLevelElement.value.isDexValueInt()) {
+      return null;
+    }
     String currentVersionHash = synthetics.getNaming().getVersionHash();
     String syntheticVersionHash = versionHashElement.value.asDexValueString().getValue().toString();
     if (!currentVersionHash.equals(syntheticVersionHash)) {
       return null;
     }
-    SyntheticKind kind =
+    int apiLevelValue = apiLevelElement.value.asDexValueInt().getValue();
+    ComputedApiLevel computedApiLevel = getSerializedApiLevel(apiLevelCompute, apiLevelValue);
+    SyntheticKind syntheticKind =
         synthetics.getNaming().fromId(kindElement.value.asDexValueInt().getValue());
-    return kind;
+    assert syntheticKind != synthetics.getNaming().API_MODEL_OUTLINE
+        || computedApiLevel.isKnownApiLevel();
+    return SynthesizedAnnotationClassInfo.create(syntheticKind, computedApiLevel);
+  }
+
+  private static int getApiLevelForSerialization(ComputedApiLevel computedApiLevel) {
+    if (computedApiLevel.isNotSetApiLevel()) {
+      return NOT_SET_API_LEVEL;
+    } else if (computedApiLevel.isUnknownApiLevel()) {
+      return UNKNOWN_API_LEVEL;
+    } else {
+      assert computedApiLevel.isKnownApiLevel();
+      return computedApiLevel.asKnownApiLevel().getApiLevel().getLevel();
+    }
+  }
+
+  private static ComputedApiLevel getSerializedApiLevel(
+      AndroidApiLevelCompute apiLevelCompute, int apiLevelValue) {
+    if (apiLevelValue == NOT_SET_API_LEVEL) {
+      return ComputedApiLevel.notSet();
+    } else if (apiLevelValue == UNKNOWN_API_LEVEL) {
+      return ComputedApiLevel.unknown();
+    } else {
+      return apiLevelCompute.of(AndroidApiLevel.getAndroidApiLevel(apiLevelValue));
+    }
   }
 
   public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
@@ -535,4 +582,29 @@
     }
     return new DexAnnotation(visibility, rewritten);
   }
+
+  public static class SynthesizedAnnotationClassInfo {
+
+    private final SyntheticKind syntheticKind;
+    private final ComputedApiLevel computedApiLevel;
+
+    private SynthesizedAnnotationClassInfo(
+        SyntheticKind syntheticKind, ComputedApiLevel computedApiLevel) {
+      this.syntheticKind = syntheticKind;
+      this.computedApiLevel = computedApiLevel;
+    }
+
+    private static SynthesizedAnnotationClassInfo create(
+        SyntheticKind syntheticKind, ComputedApiLevel computedApiLevel) {
+      return new SynthesizedAnnotationClassInfo(syntheticKind, computedApiLevel);
+    }
+
+    public SyntheticKind getSyntheticKind() {
+      return syntheticKind;
+    }
+
+    public ComputedApiLevel getComputedApiLevel() {
+      return computedApiLevel;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4ffba91..df3709f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -356,6 +356,7 @@
   public final DexString valueString = createString("value");
   public final DexString kindString = createString("kind");
   public final DexString versionHashString = createString("versionHash");
+  public final DexString apiLevelString = createString("apiLevel");
 
   // Prefix for runtime affecting yet potential class-retained annotations.
   public final DexString dalvikAnnotationOptimizationPrefix =
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 be7bf1c..2d11359 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.ComputeApiLevelOfSyntheticClass;
 import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitInterfaceGroups;
@@ -143,8 +142,7 @@
     ImmutableList.Builder<SingleClassPolicy> builder =
         ImmutableList.<SingleClassPolicy>builder()
             .add(new CheckSyntheticClasses(appView))
-            .add(new OnlyClassesWithStaticDefinitionsAndNoClassInitializer())
-            .add(new ComputeApiLevelOfSyntheticClass(appView));
+            .add(new OnlyClassesWithStaticDefinitionsAndNoClassInitializer());
     assert verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java
deleted file mode 100644
index 9038a57..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ComputeApiLevelOfSyntheticClass.java
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright (c) 2022, 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.androidapi.AndroidApiLevelCompute;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.synthesis.SyntheticItems;
-import java.util.ListIterator;
-
-public class ComputeApiLevelOfSyntheticClass extends SingleClassPolicy {
-
-  private final AppView<?> appView;
-  private final SyntheticItems syntheticItems;
-
-  public ComputeApiLevelOfSyntheticClass(AppView<?> appView) {
-    this.appView = appView;
-    this.syntheticItems = appView.getSyntheticItems();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass clazz) {
-    assert syntheticItems.isSyntheticClass(clazz);
-    clazz.forEachProgramMethod(
-        programMethod -> {
-          DexEncodedMethod definition = programMethod.getDefinition();
-          if (definition.getApiLevelForCode().isNotSetApiLevel()) {
-            ComputeApiLevelUseRegistry computeApiLevel =
-                new ComputeApiLevelUseRegistry(appView, programMethod, appView.apiLevelCompute());
-            computeApiLevel.accept(programMethod);
-            ComputedApiLevel maxApiReferenceLevel = computeApiLevel.getMaxApiReferenceLevel();
-            assert !maxApiReferenceLevel.isNotSetApiLevel();
-            definition.setApiLevelForCode(maxApiReferenceLevel);
-            definition.setApiLevelForDefinition(computeApiLevel.getMaxApiReferenceLevel());
-          }
-        });
-    return true;
-  }
-
-  @Override
-  public String getName() {
-    return "ComputeApiLevelOfSyntheticClass";
-  }
-
-  private static class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
-
-    private final AppView<?> appView;
-    private final AndroidApiLevelCompute apiLevelCompute;
-    private ComputedApiLevel maxApiReferenceLevel;
-
-    public ComputeApiLevelUseRegistry(
-        AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
-      super(appView, context);
-      this.appView = appView;
-      this.apiLevelCompute = apiLevelCompute;
-      maxApiReferenceLevel = appView.computedMinApiLevel();
-    }
-
-    @Override
-    public void registerInitClass(DexType clazz) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerRecordFieldValues(DexField[] fields) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod invokedMethod) {
-      setMaxApiReferenceLevel(invokedMethod);
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldReadFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerNewInstance(DexType type) {
-      setMaxApiReferenceLevel(type);
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldReadFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerStaticFieldWriteFromMethodHandle(DexField field) {
-      setMaxApiReferenceLevel(field);
-    }
-
-    @Override
-    public void registerConstClass(
-        DexType type,
-        ListIterator<? extends CfOrDexInstruction> iterator,
-        boolean ignoreCompatRules) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerSafeCheckCast(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      // Intentionally empty.
-    }
-
-    @Override
-    public void registerExceptionGuard(DexType guard) {
-      setMaxApiReferenceLevel(guard);
-    }
-
-    @Override
-    public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    @Override
-    public void registerCallSite(DexCallSite callSite) {
-      assert false : "Unexpected call to an instruction that should not exist on DEX";
-    }
-
-    private void setMaxApiReferenceLevel(DexReference reference) {
-      maxApiReferenceLevel =
-          maxApiReferenceLevel.max(apiLevelCompute.computeApiLevelForLibraryReference(reference));
-    }
-
-    public ComputedApiLevel getMaxApiReferenceLevel() {
-      return maxApiReferenceLevel;
-    }
-  }
-}
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 0024b64..0e899f7 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging.policies;
 
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getMembersApiReferenceLevelForMerging;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -15,14 +16,16 @@
 public class NoDifferentApiReferenceLevel extends MultiClassSameReferencePolicy<ComputedApiLevel> {
 
   private final AndroidApiLevelCompute apiLevelCompute;
-  private final AppView<?> appView;
   private final boolean enableApiCallerIdentification;
+  private final boolean enableWholeProgramOptimization;
+  private final ComputedApiLevel minApiLevel;
 
   public NoDifferentApiReferenceLevel(AppView<?> appView) {
-    this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
     enableApiCallerIdentification =
         appView.options().apiModelingOptions().isApiCallerIdentificationEnabled();
+    enableWholeProgramOptimization = appView.enableWholeProgramOptimizations();
+    minApiLevel = appView.computedMinApiLevel();
   }
 
   @Override
@@ -39,7 +42,9 @@
   public ComputedApiLevel getMergeKey(DexProgramClass clazz) {
     assert enableApiCallerIdentification;
     ComputedApiLevel apiReferenceLevelForMerging =
-        getApiReferenceLevelForMerging(appView, apiLevelCompute, clazz);
+        enableWholeProgramOptimization
+            ? getApiReferenceLevelForMerging(apiLevelCompute, clazz)
+            : getMembersApiReferenceLevelForMerging(clazz, minApiLevel);
     if (apiReferenceLevelForMerging.isUnknownApiLevel()) {
       return ineligibleForClassMerging();
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 4cc4154..8baf037 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.DexCallSite;
 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.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -22,6 +24,7 @@
 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.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.Box;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -41,6 +44,9 @@
   }
 
   public void run(Set<DexType> roots) {
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    DexItemFactory factory = appView.dexItemFactory();
+    AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
     for (DexType type : roots) {
       DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
       // Should only happen for library classes, which are filtered out.
@@ -48,7 +54,7 @@
       consumer.accept(type);
       // Super and interfaces are live, no need to add them.
       if (!DexAnnotation.hasSynthesizedClassAnnotation(
-          clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems())) {
+          clazz.annotations(), factory, syntheticItems, apiLevelCompute)) {
         traceAnnotationsDirectDependencies(clazz.annotations());
       }
       clazz.forEachField(field -> consumer.accept(field.getReference().type));
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 da2b320..dc4c7ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -537,9 +537,9 @@
     // somewhat expensive.
     if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
       ComputedApiLevel sourceApiLevel =
-          getApiReferenceLevelForMerging(appView, apiLevelCompute, sourceClass);
+          getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
       ComputedApiLevel targetApiLevel =
-          getApiReferenceLevelForMerging(appView, apiLevelCompute, targetClass);
+          getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
       if (!sourceApiLevel.equals(targetApiLevel)) {
         if (Log.ENABLED) {
           AbortReason.API_REFERENCE_LEVEL.printLogMessageForClass(sourceClass);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index ebb3ba0..8214303 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -572,7 +572,7 @@
       DexProgramClass externalSyntheticClass,
       AppView<?> appView) {
     if (shouldAnnotateSynthetics(appView.options())) {
-      SyntheticMarker.addMarkerToClass(externalSyntheticClass, kind, appView.options());
+      SyntheticMarker.addMarkerToClass(externalSyntheticClass, kind, appView);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index 67dbe6a..aebc9ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -6,14 +6,15 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.SynthesizedAnnotationClassInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import java.nio.charset.StandardCharsets;
 import org.objectweb.asm.Attribute;
 import org.objectweb.asm.ByteVector;
@@ -108,14 +109,18 @@
   }
 
   public static void addMarkerToClass(
-      DexProgramClass clazz, SyntheticKind kind, InternalOptions options) {
+      DexProgramClass clazz, SyntheticKind kind, AppView<?> appView) {
     // TODO(b/158159959): Consider moving this to the dex writer similar to the CF case.
-    assert !options.isGeneratingClassFiles();
+    assert !appView.options().isGeneratingClassFiles();
     clazz.setAnnotations(
         clazz
             .annotations()
             .getWithAddedOrReplaced(
-                DexAnnotation.createAnnotationSynthesizedClass(kind, options.itemFactory)));
+                DexAnnotation.createAnnotationSynthesizedClass(
+                    kind,
+                    appView.options().itemFactory,
+                    AndroidApiLevelUtils.getApiReferenceLevelForMerging(
+                        appView.apiLevelCompute(), clazz))));
   }
 
   public static SyntheticMarker stripMarkerFromClass(DexProgramClass clazz, AppView<?> appView) {
@@ -134,7 +139,10 @@
     SyntheticMarker marker = internalStripMarkerFromClass(clazz, appView);
     assert marker != NO_MARKER
         || !DexAnnotation.hasSynthesizedClassAnnotation(
-            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
+            clazz.annotations(),
+            appView.dexItemFactory(),
+            appView.getSyntheticItems(),
+            appView.apiLevelCompute());
     return marker;
   }
 
@@ -146,13 +154,17 @@
     if (isDefinitelyNotSyntheticProgramClass(clazz)) {
       return NO_MARKER;
     }
-    SyntheticKind kind =
+    SynthesizedAnnotationClassInfo synthesizedInfo =
         DexAnnotation.getSynthesizedClassAnnotationInfo(
-            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
-    if (kind == null) {
+            clazz.annotations(),
+            appView.dexItemFactory(),
+            appView.getSyntheticItems(),
+            appView.apiLevelCompute());
+    if (synthesizedInfo == null) {
       return NO_MARKER;
     }
     assert clazz.annotations().size() == 1;
+    SyntheticKind kind = synthesizedInfo.getSyntheticKind();
     if (kind.isSingleSyntheticMethod()) {
       if (!clazz.interfaces.isEmpty()) {
         return NO_MARKER;
@@ -164,6 +176,7 @@
       }
     }
     clazz.setAnnotations(DexAnnotationSet.empty());
+    clazz.forEachMethod(method -> method.setApiLevelForCode(synthesizedInfo.getComputedApiLevel()));
     DexType context = getSyntheticContextType(clazz.type, kind, appView.dexItemFactory());
     return new SyntheticMarker(
         kind, SynthesizingContext.fromSyntheticInputClass(clazz, context, appView));
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 841bf9a..3067de7 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -65,20 +65,24 @@
   }
 
   public static ComputedApiLevel getApiReferenceLevelForMerging(
-      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
+      AndroidApiLevelCompute apiLevelCompute, DexProgramClass clazz) {
     // The api level of a class is the max level of it's members, super class and interfaces.
     return getMembersApiReferenceLevelForMerging(
         clazz, apiLevelCompute.computeApiLevelForDefinition(clazz.allImmediateSupertypes()));
   }
 
-  private static ComputedApiLevel getMembersApiReferenceLevelForMerging(
+  public static ComputedApiLevel getMembersApiReferenceLevelForMerging(
       DexProgramClass clazz, ComputedApiLevel memberLevel) {
     // Based on b/138781768#comment57 there is almost no penalty for having an unknown reference
     // as long as we are not invoking or accessing a field on it. Therefore we can disregard static
     // types of fields and only consider method code api levels.
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.hasCode()) {
-        memberLevel = memberLevel.max(method.getApiLevelForCode());
+        ComputedApiLevel apiLevelForCode = method.getApiLevelForCode();
+        if (apiLevelForCode.isNotSetApiLevel()) {
+          return ComputedApiLevel.notSet();
+        }
+        memberLevel = memberLevel.max(apiLevelForCode);
       }
       if (memberLevel.isUnknownApiLevel()) {
         return memberLevel;
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 7480fc8..19177be 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1909,18 +1909,6 @@
     public void disableStubbingOfClasses() {
       enableStubbingOfClasses = false;
     }
-
-    private boolean isThrowable(AppView<?> appView, DexLibraryClass libraryClass) {
-      DexClass current = libraryClass;
-      while (current.getSuperType() != null) {
-        DexType superType = current.getSuperType();
-        if (superType == appView.dexItemFactory().throwableType) {
-          return true;
-        }
-        current = appView.definitionFor(current.getSuperType());
-      }
-      return false;
-    }
   }
 
   public static class ProtoShrinkingOptions {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
index 6560933..f0b5d28 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
@@ -7,17 +7,13 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.SingleTestRunResult;
@@ -127,22 +123,13 @@
   }
 
   @Test
-  public void testD8ReleaseForApiLevelWithOutlining() {
+  public void testD8ReleaseForApiLevelWithOutlining() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     assumeTrue(willHorizontallyMergeOutlines());
-    // TODO(b/268596049): Ensure that we can obtain the api level for outlines.
-    CompilationFailedException compilationFailedException =
-        assertThrows(
-            CompilationFailedException.class,
-            () ->
-                testD8(
-                    CompilationMode.RELEASE,
-                    this::inspectNumberOfClassesFromOutput,
-                    // We can pass any inspector since horizontal merging fails.
-                    classesInspector -> {}));
-    Throwable cause = compilationFailedException.getCause();
-    assertNotNull(cause);
-    assertThat(cause.getMessage(), containsString("Verification of single class policies failed"));
+    testD8(
+        CompilationMode.RELEASE,
+        this::inspectNumberOfClassesFromOutput,
+        HorizontallyMergedClassesInspector::assertNoClassesMerged);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
index 5b00775..fd37907 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
@@ -68,7 +68,7 @@
             DexAnnotation[] annotations = clazz.getDexProgramClass().annotations().annotations;
             assertEquals(1, annotations.length);
             DexEncodedAnnotation annotation = annotations[0].annotation;
-            assertEquals(2, annotation.elements.length);
+            assertEquals(3, annotation.elements.length);
             assertEquals(
                 "com.android.tools.r8.annotations.SynthesizedClassV2",
                 annotation.type.toSourceString());