Enables Desugar-Shadowable API Auto Desugaring for all Android Platform Types

#desugar #new-android-api-support #desugar-shadowed-api

- This change fully launches the automated desugaring feature for all Android APIs with a desugar-shadowable Java platform type (e.g. java.time.Duration) in its method header without restriction, including both invocable APIs and overridable APIs.
- Remove the hardcoded whitelists for the adapting candidacy of
  - Invocable API type adapter use and generation:
    - ClassName.isInPackageEligibleForTypeAdapter
  - Overridable API bridge basis
    - ClassName.isInPackageEligibleForShadowedOverridableAPIs
  - Overridable API bridge insertion
    - ClassName.isInPackageEligibleForHoldingOverridingBridges
- The above adapting candidacy of an API is superseded by the following,
  1. The containing class/interface of the API is present in the boot class path.
  2. The containing class/interface of the API has a package prefix of "android/" or "androidx/"
- Add a test that ensures all desugar-shadowed types on Android SDK platform are fully covered. that once any type is missing an expected type converter, the test will fail and report all types that requires type converter additions.

PiperOrigin-RevId: 310251287
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedPlatformTypeConverterCoverageTest.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedPlatformTypeConverterCoverageTest.java
new file mode 100644
index 0000000..7cb8f86
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedPlatformTypeConverterCoverageTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.corelibadapter;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.TYPE_ADAPTER_PACKAGE_ROOT;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.TYPE_CONVERTER_SUFFIX;
+import static org.objectweb.asm.ClassReader.SKIP_CODE;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
+import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.android.desugar.io.JarItem;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Checks a) the Java platform types with supported desugar-shadowed/mirrored type converters under
+ * {@link com.google.devtools.build.android.desugar.typeadapter} subpackages, against b) the
+ * <i>desugar-shadowable Java platform types</i> referenced by any Android platform method headers,
+ * and reports an missing error if there is any Java Platform type absent in a) but present in b).
+ *
+ * <p>A type is a <i>desugar-shadowable Java platform type</i> if and only if the type tests {@code
+ * true} for {@link ClassName#isDesugarShadowedType()}.
+ */
+@RunWith(JUnit4.class)
+public class ShadowedPlatformTypeConverterCoverageTest {
+
+  private static final Splitter SPACE_SPLITTER = Splitter.on(" ").trimResults();
+  private static final ImmutableList<String> PLATFORM_JAR_PATHS =
+      ImmutableList.copyOf(SPACE_SPLITTER.splitToList(System.getProperty("platform_jars")));
+  private static final String TYPE_CONVERTER_JAR_PATH = System.getProperty("type_converter_jar");
+
+  private final ImmutableMultimap<ClassName, MethodHeaderTypeTrackingLabel>
+      shadowedTypesOnPlatformMethodHeaders = getShadowedTypesOnPlatformMethodHeaders();
+  private final ImmutableSet<ClassName> shadowedTypesWithTypeConverterSupport =
+      getShadowedTypesWithTypeConverterSupport();
+
+  @Test
+  public void checkTypeConverterSupport_allAndroidPlatformMethodHeadersCovered() {
+    ImmutableSet<ClassName> shadowedTypesOnPlatform = shadowedTypesOnPlatformMethodHeaders.keySet();
+    Set<ClassName> shadowedTypesMissingTypeConverter =
+        Sets.difference(shadowedTypesOnPlatform, shadowedTypesWithTypeConverterSupport);
+
+    assertWithMessage(
+            String.format(
+                "Desugar-shadowable platform types missing a type converter: \n%s\n",
+                shadowedTypesMissingTypeConverter.stream()
+                    .flatMap(type -> shadowedTypesOnPlatformMethodHeaders.get(type).stream())
+                    .collect(toImmutableList())))
+        .that(shadowedTypesMissingTypeConverter)
+        .isEmpty();
+  }
+
+  private static ImmutableMultimap<ClassName, MethodHeaderTypeTrackingLabel>
+      getShadowedTypesOnPlatformMethodHeaders() {
+    ImmutableMultimap.Builder<ClassName, MethodHeaderTypeTrackingLabel> shadowedTypesBuilder =
+        ImmutableMultimap.builder();
+    PLATFORM_JAR_PATHS.stream()
+        .flatMap(jarTextPath -> JarItem.jarItemStream(Paths.get(jarTextPath)))
+        .filter(
+            jarItem ->
+                jarItem.jarEntryName().endsWith(".class")
+                    && !jarItem.jarEntryName().startsWith("META-INF/"))
+        .forEach(
+            jarItem -> {
+              try (InputStream inputStream = jarItem.getInputStream()) {
+                ClassReader cr = new ClassReader(inputStream);
+                ClassVisitor cv =
+                    new ClassMemberHeaderClassVisitor(shadowedTypesBuilder, jarItem.jarPath());
+                cr.accept(cv, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
+              } catch (IOException e) {
+                throw new IOError(e);
+              }
+            });
+    return shadowedTypesBuilder.build();
+  }
+
+  private static ImmutableSet<ClassName> getShadowedTypesWithTypeConverterSupport() {
+    String typeConverterClassFileSuffix = TYPE_CONVERTER_SUFFIX + ".class";
+    int typeConverterClassFileSuffixLength = typeConverterClassFileSuffix.length();
+    return JarItem.jarItemStream(Paths.get(TYPE_CONVERTER_JAR_PATH))
+        .map(JarItem::jarEntryName)
+        .filter(
+            jarEntryName ->
+                jarEntryName.startsWith(TYPE_ADAPTER_PACKAGE_ROOT)
+                    && jarEntryName.endsWith(typeConverterClassFileSuffix))
+        .map(
+            jarEntryName ->
+                ClassName.create(
+                    jarEntryName.substring(
+                        TYPE_ADAPTER_PACKAGE_ROOT.length(),
+                        jarEntryName.length() - typeConverterClassFileSuffixLength)))
+        .collect(toImmutableSet());
+  }
+
+  private static class ClassMemberHeaderClassVisitor extends ClassVisitor {
+
+    private final ImmutableMultimap.Builder<ClassName, MethodHeaderTypeTrackingLabel> shadowedTypes;
+    private final Path containgJar;
+
+    private ClassName className;
+    private int classAccess;
+
+    ClassMemberHeaderClassVisitor(
+        ImmutableMultimap.Builder<ClassName, MethodHeaderTypeTrackingLabel> shadowedTypes,
+        Path containingJar) {
+      super(Opcodes.ASM7);
+      this.shadowedTypes = shadowedTypes;
+      this.containgJar = containingJar;
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      super.visit(version, classAccess, name, signature, superName, interfaces);
+      className = ClassName.create(name);
+      classAccess = access;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      if (className.isAndroidDomainType()) {
+        MethodDeclInfo methodDeclInfo =
+            MethodDeclInfo.create(
+                MethodKey.create(className, name, descriptor),
+                classAccess,
+                access,
+                signature,
+                exceptions);
+        if (methodDeclInfo.isPublicAccess() || methodDeclInfo.isProtectedAccess()) {
+          ClassName returnType = methodDeclInfo.returnTypeName();
+          if (returnType.isDesugarShadowedType()) {
+            MethodHeaderTypeTrackingLabel trackingLabel =
+                MethodHeaderTypeTrackingLabel.builder()
+                    .setShadowedType(returnType)
+                    .setMethod(methodDeclInfo)
+                    .setAtReturnType(true)
+                    .setParameterTypePosition(-1)
+                    .setExceptionTypePosition(-1)
+                    .setJarPath(containgJar)
+                    .build();
+            shadowedTypes.put(trackingLabel.shadowedType(), trackingLabel);
+          }
+
+          ImmutableList<ClassName> argumentTypes = methodDeclInfo.argumentTypeNames();
+          for (int i = 0; i < argumentTypes.size(); i++) {
+            ClassName parameterType = argumentTypes.get(i);
+            if (parameterType.isDesugarShadowedType()) {
+              MethodHeaderTypeTrackingLabel trackingLabel =
+                  MethodHeaderTypeTrackingLabel.builder()
+                      .setShadowedType(parameterType)
+                      .setMethod(methodDeclInfo)
+                      .setAtReturnType(false)
+                      .setParameterTypePosition(i)
+                      .setExceptionTypePosition(-1)
+                      .setJarPath(containgJar)
+                      .build();
+              shadowedTypes.put(trackingLabel.shadowedType(), trackingLabel);
+            }
+          }
+
+          ImmutableList<ClassName> exceptionTypes = methodDeclInfo.argumentTypeNames();
+          for (int i = 0; i < exceptionTypes.size(); i++) {
+            ClassName exceptionType = exceptionTypes.get(i);
+            if (exceptionType.isDesugarShadowedType()) {
+              MethodHeaderTypeTrackingLabel trackingLabel =
+                  MethodHeaderTypeTrackingLabel.builder()
+                      .setShadowedType(exceptionType)
+                      .setMethod(methodDeclInfo)
+                      .setAtReturnType(false)
+                      .setParameterTypePosition(-1)
+                      .setExceptionTypePosition(i)
+                      .setJarPath(containgJar)
+                      .build();
+              shadowedTypes.put(trackingLabel.shadowedType(), trackingLabel);
+            }
+          }
+        }
+      }
+      return super.visitMethod(access, name, descriptor, signature, exceptions);
+    }
+  }
+
+  /** Tracks the origin of a method header type, including parameter, return and exception types. */
+  @AutoValue
+  abstract static class MethodHeaderTypeTrackingLabel {
+    abstract ClassName shadowedType();
+
+    abstract MethodDeclInfo method();
+
+    abstract boolean atReturnType();
+
+    abstract int parameterTypePosition();
+
+    abstract int exceptionTypePosition();
+
+    abstract Path jarPath();
+
+    static Builder builder() {
+      return new AutoValue_ShadowedPlatformTypeConverterCoverageTest_MethodHeaderTypeTrackingLabel
+          .Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+
+      abstract Builder setShadowedType(ClassName value);
+
+      abstract Builder setMethod(MethodDeclInfo value);
+
+      abstract Builder setAtReturnType(boolean value);
+
+      abstract Builder setParameterTypePosition(int value);
+
+      abstract Builder setExceptionTypePosition(int value);
+
+      abstract Builder setJarPath(Path value);
+
+      abstract MethodHeaderTypeTrackingLabel build();
+    }
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
index e08e1a0..5505c51 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
@@ -88,9 +88,11 @@
    * @param verbatimInvocationSite The invocation site parsed directly from the desugar input jar.
    *     No in-process label, such as "__desugar__/", is attached to this invocation site.
    */
-  static boolean shouldUseApiTypeAdapter(MethodInvocationSite verbatimInvocationSite) {
+  static boolean shouldUseApiTypeAdapter(
+      MethodInvocationSite verbatimInvocationSite, BootClassPathDigest bootClassPathDigest) {
     return verbatimInvocationSite.invocationKind() != MemberUseKind.INVOKESPECIAL
-        && verbatimInvocationSite.owner().isInPackageEligibleForTypeAdapter()
+        && verbatimInvocationSite.owner().isAndroidDomainType()
+        && bootClassPathDigest.containsType(verbatimInvocationSite.owner())
         && verbatimInvocationSite.method().getHeaderTypeNameSet().stream()
             .anyMatch(ClassName::isDesugarShadowedType);
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
index d4f386d..7aa7422 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
@@ -296,7 +296,7 @@
         return;
       }
 
-      if (shouldUseApiTypeAdapter(verbatimInvocationSite)) {
+      if (shouldUseApiTypeAdapter(verbatimInvocationSite, bootClassPathDigest)) {
         checkState(!immutableLabelApplicator.isSwitchOn());
         MethodInvocationSite adapterSite =
             ShadowedApiAdapterHelper.getAdapterInvocationSite(verbatimInvocationSite);
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/JarItem.java b/src/tools/android/java/com/google/devtools/build/android/desugar/io/JarItem.java
index 68ef521..51312e5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/io/JarItem.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/JarItem.java
@@ -22,6 +22,8 @@
 import java.io.IOError;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.stream.Stream;
@@ -42,6 +44,10 @@
     return Streams.zip(Stream.generate(() -> jarFile), jarFile.stream(), JarItem::create);
   }
 
+  public static Stream<JarItem> jarItemStream(Path jarFilePath) {
+    return jarItemStream(newJarFile(jarFilePath.toFile()));
+  }
+
   public static JarFile newJarFile(File file) {
     try {
       return new JarFile(file);
@@ -50,7 +56,15 @@
     }
   }
 
-  public InputStream getInputStream() {
+  public final Path jarPath() {
+    return Paths.get(jarFile().getName());
+  }
+
+  public final String jarEntryName() {
+    return jarEntry().getName();
+  }
+
+  public final InputStream getInputStream() {
     try {
       return jarFile().getInputStream(jarEntry());
     } catch (IOException e) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
index 98f7e87..7098720 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
@@ -38,7 +38,7 @@
 
   private static final String IMMUTABLE_LABEL_LABEL = "__final__/";
 
-  private static final String TYPE_ADAPTER_PACKAGE_ROOT =
+  public static final String TYPE_ADAPTER_PACKAGE_ROOT =
       "com/google/devtools/build/android/desugar/typeadapter/";
 
   public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
@@ -49,7 +49,7 @@
 
   private static final String TYPE_ADAPTER_SUFFIX = "Adapter";
 
-  private static final String TYPE_CONVERTER_SUFFIX = "Converter";
+  public static final String TYPE_CONVERTER_SUFFIX = "Converter";
 
   /**
    * The primitive type as specified at
@@ -228,7 +228,7 @@
         "Expected a label-free type: Actual(%s)",
         this);
     checkState(
-        isInPackageEligibleForTypeAdapter(),
+        isAndroidDomainType(),
         "Expected an Android SDK type to have an adapter: Actual (%s)",
         this);
     String binaryName =
@@ -313,47 +313,10 @@
     return !isInDesugarRuntimeLibrary();
   }
 
-  public final boolean isInPackageEligibleForTypeAdapter() {
-    // TODO(b/152573900): Update to hasPackagePrefix("android/") once all package-wise incremental
-    // rollouts are complete.
-
-    return hasAnyPackagePrefix(
-        "android/testing/",
-        "android/app/Activity",
-        "android/accessibilityservice/AccessibilityService",
-        "android/app/admin/FreezePeriod",
-        "android/app/role/RoleManager",
-        "android/app/usage/UsageStatsManager",
-        "android/hardware/display/AmbientBrightnessDayStats",
-        "android/os/SystemClock",
-        "android/service/controls/ControlsProviderService",
-        "android/service/voice/VoiceInteractionSession",
-        "android/service/voice/VoiceInteractionSession",
-        "android/telephony/SubscriptionPlan$Builder",
-        "android/telephony/TelephonyManager",
-        "android/view/textclassifier/ConversationActions$Message",
-        "android/view/textclassifier/TextClassification$Request",
-        "android/view/textclassifier/TextLinks");
-  }
-
   public final boolean isAndroidDomainType() {
     return hasAnyPackagePrefix("android/", "androidx/");
   }
 
-  public final boolean isInPackageEligibleForShadowedOverridableAPIs() {
-    // TODO(b/152573900): Update to hasPackagePrefix("android/") once all package-wise incremental
-    // rollouts are complete.
-    return hasAnyPackagePrefix(
-        "android/testing/",
-        "android/app/Activity",
-        "android/service/controls/ControlsProviderService");
-  }
-
-  public final boolean isInPackageEligibleForHoldingOverridingBridges() {
-    // Exclude platform types for overriding bridge generations.
-    return !hasAnyPackagePrefix("android/", "java/");
-  }
-
   public final boolean isInDesugarRuntimeLibrary() {
     return hasAnyPackagePrefix(
         "com/google/devtools/build/android/desugar/runtime/", TYPE_ADAPTER_PACKAGE_ROOT);
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java
new file mode 100644
index 0000000..93dc037
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.time;
+
+/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.LocalTime}. */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class LocalTimeConverter {
+
+  private LocalTimeConverter() {}
+
+  public static j$.time.LocalTime from(java.time.LocalTime value) {
+    return value == null ? null : j$.time.LocalTime.ofNanoOfDay(value.toNanoOfDay());
+  }
+
+  public static java.time.LocalTime to(j$.time.LocalTime value) {
+    return value == null ? null : java.time.LocalTime.ofNanoOfDay(value.toNanoOfDay());
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java
new file mode 100644
index 0000000..bc14919
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.time;
+
+/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.Period}. */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class PeriodConverter {
+
+  private PeriodConverter() {}
+
+  public static j$.time.Period from(java.time.Period period) {
+    return period == null
+        ? null
+        : j$.time.Period.of(period.getYears(), period.getMonths(), period.getDays());
+  }
+
+  public static java.time.Period to(j$.time.Period period) {
+    return period == null
+        ? null
+        : java.time.Period.of(period.getYears(), period.getMonths(), period.getDays());
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java
new file mode 100644
index 0000000..17b1621
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.BiFunction}.
+ */
+@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"})
+public abstract class BiFunctionConverter {
+
+  private BiFunctionConverter() {}
+
+  public static <T, U, R> j$.util.function.BiFunction<T, U, R> from(
+      final java.util.function.BiFunction<T, U, R> function) {
+    return function == null
+        ? null
+        : new j$.util.function.BiFunction<T, U, R>() {
+          @Override
+          public R apply(T t, U u) {
+            return function.apply(t, u);
+          }
+
+          @Override
+          public <V> j$.util.function.BiFunction<T, U, V> andThen(
+              j$.util.function.Function<? super R, ? extends V> after) {
+            return from(function.andThen(FunctionConverter.to(after)));
+          }
+        };
+  }
+
+  public static <T, U, R> java.util.function.BiFunction<T, U, R> to(
+      final j$.util.function.BiFunction<T, U, R> function) {
+    return function == null
+        ? null
+        : new java.util.function.BiFunction<T, U, R>() {
+          @Override
+          public R apply(T t, U u) {
+            return function.apply(t, u);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java
new file mode 100644
index 0000000..c50d217
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.BinaryOperator}.
+ */
+@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"})
+public abstract class BinaryOperatorConverter {
+
+  private BinaryOperatorConverter() {}
+
+  public static <T> j$.util.function.BinaryOperator<T> from(
+      final java.util.function.BinaryOperator<T> function) {
+    return function == null
+        ? null
+        : new j$.util.function.BinaryOperator<T>() {
+          @Override
+          public T apply(T t, T u) {
+            return function.apply(t, u);
+          }
+
+          @Override
+          public <V> j$.util.function.BiFunction<T, T, V> andThen(
+              j$.util.function.Function<? super T, ? extends V> after) {
+            return BiFunctionConverter.from(function.andThen(FunctionConverter.to(after)));
+          }
+        };
+  }
+
+  public static <T> java.util.function.BinaryOperator<T> to(
+      final j$.util.function.BinaryOperator<T> function) {
+    return function == null
+        ? null
+        : new java.util.function.BinaryOperator<T>() {
+          @Override
+          public T apply(T t, T u) {
+            return function.apply(t, u);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java
index 3f672d3..3351bfc 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java
@@ -21,7 +21,7 @@
  * java.util.function.Consumer}.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public class ConsumerConverter {
+public abstract class ConsumerConverter {
 
   private ConsumerConverter() {}
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java
new file mode 100644
index 0000000..7cde5a5
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+import j$.util.function.DoubleUnaryOperator;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.DoubleUnaryOperator}.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class DoubleUnaryOperatorConverter {
+
+  private DoubleUnaryOperatorConverter() {}
+
+  public static j$.util.function.DoubleUnaryOperator from(
+      final java.util.function.DoubleUnaryOperator function) {
+    return function == null
+        ? null
+        : new j$.util.function.DoubleUnaryOperator() {
+
+          @Override
+          public double applyAsDouble(double operand) {
+            return function.applyAsDouble(operand);
+          }
+
+          @Override
+          public DoubleUnaryOperator compose(DoubleUnaryOperator before) {
+            return from(function.compose(to(before)));
+          }
+
+          @Override
+          public DoubleUnaryOperator andThen(DoubleUnaryOperator after) {
+            return from(function.andThen(to(after)));
+          }
+        };
+  }
+
+  public static java.util.function.DoubleUnaryOperator to(
+      final j$.util.function.DoubleUnaryOperator function) {
+    return function == null
+        ? null
+        : new java.util.function.DoubleUnaryOperator() {
+          @Override
+          public double applyAsDouble(double operand) {
+            return function.applyAsDouble(operand);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java
index 3010322..77fc3d8 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java
@@ -21,7 +21,7 @@
  * java.util.function.Consumer}.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public class FunctionConverter {
+public abstract class FunctionConverter {
 
   private FunctionConverter() {}
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java
new file mode 100644
index 0000000..ce6cdf3
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.IntConsumer}.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class IntConsumerConverter {
+
+  private IntConsumerConverter() {}
+
+  public static j$.util.function.IntConsumer from(final java.util.function.IntConsumer consumer) {
+    return consumer == null
+        ? null
+        : new j$.util.function.IntConsumer() {
+
+          @Override
+          public void accept(int value) {
+            consumer.accept(value);
+          }
+
+          @Override
+          public j$.util.function.IntConsumer andThen(j$.util.function.IntConsumer after) {
+            return from(consumer.andThen(to(after)));
+          }
+        };
+  }
+
+  public static java.util.function.IntConsumer to(final j$.util.function.IntConsumer consumer) {
+    return consumer == null
+        ? null
+        : new java.util.function.IntConsumer() {
+          @Override
+          public void accept(int value) {
+            consumer.accept(value);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java
new file mode 100644
index 0000000..66dbbc1
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.IntFunction}.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class IntFunctionConverter {
+
+  private IntFunctionConverter() {}
+
+  public static <R> j$.util.function.IntFunction<R> from(
+      final java.util.function.IntFunction<R> function) {
+    return function == null
+        ? null
+        : new j$.util.function.IntFunction<R>() {
+          @Override
+          public R apply(int value) {
+            return function.apply(value);
+          }
+        };
+  }
+
+  public static <R> java.util.function.IntFunction<R> to(
+      final j$.util.function.IntFunction<R> function) {
+    return function == null
+        ? null
+        : new java.util.function.IntFunction<R>() {
+          @Override
+          public R apply(int value) {
+            return function.apply(value);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java
new file mode 100644
index 0000000..4db2426
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.Predicate}.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public abstract class PredicateConverter {
+
+  private PredicateConverter() {}
+
+  public static <T> j$.util.function.Predicate<T> from(
+      final java.util.function.Predicate<T> predicate) {
+    return predicate == null
+        ? null
+        : new j$.util.function.Predicate<T>() {
+          @Override
+          public boolean test(T t) {
+            return predicate.test(t);
+          }
+
+          @Override
+          public j$.util.function.Predicate<T> and(j$.util.function.Predicate<? super T> other) {
+            return from(predicate.and(to(other)));
+          }
+
+          @Override
+          public j$.util.function.Predicate<T> negate() {
+            return from(predicate.negate());
+          }
+
+          @Override
+          public j$.util.function.Predicate<T> or(j$.util.function.Predicate<? super T> other) {
+            return from(predicate.or(to(other)));
+          }
+        };
+  }
+
+  public static <T> java.util.function.Predicate<T> to(
+      final j$.util.function.Predicate<T> predicate) {
+    return predicate == null
+        ? null
+        : new java.util.function.Predicate<T>() {
+          @Override
+          public boolean test(T t) {
+            return predicate.test(t);
+          }
+        };
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java
new file mode 100644
index 0000000..2869878
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.typeadapter.java.util.function;
+
+/**
+ * Converts types between the desugar-mirrored and desugar-shadowed {@link
+ * java.util.function.UnaryOperator}.
+ */
+@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"})
+public abstract class UnaryOperatorConverter {
+
+  private UnaryOperatorConverter() {}
+
+  public static <T> j$.util.function.UnaryOperator<T> from(
+      final java.util.function.UnaryOperator<T> unaryOperator) {
+    return unaryOperator == null
+        ? null
+        : new j$.util.function.UnaryOperator<T>() {
+          @Override
+          public T apply(T t) {
+            return unaryOperator.apply(t);
+          }
+
+          @Override
+          public <V> j$.util.function.Function<V, T> compose(
+              j$.util.function.Function<? super V, ? extends T> before) {
+            return FunctionConverter.from(unaryOperator.compose(FunctionConverter.to(before)));
+          }
+
+          @Override
+          public <V> j$.util.function.Function<T, V> andThen(
+              j$.util.function.Function<? super T, ? extends V> after) {
+            return FunctionConverter.from(unaryOperator.andThen(FunctionConverter.to(after)));
+          }
+        };
+  }
+
+  public static <T> java.util.function.UnaryOperator<T> to(
+      final j$.util.function.UnaryOperator<T> unaryOperator) {
+    return unaryOperator == null
+        ? null
+        : new java.util.function.UnaryOperator<T>() {
+          @Override
+          public T apply(T t) {
+            return unaryOperator.apply(t);
+          }
+        };
+  }
+}