Fix all lint and doc generators

- They now all go through the same logic which annotates
  classes and methods based on what is supported.

Change-Id: Ib56038143aad62d645bd97af39857efa943af09d
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
index c093d39..fdc1f13 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -7,15 +7,8 @@
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 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.DirectMappedDexApplication;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
@@ -32,14 +25,9 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Predicate;
 
 public abstract class AbstractGenerateFiles {
 
@@ -53,6 +41,7 @@
   final InternalOptions options = new InternalOptions(factory, reporter);
 
   final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
+  final Path desugaredLibrarySpecificationPath;
   final Collection<Path> desugaredLibraryImplementation;
   final Path outputDirectory;
   final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
@@ -71,6 +60,7 @@
       Collection<Path> desugarImplementationPath,
       Path outputDirectory)
       throws Exception {
+    this.desugaredLibrarySpecificationPath = desugarConfigurationPath;
     DesugaredLibrarySpecification specification =
         readDesugaredLibraryConfiguration(desugarConfigurationPath);
     Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
@@ -81,10 +71,17 @@
     if (!Files.isDirectory(this.outputDirectory)) {
       throw new Exception("Output directory " + outputDirectory + " is not a directory");
     }
-
-    fillParallelMethods();
   }
 
+  static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+    String jar =
+        apiLevel == AndroidApiLevel.MASTER
+            ? "third_party/android_jar/lib-master/android.jar"
+            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    return Paths.get(jar);
+  }
+
+
   private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
       Path desugarConfigurationPath) {
     return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
@@ -108,135 +105,7 @@
     return app;
   }
 
