Desugared library: Collection conversion

b:222647019
Change-Id: Id040529cc8d0975abf86ae8d318aca9fa8b635c3
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
index 092b976..a61df55 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -63,6 +63,12 @@
       "retarget_method_with_emulated_dispatch": {
         "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
       },
+      "api_conversion_collection": {
+        "java.nio.channels.AsynchronousFileChannel java.nio.file.spi.FileSystemProvider#newAsynchronousFileChannel(java.nio.file.Path, java.util.Set, java.util.concurrent.ExecutorService, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+        "java.nio.channels.SeekableByteChannel java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+        "java.nio.channels.FileChannel java.nio.file.spi.FileSystemProvider#newFileChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+        "java.util.List java.nio.file.WatchKey#pollEvents()": [-1, "java.nio.file.WatchEvent"]
+      },
       "wrapper_conversion": [
         "java.nio.channels.AsynchronousChannel",
         "java.nio.channels.CompletionHandler",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index c458830..ea0f8c0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -44,7 +44,7 @@
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 import org.objectweb.asm.Opcodes;
 
@@ -408,9 +408,13 @@
       Supplier<UniqueContext> contextSupplier) {
     return internalComputeReturnConversion(
         invokedMethod,
-        returnType ->
+        (returnType, apiConversionCollection) ->
             wrapperSynthesizer.ensureConversionMethod(
-                returnType, destIsVivified, eventConsumer, contextSupplier),
+                returnType,
+                destIsVivified,
+                apiConversionCollection,
+                eventConsumer,
+                contextSupplier),
         context);
   }
 
@@ -422,17 +426,24 @@
       Supplier<UniqueContext> contextSupplier) {
     return internalComputeReturnConversion(
         invokedMethod,
-        returnType ->
+        (returnType, apiConversionCollection) ->
             wrapperSynthesizer.getExistingProgramConversionMethod(
-                returnType, destIsVivified, eventConsumer, contextSupplier),
+                returnType,
+                destIsVivified,
+                apiConversionCollection,
+                eventConsumer,
+                contextSupplier),
         context);
   }
 
   private DexMethod internalComputeReturnConversion(
-      DexMethod invokedMethod, Function<DexType, DexMethod> methodSupplier, ProgramMethod context) {
+      DexMethod invokedMethod,
+      BiFunction<DexType, DexType, DexMethod> methodSupplier,
+      ProgramMethod context) {
     DexType returnType = invokedMethod.proto.returnType;
     if (wrapperSynthesizer.shouldConvert(returnType, invokedMethod, context)) {
-      return methodSupplier.apply(returnType);
+      DexType apiConversionCollection = getReturnApiConversionCollection(invokedMethod);
+      return methodSupplier.apply(returnType, apiConversionCollection);
     }
     return null;
   }
@@ -446,9 +457,9 @@
     return internalComputeParameterConversions(
         invokedMethod,
         wrapperSynthesizer,
-        argType ->
+        (argType, apiConversionCollection) ->
             wrapperSynthesizer.ensureConversionMethod(
-                argType, destIsVivified, eventConsumer, contextSupplier),
+                argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
         context);
   }
 
@@ -461,28 +472,43 @@
     return internalComputeParameterConversions(
         invokedMethod,
         wrapperSynthesizer,
-        argType ->
+        (argType, apiConversionCollection) ->
             wrapperSynthesizer.getExistingProgramConversionMethod(
-                argType, destIsVivified, eventConsumer, contextSupplier),
+                argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
         context);
   }
 
   private DexMethod[] internalComputeParameterConversions(
       DexMethod invokedMethod,
       DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-      Function<DexType, DexMethod> methodSupplier,
+      BiFunction<DexType, DexType, DexMethod> methodSupplier,
       ProgramMethod context) {
     DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
     DexType[] parameters = invokedMethod.proto.parameters.values;
     for (int i = 0; i < parameters.length; i++) {
+      DexType apiConversionCollection = getApiConversionCollection(invokedMethod, i);
       DexType argType = parameters[i];
       if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
-        parameterConversions[i] = methodSupplier.apply(argType);
+        parameterConversions[i] = methodSupplier.apply(argType, apiConversionCollection);
       }
     }
     return parameterConversions;
   }
 
