Desugared lib: fix minification

- do not rename duplicated call-backs in R8
- make sure wrapper methods are kept in R8

Bug:148841210
Change-Id: I868ca46de98debefa3f56999ef06fea2a5e16043
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 61d7abe..c85439b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -3,25 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph.analysis;
 
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -96,28 +89,19 @@
       DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
       if (appView.definitionFor(mockType) == null) {
         assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
-        classpathClasses.add(
-            new DexClasspathClass(
-                mockType,
-                Kind.CF,
-                new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
-                ClassAccessFlags.fromDexAccessFlags(
-                    Constants.ACC_SYNTHETIC
-                        | Constants.ACC_PUBLIC
-                        | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
-                appView.dexItemFactory().objectType,
-                DexTypeList.empty(),
-                appView.dexItemFactory().createString("vivified"),
-                null,
-                Collections.emptyList(),
-                null,
-                Collections.emptyList(),
-                DexAnnotationSet.empty(),
-                DexEncodedField.EMPTY_ARRAY,
-                DexEncodedField.EMPTY_ARRAY,
-                DexEncodedMethod.EMPTY_ARRAY,
-                DexEncodedMethod.EMPTY_ARRAY,
-                appView.dexItemFactory().getSkipNameValidationForTesting()));
+        assert wrapper.instanceFields().size() == 1;
+        DexType typeToMock = wrapper.instanceFields().get(0).field.type;
+        DexClass classToMock = appView.definitionFor(typeToMock);
+        assert classToMock != null;
+        DexClasspathClass mockedSuperClass =
+            converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+        classpathClasses.add(mockedSuperClass);
+        for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+          // The mock is generated at the end of the enqueuing phase, so we need to manually set the
+          // library override.
+          assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
+          virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+        }
       }
     }
     return classpathClasses;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 5df6a03..ceed9ef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -303,6 +304,11 @@
     return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
   }
 
+  public DexClasspathClass synthesizeClasspathMock(
+      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+    return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+  }
+
   private List<DexEncodedMethod> generateCallbackMethods(
       Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
     List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index d3206e7..069015f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -12,6 +13,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -32,6 +34,7 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -107,12 +110,14 @@
   private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
   private final DexItemFactory factory;
   private final DesugaredLibraryAPIConverter converter;
+  private final DexString vivifiedSourceFile;
 
   DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.dexWrapperPrefix = factory.createString("L" + WRAPPER_PREFIX);
     this.converter = converter;
+    this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
   }
 
   public static boolean isSynthesizedWrapper(DexType type) {
@@ -347,6 +352,51 @@
     return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
+  private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
+      DexClass dexClass, DexType mockType) {
+    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+    List<DexEncodedMethod> generatedMethods = new ArrayList<>();
+    // Generate only abstract methods for library override detection.
+    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+      if (!dexEncodedMethod.isFinal()) {
+        DexMethod methodToInstall =
+            DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+                dexEncodedMethod.method, mockType, appView);
+        DexEncodedMethod newDexEncodedMethod =
+            newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
+        generatedMethods.add(newDexEncodedMethod);
+      }
+    }
+    return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+  }
+
+  DexClasspathClass synthesizeClasspathMock(
+      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+    return new DexClasspathClass(
+        mockType,
+        Kind.CF,
+        new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
+        ClassAccessFlags.fromDexAccessFlags(
+            Constants.ACC_SYNTHETIC
+                | Constants.ACC_PUBLIC
+                | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
+        appView.dexItemFactory().objectType,
+        DexTypeList.empty(),
+        vivifiedSourceFile,
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
+        appView.dexItemFactory().getSkipNameValidationForTesting());
+  }
+
   private DexEncodedMethod[] finalizeWrapperMethods(
       List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
     if (finalMethods.isEmpty()) {
@@ -376,7 +426,11 @@
       DexMethod methodToInstall, DexEncodedMethod template, Code code) {
     MethodAccessFlags newFlags = template.accessFlags.copy();
     assert newFlags.isPublic();
-    newFlags.unsetAbstract();
+    if (code == null) {
+      newFlags.setAbstract();
+    } else {
+      newFlags.unsetAbstract();
+    }
     // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
     newFlags.unsetBridge();
     newFlags.setSynthetic();
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index d506d84..1bc05e9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -276,14 +276,6 @@
         DexString reservedName = strategy.getReservedName(method, holder);
         if (reservedName != null) {
           state.reserveName(reservedName, method.method);
-          // This is reserving names which after prefix rewriting will actually override a library
-          // method.
-          if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
-            state.reserveName(
-                reservedName,
-                DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-                    method.method, method.method.holder, appView));
-          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e768589..359de49 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -213,11 +213,13 @@
 
     final AppView<?> appView;
     private final DexItemFactory factory;
+    private final boolean desugaredLibraryRenaming;
 
     public MinifierMemberNamingStrategy(AppView<?> appView) {
       super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
       this.appView = appView;
       this.factory = appView.dexItemFactory();
+      this.desugaredLibraryRenaming = appView.rewritePrefix.isRewriting();
     }
 
     @Override
@@ -260,6 +262,12 @@
           || appView.rootSet().mayNotBeMinified(method.method, appView)) {
         return method.method.name;
       }
+      if (desugaredLibraryRenaming
+          && method.isLibraryMethodOverride().isTrue()
+          && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+        // With desugared library, call-backs names are reserved here.
+        return method.method.name;
+      }
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7c468b0..7e381ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1681,6 +1681,7 @@
       ResolutionResult firstResolution =
           appView.appInfo().resolveMethod(instantiatedClass, method.method);
       markResolutionAsLive(libraryClass, firstResolution);
+      markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1695,9 +1696,9 @@
         ResolutionResult secondResolution =
             appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
         markResolutionAsLive(libraryClass, secondResolution);
+        markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
       }
 
-      markOverridesAsLibraryMethodOverrides(method, instantiatedClass);
     }
   }
 
