Shadowed Android API Desuagar: Add Automated Support for Android APIs built-in Core Types

PiperOrigin-RevId: 303881017
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java
new file mode 100644
index 0000000..9b14ee3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedAndroidApiAdapterTest.java
@@ -0,0 +1,345 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static com.google.devtools.build.android.desugar.testing.junit.DesugarTestHelpers.getRuntimePathsFromJvmFlag;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.testing.junit.AsmNode;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRule;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner;
+import com.google.devtools.build.android.desugar.testing.junit.FromParameterValueSource;
+import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress;
+import com.google.devtools.build.android.desugar.testing.junit.JdkVersion;
+import com.google.devtools.build.android.desugar.testing.junit.ParameterValueSource;
+import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+/** Tests for accessing private constructors from another class within a nest. */
+@RunWith(DesugarRunner.class)
+@JdkSuppress(minJdkVersion = JdkVersion.V11)
+public class ShadowedAndroidApiAdapterTest {
+
+  @Rule
+  public final DesugarRule desugarRule =
+      DesugarRule.builder(this, MethodHandles.lookup())
+          .addSourceInputsFromJvmFlag("input_srcs")
+          .addClasspathEntries(getRuntimePathsFromJvmFlag("desugar_jdk_jar"))
+          .addClasspathEntries(getRuntimePathsFromJvmFlag("desugar_runtime_libs"))
+          .addClasspathEntries(getRuntimePathsFromJvmFlag("jdk_jar"))
+          .addJavacOptions("-source 11", "-target 11")
+          .addCommandOptions("core_library", "true")
+          .addCommandOptions("desugar_supported_core_libs", "true")
+          .enableIterativeTransformation(2)
+          .addCommandOptions("rewrite_core_library_prefix", "javadesugar/testing/")
+          .build();
+
+  @Test
+  public void inputClassFileMajorVersions_preDesugar(
+      @AsmNode(className = "com.app.testing.CuboidCalculator", round = 0) ClassNode before) {
+    assertThat(before.version).isEqualTo(JdkVersion.V11);
+  }
+
+  @Test
+  public void inputClassFileMajorVersions_postDesugar(
+      @AsmNode(className = "com.app.testing.CuboidCalculator", round = 1) ClassNode before) {
+    assertThat(before.version).isEqualTo(JdkVersion.V1_7);
+  }
+
+  @Test
+  public void executeUserApp_invokeConstructorPreDesugar(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeConstructor",
+              round = 0)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  public void executeUserApp_invokeConstructorDesugarOnce(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeConstructor",
+              round = 1)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  public void executeUserApp_invokeConstructorDesugarTwice(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeConstructor",
+              round = 2)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstancePreDesugar(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+              round = 0)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstanceDesugarOnce(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+              round = 1)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  public void executeUserApp_invokeDerivedClassConstructorWithEmbeddedInstanceDesugarTwice(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithEmbeddedInstance",
+              round = 2)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L);
+    assertThat(result).isEqualTo(24L);
+  }
+
+  @Test
+  @ParameterValueSource({"2", "3", "4", "64"})
+  @ParameterValueSource({"20", "30", "40", "24000"})
+  public void executeUserApp_invokeDerivedClassConstructorWithDimensionsPreDesugar(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithDimensions",
+              round = 0)
+          MethodHandle method,
+      @FromParameterValueSource long width,
+      @FromParameterValueSource long length,
+      @FromParameterValueSource long height,
+      @FromParameterValueSource long expectedVolume)
+      throws Throwable {
+    long result = (long) method.invoke(width, length, height);
+    assertThat(result).isEqualTo(expectedVolume);
+  }
+
+  @Test
+  @ParameterValueSource({"2", "3", "4", "64"})
+  @ParameterValueSource({"20", "30", "40", "24000"})
+  public void executeUserApp_invokeDerivedClassConstructorWithDimensionsDesugarOnce(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithDimensions",
+              round = 1)
+          MethodHandle method,
+      @FromParameterValueSource long width,
+      @FromParameterValueSource long length,
+      @FromParameterValueSource long height,
+      @FromParameterValueSource long expectedVolume)
+      throws Throwable {
+    long result = (long) method.invoke(width, length, height);
+    assertThat(result).isEqualTo(expectedVolume);
+  }
+
+  @Test
+  @ParameterValueSource({"2", "3", "4", "64"})
+  @ParameterValueSource({"20", "30", "40", "24000"})
+  public void executeUserApp_invokeDerivedClassConstructorWithDimensionsDesugarTwice(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeDerivedClassConstructorWithDimensions",
+              round = 2)
+          MethodHandle method,
+      @FromParameterValueSource long width,
+      @FromParameterValueSource long length,
+      @FromParameterValueSource long height,
+      @FromParameterValueSource long expectedVolume)
+      throws Throwable {
+    long result = (long) method.invoke(width, length, height);
+    assertThat(result).isEqualTo(expectedVolume);
+  }
+
+  @Test
+  public void executeUserApp_invokeStaticMethodPreDesugar(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeStaticMethod",
+              round = 0)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeStaticMethodDesugarOnce(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeStaticMethod",
+              round = 1)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeStaticMethodDesugarTwice(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeStaticMethod",
+              round = 2)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeInstanceMethodPreDesugar(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeInstanceMethod",
+              round = 0)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeInstanceMethodDesugarOnce(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeInstanceMethod",
+              round = 1)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeInstanceMethodDesugarTwice(
+      @RuntimeMethodHandle(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeInstanceMethod",
+              round = 2)
+          MethodHandle method)
+      throws Throwable {
+    long result = (long) method.invoke(2L, 3L, 4L, 10L, 10L, 10L);
+    assertThat(result).isEqualTo(24000L);
+  }
+
+  @Test
+  public void executeUserApp_invokeConstructorDesugarOnceInlineConversion(
+      @AsmNode(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeConstructor",
+              round = 1)
+          MethodNode method) {
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+        .isEmpty();
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+        .isNotEmpty();
+  }
+
+  @Test
+  public void executeUserApp_invokeStaticMethodDesugarConversionVerification(
+      @AsmNode(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeStaticMethod",
+              round = 1)
+          MethodNode method) {
+    assertThat(
+            findMethodInvocations(
+                method,
+                "desugar/runtime/typeadapter/android/testing/CuboidInflaterAdapter",
+                "inflateStatic"))
+        .isNotEmpty();
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+        .isEmpty();
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+        .isEmpty();
+  }
+
+  @Test
+  public void executeUserApp_invokeInstanceMethodDesugarConversionVerification(
+      @AsmNode(
+              className = "com.app.testing.CuboidCalculator",
+              memberName = "invokeInstanceMethod",
+              round = 1)
+          MethodNode method) {
+    assertThat(
+            findMethodInvocations(
+                method,
+                "desugar/runtime/typeadapter/android/testing/CuboidInflaterAdapter",
+                "inflateInstance"))
+        .isNotEmpty();
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "from"))
+        .isEmpty();
+    assertThat(
+            findMethodInvocations(
+                method, "desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter", "to"))
+        .isEmpty();
+  }
+
+  private static ImmutableList<MethodInsnNode> findMethodInvocations(
+      MethodNode enclosingMethod, String invokedMethodOwner, String invokedMethodName) {
+    AbstractInsnNode[] instructions = enclosingMethod.instructions.toArray();
+    return Arrays.stream(instructions)
+        .filter(node -> node.getType() == AbstractInsnNode.METHOD_INSN)
+        .map(node -> (MethodInsnNode) node)
+        .filter(
+            node -> invokedMethodOwner.equals(node.owner) && invokedMethodName.equals(node.name))
+        .collect(toImmutableList());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java
new file mode 100644
index 0000000..e1078b6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_android_sdk_libs/android/testing/CuboidInflater.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.testing;
+
+import javadesugar.testing.Cuboid;
+
+/**
+ * A simulated Android platform SDK class for desugar testing. The class should not be desugared.
+ */
+public class CuboidInflater {
+
+  private final Cuboid cuboid;
+
+  public CuboidInflater(Cuboid cuboid) {
+    this.cuboid = cuboid;
+  }
+
+  public static Cuboid inflateStatic(
+      Cuboid cuboid, long widthMultiplier, long lengthMultiplier, long heightMultiplier) {
+    return new Cuboid(
+        widthMultiplier * cuboid.getWidth(),
+        lengthMultiplier * cuboid.getLength(),
+        heightMultiplier * cuboid.getHeight());
+  }
+
+  public Cuboid inflateInstance(
+      long widthMultiplier, long lengthMultiplier, long heightMultiplier) {
+    return new Cuboid()
+        .setDimensions(
+            widthMultiplier * cuboid.getWidth(),
+            lengthMultiplier * cuboid.getLength(),
+            heightMultiplier * cuboid.getHeight());
+  }
+
+  public long getVolume() {
+    return cuboid.getVolume();
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java
new file mode 100644
index 0000000..6fb49be
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_jdk_libs/javadesugar/testing/Cuboid.java
@@ -0,0 +1,62 @@
+/*
+ * 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 javadesugar.testing;
+
+/** A simulated built-in JDK class for desugar testing. */
+public final class Cuboid {
+
+  private long width;
+  private long length;
+  private long height;
+
+  public Cuboid(long width, long length, long height) {
+    this.width = width;
+    this.length = length;
+    this.height = height;
+  }
+
+  public Cuboid() {
+    this(0, 0, 0);
+  }
+
+  public Cuboid(Cuboid cuboid) {
+    this(cuboid.width, cuboid.length, cuboid.height);
+  }
+
+  public Cuboid setDimensions(long width, long length, long height) {
+    this.width = width;
+    this.length = length;
+    this.height = height;
+    return this;
+  }
+
+  public long getWidth() {
+    return width;
+  }
+
+  public long getLength() {
+    return length;
+  }
+
+  public long getHeight() {
+    return height;
+  }
+
+  public long getVolume() {
+    return width * length * height;
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.java
new file mode 100644
index 0000000..7fa3d08
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_desugar_runtime_libs/desugar/runtime/typeadapter/javadesugar/testing/CuboidConverter.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 desugar.runtime.typeadapter.javadesugar.testing;
+
+/**
+ * Simulated a manually-configured type converter between platform built-in types and
+ * desugar-mirrored types.
+ */
+public abstract class CuboidConverter {
+
+  public static jd$.testing.Cuboid from(javadesugar.testing.Cuboid cuboid) {
+    return new jd$.testing.Cuboid()
+        .setDimensions(cuboid.getWidth(), cuboid.getLength(), cuboid.getHeight());
+  }
+
+  public static javadesugar.testing.Cuboid to(jd$.testing.Cuboid cuboid) {
+    return new javadesugar.testing.Cuboid()
+        .setDimensions(cuboid.getWidth(), cuboid.getLength(), cuboid.getHeight());
+  }
+
+  private CuboidConverter() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java
new file mode 100644
index 0000000..7df0b04
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/CuboidCalculator.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app.testing;
+
+import android.testing.CuboidInflater;
+import javadesugar.testing.Cuboid;
+
+/** Simulates application-level code that's subject to desugar. */
+public final class CuboidCalculator {
+
+  public static long invokeConstructor(long width, long length, long height) {
+    Cuboid cuboid = new Cuboid(width, length, height);
+
+    // Simulated Android API.
+    CuboidInflater inflater = new CuboidInflater(cuboid);
+
+    return inflater.getVolume();
+  }
+
+  public static long invokeDerivedClassConstructorWithEmbeddedInstance(
+      long width, long length, long height) {
+    Cuboid cuboid = new Cuboid(width, length, height);
+
+    // Derived
+    MyCuboidInflater inflater = new MyCuboidInflater(cuboid);
+
+    return inflater.getVolume();
+  }
+
+  public static long invokeDerivedClassConstructorWithDimensions(
+      long width, long length, long height) {
+
+    // Derived
+    MyCuboidInflater inflater = new MyCuboidInflater(width, length, height);
+
+    return inflater.getVolume();
+  }
+
+  public static long invokeStaticMethod(
+      long width,
+      long length,
+      long height,
+      long widthMultiplier,
+      long lengthMultiplier,
+      long heightMultiplier) {
+    Cuboid cuboid = new Cuboid(width, length, height);
+
+    // Simulated Android API.
+    cuboid =
+        CuboidInflater.inflateStatic(cuboid, widthMultiplier, lengthMultiplier, heightMultiplier);
+
+    return cuboid.getVolume();
+  }
+
+  public static long invokeInstanceMethod(
+      long width,
+      long length,
+      long height,
+      long widthMultiplier,
+      long lengthMultiplier,
+      long heightMultiplier) {
+    CuboidInflater cuboidInflater = createCuboidInflater(width, length, height);
+
+    // Simulated Android API.
+    Cuboid cuboid =
+        cuboidInflater.inflateInstance(widthMultiplier, lengthMultiplier, heightMultiplier);
+
+    return cuboid.getVolume();
+  }
+
+  private static CuboidInflater createCuboidInflater(long width, long length, long height) {
+    return new CuboidInflater(new Cuboid(width, length, height));
+  }
+
+  private CuboidCalculator() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.java b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.java
new file mode 100644
index 0000000..d91a691
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/corelibadapter/fake_user_app/com/app/testing/MyCuboidInflater.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.app.testing;
+
+import android.testing.CuboidInflater;
+import javadesugar.testing.Cuboid;
+
+/**
+ * Simulates an Android SDK-derived class in user application for testing constructor desugaring.
+ */
+public class MyCuboidInflater extends CuboidInflater {
+
+  public MyCuboidInflater(Object cuboid) {
+    // Simulates the invocation of Android SDK constructor API.
+    super(cuboid instanceof Cuboid ? (Cuboid) cuboid : new Cuboid(2, 4, 8));
+  }
+
+  public MyCuboidInflater(long w, long l, long h) {
+    // Simulates the invocation of Android SDK constructor API.
+    super(w * l * h < 64 ? new Cuboid(2, 4, 8) : new Cuboid(w, l, h));
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
index a078e03..28650c0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
@@ -41,6 +41,7 @@
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/protobuf:worker_protocol_java_proto",
         "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/io",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
@@ -74,6 +75,7 @@
         "//src/tools/android/java/com/google/devtools/build/android/desugar/nest:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation:srcs",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/scan:srcs",
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
index 30d7a6b..50d5c1b 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.devtools.build.android.desugar.io.BitFlags;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
@@ -351,7 +352,8 @@
       return false;
     }
     String implemented = internalName(itf);
-    if (implemented.startsWith("java/") || implemented.startsWith("__desugar__/java/")) {
+    if (implemented.startsWith("java/")
+        || implemented.startsWith(ClassName.IN_PROCESS_LABEL + "java/")) {
       return coreLibrarySupport != null
           && (coreLibrarySupport.isRenamedCoreLibrary(implemented)
               || coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented));
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index 8dce30e..21aa352 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -31,6 +31,9 @@
 import com.google.common.io.Resources;
 import com.google.devtools.build.android.Converters.ExistingPathConverter;
 import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdaptersGenerator;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite;
+import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite.ImmutableLabelRemover;
 import com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverter;
 import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter;
@@ -41,6 +44,9 @@
 import com.google.devtools.build.android.desugar.io.OutputFileProvider;
 import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
 import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder;
 import com.google.devtools.build.android.desugar.nest.NestAnalyzer;
 import com.google.devtools.build.android.desugar.nest.NestDesugaring;
 import com.google.devtools.build.android.desugar.nest.NestDigest;
@@ -55,6 +61,7 @@
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParser;
 import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.io.ByteArrayInputStream;
 import java.io.IOError;
 import java.io.IOException;
 import java.io.InputStream;
@@ -418,7 +425,7 @@
 
   private Desugar(DesugarOptions options, Path dumpDirectory) {
     this.options = options;
-    this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
+    this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? ClassName.IN_PROCESS_LABEL : "");
     this.lambdas = new LambdaClassMaker(dumpDirectory);
     this.outputJava7 = options.minSdkVersion < 24;
     this.allowDefaultMethods =
@@ -509,6 +516,9 @@
                   options.preserveCoreLibraryOverrides)
               : null;
 
+      InvocationSiteTransformationRecordBuilder callSiteTransCollector =
+          InvocationSiteTransformationRecord.builder();
+
       desugarClassesInInput(
           inputFiles,
           outputFileProvider,
@@ -518,7 +528,8 @@
           bootclasspathReader,
           coreLibrarySupport,
           interfaceCache,
-          interfaceLambdaMethodCollector);
+          interfaceLambdaMethodCollector,
+          callSiteTransCollector);
 
       desugarAndWriteDumpedLambdaClassesToOutput(
           outputFileProvider,
@@ -529,7 +540,8 @@
           coreLibrarySupport,
           interfaceCache,
           interfaceLambdaMethodCollector.build(),
-          bridgeMethodReader);
+          bridgeMethodReader,
+          callSiteTransCollector);
 
       desugarAndWriteGeneratedClasses(
           outputFileProvider,
@@ -537,10 +549,20 @@
           classpathReader,
           depsCollector,
           bootclasspathReader,
-          coreLibrarySupport);
+          coreLibrarySupport,
+          callSiteTransCollector);
 
       copyRuntimeClasses(outputFileProvider, coreLibrarySupport);
 
+      InvocationSiteTransformationRecord callSiteTransRecord = callSiteTransCollector.build();
+      ImmutableList<FileContentProvider<ByteArrayInputStream>> coreLibAdapters =
+          ShadowedApiAdaptersGenerator.generateAdapterClasses(callSiteTransRecord);
+
+      for (FileContentProvider<ByteArrayInputStream> fileContent : coreLibAdapters) {
+        outputFileProvider.write(
+            fileContent.getBinaryPathName(), ByteStreams.toByteArray(fileContent.get()));
+      }
+
       byte[] depsInfo = depsCollector.toByteArray();
       if (depsInfo != null) {
         outputFileProvider.write(OutputFileProvider.DESUGAR_DEPS_FILENAME, depsInfo);
@@ -652,7 +674,8 @@
       ClassReaderFactory bootclasspathReader,
       @Nullable CoreLibrarySupport coreLibrarySupport,
       ClassVsInterface interfaceCache,
-      ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
+      ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
+      InvocationSiteTransformationRecordBuilder callSiteRecord)
       throws IOException {
 
     ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents =
@@ -682,7 +705,8 @@
         // copy classes from Desugar's runtime library, which we build so they need no desugaring.
         // The runtime library typically uses constructs we'd otherwise desugar, so it's easier
         // to just skip it should it appear as a regular input (for idempotency).
-        if (inputFilename.endsWith(".class") && !inputFilename.startsWith(RUNTIME_LIB_PACKAGE)) {
+        if (inputFilename.endsWith(".class")
+            && ClassName.fromClassFileName(inputFilename).isDesugarEligible(options.coreLibrary)) {
           ClassReader reader = rewriter.reader(content);
           UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS);
           ClassVisitor visitor =
@@ -696,7 +720,8 @@
                   interfaceLambdaMethodCollector,
                   writer,
                   reader,
-                  nestDigest);
+                  nestDigest,
+                  callSiteRecord);
           if (writer == visitor) {
             // Just copy the input if there are no rewritings
             outputFileProvider.write(inputFilename, reader.b);
@@ -747,7 +772,8 @@
       @Nullable CoreLibrarySupport coreLibrarySupport,
       ClassVsInterface interfaceCache,
       ImmutableSet<String> interfaceLambdaMethods,
-      @Nullable ClassReaderFactory bridgeMethodReader)
+      @Nullable ClassReaderFactory bridgeMethodReader,
+      InvocationSiteTransformationRecordBuilder callSiteTransCollector)
       throws IOException {
     checkState(
         !allowDefaultMethods || interfaceLambdaMethods.isEmpty(),
@@ -784,7 +810,8 @@
                 bridgeMethodReader,
                 lambdaClass.getValue(),
                 writer,
-                reader);
+                reader,
+                callSiteTransCollector);
         reader.accept(visitor, 0);
         checkState(
             (options.coreLibrary && coreLibrarySupport != null)
@@ -802,7 +829,8 @@
       @Nullable ClassReaderFactory classpathReader,
       DependencyCollector depsCollector,
       ClassReaderFactory bootclasspathReader,
-      @Nullable CoreLibrarySupport coreLibrarySupport)
+      @Nullable CoreLibrarySupport coreLibrarySupport,
+      InvocationSiteTransformationRecordBuilder callSiteTransCollector)
       throws IOException {
     // Write out any classes we generated along the way
     if (coreLibrarySupport != null) {
@@ -822,6 +850,7 @@
         visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
         visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
         visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+        visitor = new ShadowedApiInvocationSite(visitor, callSiteTransCollector);
       }
 
       if (!allowTryWithResources) {
@@ -887,13 +916,15 @@
       @Nullable ClassReaderFactory bridgeMethodReader,
       LambdaInfo lambdaClass,
       UnprefixingClassWriter writer,
-      ClassReader input) {
+      ClassReader input,
+      InvocationSiteTransformationRecordBuilder callSiteRecord) {
     ClassVisitor visitor = checkNotNull(writer);
 
     if (coreLibrarySupport != null) {
       visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
       visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
       visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+      visitor = new ShadowedApiInvocationSite(visitor, callSiteRecord);
     }
 
     if (!allowTryWithResources) {
@@ -980,13 +1011,17 @@
       ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
       UnprefixingClassWriter writer,
       ClassReader input,
-      NestDigest nestDigest) {
+      NestDigest nestDigest,
+      InvocationSiteTransformationRecordBuilder callSiteRecord) {
     ClassVisitor visitor = checkNotNull(writer);
 
+    visitor = new ImmutableLabelRemover(visitor);
+
     if (coreLibrarySupport != null) {
       visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
       visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
       visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+      visitor = new ShadowedApiInvocationSite(visitor, callSiteRecord);
     }
 
     if (!allowTryWithResources) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD
new file mode 100644
index 0000000..abb0127
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD
@@ -0,0 +1,29 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+    default_visibility = [
+        "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__",
+    ],
+)
+
+java_library(
+    name = "corelibadapter",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/io",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
+        "//third_party:asm",
+        "//third_party:asm-commons",
+        "//third_party:asm-tree",
+        "//third_party:auto_value",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+    visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"],
+)
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
new file mode 100644
index 0000000..4b31d10
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java
@@ -0,0 +1,149 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.MemberUseKind;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.objectweb.asm.Type;
+
+/**
+ * Static utilities that serve conversions between desugar-shadowed platform types and their
+ * desugared-mirrored counterparts.
+ */
+class ShadowedApiAdapterHelper {
+
+  private ShadowedApiAdapterHelper() {}
+
+  /**
+   * Returns {@code true} if the desugar tool transforms given invocation site in an inline
+   * strategy, i.e. inserting the parameter type conversion instructions before the give invocation
+   * site.
+   *
+   * @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 shouldUseInlineTypeConversion(MethodInvocationSite verbatimInvocationSite) {
+    return verbatimInvocationSite.isConstructorInvocation()
+        && verbatimInvocationSite.owner().isInPackageEligibleForTypeAdapter()
+        && Stream.concat(
+                Stream.of(verbatimInvocationSite.returnTypeName()),
+                verbatimInvocationSite.argumentTypeNames().stream())
+            .anyMatch(ClassName::isDesugarShadowedType);
+  }
+
+  /**
+   * Returns {@code true} if the desugar tool transforms given invocation site in an adapter
+   * strategy, that is to replace the original invocation with its corresponding adapter method.
+   *
+   * @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) {
+    return !verbatimInvocationSite.isConstructorInvocation()
+        && verbatimInvocationSite.owner().isInPackageEligibleForTypeAdapter()
+        && Stream.concat(
+                Stream.of(verbatimInvocationSite.returnTypeName()),
+                verbatimInvocationSite.argumentTypeNames().stream())
+            .anyMatch(ClassName::isDesugarShadowedType);
+  }
+
+  /**
+   * Returns an optional {@link MethodInvocationSite}, present if the given {@link ClassName} is
+   * eligible for transforming a desugar-mirrored type to a desugar-shadowed platform type.
+   */
+  static Optional<MethodInvocationSite> anyMirroredToBuiltinTypeConversion(ClassName className) {
+    return className.isDesugarMirroredType()
+        ? Optional.of(
+            MethodInvocationSite.builder()
+                .setInvocationKind(MemberUseKind.INVOKESTATIC)
+                .setMethod(
+                    MethodKey.create(
+                        className.mirroredToShadowed().typeConverterOwner(),
+                        "to",
+                        Type.getMethodDescriptor(
+                            className.mirroredToShadowed().toAsmObjectType(),
+                            className.toAsmObjectType())))
+                .setIsInterface(false)
+                .build())
+        : Optional.empty();
+  }
+
+  /**
+   * Returns an {@link MethodInvocationSite} that serves transforming a {@code
+   * shadowedTypeName}-represented type to its desugar-mirrored counterpart.
+   */
+  static MethodInvocationSite shadowedToMirroredTypeConversionSite(ClassName shadowedTypeName) {
+    checkArgument(
+        shadowedTypeName.isDesugarShadowedType(),
+        "Expected desugar-shadowed type: Actual (%s)",
+        shadowedTypeName);
+    return MethodInvocationSite.builder()
+        .setInvocationKind(MemberUseKind.INVOKESTATIC)
+        .setMethod(
+            MethodKey.create(
+                shadowedTypeName.typeConverterOwner(),
+                "from",
+                Type.getMethodDescriptor(
+                    shadowedTypeName.shadowedToMirrored().toAsmObjectType(),
+                    shadowedTypeName.toAsmObjectType())))
+        .setIsInterface(false)
+        .build();
+  }
+
+  /**
+   * Returns an {@link MethodInvocationSite} that serves transforming a {@code
+   * mirroredTypeName}-represented type to its desugar-shadowed counterpart.
+   */
+  static MethodInvocationSite mirroredToShadowedTypeConversionSite(ClassName mirroredTypeName) {
+    checkArgument(
+        mirroredTypeName.isDesugarMirroredType(),
+        "Expected mirrored type: Actual (%s)",
+        mirroredTypeName);
+    return MethodInvocationSite.builder()
+        .setInvocationKind(MemberUseKind.INVOKESTATIC)
+        .setMethod(
+            MethodKey.create(
+                mirroredTypeName.mirroredToShadowed().typeConverterOwner(),
+                "to",
+                Type.getMethodDescriptor(
+                    mirroredTypeName.mirroredToShadowed().toAsmObjectType(),
+                    mirroredTypeName.toAsmObjectType())))
+        .setIsInterface(false)
+        .build();
+  }
+
+  /**
+   * Returns an {@link MethodInvocationSite} that serves as an adapter between desugar-mirrored
+   * invocations and desugar-shadowed invocations.
+   */
+  static MethodInvocationSite getAdapterInvocationSite(MethodInvocationSite methodInvocationSite) {
+    return MethodInvocationSite.builder()
+        .setInvocationKind(MemberUseKind.INVOKESTATIC)
+        .setMethod(
+            methodInvocationSite
+                .method()
+                .toAdapterMethodForArgsAndReturnTypes(methodInvocationSite.isStaticInvocation()))
+        .setIsInterface(false)
+        .build();
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java
new file mode 100644
index 0000000..732b732
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java
@@ -0,0 +1,167 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.android.desugar.io.FileContentProvider;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord;
+import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.io.ByteArrayInputStream;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Generates type adapter classes with methods that bridge the interactions between
+ * desugared-mirrored types and their desugar-shadowed built-in types, and delivers the generated
+ * classes to runtime library.
+ */
+public final class ShadowedApiAdaptersGenerator {
+
+  private static final int TYPE_ADAPTER_CLASS_ACCESS = ACC_PUBLIC | ACC_ABSTRACT | ACC_SYNTHETIC;
+  private static final int TYPE_CONVERSION_METHOD_ACCESS = ACC_PUBLIC | ACC_STATIC;
+
+  /** A pre-collected record that tracks the adapter method requests from invocation sites. */
+  private final InvocationSiteTransformationRecord invocationAdapterSites;
+
+  /** A record with evolving map values that track adapter classes to be generated. */
+  private final ImmutableMap<ClassName, ClassWriter> typeAdapters;
+
+  /** The public API that provides the file content of generate adapter classes. */
+  public static ImmutableList<FileContentProvider<ByteArrayInputStream>> generateAdapterClasses(
+      InvocationSiteTransformationRecord callSiteTransformations) {
+    return emitClassWriters(callSiteTransformations)
+        .emitAdapterMethods()
+        .closeClassWriters()
+        .provideFileContents();
+  }
+
+  private ShadowedApiAdaptersGenerator(
+      InvocationSiteTransformationRecord invocationAdapterSites,
+      ImmutableMap<ClassName, ClassWriter> typeAdapters) {
+    this.invocationAdapterSites = invocationAdapterSites;
+    this.typeAdapters = typeAdapters;
+  }
+
+  private static ShadowedApiAdaptersGenerator emitClassWriters(
+      InvocationSiteTransformationRecord callSiteTransformations) {
+    return new ShadowedApiAdaptersGenerator(
+        callSiteTransformations,
+        callSiteTransformations.record().stream()
+            .map(ShadowedApiAdapterHelper::getAdapterInvocationSite)
+            .map(MethodInvocationSite::owner)
+            .distinct()
+            .collect(
+                toImmutableMap(
+                    className -> className,
+                    ShadowedApiAdaptersGenerator::createAdapterClassWriter)));
+  }
+
+  private static ClassWriter createAdapterClassWriter(ClassName className) {
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+    cw.visit(
+        Opcodes.V1_7,
+        TYPE_ADAPTER_CLASS_ACCESS,
+        className.binaryName(),
+        /* signature= */ null,
+        /* superName= */ "java/lang/Object",
+        /* interfaces= */ new String[0]);
+    return cw;
+  }
+
+  private ShadowedApiAdaptersGenerator emitAdapterMethods() {
+    for (MethodInvocationSite invocationSite : invocationAdapterSites.record()) {
+      MethodInvocationSite adapterSite =
+          ShadowedApiAdapterHelper.getAdapterInvocationSite(invocationSite);
+      ClassName adapterOwner = adapterSite.owner();
+      ClassWriter cv =
+          checkNotNull(
+              typeAdapters.get(adapterOwner),
+              "Expected a class writer present before writing its methods. Requested adapter"
+                  + " owner: (%s). Available adapter owners: (%s).",
+              adapterOwner,
+              typeAdapters);
+      MethodKey adapterMethodKey = adapterSite.method();
+      MethodDeclInfo adapterMethodDecl =
+          MethodDeclInfo.create(
+              adapterMethodKey,
+              TYPE_ADAPTER_CLASS_ACCESS,
+              TYPE_CONVERSION_METHOD_ACCESS,
+              /* signature= */ null,
+              /* exceptions= */ new String[] {});
+      MethodVisitor mv = adapterMethodDecl.accept(cv);
+      mv.visitCode();
+
+      int slotOffset = 0;
+      for (Type argType : adapterMethodDecl.argumentTypes()) {
+        ClassName argTypeName = ClassName.create(argType);
+        mv.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset);
+        if (argTypeName.isDesugarMirroredType()) {
+          MethodInvocationSite conversion =
+              ShadowedApiAdapterHelper.mirroredToShadowedTypeConversionSite(argTypeName);
+          conversion.accept(mv);
+        }
+        slotOffset += argType.getSize();
+      }
+
+      invocationSite.accept(mv);
+
+      ClassName adapterReturnTypeName = adapterMethodDecl.returnTypeName();
+      if (adapterReturnTypeName.isDesugarMirroredType()) {
+        MethodInvocationSite conversion =
+            ShadowedApiAdapterHelper.shadowedToMirroredTypeConversionSite(
+                adapterReturnTypeName.mirroredToShadowed());
+        conversion.accept(mv);
+      }
+
+      mv.visitInsn(adapterMethodDecl.returnType().getOpcode(Opcodes.IRETURN));
+
+      mv.visitMaxs(slotOffset, slotOffset);
+      mv.visitEnd();
+    }
+    return this;
+  }
+
+  private ShadowedApiAdaptersGenerator closeClassWriters() {
+    typeAdapters.values().forEach(ClassVisitor::visitEnd);
+    return this;
+  }
+
+  private ImmutableList<FileContentProvider<ByteArrayInputStream>> provideFileContents() {
+    return typeAdapters.entrySet().stream()
+        .map(
+            e ->
+                new FileContentProvider<>(
+                    e.getKey().classFilePathName(),
+                    () -> new ByteArrayInputStream(e.getValue().toByteArray())))
+        .collect(toImmutableList());
+  }
+}
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
new file mode 100644
index 0000000..163cdcf
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java
@@ -0,0 +1,194 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseApiTypeAdapter;
+import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseInlineTypeConversion;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.IMMUTABLE_LABEL_ATTACHER;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.IN_PROCESS_LABEL_STRIPPER;
+import static com.google.devtools.build.android.desugar.langmodel.ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder;
+import com.google.devtools.build.android.desugar.langmodel.LangModelHelper;
+import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import com.google.devtools.build.android.desugar.langmodel.SwitchableTypeMapper;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.MethodRemapper;
+import org.objectweb.asm.tree.MethodNode;
+
+/**
+ * Desugars the bytecode instructions that interacts with desugar-shadowed APIs, which is a method
+ * with desugar-shadowed types, e.g. {@code java.time.MonthDay}.
+ */
+public final class ShadowedApiInvocationSite extends ClassVisitor {
+
+  /** An evolving record that collects the adapter method requests from invocation sites. */
+  private final InvocationSiteTransformationRecordBuilder invocationSiteRecord;
+
+  public ShadowedApiInvocationSite(
+      ClassVisitor classVisitor, InvocationSiteTransformationRecordBuilder invocationSiteRecord) {
+    super(Opcodes.ASM7, classVisitor);
+    this.invocationSiteRecord = invocationSiteRecord;
+  }
+
+  @Override
+  public MethodVisitor visitMethod(
+      int access, String name, String descriptor, String signature, String[] exceptions) {
+    MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+    return mv == null
+        ? null
+        : AndroidJdkLibInvocationSiteMethodVisitor.create(api, mv, invocationSiteRecord);
+  }
+
+  /** Desugars instructions for the enclosing class visitor. */
+  private static class AndroidJdkLibInvocationSiteMethodVisitor extends MethodRemapper {
+
+    /** The tag included in the marker instruction. */
+    private static final String INLINE_PARAM_TYPE_CONVERSION_TAG = "__desugar_param_conversion__:";
+
+    private final SwitchableTypeMapper immutableLabelApplicator;
+
+    private final InvocationSiteTransformationRecordBuilder invocationSiteRecord;
+
+    /**
+     * The served method under parameter type conversion while visiting. The value can be {@code
+     * null} if no in-line type conversion is in process.
+     */
+    private MethodKey methodInParamInlineTypeConversionService;
+
+    private AndroidJdkLibInvocationSiteMethodVisitor(
+        int api,
+        MethodVisitor methodVisitor,
+        InvocationSiteTransformationRecordBuilder invocationSiteRecord,
+        SwitchableTypeMapper immutableLabelApplicator) {
+      super(api, methodVisitor, immutableLabelApplicator);
+      this.invocationSiteRecord = invocationSiteRecord;
+      this.immutableLabelApplicator = immutableLabelApplicator;
+    }
+
+    static AndroidJdkLibInvocationSiteMethodVisitor create(
+        int api,
+        MethodVisitor methodVisitor,
+        InvocationSiteTransformationRecordBuilder invocationSiteRecord) {
+      return new AndroidJdkLibInvocationSiteMethodVisitor(
+          api,
+          methodVisitor,
+          invocationSiteRecord,
+          new SwitchableTypeMapper(IN_PROCESS_LABEL_STRIPPER.andThen(IMMUTABLE_LABEL_ATTACHER)));
+    }
+
+    @Override
+    public void visitLdcInsn(Object value) {
+      if (value instanceof String
+          && ((String) value).startsWith(INLINE_PARAM_TYPE_CONVERSION_TAG)) {
+        String paramTypeConversionTag = (String) value;
+        visitParamInlineTypeConversionStart(
+            MethodKey.decode(
+                paramTypeConversionTag.substring(INLINE_PARAM_TYPE_CONVERSION_TAG.length())));
+      }
+      super.visitLdcInsn(value);
+    }
+
+    @Override
+    public void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface) {
+      MethodInvocationSite verbatimInvocationSite =
+          MethodInvocationSite.create(opcode, owner, name, descriptor, isInterface)
+              .acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER);
+
+      if (isInParamInlineTypeConversion()) {
+        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+        if (verbatimInvocationSite.method().equals(methodInParamInlineTypeConversionService)) {
+          visitParamInlineTypeConversionEnd(methodInParamInlineTypeConversionService);
+        }
+        return;
+      }
+
+      if (shouldUseInlineTypeConversion(verbatimInvocationSite)) {
+        MethodNode paramInlineInstructionsContainer = new MethodNode();
+        LangModelHelper.mapOperandStackValues(
+            paramInlineInstructionsContainer,
+            ImmutableList.of(ShadowedApiAdapterHelper::anyMirroredToBuiltinTypeConversion),
+            SHADOWED_TO_MIRRORED_TYPE_MAPPER.map(verbatimInvocationSite.argumentTypeNames()),
+            verbatimInvocationSite.argumentTypeNames(),
+            INLINE_PARAM_TYPE_CONVERSION_TAG + verbatimInvocationSite.method().encode());
+        verbatimInvocationSite.accept(paramInlineInstructionsContainer);
+
+        MethodRemapper methodRemapper = new MethodRemapper(mv, IMMUTABLE_LABEL_ATTACHER);
+        paramInlineInstructionsContainer.accept(methodRemapper);
+        return;
+      }
+
+      if (shouldUseApiTypeAdapter(verbatimInvocationSite)) {
+        checkState(!immutableLabelApplicator.isSwitchOn());
+        MethodInvocationSite adapterSite =
+            ShadowedApiAdapterHelper.getAdapterInvocationSite(verbatimInvocationSite);
+        invocationSiteRecord.addTransformation(verbatimInvocationSite);
+        adapterSite.acceptTypeMapper(IMMUTABLE_LABEL_ATTACHER).accept(mv);
+        return;
+      }
+
+      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+    }
+
+    private boolean isInParamInlineTypeConversion() {
+      boolean isInParamInlineTypeConversion = methodInParamInlineTypeConversionService != null;
+      if (isInParamInlineTypeConversion) {
+        checkState(
+            immutableLabelApplicator.isSwitchOn(),
+            "Inconsistent state: Expected immutable label applicator is turned ON");
+      } else {
+        checkState(
+            !immutableLabelApplicator.isSwitchOn(),
+            "Inconsistent state: Expected immutable label applicator is turned OFF");
+      }
+      return isInParamInlineTypeConversion;
+    }
+
+    private void visitParamInlineTypeConversionStart(MethodKey method) {
+      checkState(!isInParamInlineTypeConversion());
+      methodInParamInlineTypeConversionService = checkNotNull(method);
+      immutableLabelApplicator.turnOn();
+    }
+
+    private void visitParamInlineTypeConversionEnd(MethodKey method) {
+      checkState(isInParamInlineTypeConversion());
+      checkState(
+          method.equals(methodInParamInlineTypeConversionService),
+          "Inconsistent state: Expected to end %s, but % is in service.",
+          method,
+          methodInParamInlineTypeConversionService);
+      methodInParamInlineTypeConversionService = null;
+      immutableLabelApplicator.turnOff();
+    }
+  }
+
+  /** Strips out immutable labels at the end of desugar pipeline. */
+  public static class ImmutableLabelRemover extends ClassRemapper {
+    public ImmutableLabelRemover(ClassVisitor cv) {
+      super(cv, ClassName.IMMUTABLE_LABEL_STRIPPER);
+    }
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
index 71e6ed2..fa85f81 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java
@@ -105,7 +105,7 @@
 
   private ClassName prefix(ClassName className) {
     if (shouldPrefix(className.binaryName())) {
-      return className.prependPrefix(prefix);
+      return className.withPackagePrefix(prefix);
     }
     return className;
   }
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 75fa1c5..01ec25e 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
@@ -35,8 +35,20 @@
 
   public static final String IN_PROCESS_LABEL = "__desugar__/";
 
+  private static final String IMMUTABLE_LABEL_LABEL = "__final__/";
+
   private static final String TYPE_ADAPTER_PACKAGE_ROOT = "desugar/runtime/typeadapter/";
 
+  public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
+      new TypeMapper(className -> className.stripPackagePrefix(IN_PROCESS_LABEL));
+
+  public static final TypeMapper IMMUTABLE_LABEL_STRIPPER =
+      new TypeMapper(className -> className.stripPackagePrefix(IMMUTABLE_LABEL_LABEL));
+
+  private static final String TYPE_ADAPTER_SUFFIX = "Adapter";
+
+  private static final String TYPE_CONVERTER_SUFFIX = "Converter";
+
   /**
    * The primitive type as specified at
    * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
@@ -53,7 +65,6 @@
           .put("J", Type.LONG_TYPE)
           .put("D", Type.DOUBLE_TYPE)
           .build();
-
   /**
    * The primitive type as specified at
    * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
@@ -71,26 +82,12 @@
           .put(ClassName.create(Type.DOUBLE_TYPE), ClassName.create("java/lang/Double"))
           .build();
 
-  private static final ImmutableBiMap<String, String> DELIVERY_TYPE_MAPPINGS =
-      ImmutableBiMap.<String, String>builder()
-          .put("java/", "j$/")
-          .put("javadesugar/", "jd$/")
-          .build();
-
-  public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
-      new TypeMapper(ClassName::verbatimName);
-
-  public static final TypeMapper DELIVERY_TYPE_MAPPER =
-      new TypeMapper(ClassName::verbatimToDelivery);
-
-  public static final TypeMapper VERBATIM_TYPE_MAPPER =
-      new TypeMapper(ClassName::deliveryToVerbatim);
-
-  /**
-   * The textual binary name used to index the class name, as defined at,
-   * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
-   */
-  public abstract String binaryName();
+  private static final ImmutableBiMap<String, String> SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS =
+      ImmutableBiMap.<String, String>builder().put("javadesugar/", "jd$/").build();
+  public static final TypeMapper SHADOWED_TO_MIRRORED_TYPE_MAPPER =
+      new TypeMapper(ClassName::shadowedToMirrored);
+  public static final TypeMapper IMMUTABLE_LABEL_ATTACHER =
+      new TypeMapper(ClassName::withCoreTypeImmutableLabel);
 
   public static ClassName create(String binaryName) {
     checkArgument(
@@ -109,6 +106,29 @@
     return create(asmType.getInternalName());
   }
 
+  public static ClassName fromClassFileName(String fileName) {
+    checkArgument(
+        fileName.endsWith(".class"), "Expected a class file (*.class). Actual: (%s).", fileName);
+    return ClassName.create(fileName.substring(0, fileName.length() - ".class".length()));
+  }
+
+  private static void checkPackagePrefixFormat(String prefix) {
+    checkArgument(
+        prefix.isEmpty() || prefix.endsWith("/"),
+        "Expected (%s) to be a package prefix of ending with '/'.",
+        prefix);
+    checkArgument(
+        !prefix.contains("."),
+        "Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
+        prefix);
+  }
+
+  /**
+   * The textual binary name used to index the class name, as defined at,
+   * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
+   */
+  public abstract String binaryName();
+
   public final Type toAsmObjectType() {
     return isPrimitive() ? PRIMITIVES_TYPES.get(binaryName()) : Type.getObjectType(binaryName());
   }
@@ -162,39 +182,73 @@
     return hasPackagePrefix(IN_PROCESS_LABEL);
   }
 
-  private ClassName stripInProcessLabel() {
-    return stripPackagePrefix(IN_PROCESS_LABEL);
+  public final boolean hasImmutableLabel() {
+    return hasPackagePrefix(IMMUTABLE_LABEL_LABEL);
   }
 
-  private ClassName stripInProcessLabelIfAny() {
-    return hasInProcessLabel() ? stripPackagePrefix(IN_PROCESS_LABEL) : this;
-  }
-
-  /** Strips out in-process labels if any. */
-  public final ClassName verbatimName() {
-    return stripInProcessLabelIfAny().deliveryToVerbatim();
-  }
-
+  /**
+   * Returns a new instance of {@code ClassName} that represents the owner class with adapter
+   * methods for an Android SDK APIs.
+   */
   public final ClassName typeAdapterOwner() {
-    return verbatimName().withSimpleNameSuffix("Adapter").prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+    checkState(
+        !hasInProcessLabel() && !hasImmutableLabel(),
+        "Expected a label-free type: Actual(%s)",
+        this);
+    checkState(
+        isInPackageEligibleForTypeAdapter(),
+        "Expected an Android SDK type to have an adapter: Actual (%s)",
+        this);
+    return withSimpleNameSuffix(TYPE_ADAPTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
   }
 
+  /**
+   * Returns a new instance of {@code ClassName} that represents the owner class with conversion
+   * methods between JDK built-in types and desguar-mirrored types.
+   */
   public final ClassName typeConverterOwner() {
-    return verbatimName()
-        .withSimpleNameSuffix("Converter")
-        .prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+    checkState(
+        !hasInProcessLabel() && !hasImmutableLabel(),
+        "Expected a label-free type: Actual(%s)",
+        this);
+    checkState(
+        isDesugarShadowedType(),
+        "Expected an JDK built-in type to have an converter: Actual (%s)",
+        this);
+    return withSimpleNameSuffix(TYPE_CONVERTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
   }
 
-  public final ClassName verbatimToDelivery() {
-    return DELIVERY_TYPE_MAPPINGS.keySet().stream()
+  /**
+   * Returns a new instance of {@code ClassName} attached with an immutable label which marks the
+   * type is not subject to further desugar operations until the final label striping.
+   */
+  public final ClassName withCoreTypeImmutableLabel() {
+    return isDesugarShadowedType() ? withPackagePrefix(IMMUTABLE_LABEL_LABEL) : this;
+  }
+
+  /**
+   * Returns a new instance of {@code ClassName} that is the desugar-mirrored core type (e.g. {@code
+   * j$/time/MonthDay}) of the current shadowed built-in core type, assuming {@code this} instance
+   * is a desugared-shadowed built-in core type.
+   */
+  public final ClassName shadowedToMirrored() {
+    return SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet().stream()
         .filter(this::hasPackagePrefix)
-        .map(prefix -> replacePackagePrefix(prefix, DELIVERY_TYPE_MAPPINGS.get(prefix)))
+        .map(
+            prefix ->
+                replacePackagePrefix(prefix, SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.get(prefix)))
         .findAny()
         .orElse(this);
   }
 
-  public final ClassName deliveryToVerbatim() {
-    ImmutableBiMap<String, String> verbatimTypeMappings = DELIVERY_TYPE_MAPPINGS.inverse();
+  /**
+   * Returns a new instance of {@code ClassName} that is a shadowed built-in core type (e.g. {@code
+   * java/time/MonthDay}) of the current desugar-mirrored core type, assuming {@code this} instance
+   * is a desugar-mirrored core type.
+   */
+  public final ClassName mirroredToShadowed() {
+    ImmutableBiMap<String, String> verbatimTypeMappings =
+        SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.inverse();
     return verbatimTypeMappings.keySet().stream()
         .filter(this::hasPackagePrefix)
         .map(prefix -> replacePackagePrefix(prefix, verbatimTypeMappings.get(prefix)))
@@ -202,7 +256,7 @@
         .orElse(this);
   }
 
-  public final ClassName prependPrefix(String prefix) {
+  public final ClassName withPackagePrefix(String prefix) {
     checkPackagePrefixFormat(prefix);
     return ClassName.create(prefix + binaryName());
   }
@@ -219,11 +273,43 @@
     return prefixes.stream().anyMatch(this::hasPackagePrefix);
   }
 
-  public final ClassName stripPackagePrefix(String prefix) {
+  public final boolean isDesugarEligible(boolean enableDesugarBuiltinJdk) {
+    if (isDesugarShadowedType()) {
+      return enableDesugarBuiltinJdk;
+    }
+    return !isInPackageEligibleForTypeAdapter()
+        && !isInDesugarRuntimeLibrary()
+        && !isDesugarMirroredType();
+  }
+
+  public final boolean isInPackageEligibleForTypeAdapter() {
+    // TODO(b/152573900): Update to hasPackagePrefix("android/") once all package-wise incremental
+    // rollouts are complete.
+    return hasAnyPackagePrefix("android/testing/");
+  }
+
+  public final boolean isInDesugarRuntimeLibrary() {
+    return hasAnyPackagePrefix(
+        "com/google/devtools/build/android/desugar/runtime/", "desugar/runtime/");
+  }
+
+  public final boolean isDesugarShadowedType() {
+    return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet());
+  }
+
+  public final boolean isDesugarMirroredType() {
+    return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.values());
+  }
+
+  private ClassName stripPackagePrefix(String prefix) {
+    return hasPackagePrefix(prefix) ? stripRequiredPackagePrefix(prefix) : this;
+  }
+
+  private ClassName stripRequiredPackagePrefix(String prefix) {
     return replacePackagePrefix(/* originalPrefix= */ prefix, /* targetPrefix= */ "");
   }
 
-  public final ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
+  private ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
     checkState(
         hasPackagePrefix(originalPrefix),
         "Expected %s to have a package prefix of (%s) before stripping.",
@@ -237,27 +323,4 @@
   public ClassName acceptTypeMapper(TypeMapper typeMapper) {
     return typeMapper.map(this);
   }
-
-  private static void checkPackagePrefixFormat(String prefix) {
-    checkArgument(
-        prefix.isEmpty() || prefix.endsWith("/"),
-        "Expected (%s) to be a package prefix of ending with '/'.",
-        prefix);
-    checkArgument(
-        !prefix.contains("."),
-        "Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
-        prefix);
-  }
-
-  public boolean isInProcessCoreType() {
-    return hasInProcessLabel() && stripInProcessLabel().isVerbatimCoreType();
-  }
-
-  public boolean isVerbatimCoreType() {
-    return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.keySet());
-  }
-
-  public boolean isDeliveryCoreType() {
-    return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.values());
-  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
index a67657b..01aa824 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
@@ -21,11 +21,11 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Streams;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -158,30 +158,40 @@
    * @param mv The current method visitor that is visiting the class.
    * @param mappers Applies to an operand stack value if tested positive on {@code filter}.
    * @param expectedTypesOnOperandStack The expected types at the bottom of the operand stack. The
-   *     end of the list corresponds to the the bottom of the operand stack.
+   * @param extraConstants Extra constants to be stored in the object array.
    */
   public static ImmutableList<ClassName> collapseStackValuesToObjectArray(
       MethodVisitor mv,
       ImmutableList<Function<ClassName, Optional<MethodInvocationSite>>> mappers,
-      ImmutableList<ClassName> expectedTypesOnOperandStack) {
+      ImmutableList<ClassName> expectedTypesOnOperandStack,
+      ImmutableList<Object> extraConstants) {
     // Creates an array of java/lang/Object to store the values on top of the operand stack that
     // are subject to string concatenation.
-    int numOfValuesOnOperandStack = expectedTypesOnOperandStack.size();
+
+    for (Object constant : extraConstants) {
+      mv.visitLdcInsn(constant);
+    }
+
+    int numOfValuesOnOperandStack = expectedTypesOnOperandStack.size() + extraConstants.size();
+
     visitPushInstr(mv, numOfValuesOnOperandStack);
     mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
 
     // To preserve the order of the operands to be string-concatenated, we slot the values on
     // the top of the stack to the end of the array.
-    List<ClassName> actualTypesOnObjectArray = new ArrayList<>(expectedTypesOnOperandStack);
+    List<ClassName> actualTypesInObjectArray =
+        Streams.concat(
+                expectedTypesOnOperandStack.stream(),
+                extraConstants.stream().map(Object::getClass).map(ClassName::create))
+            .collect(Collectors.toList());
     for (int i = numOfValuesOnOperandStack - 1; i >= 0; i--) {
-      ClassName operandTypeName = expectedTypesOnOperandStack.get(i);
-      Type operandType = operandTypeName.toAsmObjectType();
+      Type arrayElementType = actualTypesInObjectArray.get(i).toAsmObjectType();
       // Pre-duplicates the array reference for next loop iteration use.
       // Post-operation stack bottom to top:
       //     ..., value_i-1, arrayref, value_i, arrayref.
       mv.visitInsn(
           getTypeSizeAlignedDupOpcode(
-              ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(operandType)));
+              ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(arrayElementType)));
 
       // Pushes the array index and adjusts the order of the values on stack top in the order
       // of <bottom/> arrayref, index, value <top/> before emitting an aastore instruction.
@@ -195,7 +205,7 @@
       mv.visitInsn(
           getTypeSizeAlignedDupOpcode(
               ImmutableList.of(Type.getType(Object.class), Type.getType(int.class)),
-              ImmutableList.of(operandType)));
+              ImmutableList.of(arrayElementType)));
 
       // Pops arrayref, index, leaving the stack top as value_i.
       // Post-operation stack bottom to top:
@@ -206,19 +216,19 @@
 
       int targetArrayIndex = i;
       mappers.stream()
-          .map(mapper -> mapper.apply(actualTypesOnObjectArray.get(targetArrayIndex)))
+          .map(mapper -> mapper.apply(actualTypesInObjectArray.get(targetArrayIndex)))
           .flatMap(Streams::stream)
           .forEach(
               typeConversionSite -> {
                 typeConversionSite.accept(mv);
-                actualTypesOnObjectArray.set(targetArrayIndex, typeConversionSite.returnTypeName());
+                actualTypesInObjectArray.set(targetArrayIndex, typeConversionSite.returnTypeName());
               });
 
       // Post-operation stack bottom to top:
       //     ..., value_i-1, arrayref.
       mv.visitInsn(Opcodes.AASTORE);
     }
-    return ImmutableList.copyOf(actualTypesOnObjectArray);
+    return ImmutableList.copyOf(actualTypesInObjectArray);
   }
 
   /**
@@ -278,6 +288,25 @@
     mv.visitInsn(Opcodes.POP);
   }
 
+  public static ImmutableList<ClassName> mapOperandStackValues(
+      MethodVisitor mv,
+      ImmutableList<Function<ClassName, Optional<MethodInvocationSite>>> mappers,
+      ImmutableList<ClassName> sourceTypesOnOperandStack,
+      ImmutableList<ClassName> targetTypesOnOperandStack,
+      String beginningMarker) {
+    ImmutableList<ClassName> classNames =
+        collapseStackValuesToObjectArray(
+            mv,
+            ImmutableList.<Function<ClassName, Optional<MethodInvocationSite>>>builder()
+                .addAll(mappers)
+                .add(LangModelHelper::anyPrimitiveToBoxedTypeInvocationSite)
+                .build(),
+            sourceTypesOnOperandStack,
+            ImmutableList.of(beginningMarker));
+    expandObjectArrayToStackValues(mv, targetTypesOnOperandStack);
+    return classNames;
+  }
+
   public static Optional<MethodInvocationSite> anyPrimitiveToStringInvocationSite(
       ClassName className) {
     return className.isPrimitive()
@@ -304,6 +333,22 @@
         .build();
   }
 
+  public static Optional<MethodInvocationSite> anyObjectToStringInvocationSite(
+      ClassName className) {
+    return className.isPrimitive()
+        ? Optional.empty()
+        : Optional.of(
+            MethodInvocationSite.builder()
+                .setInvocationKind(MemberUseKind.INVOKEVIRTUAL)
+                .setMethod(
+                    MethodKey.create(
+                        className,
+                        "toString",
+                        Type.getMethodDescriptor(Type.getType(String.class))))
+                .setIsInterface(false)
+                .build());
+  }
+
   /** Convenient factory method for converting a primitive type to string call site. */
   public static Optional<MethodInvocationSite> anyPrimitiveToBoxedTypeInvocationSite(
       ClassName className) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
index dffbb3b..704da84 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
@@ -17,6 +17,7 @@
 package com.google.devtools.build.android.desugar.langmodel;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
 import org.objectweb.asm.MethodVisitor;
 
 /** A value object that represents an method invocation site. */
@@ -65,6 +66,10 @@
     return method().getReturnTypeName();
   }
 
+  public final ImmutableList<ClassName> argumentTypeNames() {
+    return method().getArgumentTypeNames();
+  }
+
   public final boolean isStaticInvocation() {
     return invocationKind() == MemberUseKind.INVOKESTATIC;
   }
@@ -83,14 +88,6 @@
     return toBuilder().setMethod(method().acceptTypeMapper(typeMapper)).build();
   }
 
-  public final MethodInvocationSite toAdapterInvocationSite() {
-    return MethodInvocationSite.builder()
-        .setInvocationKind(MemberUseKind.INVOKESTATIC)
-        .setMethod(method().toArgumentTypeAdapter(isStaticInvocation()))
-        .setIsInterface(false)
-        .build();
-  }
-
   /** The builder for {@link MethodInvocationSite}. */
   @AutoValue.Builder
   public abstract static class MethodInvocationSiteBuilder {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
index 0bd249c..53f5b90 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
@@ -20,13 +20,17 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.objectweb.asm.Type;
 
 /** The key to index a class or interface method or constructor. */
 @AutoValue
 public abstract class MethodKey extends ClassMemberKey<MethodKey> {
 
+  private static final Splitter COLON_SPLITTER = Splitter.on(":");
+
   /** The factory method for {@link MethodKey}. */
   public static MethodKey create(ClassName ownerClass, String name, String descriptor) {
     checkState(
@@ -39,6 +43,16 @@
     return new AutoValue_MethodKey(ownerClass, name, descriptor);
   }
 
+  public final String encode() {
+    return ownerName() + ":" + name() + ":" + descriptor();
+  }
+
+  public static MethodKey decode(String textFormat) {
+    List<String> payloads = COLON_SPLITTER.splitToList(textFormat);
+    checkState(payloads.size() == 3, "Invalid text format for method key: Actual(%s)", textFormat);
+    return MethodKey.create(ClassName.create(payloads.get(0)), payloads.get(1), payloads.get(2));
+  }
+
   /** The return type of a method. */
   public Type getReturnType() {
     return Type.getReturnType(descriptor());
@@ -64,7 +78,7 @@
     return getArgumentTypes().stream().map(ClassName::create).collect(toImmutableList());
   }
 
-  public MethodKey toArgumentTypeAdapter(boolean fromStaticOrigin) {
+  public MethodKey toAdapterMethodForArgsAndReturnTypes(boolean fromStaticOrigin) {
     ClassName typeAdapterOwner = owner().typeAdapterOwner();
     checkState(
         !isConstructor(), "Argument type adapter for constructor is not supported: %s. ", this);
@@ -73,7 +87,7 @@
             typeAdapterOwner,
             name(),
             fromStaticOrigin ? descriptor() : instanceMethodToStaticDescriptor())
-        .acceptTypeMapper(ClassName.DELIVERY_TYPE_MAPPER);
+        .acceptTypeMapper(ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER);
   }
 
   /** The synthetic constructor for a private constructor. */
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java
new file mode 100644
index 0000000..abe8d1d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/SwitchableTypeMapper.java
@@ -0,0 +1,52 @@
+/*
+ *
+ *  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.langmodel;
+
+import org.objectweb.asm.commons.Remapper;
+
+/** A {@link Remapper} with a user-controlled switch which sets whether the type mapping applies. */
+public final class SwitchableTypeMapper extends Remapper {
+
+  private final TypeMapper mapper;
+  private boolean isSwitchOn;
+
+  public SwitchableTypeMapper(TypeMapper mapper) {
+    this.mapper = mapper;
+  }
+
+  @Override
+  public String map(String binaryName) {
+    return map(ClassName.create(binaryName)).binaryName();
+  }
+
+  public ClassName map(ClassName className) {
+    return isSwitchOn() ? className.acceptTypeMapper(mapper) : className;
+  }
+
+  public boolean isSwitchOn() {
+    return isSwitchOn;
+  }
+
+  public void turnOn() {
+    isSwitchOn = true;
+  }
+
+  public void turnOff() {
+    isSwitchOn = false;
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
index 4ed92bd..d8aeef0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
@@ -16,6 +16,7 @@
 
 package com.google.devtools.build.android.desugar.langmodel;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
@@ -25,15 +26,15 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
 import org.objectweb.asm.commons.Remapper;
 
 /** Maps a type to another based on binary names. */
 public final class TypeMapper extends Remapper {
 
-  private final Function<ClassName, ClassName> classNameMapper;
+  private final UnaryOperator<ClassName> classNameMapper;
 
-  public TypeMapper(Function<ClassName, ClassName> classNameMapper) {
+  public TypeMapper(UnaryOperator<ClassName> classNameMapper) {
     this.classNameMapper = classNameMapper;
   }
 
@@ -46,8 +47,7 @@
     return classNameMapper.apply(internalName);
   }
 
-  public <E extends TypeMappable<E>> ImmutableList<? extends E> map(
-      ImmutableList<E> mappableTypes) {
+  public <E extends TypeMappable<E>> ImmutableList<E> map(ImmutableList<E> mappableTypes) {
     return mappableTypes.stream().map(e -> e.acceptTypeMapper(this)).collect(toImmutableList());
   }
 
@@ -76,4 +76,8 @@
         .collect(toImmutableMap(e -> e.getKey().acceptTypeMapper(this), e -> e.getValue()));
   }
 
+  public TypeMapper andThen(TypeMapper after) {
+    return new TypeMapper(
+        className -> className.acceptTypeMapper(this).acceptTypeMapper(checkNotNull(after)));
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
index 21ca226..ed937a5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
@@ -110,7 +110,8 @@
             ImmutableList.of(LangModelHelper::anyPrimitiveToStringInvocationSite),
             Arrays.stream(Type.getArgumentTypes(descriptor))
                 .map(ClassName::create)
-                .collect(toImmutableList()));
+                .collect(toImmutableList()),
+            ImmutableList.of());
 
         String recipe = (String) bootstrapMethodArguments[0];
         visitLdcInsn(recipe);