+  public DexType getReturnApiConversionCollection(DexMethod method) {
+    return getApiConversionCollection(method, method.getArity());
+  }
+
+  public DexType getApiConversionCollection(DexMethod method, int parameterIndex) {
+    DexType[] dexTypes =
+        appView
+            .options()
+            .machineDesugaredLibrarySpecification
+            .getApiConversionCollection()
+            .get(method);
+    return dexTypes == null ? null : dexTypes[parameterIndex];
+  }
+
   private DexMethod convertedMethod(
       DexMethod method,
       boolean parameterDestIsVivified,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 8507816..f98fa3d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.CollectionConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -137,8 +138,14 @@
   public DexMethod ensureConversionMethod(
       DexType type,
       boolean destIsVivified,
+      DexType apiConversionCollection,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
+    if (apiConversionCollection != null) {
+      assert !type.isArrayType();
+      return ensureCollectionConversionMethod(
+          type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+    }
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
@@ -163,6 +170,68 @@
     return conversion;
   }
 
+  private DexMethod ensureCollectionConversionMethod(
+      DexType type,
+      boolean destIsVivified,
+      DexType apiConversionCollection,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    assert type == factory.setType || type == factory.listType;
+    DexMethod conversion =
+        ensureConversionMethod(
+            apiConversionCollection,
+            destIsVivified,
+            null, // We do not support nested collections.
+            eventConsumer,
+            contextSupplier);
+    return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
+  }
+
+  private DexMethod ensureCollectionConversionMethodFromExistingBaseConversion(
+      DexType type,
+      boolean destIsVivified,
+      DexType apiConversionCollection,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    assert type == factory.setType || type == factory.listType;
+    DexMethod conversion =
+        getExistingProgramConversionMethod(
+            apiConversionCollection,
+            destIsVivified,
+            null, // We do not support nested collections.
+            eventConsumer,
+            contextSupplier);
+    return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
+  }
+
+  private DexMethod ensureCollectionConversionMethod(
+      DexType collectionType,
+      DesugaredLibraryWrapperSynthesizerEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier,
+      DexMethod conversion) {
+    ProgramMethod arrayConversion =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.ARRAY_CONVERSION,
+                contextSupplier.get(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(factory.createProto(collectionType, collectionType))
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            codeSynthesizor ->
+                                new CollectionConversionCfCodeProvider(
+                                        appView,
+                                        codeSynthesizor.getHolderType(),
+                                        collectionType,
+                                        conversion)
+                                    .generateCfCode()));
+    eventConsumer.acceptArrayConversion(arrayConversion);
+    return arrayConversion.getReference();
+  }
+
   private DexMethod ensureArrayConversionMethod(
       DexType type,
       DexType srcType,
@@ -171,7 +240,11 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         ensureConversionMethod(
-            type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
+            type.toDimensionMinusOneType(factory),
+            srcType == type,
+            null,
+            eventConsumer,
+            contextSupplier);
     return ensureArrayConversionMethod(
         srcType, destType, eventConsumer, contextSupplier, conversion);
   }
@@ -184,7 +257,11 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         getExistingProgramConversionMethod(
-            type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
+            type.toDimensionMinusOneType(factory),
+            srcType == type,
+            null,
+            eventConsumer,
+            contextSupplier);
     return ensureArrayConversionMethod(
         srcType, destType, eventConsumer, contextSupplier, conversion);
   }
@@ -222,8 +299,14 @@
   public DexMethod getExistingProgramConversionMethod(
       DexType type,
       boolean destIsVivified,
+      DexType apiConversionCollection,
       DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
+    if (apiConversionCollection != null) {
+      assert !type.isArrayType();
+      return ensureCollectionConversionMethodFromExistingBaseConversion(
+          type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+    }
     DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
     DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 027a2ae..9a0dd0e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -48,6 +48,7 @@
 
   static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
   static final String API_LEVEL_GREATER_OR_EQUAL_KEY = "api_level_greater_or_equal";
+  static final String API_CONVERSION_COLLECTION = "api_conversion_collection";
   static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
   static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
   static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
@@ -262,6 +263,18 @@
         builder.putDontRewritePrefix(dontRewritePrefix.getAsString());
       }
     }
