New desugared library nio FileChannel implementation

- Maintain prefix for SeekableByteChannel to avoid conversions
- Custom management of FileChannel on all Apis, but no rewritting or
  or automatic wrappers for FileChannel
- Emulation of custom FileChannel with RandomAccessFile below 26
- Wrap FileChannel below 24 to support SeekableByteChannel interface on
  library boundaries

Above 24 the FileChannel should be 100% supported, below 24 there may be some locking issues to address [isOpen(), close(), begin(), end(boolean)]

Change-Id: I11ea847f97f0317b444e595cc152843d0df805dc
diff --git a/build.gradle b/build.gradle
index f775822..bde2f60 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1091,6 +1091,7 @@
 task rawBuildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
     from sourceSets.libraryDesugarConversions.output
     include "java/**/*.class"
+    include "desugar/sun/nio/fs/DesugarAndroid*.class"
     baseName 'library_desugar_conversions_raw'
     destinationDir file('build/tmp/desugaredlibrary')
 }
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
new file mode 100644
index 0000000..8de3f76
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
@@ -0,0 +1,25 @@
+// 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 desugar.sun.nio.fs;
+
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.spi.FileSystemProvider;
+
+public class DesugarAndroidDefaultFileSystemProvider {
+  private static final FileSystemProvider INSTANCE = DesugarAndroidFileSystemProvider.create();
+
+  private DesugarAndroidDefaultFileSystemProvider() {}
+
+  /** Returns the platform's default file system provider. */
+  public static FileSystemProvider instance() {
+    return INSTANCE;
+  }
+
+  /** Returns the platform's default file system. */
+  public static FileSystem theFileSystem() {
+    return INSTANCE.getFileSystem(URI.create("file:///"));
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
new file mode 100644
index 0000000..027d74f
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -0,0 +1,49 @@
+// 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 desugar.sun.nio.fs;
+
+import java.adapter.AndroidVersionTest;
+import java.io.IOException;
+import java.nio.channels.DesugarChannels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Set;
+
+/** Linux implementation of {@link FileSystemProvider} for desugar support. */
+public class DesugarAndroidFileSystemProvider
+    extends desugar.sun.nio.fs.DesugarLinuxFileSystemProvider {
+
+  public static DesugarAndroidFileSystemProvider create() {
+    return new DesugarAndroidFileSystemProvider(System.getProperty("user.dir"), "/");
+  }
+
+  DesugarAndroidFileSystemProvider(String userDir, String rootDir) {
+    super(userDir, rootDir);
+  }
+
+  @Override
+  public SeekableByteChannel newByteChannel(
+      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+    if (path.toFile().isDirectory()) {
+      throw new UnsupportedOperationException(
+          "The desugar library does not support creating a file channel on a directory: " + path);
+    }
+    // A FileChannel is a SeekableByteChannel.
+    return newFileChannel(path, options, attrs);
+  }
+
+  @Override
+  public FileChannel newFileChannel(
+      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+    if (AndroidVersionTest.is26OrAbove) {
+      throw new RuntimeException("Above Api 26, the platform FileSystemProvider should be used.");
+    }
+    return DesugarChannels.openEmulatedFileChannel(path, options, attrs);
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
deleted file mode 100644
index cf36b80..0000000
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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 desugar.sun.nio.fs;
-
-import java.nio.file.spi.FileSystemProvider;
-
-public class DesugarDefaultFileSystemProvider {
-
-  public static FileSystemProvider instance() {
-    return null;
-  }
-}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
new file mode 100644
index 0000000..566bf12
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
@@ -0,0 +1,116 @@
+// 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 desugar.sun.nio.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarLinuxFileSystemProvider extends FileSystemProvider {
+
+  DesugarLinuxFileSystemProvider(String userDir, String rootDir) {
+    super();
+  }
+
+  @Override
+  public String getScheme() {
+    return null;
+  }
+
+  @Override
+  public FileSystem newFileSystem(URI uri, Map<String, ?> map) throws IOException {
+    return null;
+  }
+
+  @Override
+  public FileSystem getFileSystem(URI uri) {
+    return null;
+  }
+
+  @Override
+  public Path getPath(URI uri) {
+    return null;
+  }
+
+  @Override
+  public SeekableByteChannel newByteChannel(
+      Path path, Set<? extends OpenOption> set, FileAttribute<?>... fileAttributes)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public DirectoryStream<Path> newDirectoryStream(Path path, Filter<? super Path> filter)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void createDirectory(Path path, FileAttribute<?>... fileAttributes) throws IOException {}
+
+  @Override
+  public void delete(Path path) throws IOException {}
+
+  @Override
+  public void copy(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+  @Override
+  public void move(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+  @Override
+  public boolean isSameFile(Path path, Path path1) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isHidden(Path path) throws IOException {
+    return false;
+  }
+
+  @Override
+  public FileStore getFileStore(Path path) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void checkAccess(Path path, AccessMode... accessModes) throws IOException {}
+
+  @Override
+  public <V extends FileAttributeView> V getFileAttributeView(
+      Path path, Class<V> aClass, LinkOption... linkOptions) {
+    return null;
+  }
+
+  @Override
+  public <A extends BasicFileAttributes> A readAttributes(
+      Path path, Class<A> aClass, LinkOption... linkOptions) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Map<String, Object> readAttributes(Path path, String s, LinkOption... linkOptions)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void setAttribute(Path path, String s, Object o, LinkOption... linkOptions)
+      throws IOException {}
+}
diff --git a/src/library_desugar/java/java/adapter/AndroidVersionTest.java b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
new file mode 100644
index 0000000..6425791
--- /dev/null
+++ b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
@@ -0,0 +1,24 @@
+// 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 java.adapter;
+
+public class AndroidVersionTest {
+
+  public static final boolean is26OrAbove = setUp("java.nio.file.FileSystems");
+  public static final boolean isHeadfull = setUp("android.os.Build");
+
+  /**
+   * Answers true if the class is present, implying the SDK is at least at the level where the class
+   * was introduced.
+   */
+  private static boolean setUp(String className) {
+    try {
+      Class.forName(className);
+      return true;
+    } catch (ClassNotFoundException ignored) {
+    }
+    return false;
+  }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index e34e571..eea074f 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -6,7 +6,7 @@
 
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
-import desugar.sun.nio.fs.DesugarDefaultFileSystemProvider;
+import desugar.sun.nio.fs.DesugarAndroidDefaultFileSystemProvider;
 import j$.nio.file.FileSystems;
 import java.net.URI;
 import java.nio.file.FileSystem;
@@ -23,27 +23,18 @@
       INSTANCE.getFileSystem(URI.create("file:///"));
 
   private static FileSystemProvider getFileSystemProvider() {
-    // Note: this fails on non Android devices.
-    try {
+    if (AndroidVersionTest.is26OrAbove) {
       // On API 26 and above, FileSystems is present.
-      Class.forName("java.nio.file.FileSystems");
       j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
       j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
       return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
-    } catch (ClassNotFoundException ignored) {
-      // We reach this path is API < 26.
     }
-    // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
-    // We cannot set the ThreadPolicy in headless and it should not matter.
-    // In headless, android.os is absent so the following line will throw.
-    // In headfull, android.os is present and we set the thread policy.
-    try {
-      Class.forName("android.os.Build");
+    if (AndroidVersionTest.isHeadfull) {
+      // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
+      // We cannot set the ThreadPolicy in headless and it should not matter.
       setThreadPolicy();
-    } catch (ClassNotFoundException ignored) {
-      // Headless mode.
     }
-    return DesugarDefaultFileSystemProvider.instance();
+    return DesugarAndroidDefaultFileSystemProvider.instance();
   }
 
   private static void setThreadPolicy() {
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index da93736..8980540 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -17,13 +17,9 @@
   private HybridFileTypeDetector() {}
 
   public static FileTypeDetector create() {
-    try {
-      // On API 26 and above, java.nio.file.Files is present.
-      Class.forName("java.nio.file.Files");
-      return new PlatformFileTypeDetector();
-    } catch (ClassNotFoundException ignored) {
-      return DesugarDefaultFileTypeDetector.create();
-    }
+    return AndroidVersionTest.is26OrAbove
+        ? new PlatformFileTypeDetector()
+        : DesugarDefaultFileTypeDetector.create();
   }
 
   static class PlatformFileTypeDetector extends FileTypeDetector {
diff --git a/src/library_desugar/java/java/nio/channels/DesugarChannels.java b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
new file mode 100644
index 0000000..6380268
--- /dev/null
+++ b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
@@ -0,0 +1,239 @@
+// 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 java.nio.channels;
+
+import java.adapter.AndroidVersionTest;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DesugarChannels {
+
+  /** Special conversion for Channel to answer a converted FileChannel if required. */
+  public static Channel convertMaybeLegacyChannelFromLibrary(Channel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (raw instanceof FileChannel) {
+      return convertMaybeLegacyFileChannelFromLibrary((FileChannel) raw);
+    }
+    return raw;
+  }
+
+  /**
+   * Below Api 24 FileChannel does not implement SeekableByteChannel. When we get one from the
+   * library, we wrap it to implement the interface.
+   */
+  public static FileChannel convertMaybeLegacyFileChannelFromLibrary(FileChannel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (raw instanceof SeekableByteChannel) {
+      return raw;
+    }
+    return new WrappedFileChannel(raw);
+  }
+
+  /**
+   * We unwrap when going to the library since we cannot intercept the calls to final methods in the
+   * library.
+   */
+  public static FileChannel convertMaybeLegacyFileChannelToLibrary(FileChannel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (raw instanceof WrappedFileChannel) {
+      return ((WrappedFileChannel) raw).delegate;
+    }
+    return raw;
+  }
+
+  static class WrappedFileChannel extends FileChannel implements SeekableByteChannel {
+
+    final FileChannel delegate;
+
+    private WrappedFileChannel(FileChannel delegate) {
+      this.delegate = delegate;
+    }
+
+    FileChannel convert(FileChannel raw) {
+      return new WrappedFileChannel(raw);
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+      return delegate.read(dst);
+    }
+
+    @Override
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+      return delegate.read(dsts, offset, length);
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+      return delegate.write(src);
+    }
+
+    @Override
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+      return delegate.write(srcs, offset, length);
+    }
+
+    @Override
+    public long position() throws IOException {
+      return delegate.position();
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+      return convert(delegate.position(newPosition));
+    }
+
+    @Override
+    public long size() throws IOException {
+      return delegate.size();
+    }
+
+    @Override
+    public FileChannel truncate(long size) throws IOException {
+      return convert(delegate.truncate(size));
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+      delegate.force(metaData);
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target)
+        throws IOException {
+      return delegate.transferTo(position, count, target);
+    }
+
+    @Override
+    public long transferFrom(ReadableByteChannel src, long position, long count)
+        throws IOException {
+      return delegate.transferFrom(src, position, count);
+    }
+
+    @Override
+    public int read(ByteBuffer dst, long position) throws IOException {
+      return delegate.read(dst, position);
+    }
+
+    @Override
+    public int write(ByteBuffer src, long position) throws IOException {
+      return delegate.write(src, position);
+    }
+
+    @Override
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+      return delegate.map(mode, position, size);
+    }
+
+    @Override
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+      return delegate.lock(position, size, shared);
+    }
+
+    @Override
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+      return delegate.tryLock(position, size, shared);
+    }
+
+    @Override
+    public void implCloseChannel() throws IOException {
+      // We cannot call the protected method, this should be effectively equivalent.
+      delegate.close();
+    }
+  }
+
+  /** The 2 open methods are present to be retargeted from FileChannel#open. */
+  public static FileChannel open(Path path, OpenOption... openOptions) throws IOException {
+    Set<OpenOption> openOptionSet = new HashSet<>();
+    Collections.addAll(openOptionSet, openOptions);
+    return open(path, openOptionSet);
+  }
+
+  public static FileChannel open(
+      Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
+      throws IOException {
+    if (AndroidVersionTest.is26OrAbove) {
+      return FileChannel.open(path, openOptions, attrs);
+    }
+    return openEmulatedFileChannel(path, openOptions, attrs);
+  }
+
+  /**
+   * All FileChannel creation go through the FileSystemProvider which then comes here if the Api is
+   * strictly below 26, and to the plaform FileSystemProvider if the Api is above or equal to 26.
+   *
+   * <p>Below Api 26 there is no way to create a FileChannel, so we create instead an emulated
+   * version using RandomAccessFile which tries, with a best effort, to support all settings.
+   *
+   * <p>The FileAttributes are ignored.
+   */
+  public static FileChannel openEmulatedFileChannel(
+      Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
+      throws IOException {
+
+    validateOpenOptions(path, openOptions);
+
+    RandomAccessFile randomAccessFile =
+        new RandomAccessFile(path.toFile(), getFileAccessModeText(openOptions));
+    if (openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+      randomAccessFile.setLength(0);
+    }
+
+    if (!openOptions.contains(StandardOpenOption.APPEND)) {
+      // This one may be retargeted, below 24, to support SeekableByteChannel.
+      return randomAccessFile.getChannel();
+    }
+
+    // TODO(b/259056135): Consider subclassing UnsupportedOperationException for desugared library.
+    // RandomAccessFile does not support APPEND.
+    // We could hack a wrapper to support APPEND in simple cases such as Files.write().
+    throw new UnsupportedOperationException();
+  }
+
+  private static void validateOpenOptions(Path path, Set<? extends OpenOption> openOptions)
+      throws NoSuchFileException {
+    // Validations that resemble sun.nio.fs.UnixChannelFactory#newFileChannel.
+    if (openOptions.contains(StandardOpenOption.READ)
+        && openOptions.contains(StandardOpenOption.APPEND)) {
+      throw new IllegalArgumentException("READ + APPEND not allowed");
+    }
+    if (openOptions.contains(StandardOpenOption.APPEND)
+        && openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+      throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
+    }
+    if (openOptions.contains(StandardOpenOption.APPEND) && !path.toFile().exists()) {
+      throw new NoSuchFileException(path.toString());
+    }
+  }
+
+  private static String getFileAccessModeText(Set<? extends OpenOption> options) {
+    if (!options.contains(StandardOpenOption.WRITE)) {
+      return "r";
+    }
+    if (options.contains(StandardOpenOption.SYNC)) {
+      return "rws";
+    }
+    if (options.contains(StandardOpenOption.DSYNC)) {
+      return "rwd";
+    }
+    return "rw";
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index c97bd79..3295333 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -166,6 +166,7 @@
         "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
         "java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
         "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
+        "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
         "java.nio.file.": "j$.nio.file."
       },
       "dont_rewrite_prefix": [
@@ -230,7 +231,8 @@
         "java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"],
         "java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"],
         "java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"],
-        "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"]
+        "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"],
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"]
       },
       "wrapper_conversion": [
         "java.nio.channels.CompletionHandler",
@@ -279,8 +281,6 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
-        "java.nio.channels.FileChannel": "j$.nio.channels.FileChannel",
-        "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -294,12 +294,10 @@
         "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
         "java.util.stream.": "j$.util.stream."
       },
-      "dont_rewrite_prefix": [
-        "java.nio.channels.FileChannel$MapMode"
-      ],
       "maintain_prefix": [
         "java.util.function.",
-        "java.io.UncheckedIOException"
+        "java.io.UncheckedIOException",
+        "java.nio.channels.SeekableByteChannel"
       ],
       "emulate_interface": {
         "java.lang.Iterable": "j$.lang.Iterable",
@@ -347,13 +345,18 @@
         "java.util.stream.IntStream java.util.stream.IntStream#flatMap(java.util.function.IntFunction)": [0, "java.util.function.IntFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.IntFunction)"],
         "java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
         "java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
-        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"]
+        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"],
+        "java.nio.channels.FileChannel java.nio.channels.FileLock#channel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.Channel java.nio.channels.FileLock#acquiredBy()": [-1, "java.nio.channels.Channel java.nio.channels.DesugarChannels#convertMaybeLegacyChannelFromLibrary(java.nio.channels.Channel)"],
+        "java.nio.channels.FileChannel java.io.RandomAccessFile#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.FileChannel java.io.FileInputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.FileChannel java.io.FileOutputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "void java.nio.channels.FileLock#<init>(java.nio.channels.FileChannel,long, long, boolean)": [0, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelToLibrary(java.nio.channels.FileChannel)"]
       },
       "never_outline_api": [
         "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
       ],
       "wrapper_conversion": [
-        "java.nio.channels.SeekableByteChannel",
         "java.util.PrimitiveIterator$OfDouble",
         "java.util.PrimitiveIterator$OfInt",
         "java.util.PrimitiveIterator$OfLong",
@@ -370,18 +373,6 @@
         "java.util.stream.LongStream",
         "java.util.stream.Stream"
       ],
-      "wrapper_conversion_excluding": {
-        "java.nio.channels.FileChannel": [
-          "long java.nio.channels.FileChannel#read(java.nio.ByteBuffer[])",
-          "long java.nio.channels.FileChannel#write(java.nio.ByteBuffer[])",
-          "java.nio.channels.FileLock java.nio.channels.FileChannel#lock()",
-          "java.nio.channels.FileLock java.nio.channels.FileChannel#tryLock()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#close()",
-          "boolean java.nio.channels.spi.AbstractInterruptibleChannel#isOpen()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#begin()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#end(boolean)"
-        ]
-      },
       "custom_conversion": {
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions",
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
@@ -442,7 +433,9 @@
         "java.time.Instant java.util.Calendar#toInstant()": "java.util.DesugarCalendar",
         "java.util.Date java.util.Date#from(java.time.Instant)": "java.util.DesugarDate",
         "java.util.GregorianCalendar java.util.GregorianCalendar#from(java.time.ZonedDateTime)": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone"
+        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone",
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.nio.file.OpenOption[])": "java.nio.channels.DesugarChannels",
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])": "java.nio.channels.DesugarChannels"
       }
     },
     {
@@ -483,7 +476,6 @@
     {
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
-        "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
         "jdk.internal.": "j$.jdk.internal.",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "sun.nio.cs.": "j$.sun.nio.cs.",
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 178255b..40f9d67 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
@@ -170,7 +170,9 @@
 
   private DexMethod ensureApiGenericConversion(
       DexMethod conversion, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
-    assert !appView.options().isDesugaredLibraryCompilation();
+    if (appView.appInfoForDesugaring().resolveMethod(conversion, false).isSingleResolution()) {
+      return conversion;
+    }
     ClasspathMethod classpathMethod =
         appView
             .getSyntheticItems()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
index c6ea5b0..2acd508 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
@@ -27,7 +27,7 @@
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.R)) {
       levelType = app.dexItemFactory.createType("Ljava/util/concurrent/Flow;");
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.N)) {
-      levelType = app.dexItemFactory.createType("Ljava/util/function/Supplier;");
+      levelType = app.dexItemFactory.createType("Ljava/util/StringJoiner;");
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.T)) {
       levelType = app.dexItemFactory.createType("Ljava/lang/invoke/VarHandle;");
     } else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 76bd656..2c99723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -80,7 +80,11 @@
           "java.util.stream.DoubleStream"
               + " java.util.stream.DoubleStream.dropWhile(java.util.function.DoublePredicate)",
           // FileStore.getBlockSize() was added in 33 which confuses the required library (30).