@@ -1714,13 +1715,13 @@
   }
 
   private void markOverridesAsLibraryMethodOverrides(
-      DexEncodedMethod libraryMethod, DexProgramClass instantiatedClass) {
+      DexMethod libraryMethod, DexProgramClass instantiatedClass) {
     Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
     Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
     while (!worklist.isEmpty()) {
       DexProgramClass clazz = worklist.removeFirst();
       assert visited.contains(clazz);
-      DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod.method);
+      DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
       if (libraryMethodOverride != null) {
         if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
           continue;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 38e8bac..04630af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -7,96 +7,137 @@
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.function.Consumer;
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class CallBackConversionTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT = StringUtils.lines("0", "1", "0", "1");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public CallBackConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .setMinApi(MIN_SUPPORTED)
+            .addProgramClasses(CustomLibClass.class)
+            .compile()
+            .writeToZip();
+  }
+
+  private static void assertDuplicatedAPI(CodeInspector i) {
+    List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
+    assertEquals(2, virtualMethods.size());
+    assertTrue(
+        virtualMethods.stream()
+            .anyMatch(
+                m ->
+                    m.getMethod()
+                        .method
+                        .proto
+                        .parameters
+                        .values[0]
+                        .toString()
+                        .equals("j$.util.function.Consumer")));
+    assertTrue(
+        virtualMethods.stream()
+            .anyMatch(
+                m ->
+                    m.getMethod()
+                        .method
+                        .proto
+                        .parameters
+                        .values[0]
+                        .toString()
+                        .equals("java.util.function.Consumer")));
+  }
+
   @Test
   public void testCallBack() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .inspect(
-            i -> {
-              // foo(j$) and foo(java)
-              List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
-              assertEquals(2, virtualMethods.size());
-              assertTrue(
-                  virtualMethods.stream()
-                      .anyMatch(
-                          m ->
-                              m.getMethod()
-                                  .method
-                                  .proto
-                                  .parameters
-                                  .values[0]
-                                  .toString()
-                                  .equals("j$.util.function.Consumer")));
-              assertTrue(
-                  virtualMethods.stream()
-                      .anyMatch(
-                          m ->
-                              m.getMethod()
-                                  .method
-                                  .proto
-                                  .parameters
-                                  .values[0]
-                                  .toString()
-                                  .equals("java.util.function.Consumer")));
-            })
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .inspect(CallBackConversionTest::assertDuplicatedAPI)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testCallBackR8() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addKeepMainRule(Impl.class)
         .noMinification()
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testCallBackR8Minifying() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addKeepMainRule(Impl.class)
-        .setMinApi(AndroidApiLevel.B)
+        .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
-        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Impl.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   private void assertLibraryOverridesThere(CodeInspector i) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index b13ade6..835d1cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -4,42 +4,145 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import static com.android.tools.r8.Collectors.toSingle;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import org.jetbrains.annotations.NotNull;
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class ConversionIntroduceInterfaceMethodTest extends DesugaredLibraryTestBase {
 
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "action called from j$ consumer",
+          "forEach called",
+          "action called from java consumer",
+          "forEach called");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public ConversionIntroduceInterfaceMethodTest(
+      TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .setMinApi(MIN_SUPPORTED)
+            .addProgramClasses(CustomLibClass.class)
+            .compile()
+            .writeToZip();
+  }
+
   @Test
-  public void testNoInterfaceMethods() throws Exception {
-    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+  public void testNoInterfaceMethodsD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
-        .setMinApi(AndroidApiLevel.B)
-        .addProgramClasses(
-            MyCollectionInterface.class,
-            MyCollectionInterfaceAbstract.class,
-            MyCollection.class,
-            Executor.class)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
         .addLibraryClasses(CustomLibClass.class)
-        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
-        .addRunClasspathFiles(customLib)
-        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
-        .assertSuccessWithOutput(
-            StringUtils.lines(
-                "action called from j$ consumer",
-                "forEach called",
-                "action called from java consumer",
-                "forEach called"));
+        .inspect(this::assertDoubleForEach)
+        .inspect(this::assertWrapperMethodsPresent)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void assertDoubleForEach(CodeInspector inspector) {
+    System.out.println(inspector.allClasses().size());
+    FoundClassSubject myCollection =
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    c.getOriginalName()
+                            .startsWith(
+                                "com.android.tools.r8.desugar.desugaredlibrary.conversiontests")
+                        && !c.getOriginalName().contains("Executor")
+                        && !c.getOriginalName().contains("$-CC")
+                        && !c.getDexClass().isInterface())
+            .collect(toSingle());
+    assertEquals(
+        "Missing duplicated forEach",
+        2,
+        myCollection.getDexClass().virtualMethods().stream()
+            .filter(m -> m.method.name.toString().equals("forEach"))
+            .count());
+  }
+
+  private void assertWrapperMethodsPresent(CodeInspector inspector) {
+    List<FoundClassSubject> wrappers =
+        inspector.allClasses().stream()
+            .filter(
+                c ->
+                    !c.getFinalName()
+                        .startsWith(
+                            "com.android.tools.r8.desugar.desugaredlibrary.conversiontests"))
+            .collect(Collectors.toList());
+    for (FoundClassSubject wrapper : wrappers) {
+      assertTrue(wrapper.virtualMethods().size() > 0);
+    }
+  }
+
+  @Test
+  public void testNoInterfaceMethodsR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
+        .addKeepMainRule(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertDoubleForEach)
+        .inspect(this::assertWrapperMethodsPresent)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   static class CustomLibClass {
@@ -91,7 +194,6 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return null;
@@ -142,12 +244,4 @@
     @Override
     public void clear() {}
   }
-
-  interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
-
-    // The following method override a method from Iterable and use a desugared type.
-    // API conversion is required.
-    @Override
-    void forEach(Consumer<? super E> action);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
index 631f721..10823c3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import static org.hamcrest.core.AllOf.allOf;
-import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.D8TestCompileResult;
@@ -97,9 +95,7 @@
             shrinkDesugaredLibrary)
         .addRunClasspathFiles(CUSTOM_LIB)
         .run(parameters.getRuntime(), Executor.class)
-        // TODO(b/139451198): Multiple library files are not supported with high API levels.
-        .assertFailureWithErrorThatMatches(
-            allOf(containsString("AbstractMethodError"), containsString("Function.apply")));
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   // If we have the exact same lambda in both, but one implements j$..Function and the other