+    if (jsonFlagSet.has(API_CONVERSION_COLLECTION)) {
+      for (Map.Entry<String, JsonElement> methodAndDescription :
+          jsonFlagSet.get(API_CONVERSION_COLLECTION).getAsJsonObject().entrySet()) {
+        JsonArray array = methodAndDescription.getValue().getAsJsonArray();
+        for (int i = 0; i < array.size(); i += 2) {
+          builder.addApiConversionCollection(
+              parseMethod(methodAndDescription.getKey()),
+              array.get(i).getAsInt(),
+              stringDescriptorToDexType(array.get(i + 1).getAsString()));
+        }
+      }
+    }
     if (jsonFlagSet.has(REWRITE_DERIVED_PREFIX_KEY)) {
       for (Map.Entry<String, JsonElement> prefixToMatch :
           jsonFlagSet.get(REWRITE_DERIVED_PREFIX_KEY).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index fc5bcca..7c17a74 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -33,6 +33,7 @@
   private final Map<DexField, DexType> retargetStaticField;
   private final Map<DexMethod, DexType> retargetMethod;
   private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
+  private final Map<DexMethod, DexType[]> apiConversionCollection;
   private final Map<DexType, DexType> legacyBackport;
   private final Map<DexType, DexType> customConversions;
   private final Set<DexMethod> dontRewriteInvocation;
@@ -50,6 +51,7 @@
       Map<DexField, DexType> retargetStaticField,
       Map<DexMethod, DexType> retargetMethod,
       Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
+      Map<DexMethod, DexType[]> apiConversionCollection,
       Map<DexType, DexType> legacyBackport,
       Map<DexType, DexType> customConversion,
       Set<DexMethod> dontRewriteInvocation,
@@ -65,6 +67,7 @@
     this.retargetStaticField = retargetStaticField;
     this.retargetMethod = retargetMethod;
     this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch;
+    this.apiConversionCollection = apiConversionCollection;
     this.legacyBackport = legacyBackport;
     this.customConversions = customConversion;
     this.dontRewriteInvocation = dontRewriteInvocation;
@@ -86,6 +89,7 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        ImmutableMap.of(),
         ImmutableSet.of(),
         ImmutableSet.of(),
         ImmutableMap.of(),
@@ -109,6 +113,7 @@
         retargetStaticField,
         retargetMethod,
         retargetMethodEmulatedDispatch,
+        apiConversionCollection,
         legacyBackport,
         customConversions,
         dontRewriteInvocation,
@@ -150,6 +155,10 @@
     return retargetMethodEmulatedDispatch;
   }
 
+  public Map<DexMethod, DexType[]> getApiConversionCollection() {
+    return apiConversionCollection;
+  }
+
   public Map<DexType, DexType> getLegacyBackport() {
     return legacyBackport;
   }
@@ -201,6 +210,7 @@
     private final Map<DexField, DexType> retargetStaticField;
     private final Map<DexMethod, DexType> retargetMethod;
     private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
+    private final Map<DexMethod, DexType[]> apiConversionCollection;
     private final Map<DexType, DexType> legacyBackport;
     private final Map<DexType, DexType> customConversions;
     private final Set<DexMethod> dontRewriteInvocation;
@@ -223,6 +233,7 @@
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
           Sets.newIdentityHashSet(),
           Sets.newIdentityHashSet(),
           new IdentityHashMap<>(),
@@ -241,6 +252,7 @@
         Map<DexField, DexType> retargetStaticField,
         Map<DexMethod, DexType> retargetMethod,
         Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
+        Map<DexMethod, DexType[]> apiConversionCollection,
         Map<DexType, DexType> backportCoreLibraryMember,
         Map<DexType, DexType> customConversions,
         Set<DexMethod> dontRewriteInvocation,
@@ -258,6 +270,7 @@
       this.retargetStaticField = new IdentityHashMap<>(retargetStaticField);
       this.retargetMethod = new IdentityHashMap<>(retargetMethod);
       this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch);
+      this.apiConversionCollection = new IdentityHashMap<>(apiConversionCollection);
       this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember);
       this.customConversions = new IdentityHashMap<>(customConversions);
       this.dontRewriteInvocation = Sets.newIdentityHashSet();
@@ -370,6 +383,14 @@
       return this;
     }
 