-          "long java.nio.file.FileStore.getBlockSize()");
+          "long java.nio.file.FileStore.getBlockSize()",
+          // The call is present but unreachable above 26.
+          "java.nio.channels.FileChannel"
+              + " java.nio.channels.DesugarChannels.openEmulatedFileChannel(java.nio.file.Path,"
+              + " java.util.Set, java.nio.file.attribute.FileAttribute[])");
 
   private final TestParameters parameters;
   private final CompilationSpecification compilationSpecification;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index a68f3ce..4602235 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -514,8 +514,7 @@
       if (clazzType.startsWith("java.")
           && !doesNotNeedWrapper(clazzType, customConversions, maintainType)
           // FileChannel is there since B but it needs wrapping due to recently added interfaces.
-          && (!preDesugarTypes.contains(clazz)
-              || clazzType.equals("java.nio.channels.FileChannel"))) {
+          && !preDesugarTypes.contains(clazz)) {
         additions.accept(clazz);
         return true;
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
new file mode 100644
index 0000000..c39147d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
@@ -0,0 +1,211 @@
+// 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 static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+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 FileChannelTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Hello World! ",
+          "Hello World! ",
+          "Bye bye. ",
+          "Hello World! ",
+          "Bye bye. ",
+          "Hello World! ",
+          "The monkey eats...",
+          "Bananas!",
+          "Bananas!",
+          "Bananas!");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FileChannelTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws IOException {
+      fisTest();
+      fosTest();
+      fileChannelOpen();
+    }
+
+    private static void fosTest() throws IOException {
+      String toWrite = "The monkey eats...";
+      Path tmp = Files.createTempFile("fos", ".txt");
+
+      ByteBuffer byteBuffer = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
+      FileOutputStream fos = new FileOutputStream(tmp.toFile());
+      FileChannel channel = fos.getChannel();
+      channel.write(byteBuffer);
+
+      List<String> lines = Files.readAllLines(tmp);
+      System.out.println(lines.get(0));
+    }
+
+    private static void fileChannelOpen() throws IOException {
+      fileChannelOpenTest();
+      fileChannelOpenSetTest();
+      fileChannelOpenLockTest();
+    }
+
+    private static void fileChannelOpenLockTest() throws IOException {
+      Path tmp = Files.createTempFile("lock", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      fc.read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fileChannelOpenTest() throws IOException {
+      Path tmp = Files.createTempFile("a", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ, StandardOpenOption.WRITE);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      // Extra indirection through the lock.
+      fc.lock().channel().read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fileChannelOpenSetTest() throws IOException {
+      Path tmp = Files.createTempFile("b", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      Set<OpenOption> options = new HashSet<>();
+      options.add(StandardOpenOption.READ);
+      FileChannel fc = FileChannel.open(tmp, options);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      fc.read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fisTest() throws IOException {
+      fisOwner();
+      fisNotOwner(true);
+      fisNotOwner(false);
+      fisOwnerTryResources();
+    }
+
+    private static void fisNotOwner(boolean closeFirst) throws IOException {
+      String toWrite = "Hello World! ";
+      String toWriteFIS = "Bye bye. ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, (toWrite + toWriteFIS).getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      ByteBuffer byteBufferFIS = ByteBuffer.allocate(toWriteFIS.length());
+      FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+      FileDescriptor fd = fileInputStream.getFD();
+      FileInputStream fis2 = new FileInputStream(fd);
+      fileInputStream.getChannel().read(byteBuffer);
+      fis2.getChannel().read(byteBufferFIS);
+
+      if (closeFirst) {
+        fileInputStream.close();
+        fis2.close();
+      } else {
+        fis2.close();
+        fileInputStream.close();
+      }
+
+      System.out.println(new String(byteBuffer.array()));
+      System.out.println(new String(byteBufferFIS.array()));
+    }
+
+    private static void fisOwner() throws IOException {
+      String toWrite = "Hello World! ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+      fileInputStream.getChannel().read(byteBuffer);
+      fileInputStream.close();
+
+      System.out.println(new String(byteBuffer.array()));
+    }
+
+    private static void fisOwnerTryResources() throws IOException {
+      String toWrite = "Hello World! ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      try (FileInputStream fileInputStream = new FileInputStream(tmp.toFile())) {
+        fileInputStream.getChannel().read(byteBuffer);
+      }
+
+      System.out.println(new String(byteBuffer.array()));
+    }
+  }
+}