-  private void fillParallelMethods() {
-    DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
-    DexMethod parallelMethod =
-        factory.createMethod(
-            factory.collectionType,
-            factory.createProto(streamType),
-            factory.createString("parallelStream"));
-    parallelMethods.add(parallelMethod);
-    DexType baseStreamType =
-        factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
-    for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
-      streamType =
-          factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
-      parallelMethod =
-          factory.createMethod(
-              streamType, factory.createProto(streamType), factory.createString("parallel"));
-      parallelMethods.add(parallelMethod);
-      // Also filter out the generated bridges for the covariant return type.
-      parallelMethod =
-          factory.createMethod(
-              streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
-      parallelMethods.add(parallelMethod);
-    }
-  }
-
-  public static class SupportedMethods {
-
-    public final Set<DexClass> classesWithAllMethodsSupported;
-    public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
-
-    public SupportedMethods(
-        Set<DexClass> classesWithAllMethodsSupported,
-        Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
-      this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
-      this.supportedMethods = supportedMethods;
-    }
-  }
-
-  SupportedMethods collectSupportedMethods(
-      AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported) throws Exception {
-
-    // Read the android.jar for the compilation API level. Read it as program instead of library
-    // to get the local information for parameter names.
-    AndroidApp library =
-        AndroidApp.builder().addProgramFiles(getAndroidJarPath(compilationApiLevel)).build();
-    DirectMappedDexApplication dexApplication =
-        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-
-    AndroidApp implementation =
-        AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
-    DirectMappedDexApplication implementationApplication =
-        new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
-
-    options.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
-    List<DexMethod> backports =
-        BackportedMethodRewriter.generateListOfBackportedMethods(
-            implementation, options, ThreadUtils.getExecutorService(1));
-
-    // Collect all the methods that the library desugar configuration adds support for.
-    Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
-    Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
-    for (DexProgramClass clazz : dexApplication.classes()) {
-      if (clazz.accessFlags.isPublic() && desugaredLibrarySpecification.isSupported(clazz.type)) {
-        DexProgramClass implementationClass =
-            implementationApplication.programDefinitionFor(clazz.getType());
-        if (implementationClass == null) {
-          throw new Exception("Implementation class not found for " + clazz.toSourceString());
-        }
-        boolean allMethodsAdded = true;
-        for (DexEncodedMethod method : clazz.methods()) {
-          if (!method.isPublic()) {
-            continue;
-          }
-          ProgramMethod implementationMethod =
-              implementationClass.lookupProgramMethod(method.getReference());
-          // Don't include methods which are not implemented by the desugared library.
-          if (supported.test(method)
-              && (implementationMethod != null || backports.contains(method.getReference()))) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
-          } else {
-            allMethodsAdded = false;
-          }
-        }
-        if (allMethodsAdded) {
-          classesWithAllMethodsSupported.add(clazz);
-        }
-      }
-
-      // All emulated interfaces static and default methods are supported.
-      if (desugaredLibrarySpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
-        assert clazz.isInterface();
-        for (DexEncodedMethod method : clazz.methods()) {
-          if (!method.isDefaultMethod() && !method.isStatic()) {
-            continue;
-          }
-          if (supported.test(method)) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
-          }
-        }
-      }
-    }
-
-    // All retargeted methods are supported.
-    desugaredLibrarySpecification.forEachRetargetMethod(
-        method -> {
-          DexClass clazz = dexApplication.contextIndependentDefinitionFor(method.getHolderType());
-          assert clazz != null;
-          DexEncodedMethod encodedMethod = clazz.lookupMethod(method);
-          if (encodedMethod == null) {
-            // Some methods are registered but present higher in the hierarchy, ignore them.
-            return;
-          }
-          if (supported.test(encodedMethod)) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(encodedMethod);
-          }
-        });
-
-    return new SupportedMethods(classesWithAllMethodsSupported, supportedMethods);
-  }
-
-  static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
-    String jar =
-        apiLevel == AndroidApiLevel.MASTER
-            ? "third_party/android_jar/lib-master/android.jar"
-            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
-    return Paths.get(jar);
-  }
-
-  abstract void run() throws Exception;
+  abstract AndroidApiLevel run() throws Exception;
 
   public static void main(String[] args) throws Exception {
     if (args.length == 3) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index 078a14a..a99db32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -14,17 +14,27 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.stream.StreamSupport;
 
 public class GenerateHtmlDoc extends AbstractGenerateFiles {
 
+  private static final String HTML_SPLIT = "<br>&nbsp;";
+  private static final int MAX_LINE_CHARACTERS = 53;
+  private static final String SUP_1 = "<sup>1</sup>";
+  private static final String SUP_2 = "<sup>2</sup>";
+  private static final String SUP_3 = "<sup>3</sup>";
+  private static final String SUP_4 = "<sup>4</sup>";
+
   public GenerateHtmlDoc(
       String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
       throws Exception {
@@ -82,17 +92,17 @@
   private abstract static class SourceBuilder<B extends GenerateHtmlDoc.SourceBuilder> {
 
     protected final DexClass clazz;
-    protected final boolean newClass;
     protected List<DexEncodedField> fields = new ArrayList<>();
-    protected List<DexEncodedMethod> constructors = new ArrayList<>();
-    protected List<DexEncodedMethod> methods = new ArrayList<>();
+    protected Map<DexEncodedMethod, MethodAnnotation> constructors =
+        new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
+    protected Map<DexEncodedMethod, MethodAnnotation> methods =
+        new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
 
     String className;
     String packageName;
 
-    private SourceBuilder(DexClass clazz, boolean newClass) {
+    private SourceBuilder(DexClass clazz) {
       this.clazz = clazz;
-      this.newClass = newClass;
       this.className = clazz.type.toSourceString();
       int index = this.className.lastIndexOf('.');
       this.packageName = index > 0 ? this.className.substring(0, index) : "";
@@ -105,32 +115,50 @@
       return self();
     }
 
-    private B addMethod(DexEncodedMethod method) {
+    private B addMethod(DexEncodedMethod method, MethodAnnotation methodAnnotation) {
       assert !method.isClassInitializer();
       if (method.isInitializer()) {
-        constructors.add(method);
+        constructors.put(method, methodAnnotation);
       } else {
-        methods.add(method);
+        methods.put(method, methodAnnotation);
       }
       return self();
     }
 
+    // If we are in a.b.c, then anything starting with a.b should not be fully qualified.
+    protected String typeInPackageRecursive(String typeName, String packageName) {
+      String rewritten = typeInPackage(typeName, packageName);
+      if (rewritten != null) {
+        return rewritten;
+      }
+      String[] split = packageName.split("\\.");
+      if (split.length > 2) {
+        String prevPackage =
+            packageName.substring(0, packageName.length() - split[split.length - 1].length() - 1);
+        return typeInPackage(typeName, prevPackage);
+      }
+      return null;
+    }
+
     protected String typeInPackage(String typeName, String packageName) {
       if (typeName.startsWith(packageName)
           && typeName.length() > packageName.length()
-          && typeName.charAt(packageName.length()) == '.'
-          && typeName.indexOf('.', packageName.length() + 1) == -1) {
-        return typeName.substring(packageName.length() + 1);
+          && typeName.charAt(packageName.length()) == '.') {
+        int last = typeName.lastIndexOf('.') + 1;
+        return typeName.substring(last);
       }
       return null;
     }
 
     protected String typeInPackage(String typeName) {
-      String result = typeInPackage(typeName, packageName);
+      String result = typeInPackageRecursive(typeName, packageName);
       if (result == null) {
         result = typeInPackage(typeName, "java.lang");
       }
       if (result == null) {
+        result = typeInPackage(typeName, "java.util.function");
+      }
+      if (result == null) {
         result = typeName;
       }
       return result.replace('$', '.');
@@ -271,7 +299,8 @@
     }
 
     HTMLBuilder appendTdPackage(String s) {
-      appendLineStart("<td><code><em>" + s + "</em></code><br>");
+      String finalString = format(s, 4);
+      appendLineStart("<td><code><em>" + finalString + "</em></code><br>");
       if (s.startsWith("java.time")) {
         append("<a href=\"#java-time-customizations\">See customizations</a><br");
       } else if (s.startsWith("java.nio")) {
@@ -280,10 +309,25 @@
       return this;
     }
 
+    private String format(String s, int i) {
+      String[] regexpSplit = s.split("\\.");
+      if (regexpSplit.length < i) {
+        return s;
+      }
+      int splitIndex = 0;
+      int mid = i / 2;
+      for (int j = 0; j < mid; j++) {
+        splitIndex += regexpSplit[j].length();
+      }
+      splitIndex += mid;
+      return s.substring(0, splitIndex) + HTML_SPLIT + s.substring(splitIndex);
+    }
+
     HTMLBuilder appendTdClassName(String s) {
+      String finalString = format(s, 2);
       appendLineEnd(
           "<code><br><br><div style=\"font-size:small;font-weight:bold;\">&nbsp;"
-              + s
+              + finalString
               + "</div></code><br><br></td>");
       return this;
     }
@@ -298,6 +342,29 @@
       return this;
     }
 
+    HTMLBuilder appendMethodLiCode(String s) {
+      if (s.length() < MAX_LINE_CHARACTERS || s.contains("()")) {
+        return appendLiCode(s);
+      }
+      StringBuilder sb = new StringBuilder();
+      String[] split = s.split("\\(");
+      sb.append(split[0]).append('(').append(HTML_SPLIT);
+      if (split[1].length() < MAX_LINE_CHARACTERS - 2) {
+        sb.append(split[1]);
+        return appendLiCode(sb.toString());
+      }
+      String[] secondSplit = split[1].split(",");
+      sb.append("&nbsp;");
+      for (int i = 0; i < secondSplit.length; i++) {
+        sb.append(secondSplit[i]);
+        if (i != secondSplit.length - 1) {
+          sb.append(',');
+          sb.append(HTML_SPLIT);
+        }
+      }
+      return appendLiCode(sb.toString());
+    }
+
     HTMLBuilder start(String tag) {
       appendLine("<" + tag + ">");
       increaseIndent();
@@ -313,11 +380,15 @@
 
   public static class HTMLSourceBuilder extends SourceBuilder<HTMLSourceBuilder> {
 
-    private final Set<DexMethod> parallelMethods;
+    private final ClassAnnotation classAnnotation;
+    private boolean parallelStreamMethod = false;
+    private boolean missingFromLatestAndroidJar = false;
+    private boolean unsupportedInMinApiRange = false;
+    private boolean covariantReturnSupported = false;
 
-    public HTMLSourceBuilder(DexClass clazz, boolean newClass, Set<DexMethod> parallelMethods) {
-      super(clazz, newClass);
-      this.parallelMethods = parallelMethods;
+    public HTMLSourceBuilder(DexClass clazz, ClassAnnotation classAnnotation) {
+      super(clazz);
+      this.classAnnotation = classAnnotation;
     }
 
     @Override
@@ -325,6 +396,30 @@
       return this;
     }
 
+    private String getTextAnnotations(MethodAnnotation annotation) {
+      if (annotation == null) {
+        return "";
+      }
+      StringBuilder stringBuilder = new StringBuilder();
+      if (annotation.parallelStreamMethod) {
+        stringBuilder.append(SUP_1);
+        parallelStreamMethod = true;
+      }
+      if (annotation.missingFromLatestAndroidJar) {
+        stringBuilder.append(SUP_2);
+        missingFromLatestAndroidJar = true;
+      }
+      if (annotation.unsupportedInMinApiRange) {
+        stringBuilder.append(SUP_3);
+        unsupportedInMinApiRange = true;
+      }
+      if (annotation.covariantReturnSupported) {
+        stringBuilder.append(SUP_4);
+        covariantReturnSupported = true;
+      }
+      return stringBuilder.toString();
+    }
+
     @Override
     public String toString() {
       HTMLBuilder builder = new HTMLBuilder();
@@ -339,7 +434,6 @@
               "ul style=\"list-style-position:inside; list-style-type: none !important;"
                   + " margin-left:0px;padding-left:0px !important;\"");
       if (!fields.isEmpty()) {
-        assert newClass; // Currently no fields are added to existing classes.
         for (DexEncodedField field : fields) {
           builder.appendLiCode(
               accessFlags(field.accessFlags)
@@ -350,49 +444,65 @@
         }
       }
       if (!constructors.isEmpty()) {
-        for (DexEncodedMethod constructor : constructors) {
-          builder.appendLiCode(
+        for (DexEncodedMethod constructor : constructors.keySet()) {
+          builder.appendMethodLiCode(
               accessFlags(constructor.accessFlags)
                   + " "
                   + typeInPackage(className)
-                  + arguments(constructor));
+                  + arguments(constructor)
+                  + getTextAnnotations(constructors.get(constructor)));
         }
       }
-      List<String> parallelM = new ArrayList<>();
       if (!methods.isEmpty()) {
-        for (DexEncodedMethod method : methods) {
-          builder.appendLiCode(
+        for (DexEncodedMethod method : methods.keySet()) {
+          builder.appendMethodLiCode(
               accessFlags(method.accessFlags)
                   + " "
                   + typeInPackage(method.getReference().proto.returnType)
                   + " "
                   + method.getReference().name
-                  + arguments(method));
-          if (parallelMethods.contains(method.getReference())) {
-            parallelM.add(method.getReference().name.toString());
-          }
+                  + arguments(method)
+                  + getTextAnnotations(methods.get(method)));
         }
       }
       builder.end("ul").end("td");
       StringBuilder commentBuilder = new StringBuilder();
-      if (newClass) {
-        commentBuilder.append("Fully implemented class.");
-      } else {
-        commentBuilder.append("Additional methods on existing class.");
+      if (classAnnotation.fullySupported) {
+        commentBuilder.append("Fully implemented class.").append(HTML_SPLIT);
       }
-      if (!parallelM.isEmpty()) {
-        commentBuilder.append(newClass ? "" : "<br>");
-        if (parallelM.size() == 1) {
-          commentBuilder
-              .append("The method <code>")
-              .append(parallelM.get(0))
-              .append("</code> is only supported from API level 21.");
-        } else {
-          commentBuilder.append("The following methods are only supported from API level 21:<br>");
-          for (int i = 0; i < parallelM.size(); i++) {
-            commentBuilder.append("<code>").append(parallelM.get(i)).append("</code><br>");
-          }
-        }
+      if (parallelStreamMethod) {
+        commentBuilder
+            .append(SUP_1)
+            .append("Supported only on devices which API level is 21 or higher.")
+            .append(HTML_SPLIT);
+      }
+      if (missingFromLatestAndroidJar) {
+        commentBuilder
+            .append(SUP_2)
+            .append("Not present in Android ")
+            .append(MAX_TESTED_ANDROID_API_LEVEL)
+            .append(" (May not resolve at compilation).")
+            .append(HTML_SPLIT);
+      }
+      if (unsupportedInMinApiRange) {
+        commentBuilder
+            .append(SUP_3)
+            .append(" Not supported at all minSDK levels.")
+            .append(HTML_SPLIT);
+      }
+      if (covariantReturnSupported) {
+        commentBuilder
+            .append(SUP_4)
+            .append(" Also supported with covariant return type.")
+            .append(HTML_SPLIT);
+      }
+      if (!classAnnotation.unsupportedMethods.isEmpty()) {
+        commentBuilder
+            .append("Some methods (")
+            .append(classAnnotation.unsupportedMethods.size())
+            .append(") present in Android ")
+            .append(MAX_TESTED_ANDROID_API_LEVEL)
+            .append(" are not supported.");
       }
       builder.appendTdP(commentBuilder.toString());
       builder.end("tr");
@@ -403,47 +513,43 @@
   private void generateClassHTML(
       PrintStream ps,
       DexClass clazz,
-      boolean newClass,
-      Predicate<DexEncodedField> fieldsFilter,
-      Predicate<DexEncodedMethod> methodsFilter) {
-    SourceBuilder builder = new HTMLSourceBuilder(clazz, newClass, parallelMethods);
+      List<DexEncodedMethod> methods,
+      ClassAnnotation classAnnotation,
+      Map<DexMethod, MethodAnnotation> methodAnnotationMap) {
+    SourceBuilder<HTMLSourceBuilder> builder = new HTMLSourceBuilder(clazz, classAnnotation);
+    // We need to extend to support fields.
     StreamSupport.stream(clazz.fields().spliterator(), false)
-        .filter(fieldsFilter)
         .filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
         .sorted(Comparator.comparing(DexEncodedField::toSourceString))
         .forEach(builder::addField);
-    StreamSupport.stream(clazz.methods().spliterator(), false)
-        .filter(methodsFilter)
+    methods.stream()
         .filter(
             method ->
                 (method.accessFlags.isPublic() || method.accessFlags.isProtected())
                     && !method.accessFlags.isBridge())
         .sorted(Comparator.comparing(DexEncodedMethod::toSourceString))
-        .forEach(builder::addMethod);
+        .forEach(m -> builder.addMethod(m, methodAnnotationMap.get(m.getReference())));
     ps.println(builder);
   }
 
-  void run() throws Exception {
+  AndroidApiLevel run() throws Exception {
     PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
-    // Full classes added.
-    SupportedMethods supportedMethods =
-        collectSupportedMethods(MAX_TESTED_ANDROID_API_LEVEL, x -> true);
-    supportedMethods.classesWithAllMethodsSupported.stream()
-        .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
-        .forEach(clazz -> generateClassHTML(ps, clazz, true, field -> true, method -> true));
 
-    // Methods added to existing classes.
-    supportedMethods.supportedMethods.keySet().stream()
-        .filter(clazz -> !supportedMethods.classesWithAllMethodsSupported.contains(clazz))
-        .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
-        .forEach(
-            clazz ->
-                generateClassHTML(
-                    ps,
-                    clazz,
-                    false,
-                    field -> false,
-                    method -> supportedMethods.supportedMethods.get(clazz).contains(method)));
+    SupportedMethodsWithAnnotations supportedMethods =
+        new SupportedMethodsGenerator(options)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+
+    // Full classes added.
+    supportedMethods.supportedMethods.forEach(
+        (clazz, methods) -> {
+          generateClassHTML(
+              ps,
+              clazz,
+              methods,
+              supportedMethods.annotatedClasses.get(clazz.type),
+              supportedMethods.annotatedMethods);
+        });
+    return MAX_TESTED_ANDROID_API_LEVEL;
   }
 
   public static void main(String[] args) throws Exception {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index ad60d69..a08eb8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
@@ -42,8 +43,6 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
 
 public class GenerateLintFiles extends AbstractGenerateFiles {
 
@@ -151,7 +150,7 @@
   private void writeLintFiles(
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
-      SupportedMethods supportedMethods)
+      SupportedMethodsWithAnnotations supportedMethods)
       throws Exception {
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
@@ -161,17 +160,21 @@
         (clazz, methods) -> {
           String classBinaryName =
               DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
-          if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
+          if (!supportedMethods.annotatedClasses.get(clazz.type).fullySupported) {
             for (DexEncodedMethod method : methods) {
               if (method.isInstanceInitializer() || method.isClassInitializer()) {
                 // No new constructors are added.
                 continue;
               }
-              desugaredApisSignatures.add(
-                  classBinaryName
-                      + '#'
-                      + method.getReference().name
-                      + method.getReference().proto.toDescriptorString());
+              MethodAnnotation methodAnnotation =
+                  supportedMethods.annotatedMethods.get(method.getReference());
+              if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
+                desugaredApisSignatures.add(
+                    classBinaryName
+                        + '#'
+                        + method.getReference().name
+                        + method.getReference().proto.toDescriptorString());
+              }
             }
           } else {
             desugaredApisSignatures.add(classBinaryName);
@@ -198,49 +201,43 @@
     consumer.finished(options.reporter);
   }
 
+  private boolean shouldAddMethodToLint(
+      MethodAnnotation methodAnnotation, AndroidApiLevel minApiLevel) {
+    if (methodAnnotation == null) {
+      return true;
+    }
+    if (methodAnnotation.unsupportedInMinApiRange) {
+      // Do not lint method which are unavailable with some min apis.
+      return false;
+    }
+    if (methodAnnotation.parallelStreamMethod) {
+      return minApiLevel == AndroidApiLevel.L;
+    }
+    assert methodAnnotation.missingFromLatestAndroidJar;
+    // We add missing methods from the latest jar, javac will add the squikles.
+    return true;
+  }
+
   private void generateLintFiles(
       AndroidApiLevel compilationApiLevel,
-      Predicate<AndroidApiLevel> generateForThisMinApiLevel,
-      BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
+      AndroidApiLevel minApiLevel,
+      SupportedMethodsWithAnnotations supportedMethods)
       throws Exception {
     System.out.print("  - generating for min API:");
-    for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
-      if (!generateForThisMinApiLevel.test(minApiLevel)) {
-        continue;
-      }
-
-      System.out.print(" " + minApiLevel);
-
-      SupportedMethods supportedMethods =
-          collectSupportedMethods(
-              compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
-      writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
-    }
-    System.out.println();
+    System.out.print(" " + minApiLevel);
+    writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
   }
 
-  void run() throws Exception {
-    // Run over all the API levels that the desugared library can be compiled with.
-    for (int apiLevel = MAX_TESTED_ANDROID_API_LEVEL.getLevel();
-        apiLevel >= desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel();
-        apiLevel--) {
-      System.out.println("Generating lint files for compile API " + apiLevel);
-      run(apiLevel);
-    }
-  }
-
-  public void run(int apiLevel) throws Exception {
-    generateLintFiles(
-        AndroidApiLevel.getAndroidApiLevel(apiLevel),
-        minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
-        (minApiLevel, method) -> {
-          assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
-          if (minApiLevel == AndroidApiLevel.L) {
-            return true;
-          }
-          assert minApiLevel == AndroidApiLevel.B;
-          return !parallelMethods.contains(method.getReference());
-        });
+  public AndroidApiLevel run() throws Exception {
+    AndroidApiLevel compilationLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    SupportedMethodsWithAnnotations supportedMethods =
+        new SupportedMethodsGenerator(options)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+    System.out.println("Generating lint files for compile API " + compilationLevel);
+    generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
+    generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods);
+    return compilationLevel;
   }
 
   public static void main(String[] args) throws Exception {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
new file mode 100644
index 0000000..c196fe9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
@@ -0,0 +1,371 @@
+// Copyright (c) 2023, 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.lint;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.lint.AbstractGenerateFiles.MAX_TESTED_ANDROID_API_LEVEL;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class SupportedMethodsGenerator {
+
+  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+
+  private final InternalOptions options;
+
+  public SupportedMethodsGenerator(InternalOptions options) {
+    this.options = options;
+  }
+
+  public SupportedMethodsWithAnnotations run(
+      Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException {
+    SupportedMethodsWithAnnotations.Builder builder = SupportedMethodsWithAnnotations.builder();
+    // First analyze everything which is supported when desugaring for api 1.
+    collectSupportedMethodsInB(desugaredLibraryImplementation, specification, builder);
+    // Second annotate all apis which are partially and/or fully supported.
+    AndroidApp library =
+        AndroidApp.builder()
+            .addProgramFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+            .build();
+    DirectMappedDexApplication appForMax =
+        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+    annotateMethodsNotOnLatestAndroidJar(appForMax, builder);
+    annotateParallelMethods(builder);
+    annotatePartialDesugaringMethods(builder, specification);
+    annotateClasses(builder, appForMax);
+    return builder.build();
+  }
+
+  private void annotateClasses(
+      SupportedMethodsWithAnnotations.Builder builder, DirectMappedDexApplication appForMax) {
+
+    builder.forEachClassAndMethods(
+        (clazz, methods) -> {
+          DexClass maxClass = appForMax.definitionFor(clazz.type);
+          List<DexMethod> missing = new ArrayList<>();
+          boolean fullySupported = true;
+          for (DexEncodedMethod method : maxClass.methods()) {
+            if (!(method.isPublic() || method.isProtectedMethod())) {
+              continue;
+            }
+            // If the method is in android.jar but not in the desugared library, or annotated, then
+            // the class is not marked as fully supported.
+            if (methods.stream().noneMatch(em -> em.getReference() == method.getReference())) {
+              missing.add(method.getReference());
+              fullySupported = false;
+            }
+            if (builder.annotatedMethods.containsKey(method.getReference())
+                && builder.annotatedMethods.get(method.getReference())
+                    != MethodAnnotation.getCovariantReturnSupported()) {
+              fullySupported = false;
+            }
+          }
+          builder.annotateClass(clazz.type, new ClassAnnotation(fullySupported, missing));
+        });
+  }
+
+  private void annotatePartialDesugaringMethods(
+      SupportedMethodsWithAnnotations.Builder builder, Path specification) throws IOException {
+    for (int api = AndroidApiLevel.K.getLevel();
+        api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
+        api++) {
+      if (api == 20) {
+        // Missing android.jar.
+        continue;
+      }
+      AndroidApiLevel androidApiLevel = AndroidApiLevel.getAndroidApiLevel(api);
+      AndroidApp library =
+          AndroidApp.builder().addProgramFiles(getAndroidJarPath(androidApiLevel)).build();
+      DirectMappedDexApplication dexApplication =
+          new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+      AppInfoWithClassHierarchy appInfo =
+          AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+              dexApplication,
+              ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+              MainDexInfo.none(),
+              GlobalSyntheticsStrategy.forNonSynthesizing(),
+              StartupOrder.empty());
+      MachineDesugaredLibrarySpecification machineSpecification =
+          getMachineSpecification(androidApiLevel, specification);
+
+      options.setMinApiLevel(androidApiLevel);
+      options.setDesugaredLibrarySpecification(machineSpecification);
+      List<DexMethod> backports =
+          BackportedMethodRewriter.generateListOfBackportedMethods(
+              library, options, ThreadUtils.getExecutorService(1));
+
+      int finalApi = api;
+      builder.forEachClassAndMethod(
+          (clazz, encodedMethod) -> {
+            DexMethod dexMethod = encodedMethod.getReference();
+            if (machineSpecification.isSupported(dexMethod)
+                || backports.contains(dexMethod)
+                || machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+              if (machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+                builder.annotateMethod(dexMethod, MethodAnnotation.getCovariantReturnSupported());
+              }
+              return;
+            }
+            if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())
+                && encodedMethod.isStatic()) {
+              // Static methods on emulated interfaces are always supported if the emulated
+              // interface is
+              // supported.
+              return;
+            }
+            MethodResolutionResult methodResolutionResult =
+                appInfo.resolveMethod(
+                    dexMethod,
+                    appInfo
+                        .contextIndependentDefinitionFor(dexMethod.getHolderType())
+                        .isInterface());
+            if (methodResolutionResult.isFailedResolution()) {
+              builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi));
+            }
+          });
+    }
+  }
+
+  private void annotateParallelMethods(SupportedMethodsWithAnnotations.Builder builder) {
+    for (DexMethod parallelMethod : getParallelMethods()) {
+      builder.annotateMethod(parallelMethod, MethodAnnotation.getParallelStreamMethod());
+    }
+  }
+
+  private void annotateMethodsNotOnLatestAndroidJar(
+      DirectMappedDexApplication appForMax, SupportedMethodsWithAnnotations.Builder builder) {
+    builder.forEachClassAndMethod(
+        (clazz, method) -> {
+          DexClass dexClass = appForMax.definitionFor(clazz.type);
+          assert dexClass != null;
+          if (dexClass.lookupMethod(method.getReference()) == null) {
+            builder.annotateMethod(
+                method.getReference(), MethodAnnotation.getMissingFromLatestAndroidJar());
+          }
+        });
+  }
+
+  static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+    String jar =
+        apiLevel == AndroidApiLevel.MASTER
+            ? "third_party/android_jar/lib-master/android.jar"
+            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    return Paths.get(jar);
+  }
+
+  private void collectSupportedMethodsInB(
+      Collection<Path> desugaredLibraryImplementation,
+      Path specification,
+      SupportedMethodsWithAnnotations.Builder builder)
+      throws IOException {
+
+    AndroidApp implementation =
+        AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
+    DirectMappedDexApplication implementationApplication =
+        new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
+
+    AndroidApp library =
+        AndroidApp.builder()
+            .addLibraryFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+            .build();
+    DirectMappedDexApplication amendedAppForMax =
+        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+
+    MachineDesugaredLibrarySpecification machineSpecification =
+        getMachineSpecification(AndroidApiLevel.B, specification);
+
+    options.setMinApiLevel(AndroidApiLevel.B);
+    options.setDesugaredLibrarySpecification(machineSpecification);
+    List<DexMethod> backports =
+        BackportedMethodRewriter.generateListOfBackportedMethods(
+            library, options, ThreadUtils.getExecutorService(1));
+
+    DesugaredLibraryAmender.run(
+        machineSpecification.getAmendLibraryMethods(),
+        machineSpecification.getAmendLibraryFields(),
+        amendedAppForMax,
+        options.reporter,
+        ComputedApiLevel.unknown());
+
+    for (DexProgramClass clazz : implementationApplication.classes()) {
+      // All emulated interfaces static and default methods are supported.
+      if (machineSpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
+        assert clazz.isInterface();
+        for (DexEncodedMethod method : clazz.methods()) {
+          if (!method.isDefaultMethod() && !method.isStatic()) {
+            continue;
+          }
+          if (method.getName().startsWith("lambda$")
+              || method.getName().toString().contains("$deserializeLambda$")) {
+            // We don't care if lambda methods are present or not.
+            continue;
+          }
+          if (method
+              .getReference()
+              .toSourceString()
+              .equals("void java.util.Collection.forEach(java.util.function.Consumer)")) {
+            // This method is present for binary compatibility. Do not mark as supported (Supported
+            // through Iterable#forEach).
+            continue;
+          }
+          builder.addSupportedMethod(clazz, method);
+        }
+        addBackports(clazz, backports, builder, amendedAppForMax);
+      } else {
+        // All methods in maintained or rewritten classes are supported.
+        if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
+            && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type)
+            && amendedAppForMax.definitionFor(clazz.type) != null) {
+          for (DexEncodedMethod method : clazz.methods()) {
+            if (!method.isPublic() && !method.isProtectedMethod()) {
+              continue;
+            }
+            builder.addSupportedMethod(clazz, method);
+          }
+          addBackports(clazz, backports, builder, amendedAppForMax);
+        }
+      }
+    }
+
+    // All retargeted methods are supported.
+    machineSpecification.forEachRetargetMethod(
+        method -> {
+          DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
+          if (dexClass != null) {
+            DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+            if (dexEncodedMethod != null) {
+              builder.addSupportedMethod(dexClass, dexEncodedMethod);
+              return;
+            }
+          }
+          dexClass = amendedAppForMax.definitionFor(method.getHolderType());
+          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+          assert dexEncodedMethod != null;
+          builder.addSupportedMethod(dexClass, dexEncodedMethod);
+        });
+  }
+
+  private void addBackports(
+      DexProgramClass clazz,
+      List<DexMethod> backports,
+      SupportedMethodsWithAnnotations.Builder builder,
+      DirectMappedDexApplication amendedAppForMax) {
+    for (DexMethod backport : backports) {
+      if (clazz.type == backport.getHolderType()) {
+        DexClass maxClass = amendedAppForMax.definitionFor(clazz.type);
+        DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport);
+        // There is a single backport not in amendedAppForMax, Stream#ofNullable.
+        assert dexEncodedMethod != null
+            || backport
+                .toString()
+                .equals(
+                    "java.util.stream.Stream java.util.stream.Stream.ofNullable(java.lang.Object)");
+        if (dexEncodedMethod == null) {
+          dexEncodedMethod =
+              DexEncodedMethod.builder()
+                  .setMethod(backport)
+                  .setAccessFlags(
+                      MethodAccessFlags.fromSharedAccessFlags(
+                          Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+                  .build();
+        }
+        builder.addSupportedMethod(clazz, dexEncodedMethod);
+      }
+    }
+  }
+
+  private MachineDesugaredLibrarySpecification getMachineSpecification(
+      AndroidApiLevel api, Path specification) throws IOException {
+    DesugaredLibrarySpecification librarySpecification =
+        DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+            StringResource.fromFile(specification),
+            options.itemFactory,
+            options.reporter,
+            false,
+            api.getLevel());
+    Path androidJarPath = getAndroidJarPath(librarySpecification.getRequiredCompilationApiLevel());
+    DexApplication app = createLoadingApp(androidJarPath, options);
+    return librarySpecification.toMachineSpecification(app, Timing.empty());
+  }
+
+  private DexApplication createLoadingApp(Path androidLib, InternalOptions options)
+      throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    assert !options.ignoreJavaLibraryOverride;
+    options.ignoreJavaLibraryOverride = true;
+    DexApplication loadingApp = applicationReader.read(executorService);
+    options.ignoreJavaLibraryOverride = false;
+    return loadingApp;
+  }
+
+  private Set<DexMethod> getParallelMethods() {
+    Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
+    DexItemFactory factory = options.dexItemFactory();
+    DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
+    DexMethod parallelMethod =
+        factory.createMethod(
+            factory.collectionType,
+            factory.createProto(streamType),
+            factory.createString("parallelStream"));
+    parallelMethods.add(parallelMethod);
+    DexType baseStreamType =
+        factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
+    for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
+      streamType =
+          factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(streamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
+      // Also filter out the generated bridges for the covariant return type.
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
+    }
+    return parallelMethods;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
new file mode 100644
index 0000000..0cd6887
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
@@ -0,0 +1,215 @@
+// Copyright (c) 2023, 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.lint;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+public class SupportedMethodsWithAnnotations {
+
+  public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
+  public final Map<DexMethod, MethodAnnotation> annotatedMethods;
+  // A fully supported class has no annotated methods, and all the methods from the latest
+  // android.jar are supported.
+  public final Map<DexType, ClassAnnotation> annotatedClasses;
+
+  SupportedMethodsWithAnnotations(
+      Map<DexClass, List<DexEncodedMethod>> supportedMethods,
+      Map<DexMethod, MethodAnnotation> annotatedMethods,
+      Map<DexType, ClassAnnotation> annotatedClasses) {
+    this.supportedMethods = supportedMethods;
+    this.annotatedMethods = annotatedMethods;
+    this.annotatedClasses = annotatedClasses;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static class Builder {
+
+    Map<DexClass, List<DexEncodedMethod>> supportedMethods = new IdentityHashMap<>();
+    Map<DexMethod, MethodAnnotation> annotatedMethods = new IdentityHashMap<>();
+    Map<DexType, ClassAnnotation> annotatedClasses = new IdentityHashMap<>();
+
+    void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+      supportedMethods.forEach(biConsumer);
+    }
+
+    void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+      supportedMethods.forEach(
+          (clazz, methods) -> {
+            for (DexEncodedMethod method : methods) {
+              biConsumer.accept(clazz, method);
+            }
+          });
+    }
+
+    void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
+      List<DexEncodedMethod> methods =
+          supportedMethods.computeIfAbsent(holder, f -> new ArrayList<>());
+      methods.add(method);
+    }
+
+    void annotateClass(DexType type, ClassAnnotation annotation) {
+      annotatedClasses.put(type, annotation);
+    }
+
+    void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+      MethodAnnotation prev = annotatedMethods.getOrDefault(method, MethodAnnotation.getDefault());
+      annotatedMethods.put(method, annotation.combine(prev));
+    }
+
+    SupportedMethodsWithAnnotations build() {
+      supportedMethods.forEach(
+          (k, v) -> v.sort(Comparator.comparing(DexEncodedMember::getReference)));
+      return new SupportedMethodsWithAnnotations(
+          ImmutableSortedMap.copyOf(supportedMethods, Comparator.comparing(DexClass::getType)),
+          ImmutableMap.copyOf(annotatedMethods),
+          ImmutableMap.copyOf(annotatedClasses));
+    }
+  }
+
+  static class ClassAnnotation {
+
+    final boolean fullySupported;
+    // Methods in latest android.jar but unsupported.
+    final List<DexMethod> unsupportedMethods;
+
+    public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
+      this.fullySupported = fullySupported;
+      unsupportedMethods.sort(Comparator.naturalOrder());
+      this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
+    }
+  }
+
+  public static class MethodAnnotation {
+
+    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
+        new MethodAnnotation(false, false, true, false, -1, -1);
+    private static final MethodAnnotation DEFAULT =
+        new MethodAnnotation(false, false, false, false, -1, -1);
+    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
+        new MethodAnnotation(true, false, false, false, -1, -1);
+    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
+        new MethodAnnotation(false, true, false, false, -1, -1);
+
+    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
+    final boolean parallelStreamMethod;
+    // Methods not in the latest android jar but still fully supported.
+    final boolean missingFromLatestAndroidJar;
+    // Methods not supported in a given min api range.
+    final boolean unsupportedInMinApiRange;
+    final boolean covariantReturnSupported;
+    final int minRange;
+    final int maxRange;
+
+    MethodAnnotation(
+        boolean parallelStreamMethod,
+        boolean missingFromLatestAndroidJar,
+        boolean covariantReturnSupported,
+        boolean unsupportedInMinApiRange,
+        int minRange,
+        int maxRange) {
+      this.parallelStreamMethod = parallelStreamMethod;
+      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
+      this.covariantReturnSupported = covariantReturnSupported;
+      this.unsupportedInMinApiRange = unsupportedInMinApiRange;
+      this.minRange = minRange;
+      this.maxRange = maxRange;
+    }
+
+    public static MethodAnnotation getCovariantReturnSupported() {
+      return COVARIANT_RETURN_SUPPORTED;
+    }
+
+    public static MethodAnnotation getDefault() {
+      return DEFAULT;
+    }
+
+    public static MethodAnnotation getParallelStreamMethod() {
+      return PARALLEL_STREAM_METHOD;
+    }
+
+    public static MethodAnnotation getMissingFromLatestAndroidJar() {
+      return MISSING_FROM_LATEST_ANDROID_JAR;
+    }
+
+    public static MethodAnnotation createMissingInMinApi(int api) {
+      return new MethodAnnotation(false, false, false, true, api, api);
+    }
+
+    public boolean isUnsupportedInMinApiRange() {
+      return unsupportedInMinApiRange;
+    }
+
+    public int getMinRange() {
+      return minRange;
+    }
+
+    public int getMaxRange() {
+      return maxRange;
+    }
+
+    public boolean isCovariantReturnSupported() {
+      return covariantReturnSupported;
+    }
+
+    public MethodAnnotation combine(MethodAnnotation other) {
+      if (this == getDefault()) {
+        return other;
+      }
+      if (other == getDefault()) {
+        return this;
+      }
+      int newMin, newMax;
+      if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
+        newMin = newMax = -1;
+      } else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
+        newMin = unsupportedInMinApiRange ? minRange : other.minRange;
+        newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
+      } else {
+        // Merge ranges if contiguous or throw.
+        if (maxRange == other.minRange - 1) {
+          newMin = minRange;
+          newMax = other.maxRange;
+        } else if (other.maxRange == minRange - 1) {
+          newMin = other.minRange;
+          newMax = maxRange;
+        } else {
+          // 20 is missing, so if maxRange or minRange are 19 the following is 21.
+          if (maxRange == 19 && other.minRange == 21) {
+            newMin = minRange;
+            newMax = other.maxRange;
+          } else if (other.maxRange == 19 && minRange == 21) {
+            newMin = other.minRange;
+            newMax = maxRange;
+          } else {
+            throw new RuntimeException("Cannot merge ranges.");
+          }
+        }
+      }
+      return new MethodAnnotation(
+          parallelStreamMethod || other.parallelStreamMethod,
+          missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
+          covariantReturnSupported || other.covariantReturnSupported,
+          unsupportedInMinApiRange || other.unsupportedInMinApiRange,
+          newMin,
+          newMax);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index a54633b..a6a0535 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -498,10 +498,10 @@
             libraryDesugaringSpecification.getSpecification(),
             libraryDesugaringSpecification.getDesugarJdkLibs(),
             out);
-    desugaredApi.run(targetApi.getLevel());
+    AndroidApiLevel compileApi = desugaredApi.run();
     return new CodeInspector(
-        out.resolve("compile_api_level_" + targetApi.getLevel())
-            .resolve("desugared_apis_" + targetApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
+        out.resolve("compile_api_level_" + compileApi.getLevel())
+            .resolve("desugared_apis_" + compileApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
   }
 
   private boolean addType(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index 55330bc..a29a6d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -5,10 +5,10 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -20,11 +20,13 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateHtmlDoc;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -42,9 +44,11 @@
 
   private List<String> lintContents;
 
-  @Parameters(name = "{0}, spec: {1}")
+  @Parameters(name = "{1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        ImmutableList.of(JDK8, JDK11_MINIMAL, JDK11, JDK11_PATH, JDK11_LEGACY));
   }
 
   public LintFilesTest(
@@ -82,35 +86,18 @@
     assertTrue(supportsAllMethodsOf("java/util/Optional"));
     assertTrue(supportsAllMethodsOf("java/util/OptionalInt"));
 
-    // No parallel* methods pre L, and all stream methods supported from L.
+    // No parallel* methods pre L, Stream are never fully supported due to takeWhile/dropWhile.
     assertEquals(
         minApiLevel == AndroidApiLevel.L,
         supportsMethodButNotAllMethodsInClass(
             "java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
     assertEquals(
-        minApiLevel == AndroidApiLevel.L, supportsAllMethodsOf("java/util/stream/DoubleStream"));
-    assertFalse(
+        minApiLevel == AndroidApiLevel.L,
         supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
-    assertEquals(
-        minApiLevel == AndroidApiLevel.B,
+    assertTrue(
         supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
-    assertEquals(
-        minApiLevel == AndroidApiLevel.L, lintContents.contains("java/util/stream/IntStream"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
-    assertEquals(
-        minApiLevel == AndroidApiLevel.B,
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
 
     assertEquals(
         libraryDesugaringSpecification != JDK8, supportsAllMethodsOf("java/util/concurrent/Flow"));
@@ -138,7 +125,7 @@
 
     // Maintain type.
     assertEquals(
-        libraryDesugaringSpecification != JDK8,
+        libraryDesugaringSpecification != JDK8 && libraryDesugaringSpecification != JDK11_LEGACY,
         supportsAllMethodsOf("java/io/UncheckedIOException"));
 
     // Retarget method.
@@ -166,7 +153,7 @@
   }
 
   @Test
-  public void testFileContent() throws Exception {
+  public void testLint() throws Exception {
     Path directory = temp.newFolder().toPath();
     Path jdkLibJar =
         libraryDesugaringSpecification == JDK8
@@ -187,38 +174,36 @@
             false,
             AndroidApiLevel.B.getLevel());
 
-    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
-      if (apiLevel.isGreaterThan(AndroidApiLevel.T)) {
-        continue;
-      }
-      Path compileApiLevelDirectory = directory.resolve("compile_api_level_" + apiLevel.getLevel());
-      if (apiLevel.getLevel()
-          < desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel()) {
-        System.out.println("!Checking " + compileApiLevelDirectory);
-        continue;
-      }
-      assertTrue(Files.exists(compileApiLevelDirectory));
-      for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
-        String desugaredApisBaseName =
-            "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
-        if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
-          assertTrue(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-          assertTrue(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-          checkFileContent(
-              minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
-        } else {
-          assertFalse(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-          assertFalse(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-        }
+    AndroidApiLevel requiredCompilationApiLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    Path compileApiLevelDirectory =
+        directory.resolve("compile_api_level_" + requiredCompilationApiLevel.getLevel());
+
+    assertTrue(Files.exists(compileApiLevelDirectory));
+    for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+      String desugaredApisBaseName =
+          "desugared_apis_" + requiredCompilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
+      if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
+        assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+        assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+        checkFileContent(
+            minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
+      } else {
+        assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+        assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
       }
     }
+  }
+
+  @Test
+  public void testHTML() throws Exception {
+    Path jdkLibJar =
+        libraryDesugaringSpecification == JDK8
+            ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+            : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
 
     Path directory2 = temp.newFolder().toPath();
-    GenerateLintFiles.main(
+    GenerateHtmlDoc.main(
         new String[] {
           "--generate-api-docs",
           libraryDesugaringSpecification.getSpecification().toString(),
@@ -231,7 +216,7 @@
     assertEquals("<tr>", html.get(0));
     assertEquals("</tr>", html.get(html.size() - 2));
     if (libraryDesugaringSpecification == JDK11 || libraryDesugaringSpecification == JDK11_PATH) {
-      assertEquals(7, html.stream().filter(s -> s.contains("Flow")).count());
+      assertEquals(6, html.stream().filter(s -> s.contains("Flow")).count());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 615e3d5..2b52f07 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -10,44 +10,19 @@
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.experimental.startup.StartupOrder;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
-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.DirectMappedDexApplication;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsGenerator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import java.io.IOException;
-import java.nio.file.Path;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,8 +35,6 @@
 
   LibraryDesugaringSpecification librarySpecification;
 
-  private DexApplication loadingApp = null;
-
   @Parameters(name = "{0}, spec: {1}")
   public static List<Object[]> data() {
     return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
@@ -74,10 +47,14 @@
   }
 
   private static List<AndroidApiLevel> getRelevantApiLevels() {
+    // B is implicit - everything is supported on B.
     return ImmutableList.of(
         AndroidApiLevel.K,
+        AndroidApiLevel.L,
+        AndroidApiLevel.M,
         AndroidApiLevel.N,
         AndroidApiLevel.O,
+        AndroidApiLevel.P,
         AndroidApiLevel.Q,
         AndroidApiLevel.R,
         AndroidApiLevel.S,
@@ -141,26 +118,29 @@
 
   @Test
   public void test() throws Exception {
-    InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter());
+    SupportedMethodsWithAnnotations supportedMethods =
+        new SupportedMethodsGenerator(new InternalOptions())
+            .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
 
-    Set<DexMethod> supportedMethodsInB = collectListOfSupportedMethods(AndroidApiLevel.B, options);
-
-    Map<AndroidApiLevel, Set<DexMethod>> failures = new IdentityHashMap<>();
     for (AndroidApiLevel api : getRelevantApiLevels()) {
-      Set<DexMethod> dexMethods =
-          verifyAllMethodsSupportedInBAreSupportedIn(supportedMethodsInB, api, options);
-      failures.put(api, dexMethods);
+      Set<DexMethod> localFailures = Sets.newIdentityHashSet();
+      supportedMethods.annotatedMethods.forEach(
+          (method, annotation) -> {
+            if (annotation.isUnsupportedInMinApiRange()) {
+              if (api.getLevel() >= annotation.getMinRange()
+                  && api.getLevel() <= annotation.getMaxRange()) {
+                localFailures.add(method);
+              }
+            }
+          });
+      Set<String> expectedFailures = getExpectedFailures(api);
+      Set<String> apiFailuresString =
+          localFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
+      if (!expectedFailures.equals(apiFailuresString)) {
+        System.out.println("Failure for api " + api);
+        assertEquals(expectedFailures, apiFailuresString);
+      }
     }
-    failures.forEach(
-        (api, apiFailures) -> {
-          Set<String> expectedFailures = getExpectedFailures(api);
-          Set<String> apiFailuresString =
-              apiFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
-          if (!expectedFailures.equals(apiFailuresString)) {
-            System.out.println("Failure for api " + api);
-            assertEquals(expectedFailures, apiFailuresString);
-          }
-        });
   }
 
   private Set<String> getExpectedFailures(AndroidApiLevel api) {
@@ -183,6 +163,10 @@
         && api.isLessThan(AndroidApiLevel.T)) {
       expectedFailures.addAll(FAILURES_TO_ARRAY);
     }
+    if (librarySpecification == JDK8 && api.isLessThan(AndroidApiLevel.T)) {
+      // Interestingly that was added somehow to JDK8 desugared library at some point...
+      expectedFailures.addAll(FAILURES_TO_ARRAY);
+    }
     if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
       expectedFailures.addAll(FAILURES_CHRONOLOGY);
       expectedFailures.addAll(FAILURES_DATE_TIME_BUILDER);
@@ -196,137 +180,4 @@
     }
     return expectedFailures;
   }
-
-  private Set<DexMethod> verifyAllMethodsSupportedInBAreSupportedIn(
-      Set<DexMethod> supportedMethodsInB, AndroidApiLevel api, InternalOptions options)
-      throws IOException {
-    Set<DexMethod> failures = Sets.newIdentityHashSet();
-    AndroidApp library =
-        AndroidApp.builder().addProgramFiles(ToolHelper.getAndroidJar(api)).build();
-    DirectMappedDexApplication dexApplication =
-        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-    AppInfoWithClassHierarchy appInfo =
-        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-            dexApplication,
-            ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-            MainDexInfo.none(),
-            GlobalSyntheticsStrategy.forNonSynthesizing(),
-            StartupOrder.empty());
-    MachineDesugaredLibrarySpecification machineSpecification =
-        getMachineSpecification(api, options);
-
-    options.setMinApiLevel(api);
-    options.setDesugaredLibrarySpecification(machineSpecification);
-    List<DexMethod> backports =
-        BackportedMethodRewriter.generateListOfBackportedMethods(
-            library, options, ThreadUtils.getExecutorService(1));
-
-    for (DexMethod dexMethod : supportedMethodsInB) {
-      if (machineSpecification.isSupported(dexMethod)) {
-        continue;
-      }
-      if (backports.contains(dexMethod)) {
-        continue;
-      }
-      if (machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
-        continue;
-      }
-      if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())) {
-        // Static methods on emulated interfaces are always supported if the emulated interface is
-        // supported.
-        continue;
-      }
-      MethodResolutionResult methodResolutionResult =
-          appInfo.resolveMethod(
-              dexMethod,
-              appInfo.contextIndependentDefinitionFor(dexMethod.getHolderType()).isInterface());
-      if (methodResolutionResult.isFailedResolution()) {
-        failures.add(dexMethod);
-      }
-    }
-    return failures;
-  }
-
-  private MachineDesugaredLibrarySpecification getMachineSpecification(
-      AndroidApiLevel api, InternalOptions options) throws IOException {
-    DesugaredLibrarySpecification specification =
-        DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-            StringResource.fromFile(librarySpecification.getSpecification()),
-            options.itemFactory,
-            options.reporter,
-            false,
-            api.getLevel());
-    Path androidJarPath = ToolHelper.getAndroidJar(specification.getRequiredCompilationApiLevel());
-    DexApplication app = getLoadingApp(androidJarPath, options);
-    return specification.toMachineSpecification(app, Timing.empty());
-  }
-
-  private DexApplication getLoadingApp(Path androidLib, InternalOptions options)
-      throws IOException {
-    if (loadingApp != null) {
-      return loadingApp;
-    }
-    AndroidApp.Builder builder = AndroidApp.builder();
-    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
-    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    assert !options.ignoreJavaLibraryOverride;
-    options.ignoreJavaLibraryOverride = true;
-    loadingApp = applicationReader.read(executorService);
-    options.ignoreJavaLibraryOverride = false;
-    return loadingApp;
-  }
-
-  private Set<DexMethod> collectListOfSupportedMethods(AndroidApiLevel api, InternalOptions options)
-      throws Exception {
-
-    AndroidApp implementation =
-        AndroidApp.builder().addProgramFiles(librarySpecification.getDesugarJdkLibs()).build();
-    DirectMappedDexApplication implementationApplication =
-        new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
-
-    AndroidApp library =
-        AndroidApp.builder().addProgramFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)).build();
-    DirectMappedDexApplication applicationForT =
-        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-
-    MachineDesugaredLibrarySpecification machineSpecification =
-        getMachineSpecification(AndroidApiLevel.B, implementationApplication.options);
-    Set<DexMethod> supportedMethods = Sets.newIdentityHashSet();
-
-    for (DexProgramClass clazz : implementationApplication.classes()) {
-      // All emulated interfaces static and default methods are supported.
-      if (machineSpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
-        assert clazz.isInterface();
-        for (DexEncodedMethod method : clazz.methods()) {
-          if (!method.isDefaultMethod() && !method.isStatic()) {
-            continue;
-          }
-          if (method.getName().startsWith("lambda$")
-              || method.getName().toString().contains("$deserializeLambda$")) {
-            // We don't care if lambda methods are present or not.
-            continue;
-          }
-          supportedMethods.add(method.getReference());
-        }
-      } else {
-        // All methods in maintained or rewritten classes are supported.
-        if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
-            && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type)
-            && applicationForT.definitionFor(clazz.type) != null) {
-          for (DexEncodedMethod method : clazz.methods()) {
-            if (!method.isPublic() && !method.isProtectedMethod()) {
-              continue;
-            }
-            supportedMethods.add(method.getReference());
-          }
-        }
-      }
-    }
-
-    // All retargeted methods are supported.
-    machineSpecification.forEachRetargetMethod(supportedMethods::add);
-
-    return supportedMethods;
-  }
 }