+    public void addApiConversionCollection(DexMethod method, int index, DexType type) {
+      DexType[] types =
+          apiConversionCollection.computeIfAbsent(method, k -> new DexType[method.getArity() + 1]);
+      int actualIndex = index == -1 ? method.getArity() : index;
+      assert types[actualIndex] == null;
+      types[actualIndex] = type;
+    }
+
     public Builder putLegacyBackport(DexType backportType, DexType rewrittenBackportType) {
       put(
           legacyBackport,
@@ -410,6 +431,7 @@
           ImmutableMap.copyOf(retargetStaticField),
           ImmutableMap.copyOf(retargetMethod),
           ImmutableMap.copyOf(retargetMethodEmulatedDispatch),
+          ImmutableMap.copyOf(apiConversionCollection),
           ImmutableMap.copyOf(legacyBackport),
           ImmutableMap.copyOf(customConversions),
           ImmutableSet.copyOf(dontRewriteInvocation),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 7367257..5533d92 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -116,6 +116,10 @@
     return rewritingFlags.getEmulatedVirtualRetargetThroughEmulatedInterface();
   }
 
+  public Map<DexMethod, DexType[]> getApiConversionCollection() {
+    return rewritingFlags.getApiConversionCollection();
+  }
+
   public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
     rewritingFlags.forEachRetargetMethod(consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 1a301da..0b8530b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -34,6 +34,7 @@
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
       Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
       Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface,
+      Map<DexMethod, DexType[]> apiConversionCollection,
       Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
       Map<DexType, List<DexMethod>> wrappers,
       Map<DexType, DexType> legacyBackport,
@@ -50,6 +51,7 @@
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
     this.emulatedVirtualRetargetThroughEmulatedInterface =
         emulatedVirtualRetargetThroughEmulatedInterface;
+    this.apiConversionCollection = apiConversionCollection;
     this.emulatedInterfaces = emulatedInterfaces;
     this.wrappers = wrappers;
     this.legacyBackport = legacyBackport;
@@ -86,6 +88,9 @@
   // dispatch. The method has to override an emulated interface method.
   private final Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface;
 
+  // Encodes weither specific parameter collections need to be wrapped differently.
+  private final Map<DexMethod, DexType[]> apiConversionCollection;
+
   // Emulated interface descriptors.
   private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
 
@@ -130,6 +135,10 @@
     return emulatedVirtualRetargetThroughEmulatedInterface;
   }
 
+  public Map<DexMethod, DexType[]> getApiConversionCollection() {
+    return apiConversionCollection;
+  }
+
   public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
     staticRetarget.keySet().forEach(consumer);
     nonEmulatedVirtualRetarget.keySet().forEach(consumer);
@@ -222,6 +231,8 @@
         emulatedVirtualRetarget = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod>
         emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexMethod, DexType[]> apiConversionCollection =
+        ImmutableMap.builder();
     private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
@@ -275,6 +286,10 @@
       emulatedVirtualRetargetThroughEmulatedInterface.put(src, dest);
     }
 
+    public void addApiConversionCollection(DexMethod method, DexType[] dexTypes) {
+      apiConversionCollection.put(method, dexTypes);
+    }
+
     public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
       wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
     }
@@ -313,6 +328,7 @@
           nonEmulatedVirtualRetarget.build(),
           emulatedVirtualRetarget.build(),
           emulatedVirtualRetargetThroughEmulatedInterface.build(),
+          apiConversionCollection.build(),
           emulatedInterfaces.build(),
           wrappers.build(),
           legacyBackport.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 36ad9cf..99d816e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -96,6 +96,7 @@
         ComputedApiLevel.unknown());
     rewritingFlags.getAmendLibraryMethod().forEach(builder::amendLibraryMethod);
     rewritingFlags.getAmendLibraryField().forEach(builder::amendLibraryField);
+    rewritingFlags.getApiConversionCollection().forEach(builder::addApiConversionCollection);
     new HumanToMachineRetargetConverter(appInfo)
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
index 98ec596..fc80fe8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -257,4 +257,121 @@
       return standardCfCodeFromInstructions(instructions);
     }
   }
+
+  public static class CollectionConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    private final DexType collectionType;
+    private final DexMethod conversion;
+
+    public CollectionConversionCfCodeProvider(
+        AppView<?> appView, DexType holder, DexType collectionType, DexMethod conversion) {
+      super(appView, holder);
+      this.collectionType = collectionType;
+      this.conversion = conversion;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (arg == null) { return null; }
+      generateNullCheck(instructions);
+      instructions.add(
+          new CfFrame(
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+                  .put(0, FrameType.initialized(collectionType))
+                  .build(),
+              ImmutableDeque.of()));
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(collectionType))
+              .put(1, FrameType.initialized(collectionType))
+              .put(2, FrameType.initialized(factory.iteratorType))
+              .build();
+
+      // Collection<E> t1 = new Collection<E>();
+      if (collectionType == factory.setType) {
+        DexType hashSetType = factory.createType("Ljava/util/HashSet;");
+        instructions.add(new CfNew(hashSetType));
+        instructions.add(
+            new CfInvoke(
+                Opcodes.INVOKESPECIAL,
+                factory.createMethod(
+                    hashSetType,
+                    factory.createProto(factory.voidType),
+                    factory.constructorMethodName),
+                false));
+      } else {
+        assert collectionType == factory.listType;
+        DexType arrayListType = factory.createType("Ljava/util/ArrayList;");
+        instructions.add(new CfNew(arrayListType));
+        instructions.add(
+            new CfInvoke(
+                Opcodes.INVOKESPECIAL,
+                factory.createMethod(
+                    arrayListType,
+                    factory.createProto(factory.voidType),
+                    factory.constructorMethodName),
+                false));
+      }
+      instructions.add(new CfStore(ValueType.OBJECT, 1));
+
+      // Iterator<E> t2 = receiver.iterator();
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKEINTERFACE,
+              factory.createMethod(
+                  factory.collectionType, factory.createProto(factory.iteratorType), "iterator"),
+              true));
+      instructions.add(new CfStore(ValueType.OBJECT, 2));
+
+      // while(t2.hasNext())
+      CfLabel returnLabel = new CfLabel();
+      CfLabel loopLabel = new CfLabel();
+      instructions.add(loopLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKEINTERFACE,
+              factory.createMethod(
+                  factory.iteratorType, factory.createProto(factory.booleanType), "hasNext"),
+              true));
+      instructions.add(new CfConstNumber(0, ValueType.INT));
+      instructions.add(new CfIfCmp(If.Type.EQ, ValueType.INT, returnLabel));
+
+      // {t1.add(convert(t2.next());}
+      instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKEINTERFACE,
+              factory.createMethod(
+                  factory.iteratorType,
+                  factory.createProto(conversion.getArgumentType(0, true)),
+                  "next"),
+              true));
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKEINTERFACE,
+              factory.createMethod(
+                  factory.collectionType,
+                  factory.createProto(factory.booleanType, factory.objectType),
+                  "add"),
+              true));
+      instructions.add(new CfGoto(loopLabel));
+
+      // return t1;
+      instructions.add(returnLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
+      instructions.add(new CfReturn(ValueType.fromDexType(collectionType)));
+
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index f452df5..d9154e7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -49,7 +49,7 @@
 
 public class DesugaredLibraryTestBase extends TestBase {
 
-  private static final boolean FORCE_JDK11_DESUGARED_LIB = false;
+  private static final boolean FORCE_JDK11_DESUGARED_LIB = true;
 
   @BeforeClass
   public static void setUpDesugaredLibrary() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
new file mode 100644
index 0000000..0a9377f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import org.junit.Assume;
+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 ChannelSetTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "unsupported");
+  private static final String EXPECTED_RESULT_26 =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "wrapper start",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World");
+  private static Path CUSTOM_LIB;
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(CustomLib.class)
+            .setMinApi(AndroidApiLevel.B)
+            .compile()
+            .writeToZip();
+  }
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    // Skip Android 4.4.4 due to missing libjavacrypto.
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
+  }
+
+  public ChannelSetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private String getExpectedResult() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+        ? EXPECTED_RESULT_26
+        : EXPECTED_RESULT;
+  }
+
+  private LibraryDesugaringTestConfiguration pathConfiguration() {
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addProgramClasses(TestClass.class)
+        .addLibraryClasses(CustomLib.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Integer.toString(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    testForR8(Backend.DEX)
+        .addLibraryFiles(getLibraryFile())
+        .addProgramClasses(TestClass.class)
+        .addLibraryClasses(CustomLib.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(pathConfiguration())
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Integer.toString(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(getExpectedResult());
+  }
+
+  public static class CustomLib {
+
+    // Answers effectively a Path wrapper.
+    public static Path get(String path) {
+      return Paths.get(path);
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws Throwable {
+      Path path = Files.createTempFile("example", ".txt");
+      String hello = "Hello World";
+      Set<OpenOption> openOptions = new HashSet<>();
+      openOptions.add(LinkOption.NOFOLLOW_LINKS);
+      writeHelloWorldIntoFile(path, hello);
+      readHelloWithPathApis(path, hello, openOptions);
+      // The rest of tests are testing API conversion, which is possible only on + devices.
+      if (Integer.parseInt(args[0]) < 26) {
+        return;
+      }
+      System.out.println("wrapper start");
+      Path pathWrapper = CustomLib.get(path.toString());
+      readHelloWithPathWrapperApis(pathWrapper, hello, openOptions);
+      try {
+        try (SeekableByteChannel channel =
+            pathWrapper.getFileSystem().provider().newByteChannel(path, openOptions)) {
+          readFromChannel(channel, hello.length());
+        }
+      } catch (ProviderMismatchException e) {
+        System.out.println("provider missmatch");
+      }
+    }
+
+    private static void readHelloWithPathWrapperApis(
+        Path pathWrapper, String hello, Set<OpenOption> openOptions)
+        throws IOException, InterruptedException, ExecutionException {
+      try (SeekableByteChannel channel =
+          pathWrapper.getFileSystem().provider().newByteChannel(pathWrapper, openOptions)) {
+        readFromChannel(channel, hello.length());
+      }
+      try (FileChannel channel =
+          pathWrapper.getFileSystem().provider().newFileChannel(pathWrapper, openOptions)) {
+        readFromChannel(channel, hello.length());
+      }
+      try {
+        try (AsynchronousFileChannel channel =
+            pathWrapper
+                .getFileSystem()
+                .provider()
+                .newAsynchronousFileChannel(pathWrapper, openOptions, ForkJoinPool.commonPool())) {
+          ByteBuffer byteBuffer = ByteBuffer.allocate(hello.length());
+          Future<Integer> readFuture = channel.read(byteBuffer, 0);
+          // We force the future to await here with get().
+          int read = readFuture.get();
+          System.out.println("bytes read: " + read);
+          System.out.println("String read: " + new String(byteBuffer.array()));
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+    }
+
+    private static void readHelloWithPathApis(Path path, String hello, Set<OpenOption> openOptions)
+        throws IOException, InterruptedException, ExecutionException {
+      try (SeekableByteChannel channel =
+          path.getFileSystem().provider().newByteChannel(path, openOptions)) {
+        readFromChannel(channel, hello.length());
+      }
+      try (FileChannel channel =
+          path.getFileSystem().provider().newFileChannel(path, openOptions)) {
+        readFromChannel(channel, hello.length());
+      }
+      try {
+        try (AsynchronousFileChannel channel =
+            path.getFileSystem()
+                .provider()
+                .newAsynchronousFileChannel(path, openOptions, ForkJoinPool.commonPool())) {
+          ByteBuffer byteBuffer = ByteBuffer.allocate(hello.length());
+          Future<Integer> readFuture = channel.read(byteBuffer, 0);
+          int read = readFuture.get();
+          System.out.println("bytes read: " + read);
+          System.out.println("String read: " + new String(byteBuffer.array()));
+        }
+      } catch (NoClassDefFoundError | UnsupportedOperationException e) {
+        // ForkJoinPool is missing (officially below Android 21, in practice somewhere below).
+        // The method newAsynchronousFileChannel is unsupported on DesugarFileSystems.
+        System.out.println("unsupported");
+      }
+    }
+
+    private static void writeHelloWorldIntoFile(Path path, String hello) throws IOException {
+      try (SeekableByteChannel channel =
+          Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
+        ByteBuffer byteBuffer = ByteBuffer.wrap(hello.getBytes());
+        int write = channel.write(byteBuffer);
+        System.out.println("bytes written: " + write);
+        System.out.println("String written: " + hello);
+      }
+    }
+
+    private static void readFromChannel(SeekableByteChannel channel, int helloWorldSize)
+        throws IOException {
+      ByteBuffer byteBuffer = ByteBuffer.allocate(helloWorldSize);
+      int read = channel.read(byteBuffer);
+      System.out.println("bytes read: " + read);
+      System.out.println("String read: " + new String(byteBuffer.array()));
+    }
+  }
+}