emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android 65b58251b1b 1/5: Update Android port


From: Po Lu
Subject: feature/android 65b58251b1b 1/5: Update Android port
Date: Thu, 27 Jul 2023 08:57:03 -0400 (EDT)

branch: feature/android
commit 65b58251b1b07b50672367c675efdfdc97354c4a
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Update Android port
    
    * configure.ac (ANDROID_STUBIFY): Add androidvfs.o when building
    libemacs.so.
    * doc/emacs/android.texi (Android): Add `Android Document Providers'.
    (Android Startup): Update the location of the content identifier
    directory.
    (Android File System): Describe access to document provider
    directories.
    (Android Document Providers): New node.
    * doc/emacs/emacs.texi (Top): Update the menu for the Android
    appendix.
    * java/Makefile.in (filename, install_temp/assets/build_info): Make
    directory-tree depend on build_info.
    * java/org/gnu/emacs/EmacsActivity.java (onActivityResult): New
    function.  When a document tree is accepted, persist access to it.
    * java/org/gnu/emacs/EmacsDirectoryEntry.java (EmacsDirectoryEntry):
    New struct.
    * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy): Use
    EmacsService.buildContentName.
    * java/org/gnu/emacs/EmacsService.java (getEmacsView, openContentUri)
    (checkContentUri): Remove excessive debug logging.
    (buildContentName, getDocumentAuthorities, requestDirectoryAccess)
    (getDocumentTrees, decodeFileName, documentIdFromName, getTreeUri)
    (statDocument, accessDocument, openDocumentDirectory, readDirectoryEntry)
    (openDocument, createDocument): New functions.
    
    * lib-src/asset-directory-tool.c: Improve commentary by illustrating
    the difference between directory and ordinary files.
    
    * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags)
    (struct android_emacs_service, android_extract_long)
    (android_scan_directory_tree, android_is_directory)
    (android_get_asset_name, android_url_encode, android_content_name_p)
    (android_get_content_name, android_check_content_access, android_fstat)
    (android_fstatat, android_file_access_p, android_hack_asset_fd_fallback)
    (android_detect_ashmem, android_hack_asset_fd, android_close_on_exec)
    (android_open, android_close, android_fclose, android_create_lib_link)
    (android_faccessat, struct android_dir, android_opendir, android_dirfd)
    (android_readdir, android_closedir, android_lookup_asset_directory_fd)
    (android_exception_check_3, android_get_current_api_level)
    (android_open_asset, android_close_asset, android_asset_read_quit)
    (android_asset_read, android_asset_lseek, android_asset_fstat): Move
    content and asset related functions to androidvfs.c.
    (android_init_emacs_service): Obtain handles for new JNI functions.
    (initEmacsParams): Initialize the VFS layer.
    (android_request_directory_access): New function.
    (android_display_toast): Remove unused function.
    
    * src/android.h (android_get_current_api_level): Assume that
    this function never returns less than __ANDROID_API__.
    (struct android_emacs_service): Move `struct
    android_emacs_service' here.
    
    * src/androidfns.c (Fandroid_request_directory_access): New
    interactive function.
    (syms_of_androidfns): Register new subr.
    
    * src/androidvfs.c (struct android_vdir, struct android_vops)
    (struct android_vnode, struct android_special_vnode)
    (enum android_vnode_type, struct android_cursor_class)
    (struct emacs_directory_entry_class)
    (struct android_parcel_file_descriptor_class)
    (android_init_cursor_class, android_init_entry_class)
    (android_init_fd_class, android_vfs_canonicalize_name)
    (struct android_unix_vnode, struct android_unix_vdir, unix_vfs_ops)
    (android_unix_name, android_unix_vnode, android_unix_open)
    (android_unix_close, android_unix_unlink, android_unix_symlink)
    (android_unix_rmdir, android_unix_rename, android_unix_stat)
    (android_unix_access, android_unix_mkdir, android_unix_readdir)
    (android_unix_closedir, android_unix_dirfd, android_unix_opendir)
    (android_extract_long, android_scan_directory_tree)
    (android_is_directory, android_init_assets)
    (android_hack_asset_fd_fallback, android_detect_ashmem)
    (android_hack_asset_fd, struct android_afs_vnode)
    (struct android_afs_vdir, struct android_afs_open_fd, afs_vfs_ops)
    (android_afs_name, android_afs_initial, android_close_on_exec)
    (android_afs_open, android_afs_close, android_afs_unlink)
    (android_afs_symlink, android_afs_rmdir, android_afs_rename)
    (android_afs_stat, android_afs_access, android_afs_mkdir)
    (android_afs_readdir, android_afs_closedir, android_afs_dirfd)
    (android_afs_opendir, android_afs_get_directory_name)
    (struct android_content_vdir, content_vfs_ops)
    (content_directory_contents, android_content_name)
    (android_content_open, android_content_close)
    (android_content_unlink, android_content_symlink)
    (android_content_rmdir, android_content_rename)
    (android_content_stat, android_content_access)
    (android_content_mkdir, android_content_readdir)
    (android_content_closedir, android_content_dirfd)
    (android_content_opendir, android_content_get_directory_name)
    (android_content_initial, android_get_content_name)
    (android_check_content_access, struct android_authority_vnode)
    (authority_vfs_ops, android_authority_name, android_authority_open)
    (android_authority_close, android_authority_unlink)
    (android_authority_symlink, android_authority_rmdir)
    (android_authority_rename, android_authority_stat)
    (android_authority_access, android_authority_mkdir)
    (android_authority_opendir, android_authority_initial)
    (struct android_saf_root_vnode, struct android_saf_root_vdir)
    (saf_root_vfs_ops, android_saf_root_name, android_saf_root_open)
    (android_saf_root_close, android_saf_root_unlink)
    (android_saf_root_symlink, android_saf_root_rmdir)
    (android_saf_root_rename, android_saf_root_stat)
    (android_saf_root_access, android_saf_root_mkdir)
    (android_saf_root_readdir, android_saf_root_closedir)
    (android_saf_root_dirfd, android_saf_root_opendir)
    (android_saf_root_initial, android_saf_root_get_directory)
    (android_saf_stat, android_saf_access)
    (struct android_saf_tree_vnode, struct android_saf_tree_vdir)
    (saf_tree_vfs_ops, android_document_id_from_name)
    (android_saf_tree_name, android_saf_tree_open)
    (android_saf_tree_close, android_saf_tree_unlink)
    (android_saf_tree_symlink, android_saf_tree_rmdir)
    (android_saf_tree_rename, android_saf_tree_stat)
    (android_saf_tree_access, android_saf_tree_mkdir)
    (android_saf_tree_opendir_1, android_saf_tree_readdir)
    (android_saf_tree_closedir, android_saf_tree_dirfd)
    (android_saf_tree_opendir, android_saf_tree_from_name)
    (android_saf_tree_get_directory, android_saf_file_vnode)
    (saf_file_vfs_ops, android_saf_file_name, android_saf_file_open)
    (android_saf_file_unlink, android_saf_file_rmdir)
    (android_saf_file_opendir, android_close_parcel_fd)
    (android_saf_new_vnode, android_saf_new_name, android_saf_new_open)
    (android_saf_new_unlink, android_saf_new_symlink)
    (android_saf_new_rmdir, android_saf_new_rename)
    (android_saf_new_stat, android_saf_new_access)
    (android_saf_new_mkdir, android_saf_new_opendir, root_vfs_ops)
    (special_vnodes, android_root_name, android_name_file)
    (android_vfs_init, android_open, android_unlink, android_symlink)
    (android_rmdir, android_mkdir, android_renameat_noreplace)
    (android_rename, android_fstat, android_fstatat_1, android_fstatat)
    (android_faccessat, android_fdopen, android_close, android_fclose)
    (android_open_asset, android_close_asset, android_asset_read_quit)
    (android_asset_read, android_asset_lseek, android_asset_fstat)
    (android_opendir, android_dirfd, android_readdir)
    (android_closedir): Move file system emulation routines here.
    Introduce a new ``VFS'' layer for translating between
    Emacs-specific file names and the various disparate interfaces
    for accessing files on Android.
    
    * src/callproc.c (delete_temp_file):
    * src/charset.c (load_charset_map_from_file):
    * src/dired.c:
    * src/emacs.c (Fkill_emacs):
    * src/fileio.c (check_mutable_filename, Fcopy_file)
    (Fmake_directory_internal, Fdelete_directory_internal)
    (Fdelete_file, Frename_file, Fadd_name_to_file)
    (Fmake_symbolic_link, file_accessible_directory_p, Fset_file_modes)
    (Fset_file_times, write_region):
    * src/filelock.c (get_boot_time, rename_lock_file)
    (create_lock_file, current_lock_owner, unlock_file):
    * src/image.c (slurp_file, png_load_body, jpeg_load_body):
    * src/keyboard.c (Fopen_dribble_file):
    * src/lisp.h:
    * src/lread.c (Fload):
    * src/process.c (handle_child_signal):
    * src/sysdep.c (init_standard_fds, emacs_fopen, emacs_fdopen)
    (emacs_unlink, emacs_symlink, emacs_rmdir, emacs_mkdir)
    (emacs_renameat_noreplace, emacs_rename):
    * src/term.c (Fresume_tty, init_tty): Use and add new wrappers
    for fopen, fdopen, unlink, symlink, rmdir, mkdir,
    renameat_norepalce and rename.
---
 configure.ac                                |    4 +-
 doc/emacs/android.texi                      |   64 +-
 doc/emacs/emacs.texi                        |    1 +
 java/Makefile.in                            |    9 +-
 java/org/gnu/emacs/EmacsActivity.java       |   38 +
 java/org/gnu/emacs/EmacsDirectoryEntry.java |   33 +
 java/org/gnu/emacs/EmacsOpenActivity.java   |   12 +-
 java/org/gnu/emacs/EmacsService.java        | 1041 ++++-
 lib-src/asset-directory-tool.c              |    7 +-
 src/android.c                               | 1801 ++------
 src/android.h                               |   96 +-
 src/androidfns.c                            |   31 +-
 src/androidvfs.c                            | 6137 +++++++++++++++++++++++++++
 src/callproc.c                              |    2 +-
 src/charset.c                               |    2 +-
 src/dired.c                                 |    2 +-
 src/emacs.c                                 |    2 +-
 src/fileio.c                                |   96 +-
 src/filelock.c                              |   22 +-
 src/image.c                                 |    6 +-
 src/keyboard.c                              |    4 +-
 src/lisp.h                                  |    8 +
 src/lread.c                                 |    2 +-
 src/process.c                               |   10 +
 src/sysdep.c                                |   82 +-
 src/term.c                                  |    4 +-
 26 files changed, 7839 insertions(+), 1677 deletions(-)

diff --git a/configure.ac b/configure.ac
index 8d54ea0de16..ff9f604221f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2689,8 +2689,8 @@ for Android, but all API calls need to be stubbed out])
     # sfntfont-android.o.
     ANDROID_OBJ="$ANDROID_OBJ sfnt.o sfntfont.o sfntfont-android.o"
 
-    # Build androidselect.o.
-    ANDROID_OBJ="$ANDROID_OBJ androidselect.o"
+    # Build androidselect.o and androidvfs.o.
+    ANDROID_OBJ="$ANDROID_OBJ androidselect.o androidvfs.o"
 
     # Check for some functions not always present in the NDK.
     AC_CHECK_DECLS([android_get_device_api_level])
diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index 86af851efd2..1f32fdfc1d2 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -17,6 +17,7 @@ about using such devices with Emacs, @pxref{Other Input 
Devices}.
 * What is Android?::            Preamble.
 * Android Startup::             Starting up Emacs on Android.
 * Android File System::         The Android file system.
+* Android Document Providers::  Accessing files from other programs.
 * Android Environment::         Running Emacs under Android.
 * Android Windowing::           The Android window system.
 * Android Fonts::               Font selection under Android.
@@ -142,12 +143,12 @@ it starts Emacs and gives it the file to open as an 
argument.  Note
 that if that Emacs in turn does not start the Emacs server, subsequent
 attempts to open the file with the wrapper will fail.
 
-@cindex /content directory, android
+@cindex /content/by-authority directory, android
   Some files are given to Emacs as ``content identifiers'' that the
 system provides access to outside the normal filesystem APIs.  Emacs
-uses a pseudo-directory named @file{/content} to access those files.
-Do not make any assumptions about the contents of this directory, or
-try to open files in it yourself.
+uses a pseudo-directory named @file{/content/by-authority} to access
+those files.  Do not make any assumptions about the contents of this
+directory, or try to open files in it yourself.
 
   This feature is not provided on Android 4.3 and earlier, in which
 case such files are copied to a temporary directory before being
@@ -180,12 +181,13 @@ that result from such an implementation:
 @item
 Subprocesses (such as @command{ls}) can not run from the
 @file{/assets} directory; if you try to run a subprocess with
-@code{current-directory} set to @file{/assets} or a subdirectory
-thereof, it will run from the home directory instead.
+@code{current-directory} set to @file{/assets},
+@file{/content/storage} or a subdirectory thereof, it will run from
+the home directory instead.
 
 @item
 There are no @file{.} and @file{..} directories inside the
-@file{/assets} directory.
+@file{/assets} or @file{/content} directory.
 
 @item
 Files in the @file{/assets} directory are always read only, and may be
@@ -193,7 +195,7 @@ read in to memory more than once each time they are opened.
 @end itemize
 
   Aside from the @file{/assets} directory, Android programs normally
-have access to three other directories.  They are:
+have access to four other directories.  They are:
 
 @itemize @bullet
 @item
@@ -209,6 +211,12 @@ contains utility executables alongside Emacs itself.
 The @dfn{external storage} directory.  This is accessible to Emacs
 when the user grants the ``Files and Media'' permission to Emacs via
 system settings.
+
+@item
+Directories provided by @dfn{document providers} on Android 5.0 and
+later.  These directories exist outside the normal Unix filesystem,
+containing files provided by external programs (@pxref{Android
+Document Providers}.)
 @end itemize
 
   The external storage directory is found at @file{/sdcard}.  The
@@ -265,6 +273,46 @@ files under @file{/sdcard} as usual.
   These settings are not present on many proprietary versions of
 Android.
 
+@node Android Document Providers
+@section Accessing files from other programs under Android
+@cindex document providers, Android
+@cindex /content/storage directory, Android
+
+  Android 5.0 introduces a new sort of program, the ``document
+provider'': these programs are small programs that provide access to
+their own files outside both the asset manager and the Unix
+filesystem.  Emacs supports accessing files and directories they
+provide, placing their files within the directory
+@file{/content/storage}.
+
+@findex android-request-directory-access
+  Before Emacs is granted access to any of these directories, it must
+first request the right to access it.  This is done by running the
+command (@pxref{M-x}) @code{android-request-directory-access}, which
+displays a file selection dialog.
+
+  If a directory is selected within this dialog, its contents are
+subsequently made available within a new directory named
+@file{/content/storage/@var{authority}/@var{id}}, where
+@var{authority} is the name of the document provider, and @var{id} is
+a unique identifier assigned to the directory by the document
+provider.
+
+  Because these directories do not exist within the Unix file-system,
+sub-processes cannot be created within them, just as with the
+@file{/assets} directory (@pxref{Android File System}.)  In addition,
+although Emacs can normally write and create files inside these
+directories, it cannot create symlinks or hard links.
+
+@c TODO: fix this!
+  Since document providers are allowed to perform expensive network
+operations to obtain file contents, a file access operation within one
+of these directories will possibly take a significant amount of time.
+Emacs presently does not support quitting out of such file system
+operations, and the timeouts applied are fully subject to the
+discretion of the system and the document provider that is responding
+to these operations.
+
 @node Android Environment
 @section Running Emacs under Android
 
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index b255e679d5f..7a21eb49e24 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -1267,6 +1267,7 @@ Emacs and Android
 * What is Android?::            Preamble.
 * Android Startup::             Starting up Emacs on Android.
 * Android File System::         The Android file system.
+* Android Document Providers::  Accessing files from other programs.
 * Android Environment::         Running Emacs under Android.
 * Android Windowing::           The Android window system.
 * Android Fonts::               Font selection under Android.
diff --git a/java/Makefile.in b/java/Makefile.in
index d11278e6110..804d4669c7a 100644
--- a/java/Makefile.in
+++ b/java/Makefile.in
@@ -233,7 +233,8 @@ ifneq ($(NDK_BUILD_SHARED),)
 endif
 
 install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool \
-  install_temp install_temp/assets/version
+  install_temp install_temp/assets/version                        \
+  install_temp/assets/build_info
        $(AM_V_GEN) $(libsrc)/asset-directory-tool install_temp/assets \
          install_temp/assets/directory-tree
 
@@ -246,9 +247,9 @@ install_temp/assets/version: install_temp
 install_temp/assets/build_info: install_temp
        $(AM_V_GEN) { hostname; date +%s; } > $@
 
-emacs.apk-in: install_temp install_temp/assets/directory-tree \
-  install_temp/assets/version install_temp/assets/build_info  \
-  AndroidManifest.xml
+emacs.apk-in: install_temp install_temp/assets/directory-tree  \
+  AndroidManifest.xml install_temp/assets/version             \
+  install_temp/assets/build_info
 # Package everything.  Specifying the assets on this command line is
 # necessary for AAssetManager_getNextFileName to work on old versions
 # of Android.  Make sure not to generate R.java, as it's already been
diff --git a/java/org/gnu/emacs/EmacsActivity.java 
b/java/org/gnu/emacs/EmacsActivity.java
index d7b51388929..86fed5396d7 100644
--- a/java/org/gnu/emacs/EmacsActivity.java
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 
 import android.app.Activity;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 
@@ -33,6 +34,8 @@ import android.os.Bundle;
 
 import android.util.Log;
 
+import android.net.Uri;
+
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -48,6 +51,9 @@ public class EmacsActivity extends Activity
 {
   public static final String TAG = "EmacsActivity";
 
+  /* ID for URIs from a granted document tree.  */
+  public static final int ACCEPT_DOCUMENT_TREE = 1;
+
   /* The currently attached EmacsWindow, or null if none.  */
   private EmacsWindow window;
 
@@ -431,4 +437,36 @@ public class EmacsActivity extends Activity
     /* Update the window insets.  */
     syncFullscreenWith (window);
   }
+
+
+
+  @Override
+  public final void
+  onActivityResult (int requestCode, int resultCode, Intent data)
+  {
+    ContentResolver resolver;
+    Uri uri;
+    int flags;
+
+    switch (requestCode)
+      {
+      case ACCEPT_DOCUMENT_TREE:
+
+       /* A document granted through
+          EmacsService.requestDirectoryAccess.  */
+
+       if (resultCode == RESULT_OK)
+         {
+           resolver = getContentResolver ();
+           uri = data.getData ();
+           flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+           if (uri != null)
+             resolver.takePersistableUriPermission (uri, flags);
+         }
+
+       break;
+      }
+  }
 };
diff --git a/java/org/gnu/emacs/EmacsDirectoryEntry.java 
b/java/org/gnu/emacs/EmacsDirectoryEntry.java
new file mode 100644
index 00000000000..9c10f2e8771
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDirectoryEntry.java
@@ -0,0 +1,33 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* Structure holding a single ``directory entry'' from a document
+   provider.  */
+
+public class EmacsDirectoryEntry
+{
+  /* The type of this directory entry.  0 means a regular file and 1
+     means a directory.  */
+  public int d_type;
+
+  /* The display name of the file represented.  */
+  public String d_name;
+};
diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java 
b/java/org/gnu/emacs/EmacsOpenActivity.java
index 9411f85d434..3832cd2faab 100644
--- a/java/org/gnu/emacs/EmacsOpenActivity.java
+++ b/java/org/gnu/emacs/EmacsOpenActivity.java
@@ -243,18 +243,8 @@ public final class EmacsOpenActivity extends Activity
 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
       {
-       content = "/content/" + uri.getEncodedAuthority ();
-
-       for (String segment : uri.getPathSegments ())
-         content += "/" + Uri.encode (segment);
-
-       /* Append the URI query.  */
-
-       if (uri.getEncodedQuery () != null)
-         content += "?" + uri.getEncodedQuery ();
-
+       content = EmacsService.buildContentName (uri);
        Log.d (TAG, "checkReadableOrCopy: " + content);
-
        return content;
       }
 
diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
index 6240b0e475a..6059439551f 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -23,13 +23,19 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
+import android.database.Cursor;
+
 import android.graphics.Matrix;
 import android.graphics.Point;
 
+import android.webkit.MimeTypeMap;
+
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.inputmethod.CursorAnchorInfo;
@@ -45,9 +51,12 @@ import android.content.Context;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.UriPermission;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager;
+
 import android.content.res.AssetManager;
 
 import android.hardware.input.InputManager;
@@ -65,6 +74,7 @@ import android.os.VibratorManager;
 import android.os.VibrationEffect;
 
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
 
 import android.util.Log;
 import android.util.DisplayMetrics;
@@ -77,14 +87,21 @@ import android.widget.Toast;
 public final class EmacsService extends Service
 {
   public static final String TAG = "EmacsService";
-  public static volatile EmacsService SERVICE;
+
+  /* The started Emacs service object.  */
+  public static EmacsService SERVICE;
 
   /* If non-NULL, an extra argument to pass to
      `android_emacs_init'.  */
   public static String extraStartupArgument;
 
+  /* The thread running Emacs C code.  */
   private EmacsThread thread;
+
+  /* Handler used to run tasks on the main thread.  */
   private Handler handler;
+
+  /* Content resolver used to access URIs.  */
   private ContentResolver resolver;
 
   /* Keep this in synch with androidgui.h.  */
@@ -92,6 +109,13 @@ public final class EmacsService extends Service
   public static final int IC_MODE_ACTION = 1;
   public static final int IC_MODE_TEXT   = 2;
 
+  /* File access mode constants.  See `man 7 inode'.  */
+  public static final int S_IRUSR = 0000400;
+  public static final int S_IWUSR = 0000200;
+  public static final int S_IFCHR = 0020000;
+  public static final int S_IFDIR = 0040000;
+  public static final int S_IFREG = 0100000;
+
   /* Display metrics used by font backends.  */
   public DisplayMetrics metrics;
 
@@ -305,6 +329,7 @@ public final class EmacsService extends Service
     view = new EmacsHolder<EmacsView> ();
 
     runnable = new Runnable () {
+       @Override
        public void
        run ()
        {
@@ -557,7 +582,7 @@ public final class EmacsService extends Service
     return String.valueOf (keysym);
   }
 
-  
+
 
   /* Start the Emacs service if necessary.  On Android 26 and up,
      start Emacs as a foreground service with a notification, to avoid
@@ -872,6 +897,10 @@ public final class EmacsService extends Service
     icEndSynchronous ();
   }
 
+
+
+  /* Content provider functions.  */
+
   /* Open a content URI described by the bytes BYTES, a non-terminated
      string; make it writable if WRITABLE, and readable if READABLE.
      Truncate the file if TRUNCATE.
@@ -905,9 +934,8 @@ public final class EmacsService extends Service
       {
        /* The usual file name encoding question rears its ugly head
           again.  */
-       name = new String (bytes, "UTF-8");
-       Log.d (TAG, "openContentUri: " + Uri.parse (name));
 
+       name = new String (bytes, "UTF-8");
        fd = resolver.openFileDescriptor (Uri.parse (name), mode);
 
        /* Use detachFd on newer versions of Android or plain old
@@ -947,7 +975,6 @@ public final class EmacsService extends Service
        /* The usual file name encoding question rears its ugly head
           again.  */
        name = new String (string, "UTF-8");
-       Log.d (TAG, "checkContentUri: " + Uri.parse (name));
       }
     catch (UnsupportedEncodingException exception)
       {
@@ -960,25 +987,58 @@ public final class EmacsService extends Service
     if (writable)
       mode += "w";
 
-    Log.d (TAG, "checkContentUri: checking against mode " + mode);
-
     try
       {
        fd = resolver.openFileDescriptor (Uri.parse (name), mode);
        fd.close ();
 
-       Log.d (TAG, "checkContentUri: YES");
-
        return true;
       }
     catch (Exception exception)
       {
-       Log.d (TAG, "checkContentUri: NO");
-       Log.d (TAG, exception.toString ());
-       return false;
+       /* Fall through.  */
       }
+
+    return false;
   }
 
+  /* Build a content file name for URI.
+
+     Return a file name within the /contents/by-authority
+     pseudo-directory that `android_get_content_name' can then
+     transform back into an encoded URI.
+
+     A content name consists of any number of unencoded path segments
+     separated by `/' characters, possibly followed by a question mark
+     and an encoded query string.  */
+
+  public static String
+  buildContentName (Uri uri)
+  {
+    StringBuilder builder;
+
+    builder = new StringBuilder ("/content/by-authority/");
+    builder.append (uri.getAuthority ());
+
+    /* First, append each path segment.  */
+
+    for (String segment : uri.getPathSegments ())
+      {
+       /* FIXME: what if segment contains a slash character? */
+       builder.append ('/');
+       builder.append (uri.encode (segment));
+      }
+
+    /* Now, append the query string if necessary.  */
+
+    if (uri.getEncodedQuery () != null)
+      builder.append ('?').append (uri.getEncodedQuery ());
+
+    return builder.toString ();
+  }
+
+
+
   private long[]
   queryBattery19 ()
   {
@@ -1096,4 +1156,961 @@ public final class EmacsService extends Service
     window.view.imManager.updateExtractedText (window.view,
                                               token, text);
   }
+
+
+
+  /* Document tree management functions.  These functions shouldn't be
+     called before Android 5.0.
+
+     TODO: a timeout, let alone quitting, has yet to be implemented
+     for any of these functions.  */
+
+  /* Return an array of each document authority providing at least one
+     tree URI that Emacs holds the rights to persistently access.  */
+
+  public String[]
+  getDocumentAuthorities ()
+  {
+    List<UriPermission> permissions;
+    HashSet<String> allProviders;
+    Uri uri;
+
+    permissions = resolver.getPersistedUriPermissions ();
+    allProviders = new HashSet<String> ();
+
+    for (UriPermission permission : permissions)
+      {
+       uri = permission.getUri ();
+
+       if (DocumentsContract.isTreeUri (uri)
+           && permission.isReadPermission ())
+         allProviders.add (uri.getAuthority ());
+      }
+
+    return allProviders.toArray (new String[0]);
+  }
+
+  /* Start a file chooser activity to request access to a directory
+     tree.
+
+     Value is 1 if the activity couldn't be started for some reason,
+     and 0 in any other case.  */
+
+  public int
+  requestDirectoryAccess ()
+  {
+    Runnable runnable;
+    final EmacsHolder<Integer> rc;
+
+    /* Return 1 if Android is too old to support this feature.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+      return 1;
+
+    rc = new EmacsHolder<Integer> ();
+    rc.thing = Integer.valueOf (1);
+
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         EmacsActivity activity;
+         Intent intent;
+         int id;
+
+         synchronized (this)
+           {
+             /* Try to obtain an activity that will receive the
+                response from the file chooser dialog.  */
+
+             if (EmacsActivity.focusedActivities.isEmpty ())
+               {
+                 /* If focusedActivities is empty then this dialog
+                    may have been displayed immediately after another
+                    popup dialog was dismissed.  Try the
+                    EmacsActivity to be focused.  */
+
+                 activity = EmacsActivity.lastFocusedActivity;
+
+                 if (activity == null)
+                   {
+                     /* Still no luck.  Return failure.  */
+                     notify ();
+                     return;
+                   }
+               }
+             else
+               activity = EmacsActivity.focusedActivities.get (0);
+
+             /* Now create the intent.  */
+             intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
+
+             try
+               {
+                 id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
+                 activity.startActivityForResult (intent, id, null);
+                 rc.thing = Integer.valueOf (0);
+               }
+             catch (Exception e)
+               {
+                 e.printStackTrace ();
+               }
+
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+    return rc.thing;
+  }
+
+  /* Return an array of each tree provided by the document PROVIDER
+     that Emacs has permission to access.
+
+     Value is an array if the provider really does exist, NULL
+     otherwise.  */
+
+  public String[]
+  getDocumentTrees (byte provider[])
+  {
+    String providerName;
+    List<String> treeList;
+    List<UriPermission> permissions;
+    Uri uri;
+
+    try
+      {
+       providerName = new String (provider, "ASCII");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    permissions = resolver.getPersistedUriPermissions ();
+    treeList = new ArrayList<String> ();
+
+    for (UriPermission permission : permissions)
+      {
+       uri = permission.getUri ();
+
+       if (DocumentsContract.isTreeUri (uri)
+           && uri.getAuthority ().equals (providerName)
+           && permission.isReadPermission ())
+         /* Make sure the tree document ID is encoded.  */
+         treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri)));
+      }
+
+    return treeList.toArray (new String[0]);
+  }
+
+  /* Decode the specified STRING into a String object using the UTF-8
+     format.  If an exception is thrown, return null.  */
+
+  private String
+  decodeFileName (byte[] string)
+  {
+    try
+      {
+       return new String (string, "UTF-8");
+      }
+    catch (Exception e) /* UnsupportedEncodingException, etc.  */
+      {
+       ;;
+      }
+
+    return null;
+  }
+
+  /* Find the document ID of the file within TREE_URI designated by
+     NAME.
+
+     NAME is a ``file name'' comprised of the display names of
+     individual files.  Each constituent component prior to the last
+     must name a directory file within TREE_URI.
+
+     Upon success, return 0 or 1 (contingent upon whether or not the
+     last component within NAME is a directory) and place the document
+     ID of the named file in ID_RETURN[0].
+
+     If the designated file can't be located, but each component of
+     NAME up to the last component can and is a directory, return -2
+     and the ID of the last component located in ID_RETURN[0];
+
+     If the designated file can't be located, return -1.  */
+
+  private int
+  documentIdFromName (String tree_uri, byte name[],
+                     String[] id_return)
+  {
+    Uri uri, treeUri;
+    String nameString, id, type;
+    String[] components, projection;
+    Cursor cursor;
+    int column;
+
+    projection = new String[] {
+      Document.COLUMN_DISPLAY_NAME,
+      Document.COLUMN_DOCUMENT_ID,
+      Document.COLUMN_MIME_TYPE,
+    };
+
+    /* Parse the URI identifying the tree first.  */
+    uri = Uri.parse (tree_uri);
+
+    /* Next, decode NAME.  */
+    nameString = decodeFileName (name);
+
+    /* Now, split NAME into its individual components.  */
+    components = nameString.split ("/");
+
+    /* Set id and type to the value at the root of the tree.  */
+    type = id = null;
+
+    /* For each component... */
+
+    for (String component : components)
+      {
+       /* Java split doesn't behave very much like strtok when it
+          comes to trailing and leading delimiters...  */
+       if (component.isEmpty ())
+         continue;
+
+       /* Create the tree URI for URI from ID if it exists, or the
+          root otherwise.  */
+
+       if (id == null)
+         id = DocumentsContract.getTreeDocumentId (uri);
+
+       treeUri
+         = DocumentsContract.buildChildDocumentsUriUsingTree (uri, id);
+
+       /* Look for a file in this directory by the name of
+          component.  */
+
+       try
+         {
+           cursor = resolver.query (treeUri, projection,
+                                    (Document.COLUMN_DISPLAY_NAME
+                                     + " = ?s"),
+                                    new String[] { component, }, null);
+         }
+       catch (SecurityException exception)
+         {
+           /* A SecurityException can be thrown if Emacs doesn't have
+              access to treeUri.  */
+           return -1;
+         }
+       catch (Exception exception)
+         {
+           exception.printStackTrace ();
+
+           /* Why is this? */
+           return -1;
+         }
+
+       if (cursor == null)
+         return -1;
+
+       while (true)
+         {
+           /* Even though the query selects for a specific display
+              name, some content providers nevertheless return every
+              file within the directory.  */
+
+           if (!cursor.moveToNext ())
+             {
+               cursor.close ();
+
+               /* If the last component considered is a
+                  directory... */
+               if ((type == null
+                    || type.equals (Document.MIME_TYPE_DIR))
+                   /* ... and type and id currently represent the
+                      penultimate component.  */
+                   && component == components[components.length  - 1])
+                 {
+                   /* The cursor is empty.  In this case, return -2
+                      and the current document ID (belonging to the
+                      previous component) in ID_RETURN.  */
+
+                   id_return[0] = id;
+
+                   /* But return -1 on the off chance that id is
+                      null.  */
+
+                   if (id == null)
+                     return -1;
+
+                   return -2;
+                 }
+
+               /* The last component found is not a directory, so
+                  return -1.  */
+               return -1;
+             }
+
+           /* So move CURSOR to a row with the right display
+              name.  */
+
+           column = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+
+           if (column < 0)
+             continue;
+
+           try
+             {
+               nameString = cursor.getString (column);
+             }
+           catch (Exception exception)
+             {
+               cursor.close ();
+               return -1;
+             }
+
+           /* Break out of the loop only once a matching component is
+              found.  */
+
+           if (nameString.equals (component))
+             break;
+         }
+
+       /* Look for a column by the name of COLUMN_DOCUMENT_ID.  */
+
+       column = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
+
+       if (column < 0)
+         {
+           cursor.close ();
+           return -1;
+         }
+
+       /* Now replace ID with the document ID.  */
+
+       try
+         {
+           id = cursor.getString (column);
+         }
+       catch (Exception exception)
+         {
+           cursor.close ();
+           return -1;
+         }
+
+       /* If this is the last component, be sure to initialize the
+          document type.  */
+
+       if (component == components[components.length - 1])
+         {
+           column
+             = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+           if (column < 0)
+             {
+               cursor.close ();
+               return -1;
+             }
+
+           try
+             {
+               type = cursor.getString (column);
+             }
+           catch (Exception exception)
+             {
+               cursor.close ();
+               return -1;
+             }
+
+           /* Type may be NULL depending on how the Cursor returned
+              is implemented.  */
+
+           if (type == null)
+             {
+               cursor.close ();
+               return -1;
+             }
+         }
+
+       /* Now close the cursor.  */
+       cursor.close ();
+
+       /* ID may have become NULL if the data is in an invalid
+          format.  */
+       if (id == null)
+         return -1;
+      }
+
+    /* Here, id is either NULL (meaning the same as TREE_URI), and
+       type is either NULL (in which case id should also be NULL) or
+       the MIME type of the file.  */
+
+    /* First return the ID.  */
+
+    if (id == null)
+      id_return[0] = DocumentsContract.getTreeDocumentId (uri);
+    else
+      id_return[0] = id;
+
+    /* Next, return whether or not this is a directory.  */
+    if (type == null || type.equals (Document.MIME_TYPE_DIR))
+      return 1;
+
+    return 0;
+  }
+
+  /* Return an encoded document URI representing a tree with the
+     specified IDENTIFIER supplied by the authority AUTHORITY.
+
+     Return null instead if Emacs does not have permanent access
+     to the specified document tree recorded on disk.  */
+
+  public String
+  getTreeUri (String tree, String authority)
+  {
+    Uri uri, grantedUri;
+    List<UriPermission> permissions;
+
+    /* First, build the URI.  */
+    tree = Uri.decode (tree);
+    uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
+
+    /* Now, search for it within the list of persisted URI
+       permissions.  */
+    permissions = resolver.getPersistedUriPermissions ();
+
+    for (UriPermission permission : permissions)
+      {
+       /* If the permission doesn't entitle Emacs to read access,
+          skip it.  */
+
+       if (!permission.isReadPermission ())
+         continue;
+
+        grantedUri = permission.getUri ();
+
+       if (grantedUri.equals (uri))
+         return uri.toString ();
+      }
+
+    /* Emacs doesn't have permission to access this tree URI.  */
+    return null;
+  }
+
+  /* Return file status for the document designated by the given
+     DOCUMENTID and tree URI.  If DOCUMENTID is NULL, use the document
+     ID in URI itself.
+
+     Value is null upon failure, or an array of longs [MODE, SIZE,
+     MTIM] upon success, where MODE contains the file type and access
+     modes of the file as in `struct stat', SIZE is the size of the
+     file in BYTES or -1 if not known, and MTIM is the time of the
+     last modification to this file in milliseconds since 00:00,
+     January 1st, 1970.  */
+
+  public long[]
+  statDocument (String uri, String documentId)
+  {
+    Uri uriObject;
+    String[] projection;
+    long[] stat;
+    int index;
+    long tem;
+    String tem1;
+    Cursor cursor;
+
+    uriObject = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (uriObject);
+
+    /* Create a document URI representing DOCUMENTID within URI's
+       authority.  */
+
+    uriObject
+      = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
+
+    /* Now stat this document.  */
+
+    projection = new String[] {
+      Document.COLUMN_FLAGS,
+      Document.COLUMN_LAST_MODIFIED,
+      Document.COLUMN_MIME_TYPE,
+      Document.COLUMN_SIZE,
+    };
+
+    try
+      {
+       cursor = resolver.query (uriObject, projection, null,
+                                null, null);
+      }
+    catch (SecurityException exception)
+      {
+       /* A SecurityException can be thrown if Emacs doesn't have
+          access to uriObject.  */
+       return null;
+      }
+    catch (UnsupportedOperationException exception)
+      {
+       exception.printStackTrace ();
+
+       /* Why is this? */
+       return null;
+      }
+
+    if (cursor == null || !cursor.moveToFirst ())
+      return null;
+
+    /* Create the array of file status.  */
+    stat = new long[3];
+
+    try
+      {
+       index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+       if (index < 0)
+         return null;
+
+       tem = cursor.getInt (index);
+
+       stat[0] |= S_IRUSR;
+       if ((tem & Document.FLAG_SUPPORTS_WRITE) != 0)
+         stat[0] |= S_IWUSR;
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+           && (tem & Document.FLAG_VIRTUAL_DOCUMENT) != 0)
+         stat[0] |= S_IFCHR;
+
+       index = cursor.getColumnIndex (Document.COLUMN_SIZE);
+       if (index < 0)
+         return null;
+
+       if (cursor.isNull (index))
+         stat[1] = -1; /* The size is unknown.  */
+       else
+         stat[1] = cursor.getLong (index);
+
+       index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+       if (index < 0)
+         return null;
+
+       tem1 = cursor.getString (index);
+
+       /* Check if this is a directory file.  */
+       if (tem1.equals (Document.MIME_TYPE_DIR)
+           /* Files shouldn't be specials and directories at the same
+              time, but Android doesn't forbid document providers
+              from returning this information.  */
+           && (stat[0] & S_IFCHR) == 0)
+         /* Since FLAG_SUPPORTS_WRITE doesn't apply to directories,
+            just assume they're writable.  */
+         stat[0] |= S_IFDIR | S_IWUSR;
+
+       /* If this file is neither a character special nor a
+          directory, indicate that it's a regular file.  */
+
+       if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0)
+         stat[0] |= S_IFREG;
+
+       index = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED);
+
+       if (index >= 0 && !cursor.isNull (index))
+         {
+           /* Content providers are allowed to not provide mtime.  */
+           tem = cursor.getLong (index);
+           stat[2] = tem;
+         }
+      }
+    catch (Exception exception)
+      {
+       /* Whether or not type errors cause exceptions to be signaled
+          is defined ``by the implementation of Cursor'', whatever
+          that means.  */
+       exception.printStackTrace ();
+       return null;
+      }
+
+    return stat;
+  }
+
+  /* Find out whether Emacs has access to the document designated by
+     the specified DOCUMENTID within the tree URI.  If DOCUMENTID is
+     NULL, use the document ID in URI itself.
+
+     If WRITABLE, also check that the file is writable, which is true
+     if it is either a directory or its flags contains
+     FLAG_SUPPORTS_WRITE.
+
+     Value is 0 if the file is accessible, and one of the following if
+     not:
+
+       -1, if the file does not exist.
+       -2, upon a security exception or if WRITABLE the file
+           is not writable.
+       -3, upon any other error.  */
+
+  public int
+  accessDocument (String uri, String documentId, boolean writable)
+  {
+    Uri uriObject;
+    String[] projection;
+    int tem, index;
+    String tem1;
+    Cursor cursor;
+
+    uriObject = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (uriObject);
+
+    /* Create a document URI representing DOCUMENTID within URI's
+       authority.  */
+
+    uriObject
+      = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
+
+    /* Now stat this document.  */
+
+    projection = new String[] {
+      Document.COLUMN_FLAGS,
+      Document.COLUMN_MIME_TYPE,
+    };
+
+    try
+      {
+       cursor = resolver.query (uriObject, projection, null,
+                                null, null);
+      }
+    catch (SecurityException exception)
+      {
+       /* A SecurityException can be thrown if Emacs doesn't have
+          access to uriObject.  */
+       return -2;
+      }
+    catch (UnsupportedOperationException exception)
+      {
+       exception.printStackTrace ();
+
+       /* Why is this? */
+       return -3;
+      }
+
+    if (cursor == null || !cursor.moveToFirst ())
+      return -1;
+
+    if (!writable)
+      return 0;
+
+    try
+      {
+       index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+       if (index < 0)
+         return -3;
+
+       /* Get the type of this file to check if it's a directory.  */
+       tem1 = cursor.getString (index);
+
+       /* Check if this is a directory file.  */
+       if (tem1.equals (Document.MIME_TYPE_DIR))
+         {
+           /* If so, don't check for FLAG_SUPPORTS_WRITE.
+              Check for FLAG_DIR_SUPPORTS_CREATE instead.  */
+
+           if (!writable)
+             return 0;
+
+           index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+           if (index < 0)
+             return -3;
+
+           tem = cursor.getInt (index);
+           if ((tem & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
+             return -3;
+
+           return 0;
+         }
+
+       index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+       if (index < 0)
+         return -3;
+
+       tem = cursor.getInt (index);
+       if (writable && (tem & Document.FLAG_SUPPORTS_WRITE) == 0)
+         return -3;
+      }
+    catch (Exception exception)
+      {
+       /* Whether or not type errors cause exceptions to be signaled
+          is defined ``by the implementation of Cursor'', whatever
+          that means.  */
+       exception.printStackTrace ();
+       return -3;
+      }
+
+    return 0;
+  }
+
+  /* Open a cursor representing each entry within the directory
+     designated by the specified DOCUMENTID within the tree URI.
+
+     If DOCUMENTID is NULL, use the document ID within URI itself.
+     Value is NULL upon failure.  */
+
+  public Cursor
+  openDocumentDirectory (String uri, String documentId)
+  {
+    Uri uriObject;
+    Cursor cursor;
+    String projection[];
+
+    uriObject = Uri.parse (uri);
+
+    /* If documentId is not set, use the document ID of the tree URI
+       itself.  */
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (uriObject);
+
+    /* Build a URI representing each directory entry within
+       DOCUMENTID.  */
+
+    uriObject
+      = DocumentsContract.buildChildDocumentsUriUsingTree (uriObject,
+                                                          documentId);
+
+    projection = new String [] {
+      Document.COLUMN_DISPLAY_NAME,
+      Document.COLUMN_MIME_TYPE,
+    };
+
+    try
+      {
+       cursor = resolver.query (uriObject, projection, null, null,
+                                null);
+      }
+    catch (SecurityException exception)
+      {
+       /* A SecurityException can be thrown if Emacs doesn't have
+          access to uriObject.  */
+       return null;
+      }
+    catch (UnsupportedOperationException exception)
+      {
+       exception.printStackTrace ();
+
+       /* Why is this? */
+       return null;
+      }
+
+    /* Return the cursor.  */
+    return cursor;
+  }
+
+  /* Read a single directory entry from the specified CURSOR.  Return
+     NULL if at the end of the directory stream, and a directory entry
+     with `d_name' set to NULL if an error occurs.  */
+
+  public EmacsDirectoryEntry
+  readDirectoryEntry (Cursor cursor)
+  {
+    EmacsDirectoryEntry entry;
+    int index;
+    String name, type;
+
+    entry = new EmacsDirectoryEntry ();
+
+    while (true)
+      {
+       if (!cursor.moveToNext ())
+         return null;
+
+       /* First, retrieve the display name.  */
+       index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+
+       if (index < 0)
+         /* Return an invalid directory entry upon failure.  */
+         return entry;
+
+       try
+         {
+           name = cursor.getString (index);
+         }
+       catch (Exception exception)
+         {
+           return entry;
+         }
+
+       /* Skip this entry if its name cannot be represented.  */
+
+       if (name.equals ("..") || name.equals (".") || name.contains ("/"))
+         continue;
+
+       /* Now, look for its type.  */
+
+       index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+       if (index < 0)
+         /* Return an invalid directory entry upon failure.  */
+         return entry;
+
+       try
+         {
+           type = cursor.getString (index);
+         }
+       catch (Exception exception)
+         {
+           return entry;
+         }
+
+       if (type.equals (Document.MIME_TYPE_DIR))
+         entry.d_type = 1;
+       entry.d_name = name;
+       return entry;
+      }
+
+    /* Not reached.  */
+  }
+
+  /* Open a file descriptor for a file document designated by
+     DOCUMENTID within the document tree identified by URI.  If
+     TRUNCATE and the document already exists, truncate its contents
+     before returning.
+
+     On Android 9.0 and earlier, always open the document in
+     ``read-write'' mode; this instructs the document provider to
+     return a seekable file that is stored on disk and returns correct
+     file status.
+
+     Under newer versions of Android, open the document in a
+     non-writable mode if WRITE is false.  This is possible because
+     these versions allow Emacs to explicitly request a seekable
+     on-disk file.
+
+     Value is NULL upon failure or a parcel file descriptor upon
+     success.  Call `ParcelFileDescriptor.close' on this file
+     descriptor instead of using the `close' system call.  */
+
+  public ParcelFileDescriptor
+  openDocument (String uri, String documentId, boolean write,
+               boolean truncate)
+  {
+    Uri treeUri, documentUri;
+    String mode;
+    ParcelFileDescriptor fileDescriptor;
+
+    treeUri = Uri.parse (uri);
+
+    /* documentId must be set for this request, since it doesn't make
+       sense to ``open'' the root of the directory tree.  */
+
+    documentUri
+      = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId);
+
+    try
+      {
+       if (write || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
+         {
+           /* Select the mode used to open the file.  `rw' means open
+              a stat-able file, while `rwt' means that and to
+              truncate the file as well.  */
+
+           if (truncate)
+             mode = "rwt";
+           else
+             mode = "rw";
+
+           fileDescriptor
+             = resolver.openFileDescriptor (documentUri, mode,
+                                            null);
+         }
+       else
+         {
+           /* Select the mode used to open the file.  `openFile'
+              below means always open a stat-able file.  */
+
+           if (truncate)
+             /* Invalid mode! */
+             return null;
+           else
+             mode = "r";
+
+           fileDescriptor = resolver.openFile (documentUri, mode, null);
+         }
+      }
+    catch (Exception exception)
+      {
+       return null;
+      }
+
+    return fileDescriptor;
+  }
+
+  /* Create a new document with the given display NAME within the
+     directory identified by DOCUMENTID inside the document tree
+     designated by URI.
+
+     If DOCUMENTID is NULL, create the document inside the root of
+     that tree.
+
+     Return the document ID of the new file upon success, NULL
+     otherwise.  */
+
+  public String
+  createDocument (String uri, String documentId, String name)
+  {
+    String mimeType, separator, mime, extension;
+    int index;
+    MimeTypeMap singleton;
+    Uri directoryUri, docUri;
+
+    /* Try to get the MIME type for this document.
+       Default to ``application/octet-stream''.  */
+
+    mimeType = "application/octet-stream";
+
+    /* Abuse WebView stuff to get the file's MIME type.  */
+
+    index = name.lastIndexOf ('.');
+
+    if (index > 0)
+      {
+       singleton = MimeTypeMap.getSingleton ();
+       extension = name.substring (index + 1);
+       mime = singleton.getMimeTypeFromExtension (extension);
+
+       if (mime != null)
+         mimeType = mime;
+      }
+
+    /* Now parse URI.  */
+    directoryUri = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (directoryUri);
+
+    /* And build a file URI referring to the directory.  */
+
+    directoryUri
+      = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri,
+                                                          documentId);
+
+    try
+      {
+       docUri = DocumentsContract.createDocument (resolver,
+                                                  directoryUri,
+                                                  mimeType, name);
+
+       if (docUri == null)
+         return null;
+
+       /* Return the ID of the new document.  */
+       return DocumentsContract.getDocumentId (docUri);
+      }
+    catch (Exception exception)
+      {
+       exception.printStackTrace ();
+      }
+
+    return null;
+  }
 };
diff --git a/lib-src/asset-directory-tool.c b/lib-src/asset-directory-tool.c
index 239ab083b66..99c954a3922 100644
--- a/lib-src/asset-directory-tool.c
+++ b/lib-src/asset-directory-tool.c
@@ -45,7 +45,12 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    the first file or directory, a NULL byte and an unsigned int
    indicating the offset from the start of the file to the start of
    the next sibling.  Following that is a list of subdirectories or
-   files in the same format.  The long is stored LSB first.  */
+   files in the same format.  The long is stored LSB first.
+
+   Directories can be distinguished from ordinary files through the
+   last bytes of their file names (immediately previous to their
+   terminating NULL bytes), which are set to the directory separator
+   character `/'.  */
 
 
 
diff --git a/src/android.c b/src/android.c
index 6fcaa40b2a9..b1d7b75c129 100644
--- a/src/android.c
+++ b/src/android.c
@@ -29,6 +29,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <math.h>
 #include <string.h>
 #include <stdckdint.h>
+#include <intprops.h>
 #include <timespec.h>
 #include <libgen.h>
 
@@ -56,17 +57,9 @@ bool android_init_gui;
 
 #ifndef ANDROID_STUBIFY
 
-#if __ANDROID_API__ >= 9
-#include <android/asset_manager.h>
-#include <android/asset_manager_jni.h>
-#else
-#include "android-asset.h"
-#endif
-
 #include <android/bitmap.h>
 #include <android/log.h>
 
-#include <linux/ashmem.h>
 #include <linux/unistd.h>
 
 #include <sys/syscall.h>
@@ -78,50 +71,6 @@ bool android_init_gui;
 #define ANDROID_THROW(env, class, msg)                                 \
   ((*(env))->ThrowNew ((env), (*(env))->FindClass ((env), class), msg))
 
-#define ANDROID_MAX_ASSET_FD 65535
-
-struct android_fd_table_entry
-{
-  /* Various flags associated with this table.  */
-  short flags;
-
-  /* The stat buffer associated with this entry.  */
-  struct stat statb;
-};
-
-enum android_fd_table_entry_flags
-  {
-    ANDROID_FD_TABLE_ENTRY_IS_VALID = 1,
-  };
-
-struct android_emacs_service
-{
-  jclass class;
-  jmethodID fill_rectangle;
-  jmethodID fill_polygon;
-  jmethodID draw_rectangle;
-  jmethodID draw_line;
-  jmethodID draw_point;
-  jmethodID clear_window;
-  jmethodID clear_area;
-  jmethodID ring_bell;
-  jmethodID query_tree;
-  jmethodID get_screen_width;
-  jmethodID get_screen_height;
-  jmethodID detect_mouse;
-  jmethodID name_keysym;
-  jmethodID browse_url;
-  jmethodID restart_emacs;
-  jmethodID update_ic;
-  jmethodID reset_ic;
-  jmethodID open_content_uri;
-  jmethodID check_content_uri;
-  jmethodID query_battery;
-  jmethodID display_toast;
-  jmethodID update_extracted_text;
-  jmethodID update_cursor_anchor_info;
-};
-
 struct android_emacs_pixmap
 {
   jclass class;
@@ -174,12 +123,6 @@ struct android_emacs_cursor
 /* The API level of the current device.  */
 static int android_api_level;
 
-/* The asset manager being used.  */
-static AAssetManager *asset_manager;
-
-/* Whether or not Emacs has been initialized.  */
-static int emacs_initialized;
-
 /* The directory used to store site-lisp.  */
 char *android_site_load_path;
 
@@ -206,11 +149,6 @@ double android_scaled_pixel_density;
 /* The Android application data directory.  */
 static char *android_files_dir;
 
-/* Array of structures used to hold asset information corresponding to
-   a file descriptor.  This assumes Emacs does not do funny things
-   with dup.  It currently does not.  */
-static struct android_fd_table_entry android_table[ANDROID_MAX_ASSET_FD];
-
 /* The Java environment being used for the main thread.  */
 JNIEnv *android_java_env;
 
@@ -235,10 +173,10 @@ static jclass android_rect_class;
 static jmethodID android_rect_constructor;
 
 /* The EmacsService object.  */
-static jobject emacs_service;
+jobject emacs_service;
 
 /* Various methods associated with the EmacsService.  */
-static struct android_emacs_service service_class;
+struct android_emacs_service service_class;
 
 /* Various methods associated with the EmacsPixmap class.  */
 static struct android_emacs_pixmap pixmap_class;
@@ -881,224 +819,6 @@ android_run_debug_thread (void *data)
 
 
 
-/* Asset directory handling functions.  ``directory-tree'' is a file in
-   the root of the assets directory describing its contents.
-
-   See lib-src/asset-directory-tool for more details.  */
-
-/* The Android directory tree.  */
-static const char *directory_tree;
-
-/* The size of the directory tree.  */
-static size_t directory_tree_size;
-
-/* Read an unaligned (32-bit) long from the address POINTER.  */
-
-static unsigned int
-android_extract_long (char *pointer)
-{
-  unsigned int number;
-
-  memcpy (&number, pointer, sizeof number);
-  return number;
-}
-
-/* Scan to the file FILE in the asset directory tree.  Return a
-   pointer to the end of that file (immediately before any children)
-   in the directory tree, or NULL if that file does not exist.
-
-   If returning non-NULL, also return the offset to the end of the
-   last subdirectory or file in *LIMIT_RETURN.  LIMIT_RETURN may be
-   NULL.
-
-   FILE must have less than 11 levels of nesting.  If it ends with a
-   trailing slash, then NULL will be returned if it is not actually a
-   directory.  */
-
-static const char *
-android_scan_directory_tree (char *file, size_t *limit_return)
-{
-  char *token, *saveptr, *copy, *copy1, *start, *max, *limit;
-  size_t token_length, ntokens, i;
-  char *tokens[10];
-
-  USE_SAFE_ALLOCA;
-
-  /* Skip past the 5 byte header.  */
-  start = (char *) directory_tree + 5;
-
-  /* Figure out the current limit.  */
-  limit = (char *) directory_tree + directory_tree_size;
-
-  /* Now, split `file' into tokens, with the delimiter being the file
-     name separator.  Look for the file and seek past it.  */
-
-  ntokens = 0;
-  saveptr = NULL;
-  copy = copy1 = xstrdup (file);
-  memset (tokens, 0, sizeof tokens);
-
-  while ((token = strtok_r (copy, "/", &saveptr)))
-    {
-      copy = NULL;
-
-      /* Make sure ntokens is within bounds.  */
-      if (ntokens == ARRAYELTS (tokens))
-       {
-         xfree (copy1);
-         goto fail;
-       }
-
-      tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1);
-      memcpy (tokens[ntokens], token, strlen (token) + 1);
-      ntokens++;
-    }
-
-  /* Free the copy created for strtok_r.  */
-  xfree (copy1);
-
-  /* If there are no tokens, just return the start of the directory
-     tree.  */
-
-  if (!ntokens)
-    {
-      SAFE_FREE ();
-
-      /* Return the size of the directory tree as the limit.
-         Do not subtract the initial header bytes, as the limit
-         is an offset from the start of the file.  */
-
-      if (limit_return)
-       *limit_return = directory_tree_size;
-
-      return start;
-    }
-
-  /* Loop through tokens, indexing the directory tree each time.  */
-
-  for (i = 0; i < ntokens; ++i)
-    {
-      token = tokens[i];
-
-      /* Figure out how many bytes to compare.  */
-      token_length = strlen (token);
-
-    again:
-
-      /* If this would be past the directory, return NULL.  */
-      if (start + token_length > limit)
-       goto fail;
-
-      /* Now compare the file name.  */
-      if (!memcmp (start, token, token_length))
-       {
-         /* They probably match.  Find the NULL byte.  It must be
-            either one byte past start + token_length, with the last
-            byte a trailing slash (indicating that it is a
-            directory), or just start + token_length.  Return 4 bytes
-            past the next NULL byte.  */
-
-         max = memchr (start, 0, limit - start);
-
-         if (max != start + token_length
-             && !(max == start + token_length + 1
-                  && *(max - 1) == '/'))
-           goto false_positive;
-
-         /* Return it if it exists and is in range, and this is the
-            last token.  Otherwise, set it as start and the limit as
-            start + the offset and continue the loop.  */
-
-         if (max && max + 5 <= limit)
-           {
-             if (i < ntokens - 1)
-               {
-                 start = max + 5;
-                 limit = ((char *) directory_tree
-                          + android_extract_long (max + 1));
-
-                 /* Make sure limit is still in range.  */
-                 if (limit > directory_tree + directory_tree_size
-                     || start > directory_tree + directory_tree_size)
-                   goto fail;
-
-                 continue;
-               }
-
-             /* Now see if max is not a directory and file is.  If
-                file is a directory, then return NULL.  */
-             if (*(max - 1) != '/' && file[strlen (file) - 1] == '/')
-               max = NULL;
-             else
-               {
-                 /* Figure out the limit.  */
-                 if (limit_return)
-                   *limit_return = android_extract_long (max + 1);
-
-                 /* Go to the end of this file.  */
-                 max += 5;
-               }
-
-             SAFE_FREE ();
-             return max;
-           }
-
-         /* Return NULL otherwise.  */
-         __android_log_print (ANDROID_LOG_WARN, __func__,
-                              "could not scan to end of directory tree"
-                              ": %s", file);
-         goto fail;
-       }
-
-    false_positive:
-
-      /* No match was found.  Set start to the next sibling and try
-        again.  */
-
-      start = memchr (start, 0, limit - start);
-
-      if (!start || start + 5 > limit)
-       goto fail;
-
-      start = ((char *) directory_tree
-              + android_extract_long (start + 1));
-
-      /* Make sure start is still in bounds.  */
-
-      if (start > limit)
-       goto fail;
-
-      /* Continue the loop.  */
-      goto again;
-    }
-
- fail:
-  SAFE_FREE ();
-  return NULL;
-}
-
-/* Return whether or not the directory tree entry DIR is a
-   directory.
-
-   DIR should be a value returned by
-   `android_scan_directory_tree'.  */
-
-static bool
-android_is_directory (const char *dir)
-{
-  /* If the directory is the directory tree, then it is a
-     directory.  */
-  if (dir == directory_tree + 5)
-    return true;
-
-  /* Otherwise, look 5 bytes behind.  If it is `/', then it is a
-     directory.  */
-  return (dir - 6 >= directory_tree
-         && *(dir - 6) == '/');
-}
-
-
-
 /* Intercept USER_FULL_NAME and return something that makes sense if
    pw->pw_gecos is NULL.  */
 
@@ -1158,46 +878,106 @@ android_is_special_directory (const char *name, const 
char *dir)
     }
 }
 
-/* Given a real file name, return the part that describes its asset
-   path, or NULL if it is not an asset.
+#if 0
 
-   If FILENAME contains only `/assets', return `/' to indicate the
-   root of the assets hierarchy.  */
+/* URL-encode N bytes of the specified STRING into at most N bytes of
+   BUFFER; STRING is assumed to be encoded in a `utf-8-emacs'
+   compatible coding system.  Value is the number of bytes encoded
+   (excluding the trailing null byte placed at the end of the encoded
+   text) or -1 upon failure.  */
 
-static const char *
-android_get_asset_name (const char *filename)
+static ssize_t
+android_url_encode (const char *restrict string, size_t length,
+                   char *restrict buffer, size_t n)
 {
-  const char *name;
+  int len, character;
+  size_t num_encoded;
+  char *end;
+  char format[1 + 25];
 
-  name = android_is_special_directory (filename, "/assets");
+  /* For each multibyte character... */
 
-  if (!name)
-    return NULL;
+  end = string + length;
+  num_encoded = 0;
+
+  while (string < end)
+    {
+      /* XXX: Android documentation claims that URIs is encoded
+        according to the ``Unicode'' scheme, but what this means in
+        reality is that the URI is encoded in UTF-8, and then
+        each of its bytes are encoded.  */
+      /* Find the length of the multibyte character at STRING.  */
+      len = /* multibyte_length (string, end, true, true) */ 1;
+
+      /* 0 means that STRING is not a valid multibyte string.  */
+      if (!len || string + len > end)
+       goto failure;
+
+      /* Now fetch the character and increment string.  */
+      /* character = /\* STRING_CHAR ((unsigned char *) string) *\/; */
+      character = *(unsigned char *) string;
+      string += len;
+
+      /* If CHARACTER is not a letter or an unreserved character,
+        escape it.  */
+
+      if (!((character >= 'A'
+            && character <= 'Z')
+           || (character >= 'a'
+               && character <= 'z')
+           || (character >= '0'
+               && character <= '9')
+           || character == '_'
+           || character == '-'
+           || character == '!'
+           || character == '.'
+           || character == '~'
+           || character == '\''
+           || character == '('
+           || character == ')'
+           || character == '*'))
+       {
+         len = sprintf (format, "%%%X", (unsigned int) character);
+         if (len < 0)
+           goto failure;
 
-  /* If NAME is empty, return /.  Otherwise, return the name relative
-     to /assets/.  */
+         /* See if there is enough space left to hold the encoded
+            string.  */
 
-  if (*name)
-    return name;
+         if (n < len)
+           goto failure;
 
-  return "/";
-}
+         n -= len;
+         num_encoded += len;
 
-/* Return whether or not the specified FILENAME actually resolves to a
-   content resolver URI.  */
+         /* Copy the encoded string to STRING.  */
+         memcpy (buffer, format, n);
+         buffer += len;
+       }
+      else
+       {
+         /* No more space within BUFFER.  */
+         if (!n)
+           goto failure;
 
-static bool
-android_content_name_p (const char *filename)
-{
-  /* Content URIs aren't supported before Android 4.4, so return
-     false.  */
+         /* Don't encode this ASCII character; just store it.  */
+         n--, num_encoded++;
+         *(buffer++) = character;
+       }
+    }
+
+  /* If there's no space for a trailing null byte or more bytes have
+     been encoded than representable in ssize_t, fail.  */
 
-  if (android_api_level < 19)
-    return false;
+  if (!n || num_encoded > SSIZE_MAX)
+    goto failure;
+
+  /* Store the terminating NULL byte.  */
+  *buffer = '\0';
+  return num_encoded;
 
-  return (android_is_special_directory (filename,
-                                       "/content")
-         != NULL);
+ failure:
+  return -1;
 }
 
 /* Return the content URI corresponding to a `/content' file name,
@@ -1209,10 +989,9 @@ static const char *
 android_get_content_name (const char *filename)
 {
   static char buffer[PATH_MAX + 1];
-  char *head, *token, *saveptr, *copy;
-  size_t n;
-
-  n = PATH_MAX;
+  char *head, *token, *next, *saveptr, *copy, *mark, *mark1;
+  ssize_t rc;
+  size_t n, length;
 
   /* Find the file name described if it starts with `/content'.  If
      just the directory is described, return content://.  */
@@ -1229,743 +1008,148 @@ android_get_content_name (const char *filename)
      URI.  */
 
   copy = xstrdup (filename);
-  token = saveptr = NULL;
+  mark = saveptr = NULL;
   head = stpcpy (buffer, "content:/");
 
   /* Split FILENAME by slashes.  */
 
-  while ((token = strtok_r (!token ? copy : NULL,
-                           "/", &saveptr)))
-    {
-      head = stpncpy (head, "/", n--);
-      head = stpncpy (head, token, n);
-
-      /* Check that head has not overflown the buffer.  */
-      eassert ((head - buffer) <= PATH_MAX);
-
-      n = PATH_MAX - (head - buffer);
-    }
-
-  /* Make sure the given buffer ends up NULL terminated.  */
-  buffer[PATH_MAX] = '\0';
-  xfree (copy);
-
-  return buffer;
-}
-
-/* Return whether or not the specified FILENAME is an accessible
-   content URI.  MODE specifies what to check.  */
-
-static bool
-android_check_content_access (const char *filename, int mode)
-{
-  const char *name;
-  jobject string;
-  size_t length;
-  jboolean rc;
-
-  name = android_get_content_name (filename);
-  length = strlen (name);
-
-  string = (*android_java_env)->NewByteArray (android_java_env,
-                                             length);
-  android_exception_check ();
-
-  (*android_java_env)->SetByteArrayRegion (android_java_env,
-                                          string, 0, length,
-                                          (jbyte *) name);
-  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
-                                              emacs_service,
-                                              service_class.check_content_uri,
-                                              string,
-                                              (jboolean) ((mode & R_OK)
-                                                          != 0),
-                                              (jboolean) ((mode & W_OK)
-                                                          != 0));
-  android_exception_check_1 (string);
-  ANDROID_DELETE_LOCAL_REF (string);
-
-  return rc;
-}
-
-/* Like fstat.  However, look up the asset corresponding to the file
-   descriptor.  If it exists, return the right information.  */
-
-int
-android_fstat (int fd, struct stat *statb)
-{
-  if (fd < ANDROID_MAX_ASSET_FD
-      && (android_table[fd].flags
-         & ANDROID_FD_TABLE_ENTRY_IS_VALID))
-    {
-      memcpy (statb, &android_table[fd].statb,
-             sizeof *statb);
-      return 0;
-    }
-
-  return fstat (fd, statb);
-}
-
-static int android_lookup_asset_directory_fd (int,
-                                             const char *restrict *,
-                                             const char *restrict);
-
-/* Like fstatat.  However, if dirfd is AT_FDCWD and PATHNAME is an
-   asset, find the information for the corresponding asset, and if
-   dirfd is an offset into directory_tree as returned by
-   `android_dirfd', find the information within the corresponding
-   directory tree entry.  */
-
-int
-android_fstatat (int dirfd, const char *restrict pathname,
-                struct stat *restrict statbuf, int flags)
-{
-  AAsset *asset_desc;
-  const char *asset;
-  const char *asset_dir;
-  int fd, rc;
-
-  /* Look up whether or not DIRFD belongs to an open struct
-     android_dir.  */
-
-  if (dirfd != AT_FDCWD)
-    dirfd
-      = android_lookup_asset_directory_fd (dirfd, &pathname,
-                                          pathname);
-
-  if (dirfd == AT_FDCWD
-      && asset_manager
-      && (asset = android_get_asset_name (pathname)))
-    {
-      /* Look up whether or not PATHNAME happens to be a
-        directory.  */
-      asset_dir = android_scan_directory_tree ((char *) asset,
-                                              NULL);
-
-      if (!asset_dir)
-       {
-         errno = ENOENT;
-         return -1;
-       }
-
-      if (android_is_directory (asset_dir))
-       {
-         memset (statbuf, 0, sizeof *statbuf);
-
-         /* Fill in the stat buffer.  */
-         statbuf->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
-         return 0;
-       }
-
-      /* AASSET_MODE_STREAMING is fastest here.  */
-      asset_desc = AAssetManager_open (asset_manager, asset,
-                                      AASSET_MODE_STREAMING);
-
-      if (!asset_desc)
-       return ENOENT;
-
-      memset (statbuf, 0, sizeof *statbuf);
-
-      /* Fill in the stat buffer.  */
-      statbuf->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
-      statbuf->st_size = AAsset_getLength (asset_desc);
-
-      /* Close the asset.  */
-      AAsset_close (asset_desc);
-      return 0;
-    }
-
-  if (dirfd == AT_FDCWD
-      && android_init_gui
-      && android_content_name_p (pathname))
-    {
-      /* This is actually a content:// URI.  Open that file and call
-        stat on it.  */
-
-      fd = android_open (pathname, O_RDONLY, 0);
-
-      if (fd < 0)
-       return -1;
-
-      rc = fstat (fd, statbuf);
-      android_close (fd);
-      return rc;
-    }
-
-  return fstatat (dirfd, pathname, statbuf, flags);
-}
-
-/* Return if NAME, a file name relative to the /assets directory, is
-   accessible, as long as !(amode & W_OK).  */
-
-static bool
-android_file_access_p (const char *name, int amode)
-{
-  if (!asset_manager)
-    return false;
+  token = strtok_r (copy, "/", &saveptr);
 
-  if (!(amode & W_OK))
+  while (token)
     {
-      if (!strcmp (name, "") || !strcmp (name, "/"))
-       /* /assets always exists.  */
-       return true;
-
-      /* Check if the file exists by looking in the ``directory tree''
-        asset generated during the build process.  This is used
-        instead of the AAsset functions, because the latter are
-        buggy and treat directories inconsistently.  */
-      return android_scan_directory_tree ((char *) name, NULL) != NULL;
-    }
-
-  return false;
-}
-
-/* Do the same as android_hack_asset_fd, but use an unlinked temporary
-   file to cater to old Android kernels where ashmem files are not
-   readable.  */
-
-static int
-android_hack_asset_fd_fallback (AAsset *asset)
-{
-  int fd;
-  char filename[PATH_MAX];
-  size_t size;
-  void *mem;
-
-  /* Assets must be small enough to fit in size_t, if off_t is
-     larger.  */
-  size = AAsset_getLength (asset);
-
-  /* Get an unlinked file descriptor from a file in the cache
-     directory, which is guaranteed to only be written to by Emacs.
-     Creating an ashmem file descriptor and reading from it doesn't
-     work on these old Android versions.  */
-
-  snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d",
-           android_cache_dir, getpid ());
-  fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
-            S_IRUSR | S_IWUSR);
-
-  if (fd < 0)
-    return -1;
-
-  if (unlink (filename))
-    goto fail;
-
-  if (ftruncate (fd, size))
-    goto fail;
-
-  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
-  if (mem == MAP_FAILED)
-    {
-      __android_log_print (ANDROID_LOG_ERROR, __func__,
-                          "mmap: %s", strerror (errno));
-      goto fail;
-    }
-
-  if (AAsset_read (asset, mem, size) != size)
-    {
-      /* Too little was read.  Close the file descriptor and
-        report an error.  */
-      __android_log_print (ANDROID_LOG_ERROR, __func__,
-                          "AAsset_read: %s", strerror (errno));
-      goto fail;
-    }
-
-  munmap (mem, size);
-  return fd;
-
- fail:
-  close (fd);
-  return -1;
-}
+      /* Compute the number of bytes remaining in buffer excluding a
+        trailing null byte.  */
+      n = PATH_MAX - (head - buffer);
 
-/* Pointer to the `ASharedMemory_create' function which is loaded
-   dynamically.  */
-static int (*asharedmemory_create) (const char *, size_t);
+      /* Write / to the buffer.  Return failure if there is no space
+        for it.  */
 
-/* Return whether or not shared memory file descriptors can also be
-   read from, and are thus suitable for creating asset files.
+      if (!n)
+       goto failure;
 
-   This does not work on some ancient Android systems running old
-   versions of the kernel.  */
+      *head++ = '/';
+      n--;
 
-static bool
-android_detect_ashmem (void)
-{
-  int fd, rc;
-  void *mem;
-  char test_buffer[10];
+      /* Find the next token now.  */
+      next = strtok_r (NULL, "/", &saveptr);
 
-  memcpy (test_buffer, "abcdefghi", 10);
+      /* Detect and avoid encoding an encoded URL query affixed to the
+        end of the last component within the content file name.
 
-  /* Create the file descriptor to be used for the test.  */
+         Content URIs can include a query describing parameters that
+         must be provided to the content provider.  They are separated
+         from the rest of the URI by a single question mark character,
+         which should not be encoded.
 
-  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
-     prefer that over using ASharedMemory.  */
+         However, the distinction between the separator and question
+         marks that appear inside file name components is lost when a
+         content URI is decoded into a content path.  To compensate
+         for this loss of information, Emacs assumes that the last
+         question mark is always a URI separator, and suffixes content
+         file names which contain question marks with a trailing
+         question mark.  */
 
-  if (android_api_level <= 28)
-    {
-      fd = open ("/dev/ashmem", O_RDWR);
-
-      if (fd < 0)
-       return false;
-
-      /* An empty name means the memory area will exist until the file
-        descriptor is closed, because no other process can
-        attach.  */
-      rc = ioctl (fd, ASHMEM_SET_NAME, "");
-
-      if (rc < 0)
+      if (!next)
        {
-         close (fd);
-         return false;
-       }
+         /* Find the last question mark character.  */
 
-      rc = ioctl (fd, ASHMEM_SET_SIZE, sizeof test_buffer);
+         mark1 = strchr (token, '?');
 
-      if (rc < 0)
-       {
-         close (fd);
-         return false;
-       }
-    }
-  else
-    {
-      /* On the other hand, SELinux restrictions on Android 29 and
-        later require that Emacs use a system service to obtain
-        shared memory.  Load this dynamically, as this service is not
-        available on all versions of the NDK.  */
-
-      if (!asharedmemory_create)
-       {
-         *(void **) (&asharedmemory_create)
-           = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
-
-         if (!asharedmemory_create)
+         while (mark1)
            {
-             __android_log_print (ANDROID_LOG_FATAL, __func__,
-                                  "dlsym: %s\n",
-                                  strerror (errno));
-             emacs_abort ();
+             mark = mark1;
+             mark1 = strchr (mark + 1, '?');
            }
        }
 
-      fd = asharedmemory_create ("", sizeof test_buffer);
-
-      if (fd < 0)
-       return false;
-    }
-
-  /* Now map the resource and write the test contents.  */
-
-  mem = mmap (NULL, sizeof test_buffer, PROT_WRITE,
-             MAP_SHARED, fd, 0);
-  if (mem == MAP_FAILED)
-    {
-      close (fd);
-      return false;
-    }
-
-  /* Copy over the test contents.  */
-  memcpy (mem, test_buffer, sizeof test_buffer);
-
-  /* Return anyway even if munmap fails.  */
-  munmap (mem, sizeof test_buffer);
-
-  /* Try to read the content back into test_buffer.  If this does not
-     compare equal to the original string, or the read fails, then
-     ashmem descriptors are not readable on this system.  */
-
-  if ((read (fd, test_buffer, sizeof test_buffer)
-       != sizeof test_buffer)
-      || memcmp (test_buffer, "abcdefghi", sizeof test_buffer))
-    {
-      __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "/dev/ashmem does not produce real"
-                          " temporary files on this system, so"
-                          " Emacs will fall back to creating"
-                          " unlinked temporary files.");
-      close (fd);
-      return false;
-    }
-
-  close (fd);
-  return true;
-}
-
-/* Get a file descriptor backed by a temporary in-memory file for the
-   given asset.  */
-
-static int
-android_hack_asset_fd (AAsset *asset)
-{
-  static bool ashmem_readable_p;
-  static bool ashmem_initialized;
-  int fd, rc;
-  unsigned char *mem;
-  size_t size;
-
-  /* The first time this function is called, try to determine whether
-     or not ashmem file descriptors can be read from.  */
-
-  if (!ashmem_initialized)
-    ashmem_readable_p
-      = android_detect_ashmem ();
-  ashmem_initialized = true;
-
-  /* If it isn't, fall back.  */
-
-  if (!ashmem_readable_p)
-    return android_hack_asset_fd_fallback (asset);
-
-  /* Assets must be small enough to fit in size_t, if off_t is
-     larger.  */
-  size = AAsset_getLength (asset);
-
-  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
-     prefer that over using ASharedMemory.  */
-
-  if (android_api_level <= 28)
-    {
-      fd = open ("/dev/ashmem", O_RDWR);
-
-      if (fd < 0)
-       return -1;
-
-      /* An empty name means the memory area will exist until the file
-        descriptor is closed, because no other process can
-        attach.  */
-      rc = ioctl (fd, ASHMEM_SET_NAME, "");
-
-      if (rc < 0)
+      if (mark)
        {
-         __android_log_print (ANDROID_LOG_ERROR, __func__,
-                              "ioctl ASHMEM_SET_NAME: %s",
-                              strerror (errno));
-         close (fd);
-         return -1;
-       }
+         /* First, encode the part leading to the question mark
+            character.  */
 
-      rc = ioctl (fd, ASHMEM_SET_SIZE, size);
+         rc = 0;
+         if (mark > token)
+           rc = android_url_encode (token, mark - token,
+                                    head, n + 1);
 
-      if (rc < 0)
-       {
-         __android_log_print (ANDROID_LOG_ERROR, __func__,
-                              "ioctl ASHMEM_SET_SIZE: %s",
-                              strerror (errno));
-         close (fd);
-         return -1;
-       }
+         /* If this fails, bail out.  */
 
-      if (!size)
-       return fd;
+         if (rc < 0)
+           goto failure;
 
-      /* Now map the resource.  */
-      mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
-      if (mem == MAP_FAILED)
-       {
-         __android_log_print (ANDROID_LOG_ERROR, __func__,
-                              "mmap: %s", strerror (errno));
-         close (fd);
-         return -1;
-       }
+         /* Copy mark to the file name.  */
 
-      if (AAsset_read (asset, mem, size) != size)
-       {
-         /* Too little was read.  Close the file descriptor and
-            report an error.  */
-         __android_log_print (ANDROID_LOG_ERROR, __func__,
-                              "AAsset_read: %s", strerror (errno));
-         close (fd);
-         return -1;
-       }
+         n -= rc, head += rc;
+         length = strlen (mark);
 
-      /* Return anyway even if munmap fails.  */
-      munmap (mem, size);
-      return fd;
-    }
+         if (n < length)
+           goto failure;
 
-  /* On the other hand, SELinux restrictions on Android 29 and later
-     require that Emacs use a system service to obtain shared memory.
-     Load this dynamically, as this service is not available on all
-     versions of the NDK.  */
+         strcpy (head, mark);
 
-  if (!asharedmemory_create)
-    {
-      *(void **) (&asharedmemory_create)
-       = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
-
-      if (!asharedmemory_create)
-       {
-         __android_log_print (ANDROID_LOG_FATAL, __func__,
-                              "dlsym: %s\n",
-                              strerror (errno));
-         emacs_abort ();
+         /* Now break out of the loop, since this is the last
+            component anyway.  */
+         break;
        }
-    }
-
-  fd = asharedmemory_create ("", size);
-
-  if (fd < 0)
-    {
-      __android_log_print (ANDROID_LOG_ERROR, __func__,
-                          "ASharedMemory_create: %s",
-                          strerror (errno));
-      return -1;
-    }
-
-  /* Now map the resource.  */
-  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
-  if (mem == MAP_FAILED)
-    {
-      __android_log_print (ANDROID_LOG_ERROR, __func__,
-                          "mmap: %s", strerror (errno));
-      close (fd);
-      return -1;
-    }
-
-  if (AAsset_read (asset, mem, size) != size)
-    {
-      /* Too little was read.  Close the file descriptor and
-        report an error.  */
-      __android_log_print (ANDROID_LOG_ERROR, __func__,
-                          "AAsset_read: %s", strerror (errno));
-      close (fd);
-      return -1;
-    }
-
-  /* Return anyway even if munmap fails.  */
-  munmap (mem, size);
-  return fd;
-}
-
-/* Make FD close-on-exec.  If any system call fails, do not abort, but
-   log a warning to the system log.  */
-
-static void
-android_close_on_exec (int fd)
-{
-  int flags, rc;
+      else
+       /* Now encode this file name component into the buffer.  */
+       rc = android_url_encode (token, strlen (token),
+                                head, n + 1);
 
-  flags = fcntl (fd, F_GETFD);
+      if (rc < 0)
+       goto failure;
 
-  if (flags < 0)
-    {
-      __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "fcntl: %s", strerror (errno));
-      return;
+      head += rc;
+      token = next;
     }
 
-  rc = fcntl (fd, F_SETFD, flags | O_CLOEXEC);
+  /* buffer must have been null terminated by
+     `android_url_encode'.  */
+  xfree (copy);
+  return buffer;
 
-  if (rc < 0)
-    {
-      __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "fcntl: %s", strerror (errno));
-      return;
-    }
+ failure:
+  xfree (copy);
+  return NULL;
 }
 
-/* `open' and such are modified even though they exist on Android,
-   because Emacs treats "/assets/" as a special directory that must
-   contain all assets in the application package.  */
+/* Return whether or not the specified FILENAME is an accessible
+   content URI.  MODE specifies what to check.  */
 
-int
-android_open (const char *filename, int oflag, mode_t mode)
+static bool
+android_check_content_access (const char *filename, int mode)
 {
   const char *name;
-  AAsset *asset;
-  int fd;
-  size_t length;
   jobject string;
+  size_t length;
+  jboolean rc;
 
-  if (asset_manager && (name = android_get_asset_name (filename)))
-    {
-      /* If Emacs is trying to write to the file, return NULL.  */
-
-      if (oflag & O_WRONLY || oflag & O_RDWR)
-       {
-         errno = EROFS;
-         return -1;
-       }
-
-      if (oflag & O_DIRECTORY)
-       {
-         errno = ENOTSUP;
-         return -1;
-       }
-
-      /* If given AASSET_MODE_BUFFER (which is what Emacs probably
-        does, given that a file descriptor is not always available),
-        the framework fails to uncompress the data before it returns
-        a file descriptor.  */
-      asset = AAssetManager_open (asset_manager, name,
-                                 AASSET_MODE_STREAMING);
-
-      if (!asset)
-       {
-         errno = ENOENT;
-         return -1;
-       }
-
-      /* Create a shared memory file descriptor containing the asset
-        contents.
-
-         The documentation misleads people into thinking that
-         AAsset_openFileDescriptor does precisely this.  However, it
-         instead returns an offset into any uncompressed assets in the
-         ZIP archive.  This cannot be found in its documentation.  */
-
-      fd = android_hack_asset_fd (asset);
-
-      if (fd == -1)
-       {
-         AAsset_close (asset);
-         errno = ENXIO;
-         return -1;
-       }
-
-      /* If O_CLOEXEC is specified, make the file descriptor close on
-        exec too.  */
-      if (oflag & O_CLOEXEC)
-       android_close_on_exec (fd);
-
-      if (fd >= ANDROID_MAX_ASSET_FD || fd < 0)
-       {
-         /* Too bad.  Pretend this is an out of memory error.  */
-         errno = ENOMEM;
-
-         if (fd >= 0)
-           close (fd);
-
-         fd = -1;
-       }
-      else
-       {
-         assert (!(android_table[fd].flags
-                   & ANDROID_FD_TABLE_ENTRY_IS_VALID));
-         android_table[fd].flags = ANDROID_FD_TABLE_ENTRY_IS_VALID;
-         memset (&android_table[fd].statb, 0,
-                 sizeof android_table[fd].statb);
-
-         /* Fill in some information that will be reported to
-            callers of android_fstat, among others.  */
-         android_table[fd].statb.st_mode
-           = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
-
-         /* Owned by root.  */
-         android_table[fd].statb.st_uid = 0;
-         android_table[fd].statb.st_gid = 0;
-
-         /* Size of the file.  */
-         android_table[fd].statb.st_size
-           = AAsset_getLength (asset);
-       }
-
-      AAsset_close (asset);
-      return fd;
-    }
-
-  if (android_init_gui && android_content_name_p (filename))
-    {
-      /* This is a content:// URI.  Ask the system for a descriptor to
-        that file.  */
-
-      name = android_get_content_name (filename);
-      length = strlen (name);
-
-      /* Check if the mode is valid.  */
-
-      if (oflag & O_DIRECTORY)
-       {
-         errno = ENOTSUP;
-         return -1;
-       }
-
-      /* Allocate a buffer to hold the file name.  */
-      string = (*android_java_env)->NewByteArray (android_java_env,
-                                                 length);
-      if (!string)
-       {
-         (*android_java_env)->ExceptionClear (android_java_env);
-         errno = ENOMEM;
-         return -1;
-       }
-      (*android_java_env)->SetByteArrayRegion (android_java_env,
-                                              string, 0, length,
-                                              (jbyte *) name);
-
-      /* Try to open the file descriptor.  */
-
-      fd
-       = (*android_java_env)->CallIntMethod (android_java_env,
-                                             emacs_service,
-                                             service_class.open_content_uri,
-                                             string,
-                                             (jboolean) ((mode & O_WRONLY
-                                                          || mode & O_RDWR)
-                                                         != 0),
-                                             (jboolean) !(mode & O_WRONLY),
-                                             (jboolean) ((mode & O_TRUNC)
-                                                         != 0));
-
-      if ((*android_java_env)->ExceptionCheck (android_java_env))
-       {
-         (*android_java_env)->ExceptionClear (android_java_env);
-         errno = ENOMEM;
-         ANDROID_DELETE_LOCAL_REF (string);
-         return -1;
-       }
-
-      /* If fd is -1, just assume that the file does not exist,
-        and return -1 with errno set to ENOENT.  */
-
-      if (fd == -1)
-       {
-         errno = ENOENT;
-         goto skip;
-       }
-
-      if (mode & O_CLOEXEC)
-       android_close_on_exec (fd);
-
-    skip:
-      ANDROID_DELETE_LOCAL_REF (string);
-      return fd;
-    }
-
-  return open (filename, oflag, mode);
-}
-
-/* Like close.  However, remove the file descriptor from the asset
-   table as well.  */
-
-int
-android_close (int fd)
-{
-  if (fd < ANDROID_MAX_ASSET_FD)
-    android_table[fd].flags = 0;
-
-  return close (fd);
-}
-
-/* Like fclose.  However, remove any information associated with
-   FILE's file descriptor from the asset table as well.  */
-
-int
-android_fclose (FILE *stream)
-{
-  int fd;
+  name = android_get_content_name (filename);
+  length = strlen (name);
 
-  fd = fileno (stream);
+  string = (*android_java_env)->NewByteArray (android_java_env,
+                                             length);
+  android_exception_check ();
 
-  if (fd != -1 && fd < ANDROID_MAX_ASSET_FD)
-    android_table[fd].flags = 0;
+  (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                          string, 0, length,
+                                          (jbyte *) name);
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              emacs_service,
+                                              service_class.check_content_uri,
+                                              string,
+                                              (jboolean) ((mode & R_OK)
+                                                          != 0),
+                                              (jboolean) ((mode & W_OK)
+                                                          != 0));
+  android_exception_check_1 (string);
+  ANDROID_DELETE_LOCAL_REF (string);
 
-  return fclose (stream);
+  return rc;
 }
 
+#endif /* 0 */
+
 /* Return the current user's ``home'' directory, which is actually the
    app data directory on Android.  */
 
@@ -2015,7 +1199,6 @@ android_create_lib_link (void)
   char *filename;
   char lib_directory[PATH_MAX];
   int fd;
-  struct stat statb;
 
   /* Find the directory containing the files directory.  */
   filename = dirname (android_files_dir);
@@ -2105,16 +1288,8 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
   int pipefd[2];
   pthread_t thread;
   const char *java_string;
-  AAsset *asset;
 
-  /* This may be called from multiple threads.  setEmacsParams should
-     only ever be called once.  */
-  if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_SEQ_CST))
-    {
-      ANDROID_THROW (env, "java/lang/IllegalArgumentException",
-                    "Emacs was already initialized!");
-      return;
-    }
+  /* This function should only be called from the main thread.  */
 
   android_pixel_density_x = pixel_density_x;
   android_pixel_density_y = pixel_density_y;
@@ -2124,40 +1299,6 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
                       "Initializing "PACKAGE_STRING"...\nPlease report bugs to 
"
                       PACKAGE_BUGREPORT".  Thanks.\n");
 
-  /* Set the asset manager.  */
-  asset_manager = AAssetManager_fromJava (env, local_asset_manager);
-
-  /* Initialize the directory tree.  */
-  asset = AAssetManager_open (asset_manager, "directory-tree",
-                             AASSET_MODE_BUFFER);
-
-  if (!asset)
-    {
-      __android_log_print (ANDROID_LOG_FATAL, __func__,
-                          "Failed to open directory tree");
-      emacs_abort ();
-    }
-
-  directory_tree = AAsset_getBuffer (asset);
-
-  if (!directory_tree)
-    emacs_abort ();
-
-  /* Now figure out how big the directory tree is, and compare the
-     first few bytes.  */
-  directory_tree_size = AAsset_getLength (asset);
-  if (directory_tree_size < 5
-      || memcmp (directory_tree, "EMACS", 5))
-    {
-      __android_log_print (ANDROID_LOG_FATAL, __func__,
-                          "Directory tree has bad magic");
-      emacs_abort ();
-    }
-
-  /* Hold a VM reference to the asset manager to prevent the native
-     object from being deleted.  */
-  (*env)->NewGlobalRef (env, local_asset_manager);
-
   if (emacs_service_object)
     {
       /* Create a pipe and duplicate it to stdout and stderr.  Next,
@@ -2304,7 +1445,10 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
   /* Set up events.  */
   android_init_events ();
 
-  /* OK, setup is now complete.  The caller may start the Emacs thread
+  /* Set up the Android virtual filesystem layer.  */
+  android_vfs_init (env, local_asset_manager);
+
+  /* OK, setup is now complete.  The caller may call initEmacs
      now.  */
 }
 
@@ -2405,6 +1549,33 @@ android_init_emacs_service (void)
               "Landroid/view/inputmethod/ExtractedText;I)V");
   FIND_METHOD (update_cursor_anchor_info, "updateCursorAnchorInfo",
               "(Lorg/gnu/emacs/EmacsWindow;FFFF)V");
+  FIND_METHOD (get_document_authorities, "getDocumentAuthorities",
+              "()[Ljava/lang/String;");
+  FIND_METHOD (request_directory_access, "requestDirectoryAccess",
+              "()I");
+  FIND_METHOD (get_document_trees, "getDocumentTrees",
+              "([B)[Ljava/lang/String;");
+  FIND_METHOD (document_id_from_name, "documentIdFromName",
+              "(Ljava/lang/String;[B[Ljava/lang/String;)I");
+  FIND_METHOD (get_tree_uri, "getTreeUri",
+              "(Ljava/lang/String;Ljava/lang/String;)"
+              "Ljava/lang/String;");
+  FIND_METHOD (stat_document, "statDocument",
+              "(Ljava/lang/String;Ljava/lang/String;)[J");
+  FIND_METHOD (access_document, "accessDocument",
+              "(Ljava/lang/String;Ljava/lang/String;Z)I");
+  FIND_METHOD (open_document_directory, "openDocumentDirectory",
+              "(Ljava/lang/String;Ljava/lang/String;)"
+              "Landroid/database/Cursor;");
+  FIND_METHOD (read_directory_entry, "readDirectoryEntry",
+              "(Landroid/database/Cursor;)Lorg/gnu/emacs/"
+              "EmacsDirectoryEntry;");
+  FIND_METHOD (open_document, "openDocument",
+              "(Ljava/lang/String;Ljava/lang/String;ZZ)"
+              "Landroid/os/ParcelFileDescriptor;");
+  FIND_METHOD (create_document, "createDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)Ljava/lang/String;");
 #undef FIND_METHOD
 }
 
@@ -6255,329 +5426,6 @@ android_toggle_on_screen_keyboard (android_window 
window, bool show)
 
 
 
-/* Like faccessat, except it also understands DIRFD opened using
-   android_dirfd.  */
-
-int
-android_faccessat (int dirfd, const char *pathname, int mode, int flags)
-{
-  const char *asset;
-
-  if (dirfd != AT_FDCWD)
-    dirfd
-      = android_lookup_asset_directory_fd (dirfd, &pathname,
-                                          pathname);
-
-  /* Check if pathname is actually an asset.  If that is the case,
-     simply fall back to android_file_access_p.  */
-
-  if (dirfd == AT_FDCWD
-      && asset_manager
-      && (asset = android_get_asset_name (pathname)))
-    {
-      if (android_file_access_p (asset, mode))
-       return 0;
-
-      /* Set errno to an appropriate value.  */
-      errno = ENOENT;
-      return 1;
-    }
-
-  /* Check if pathname is actually a content resolver URI.  */
-
-  if (dirfd == AT_FDCWD
-      && android_init_gui
-      && android_content_name_p (pathname))
-    {
-      if (android_check_content_access (pathname, mode))
-       return 0;
-
-      /* Set errno to an appropriate value.  */
-      errno = ENOENT;
-      return 1;
-    }
-
-#if __ANDROID_API__ >= 16
-  /* When calling `faccessat', make sure to clear the flag AT_EACCESS.
-
-     Android's faccessat simply fails if FLAGS contains AT_EACCESS, so
-     replace it with zero here.  This isn't caught at configuration-time
-     as Emacs is being cross compiled.
-
-     This takes place only when building for Android 16 and later,
-     because earlier versions use a Gnulib replacement that lacks these
-     issues.  */
-
-  return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
-#else /* __ANDROID_API__ < 16 */
-  return faccessat (dirfd, pathname, mode, flags);
-#endif /* __ANDROID_API__ >= 16 */
-}
-
-
-
-/* Directory listing emulation.  */
-
-struct android_dir
-{
-  /* The real DIR *, if it exists.  */
-  DIR *dir;
-
-  /* Otherwise, the pointer to the directory in directory_tree.  */
-  char *asset_dir;
-
-  /* And the end of the files in asset_dir.  */
-  char *asset_limit;
-
-  /* The next struct android_dir.  */
-  struct android_dir *next;
-
-  /* Path to the directory relative to /.  */
-  char *asset_file;
-
-  /* File descriptor used when asset_dir is set.  */
-  int fd;
-};
-
-/* List of all struct android_dir's corresponding to an asset
-   directory that are currently open.  */
-static struct android_dir *android_dirs;
-
-/* Like opendir.  However, return an asset directory if NAME points to
-   an asset.  */
-
-struct android_dir *
-android_opendir (const char *name)
-{
-  struct android_dir *dir;
-  char *asset_dir;
-  const char *asset_name;
-  size_t limit, length;
-
-  asset_name = android_get_asset_name (name);
-
-  /* If the asset manager exists and NAME is an asset, return an asset
-     directory.  */
-  if (asset_manager && asset_name)
-    {
-      asset_dir
-       = (char *) android_scan_directory_tree ((char *) asset_name,
-                                               &limit);
-
-      if (!asset_dir)
-       {
-         errno = ENOENT;
-         return NULL;
-       }
-
-      length = strlen (name);
-
-      dir = xmalloc (sizeof *dir);
-      dir->dir = NULL;
-      dir->asset_dir = asset_dir;
-      dir->asset_limit = (char *) directory_tree + limit;
-      dir->fd = -1;
-      dir->asset_file = xzalloc (length + 2);
-
-      /* Make sure dir->asset_file is terminated with /.  */
-      strcpy (dir->asset_file, name);
-      if (dir->asset_file[length - 1] != '/')
-       dir->asset_file[length] = '/';
-
-      /* Make sure dir->asset_limit is within bounds.  It is a limit,
-        and as such can be exactly one byte past directory_tree.  */
-      if (dir->asset_limit > directory_tree + directory_tree_size)
-       {
-         xfree (dir);
-         __android_log_print (ANDROID_LOG_VERBOSE, __func__,
-                              "Invalid dir tree, limit %zu, size %zu\n",
-                              limit, directory_tree_size);
-         dir = NULL;
-         errno = EACCES;
-       }
-
-      dir->next = android_dirs;
-      android_dirs = dir;
-
-      return dir;
-    }
-
-  /* Otherwise, open the directory normally.  */
-  dir = xmalloc (sizeof *dir);
-  dir->asset_dir = NULL;
-  dir->dir = opendir (name);
-
-  if (!dir->dir)
-    {
-      xfree (dir);
-      return NULL;
-    }
-
-  return dir;
-}
-
-/* Like dirfd.  However, value is not a real directory file descriptor
-   if DIR is an asset directory.  */
-
-int
-android_dirfd (struct android_dir *dirp)
-{
-  int fd;
-
-  if (dirp->dir)
-    return dirfd (dirp->dir);
-  else if (dirp->fd != -1)
-    return dirp->fd;
-
-  fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
-
-  /* Record this file descriptor in dirp.  */
-  dirp->fd = fd;
-  return fd;
-}
-
-/* Like readdir, except it understands asset directories.  */
-
-struct dirent *
-android_readdir (struct android_dir *dir)
-{
-  static struct dirent dirent;
-  const char *last;
-
-  if (dir->asset_dir)
-    {
-      /* There are no more files to read.  */
-      if (dir->asset_dir >= dir->asset_limit)
-       return NULL;
-
-      /* Otherwise, scan forward looking for the next NULL byte.  */
-      last = memchr (dir->asset_dir, 0,
-                    dir->asset_limit - dir->asset_dir);
-
-      /* No more NULL bytes remain.  */
-      if (!last)
-       return NULL;
-
-      /* Forward last past the NULL byte.  */
-      last++;
-
-      /* Make sure it is still within the directory tree.  */
-      if (last >= directory_tree + directory_tree_size)
-       return NULL;
-
-      /* Now, fill in the dirent with the name.  */
-      memset (&dirent, 0, sizeof dirent);
-      dirent.d_ino = 0;
-      dirent.d_off = 0;
-      dirent.d_reclen = sizeof dirent;
-
-      /* If this is not a directory, return DT_UNKNOWN.  Otherwise,
-        return DT_DIR.  */
-
-      if (android_is_directory (dir->asset_dir))
-       dirent.d_type = DT_DIR;
-      else
-       dirent.d_type = DT_UNKNOWN;
-
-      /* Note that dir->asset_dir is actually a NULL terminated
-        string.  */
-      memcpy (dirent.d_name, dir->asset_dir,
-             MIN (sizeof dirent.d_name,
-                  last - dir->asset_dir));
-      dirent.d_name[sizeof dirent.d_name - 1] = '\0';
-
-      /* Strip off the trailing slash, if any.  */
-      if (dirent.d_name[MIN (sizeof dirent.d_name,
-                            last - dir->asset_dir)
-                       - 2] == '/')
-       dirent.d_name[MIN (sizeof dirent.d_name,
-                          last - dir->asset_dir)
-                     - 2] = '\0';
-
-      /* Finally, forward dir->asset_dir to the file past last.  */
-      dir->asset_dir = ((char *) directory_tree
-                       + android_extract_long ((char *) last));
-
-      return &dirent;
-    }
-
-  return readdir (dir->dir);
-}
-
-/* Like closedir, but it also closes asset manager directories.  */
-
-void
-android_closedir (struct android_dir *dir)
-{
-  struct android_dir **next, *tem;
-
-  if (dir->dir)
-    closedir (dir->dir);
-  else
-    {
-      if (dir->fd != -1)
-       close (dir->fd);
-
-      /* Unlink this directory from the list of all asset manager
-        directories.  */
-
-      for (next = &android_dirs; (tem = *next);)
-       {
-         if (tem == dir)
-           *next = dir->next;
-         else
-           next = &(*next)->next;
-       }
-
-      /* Free the asset file name.  */
-      xfree (dir->asset_file);
-    }
-
-  /* There is no need to close anything else, as the directory tree
-     lies in statically allocated memory.  */
-
-  xfree (dir);
-}
-
-/* Subroutine used by android_fstatat and android_faccessat.  If DIRFD
-   belongs to an open asset directory and FILE is a relative file
-   name, then return AT_FDCWD and the absolute file name of the
-   directory prepended to FILE in *PATHNAME.  Else, return DIRFD.  */
-
-int
-android_lookup_asset_directory_fd (int dirfd,
-                                  const char *restrict *pathname,
-                                  const char *restrict file)
-{
-  struct android_dir *dir;
-  static char *name;
-
-  if (file[0] == '/')
-    return dirfd;
-
-  for (dir = android_dirs; dir; dir = dir->next)
-    {
-      if (dir->fd == dirfd && dirfd != -1)
-       {
-         if (name)
-           xfree (name);
-
-         /* dir->asset_file is always separator terminated.  */
-         name = xzalloc (strlen (dir->asset_file)
-                         + strlen (file) + 1);
-         strcpy (name, dir->asset_file);
-         strcpy (name + strlen (dir->asset_file),
-                 file);
-         *pathname = name;
-         return AT_FDCWD;
-       }
-    }
-
-  return dirfd;
-}
-
-
-
 /* emacs_abort implementation for Android.  This logs a stack
    trace.  */
 
@@ -6787,6 +5635,28 @@ android_exception_check_2 (jobject object, jobject 
object1)
   memory_full (0);
 }
 
+/* Like android_exception_check_2, except it takes more than two local
+   reference arguments.  */
+
+void
+android_exception_check_3 (jobject object, jobject object1,
+                          jobject object2)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  ANDROID_DELETE_LOCAL_REF (object1);
+  ANDROID_DELETE_LOCAL_REF (object2);
+  memory_full (0);
+}
+
 /* Check for JNI problems based on the value of OBJECT.
 
    Signal out of memory if OBJECT is NULL.  OBJECT1 means the
@@ -7160,7 +6030,7 @@ android_restart_emacs (void)
    turn which APIs Emacs can safely use.  */
 
 int
-android_get_current_api_level (void)
+(android_get_current_api_level) (void)
 {
   return android_api_level;
 }
@@ -7206,30 +6076,25 @@ android_query_battery (struct android_battery_state 
*status)
   return 0;
 }
 
-/* Display a small momentary notification on screen containing
-   TEXT, which must be in the modified UTF encoding used by the
-   JVM.  */
+/* Display a file panel and grant Emacs access to the SAF directory
+   within it.  Value is 1 upon failure and 0 upon success (which only
+   indicates that the panel has been displayed successfully; the panel
+   may still be dismissed without a file being selected.)  */
 
-void
-android_display_toast (const char *text)
+int
+android_request_directory_access (void)
 {
-  jstring string;
+  jint rc;
+  jmethodID method;
 
-  /* Make the string.  */
-  string = (*android_java_env)->NewStringUTF (android_java_env,
-                                             text);
+  method = service_class.request_directory_access;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method);
   android_exception_check ();
 
-  /* Display the toast.  */
-  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
-                                                emacs_service,
-                                                service_class.class,
-                                                service_class.display_toast,
-                                                string);
-  android_exception_check_1 (string);
-
-  /* Delete the local reference to the string.  */
-  ANDROID_DELETE_LOCAL_REF (string);
+  return rc;
 }
 
 
@@ -7709,156 +6574,6 @@ android_set_fullscreen (android_window window, bool 
fullscreen)
 
 
 
-/* External asset management interface.  By using functions here
-   to read and write from files, Emacs can avoid opening a
-   shared memory file descriptor for each ``asset'' file.  */
-
-/* Like android_open.  However, return a structure that can
-   either directly hold an AAsset or a file descriptor.
-
-   Value is the structure upon success.  Upon failure, value
-   consists of an uninitialized file descriptor, but its asset
-   field is set to -1, and errno is set accordingly.  */
-
-struct android_fd_or_asset
-android_open_asset (const char *filename, int oflag, mode_t mode)
-{
-  const char *name;
-  struct android_fd_or_asset fd;
-  AAsset *asset;
-
-  /* Initialize FD by setting its asset to an invalid
-     pointer.  */
-  fd.asset = (void *) -1;
-
-  /* See if this is an asset.  */
-
-  if (asset_manager && (name = android_get_asset_name (filename)))
-    {
-      /* Return failure for unsupported flags.  */
-
-      if (oflag & O_WRONLY || oflag & O_RDWR)
-       {
-         errno = EROFS;
-         return fd;
-       }
-
-      if (oflag & O_DIRECTORY)
-       {
-         errno = ENOTSUP;
-         return fd;
-       }
-
-      /* Now try to open the asset.  */
-      asset = AAssetManager_open (asset_manager, name,
-                                 AASSET_MODE_STREAMING);
-
-      if (!asset)
-       {
-         errno = ENOENT;
-         return fd;
-       }
-
-      /* Return the asset.  */
-      fd.asset = asset;
-      return fd;
-    }
-
-  /* If the file is not an asset, fall back to android_open and
-     get a regular file descriptor.  */
-
-  fd.fd = android_open (filename, oflag, mode);
-  if (fd.fd < 0)
-    return fd;
-
-  /* Set fd.asset to NULL, signifying that it is a file
-     descriptor.  */
-  fd.asset = NULL;
-  return fd;
-}
-
-/* Like android_close.  However, it takes a ``file descriptor''
-   opened using android_open_asset.  */
-
-int
-android_close_asset (struct android_fd_or_asset asset)
-{
-  if (!asset.asset)
-    return android_close (asset.fd);
-
-  AAsset_close (asset.asset);
-  return 0;
-}
-
-/* Like `emacs_read_quit'.  However, it handles file descriptors
-   opened using `android_open_asset' as well.  */
-
-ssize_t
-android_asset_read_quit (struct android_fd_or_asset asset,
-                        void *buffer, size_t size)
-{
-  if (!asset.asset)
-    return emacs_read_quit (asset.fd, buffer, size);
-
-  /* It doesn't seem possible to quit from inside AAsset_read,
-     sadly.  */
-  return AAsset_read (asset.asset, buffer, size);
-}
-
-/* Like `read'.  However, it handles file descriptors opened
-   using `android_open_asset' as well.  */
-
-ssize_t
-android_asset_read (struct android_fd_or_asset asset,
-                   void *buffer, size_t size)
-{
-  if (!asset.asset)
-    return read (asset.fd, buffer, size);
-
-  /* It doesn't seem possible to quit from inside AAsset_read,
-     sadly.  */
-  return AAsset_read (asset.asset, buffer, size);
-}
-
-/* Like `lseek', but it handles ``file descriptors'' opened with
-   android_open_asset.  */
-
-off_t
-android_asset_lseek (struct android_fd_or_asset asset, off_t off,
-                    int whence)
-{
-  if (!asset.asset)
-    return lseek (asset.fd, off, whence);
-
-  return AAsset_seek (asset.asset, off, whence);
-}
-
-/* Like `fstat'.  */
-
-int
-android_asset_fstat (struct android_fd_or_asset asset,
-                    struct stat *statb)
-{
-  if (!asset.asset)
-    return fstat (asset.fd, statb);
-
-  /* Clear statb.  */
-  memset (statb, 0, sizeof *statb);
-
-  /* Set the mode.  */
-  statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
-
-  /* Owned by root.  */
-  statb->st_uid = 0;
-  statb->st_gid = 0;
-
-  /* Size of the file.  */
-  statb->st_size = AAsset_getLength (asset.asset);
-  return 0;
-}
-
-
-
 /* Window cursor support.  */
 
 android_cursor
@@ -8088,4 +6803,4 @@ android_project_image_nearest (struct android_image 
*image,
   emacs_abort ();
 }
 
-#endif
+#endif /* !ANDROID_STUBIFY */
diff --git a/src/android.h b/src/android.h
index 2323690fa25..94a3ad46e74 100644
--- a/src/android.h
+++ b/src/android.h
@@ -36,26 +36,46 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include "androidgui.h"
 #include "lisp.h"
-#endif
+#endif /* ANDROID_STUBIFY */
 
 extern bool android_init_gui;
 
 #ifndef ANDROID_STUBIFY
 
+extern char *android_cache_dir;
+
 extern int android_emacs_init (int, char **, char *);
 extern int android_select (int, fd_set *, fd_set *, fd_set *,
                           struct timespec *);
-
-extern int android_open (const char *, int, mode_t);
 extern char *android_user_full_name (struct passwd *);
+
+
+
+/* File I/O operations.  Many of these are defined in
+   androidvfs.c.  */
+
 extern const char *android_is_special_directory (const char *, const char *);
+extern const char *android_get_home_directory (void);
+
+extern void android_vfs_init (JNIEnv *, jobject);
+
+extern int android_open (const char *, int, mode_t);
 extern int android_fstat (int, struct stat *);
 extern int android_fstatat (int, const char *restrict,
                            struct stat *restrict, int);
 extern int android_faccessat (int, const char *, int, int);
 extern int android_close (int);
+extern FILE *android_fdopen (int, const char *);
 extern int android_fclose (FILE *);
-extern const char *android_get_home_directory (void);
+extern int android_unlink (const char *);
+extern int android_symlink (const char *, const char *);
+extern int android_rmdir (const char *);
+extern int android_mkdir (const char *, mode_t);
+extern int android_renameat_noreplace (int, const char *,
+                                      int, const char *);
+extern int android_rename (const char *, const char *);
+
+
 
 extern double android_pixel_density_x, android_pixel_density_y;
 extern double android_scaled_pixel_density;
@@ -89,6 +109,7 @@ extern jstring android_build_jstring (const char *);
 extern void android_exception_check (void);
 extern void android_exception_check_1 (jobject);
 extern void android_exception_check_2 (jobject, jobject);
+extern void android_exception_check_3 (jobject, jobject, jobject);
 extern void android_exception_check_nonnull (void *, jobject);
 extern void android_exception_check_nonnull_1 (void *, jobject, jobject);
 
@@ -96,18 +117,29 @@ extern void android_get_keysym_name (int, char *, size_t);
 extern void android_wait_event (void);
 extern void android_toggle_on_screen_keyboard (android_window, bool);
 extern _Noreturn void android_restart_emacs (void);
-extern int android_get_current_api_level (void);
+extern int android_request_directory_access (void);
+extern int android_get_current_api_level (void)
+  __attribute__ ((pure));
+
+/* Define `android_get_current_api_level' to a macro that the compiler
+   knows will always return at least __ANDROID_API__.  */
+
+#define android_get_current_api_level()                                \
+  ({ int value;                                                        \
+                                                               \
+     value = (android_get_current_api_level) ();               \
+     eassume (value >= __ANDROID_API__); value; })
 
 
 
 /* Directory listing emulation.  */
 
-struct android_dir;
+struct android_vdir;
 
-extern struct android_dir *android_opendir (const char *);
-extern int android_dirfd (struct android_dir *);
-extern struct dirent *android_readdir (struct android_dir *);
-extern void android_closedir (struct android_dir *);
+extern struct android_vdir *android_opendir (const char *);
+extern int android_dirfd (struct android_vdir *);
+extern struct dirent *android_readdir (struct android_vdir *);
+extern void android_closedir (struct android_vdir *);
 
 
 
@@ -211,8 +243,52 @@ extern int android_rewrite_spawn_argv (const char ***);
 #ifndef ANDROID_STUBIFY
 #include <jni.h>
 
+struct android_emacs_service
+{
+  jclass class;
+  jmethodID fill_rectangle;
+  jmethodID fill_polygon;
+  jmethodID draw_rectangle;
+  jmethodID draw_line;
+  jmethodID draw_point;
+  jmethodID clear_window;
+  jmethodID clear_area;
+  jmethodID ring_bell;
+  jmethodID query_tree;
+  jmethodID get_screen_width;
+  jmethodID get_screen_height;
+  jmethodID detect_mouse;
+  jmethodID name_keysym;
+  jmethodID browse_url;
+  jmethodID restart_emacs;
+  jmethodID update_ic;
+  jmethodID reset_ic;
+  jmethodID open_content_uri;
+  jmethodID check_content_uri;
+  jmethodID query_battery;
+  jmethodID update_extracted_text;
+  jmethodID update_cursor_anchor_info;
+  jmethodID get_document_authorities;
+  jmethodID request_directory_access;
+  jmethodID get_document_trees;
+  jmethodID document_id_from_name;
+  jmethodID get_tree_uri;
+  jmethodID stat_document;
+  jmethodID access_document;
+  jmethodID open_document_directory;
+  jmethodID read_directory_entry;
+  jmethodID open_document;
+  jmethodID create_document;
+};
+
 extern JNIEnv *android_java_env;
 
+/* The EmacsService object.  */
+extern jobject emacs_service;
+
+/* Various methods associated with the EmacsService.  */
+extern struct android_emacs_service service_class;
+
 #define ANDROID_DELETE_LOCAL_REF(ref)                          \
   ((*android_java_env)->DeleteLocalRef (android_java_env,      \
                                        (ref)))
diff --git a/src/androidfns.c b/src/androidfns.c
index dcc9ab83427..0270f58b6b9 100644
--- a/src/androidfns.c
+++ b/src/androidfns.c
@@ -3036,6 +3036,32 @@ for more details about these values.  */)
 
 
 
+/* Directory access requests.  */
+
+DEFUN ("android-request-directory-access", Fandroid_request_directory_access,
+       Sandroid_request_directory_access, 0, 0, "",
+       doc: /* Request access to a directory within external storage.
+On Android 5.0 and later, prompt for a directory within external or
+application storage, and grant access to it; some of these directories
+cannot be accessed through the regular `/sdcard' filesystem.
+
+If access to the directory is granted, it will eventually appear
+within the directory `/content/storage'.  */)
+  (void)
+{
+  if (android_get_current_api_level () < 21)
+    error ("Emacs can only access application storage on"
+          " Android 5.0 and later");
+
+  if (!android_init_gui)
+    return Qnil;
+
+  android_request_directory_access ();
+  return Qnil;
+}
+
+
+
 /* Miscellaneous input method related stuff.  */
 
 /* Report X, Y, by the phys cursor width and height as the cursor
@@ -3062,7 +3088,7 @@ android_set_preeditarea (struct window *w, int x, int y)
                                     y + w->phys_cursor_height);
 }
 
-#endif
+#endif /* !ANDROID_STUBIFY */
 
 
 
@@ -3220,6 +3246,7 @@ This option has no effect on Android 9 and earlier.  */);
   defsubr (&Sx_server_version);
 #ifndef ANDROID_STUBIFY
   defsubr (&Sandroid_query_battery);
+  defsubr (&Sandroid_request_directory_access);
 
   tip_timer = Qnil;
   staticpro (&tip_timer);
@@ -3235,5 +3262,5 @@ This option has no effect on Android 9 and earlier.  */);
   staticpro (&tip_dx);
   tip_dy = Qnil;
   staticpro (&tip_dy);
-#endif
+#endif /* !ANDROID_STUBIFY */
 }
diff --git a/src/androidvfs.c b/src/androidvfs.c
new file mode 100644
index 00000000000..a32471d250e
--- /dev/null
+++ b/src/androidvfs.c
@@ -0,0 +1,6137 @@
+/* Android virtual file-system support for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <errno.h>
+#include <minmax.h>
+#include <string.h>
+
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <linux/ashmem.h>
+
+#include "android.h"
+#include "systime.h"
+
+#if __ANDROID_API__ >= 9
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#else /* __ANDROID_API__ < 9 */
+#include "android-asset.h"
+#endif /* __ANDROID_API__ >= 9 */
+
+#include <android/log.h>
+
+/* This file implements support for the various special-purpose
+   directories found on Android systems.  Such directories are not
+   mounted in the Unix virtual file-system, instead being accessible
+   through special API calls; Emacs pretends they are mounted at
+   specific folders within the root directory.
+
+   There are presently two directories: /assets, granting access to
+   asset files stored within the APK, and /content, providing direct
+   access to content URIs (in Android 4.4 and later) and content
+   directory trees (in Android 5.0 and later.)
+
+   This file implements substitutes for the C library `open', `fstat',
+   `close', `fclose', `unlink', `symlink', `rmdir', `rename', `stat'
+   system call wrappers, which process file names through ``VFS
+   nodes'' representing conceptual files, that are really no more than
+   tables of function pointers.
+
+   The primary function of a node is to `name' children.  This takes a
+   relative file name and returns a second VFS node tied to a child
+   that exists within this node (or a child thereof, ad infinite.)
+
+   Other functions are also defined: functions to open file
+   descriptors, and substitutes for each of the C library system call
+   wrappers replaced.  Each of these functions accepts two vnodes, and
+   is expected to otherwise behave like the C library system calls
+   replaced.
+
+   When the virtual file system needs to locate the vnode associated
+   with a file name, it starts searching at the root vnode.  Its
+   `name' function then creates vnodes as appropriate for the
+   components of the file name, which repeats recursively until the
+   vnode designating the file name is found.
+
+   The substitute functions defined have two caveats, which however
+   don't prove problematic in an Emacs context: the first is that the
+   treatment of `..' is inconsistent with Unix, and has not really
+   been tested, while the second is that errno values do not always
+   conform to what the corresponding Unix system calls may return.  */
+
+/* Structure describing an array of VFS operations.  */
+
+struct android_vnode;
+
+struct android_vdir
+{
+  /* Return a `struct dirent' describing the next file in this
+     directory stream, or NULL if the stream has reached its end.  */
+  struct dirent *(*readdir) (struct android_vdir *);
+
+  /* Close and release all resources allocated for this directory
+     stream.  */
+  void (*closedir) (struct android_vdir *);
+
+  /* Return a ``file descriptor'' tied to this directory stream.  */
+  int (*dirfd) (struct android_vdir *);
+};
+
+struct android_vops
+{
+  /* Name a child of the given VFS node, which should be a
+     directory.
+
+     LENGTH should be the length of NAME, excluding that of any
+     trailing NULL byte.
+
+     NAME should be a normalized and NULL-terminated relative file
+     name; it may contain a leading separator characters, but no
+     consecutive ones.
+
+     If NAME is empty, create another VFS node designating the same
+     file instead.
+
+     NAME should also be located within writable storage; it may be
+     overwritten as the vnode sees fit.
+
+     Value is a VFS node corresponding to the child, or NULL upon
+     failure.
+
+     A VFS node may be returned even if NAME does not exist, the
+     expectation being that either a later filesystem operation will
+     fail, or will create the file.  */
+  struct android_vnode *(*name) (struct android_vnode *, char *, size_t);
+
+  /* Open the specified VNODE, returning either a file descriptor or
+     an asset file descriptor.
+
+     FLAGS and MODE mean the same as they do to the Unix `open' system
+     call.
+
+     ASSET_P stipulates if an asset file descriptor may be returned;
+     if true, *ASSET may be set to an asset file descriptor.
+
+     If an asset file descriptor is unavailable or ASSET_P is false,
+     *FD will be set to a file descriptor.
+
+     If the vnode cannot be opened, value is -1 with errno set
+     accordingly.  Otherwise, value is 0 if a file descriptor was
+     returned, and 1 if an asset file descriptor was returned.  */
+  int (*open) (struct android_vnode *, int, mode_t, bool,
+              int *, AAsset **);
+
+  /* Close the specified VNODE, releasing all of its resources.
+     Save errno before making system calls that may set it, and
+     restore it to its original value before returning.
+
+     This is unrelated to `android_close', which primarily releases on
+     stat buffers linked to file or asset file descriptors.  */
+  void (*close) (struct android_vnode *);
+
+  /* Unlink the file and the specified VNODE.  Value and errno are the
+     same as Unix `unlink'.  */
+  int (*unlink) (struct android_vnode *);
+
+  /* Create a symlink from the specified VNODE to the target TARGET.
+     Value and errno are the same as `symlink' on Linux (which notably
+     means that errno is set to EPERM if VNODE doesn't support
+     symlinks.)  */
+  int (*symlink) (const char *, struct android_vnode *);
+
+  /* Remove VNODE from its parent directory.  VNODE must be an empty
+     directory.  Value and errno are the same as Unix `rmdir'.  */
+  int (*rmdir) (struct android_vnode *);
+
+  /* Move the file designated by SRC to DST, overwriting DST if
+     KEEP_EXISTING is false.
+
+     If KEEP_EXISTING is true and DST already exists, value is -1 with
+     errno set to EEXIST.
+
+     If VNODE does not natively support checking for a preexisting DST
+     and KEEP_EXISTING is true, value is -1 with errno set to ENOSYS.
+
+     Value is otherwise the same as `rename'.  */
+  int (*rename) (struct android_vnode *, struct android_vnode *, bool);
+
+  /* Return statistics for the specified VNODE.
+     Value and errno are the same as with Unix `stat'.  */
+  int (*stat) (struct android_vnode *, struct stat *);
+
+  /* Return whether or not VNODE is accessible.
+     Value, errno and MODE are the same as with Unix `access'.  */
+  int (*access) (struct android_vnode *, int);
+
+  /* Make a directory designated by VNODE, like Unix `mkdir'.  */
+  int (*mkdir) (struct android_vnode *, mode_t);
+
+  /* Open the specified VNODE as a directory.
+     Value is a ``directory handle'', or NULL upon failure.  */
+  struct android_vdir *(*opendir) (struct android_vnode *);
+};
+
+struct android_vnode
+{
+  /* Operations associated with this vnode.  */
+  struct android_vops *ops;
+
+  /* Type of this vnode and its flags.  */
+  short type, flags;
+};
+
+/* Structure describing a special named vnode relative to the root
+   vnode, or another directory vnode.  */
+
+struct android_special_vnode
+{
+  /* The name of the special file.  */
+  const char *name;
+
+  /* The length of that name.  */
+  size_t length;
+
+  /* Function called to create the initial vnode from the rest of the
+     component.  */
+  struct android_vnode *(*initial) (char *, size_t);
+};
+
+enum android_vnode_type
+  {
+    ANDROID_VNODE_UNIX,
+    ANDROID_VNODE_AFS,
+    ANDROID_VNODE_CONTENT,
+    ANDROID_VNODE_CONTENT_AUTHORITY,
+    ANDROID_VNODE_SAF_ROOT,
+    ANDROID_VNODE_SAF_TREE,
+    ANDROID_VNODE_SAF_FILE,
+    ANDROID_VNODE_SAF_NEW,
+  };
+
+
+
+/* Structure describing the android.database.Cursor class.  */
+
+struct android_cursor_class
+{
+  jclass class;
+  jmethodID close;
+};
+
+/* Structure describing the EmacsDirectoryEntry class.  */
+
+struct emacs_directory_entry_class
+{
+  jclass class;
+  jfieldID d_type;
+  jfieldID d_name;
+};
+
+/* Structure describing the android.os.ParcelFileDescriptor class used
+   to wrap file descriptors sent over IPC.  */
+
+struct android_parcel_file_descriptor_class
+{
+  jclass class;
+  jmethodID close;
+  jmethodID get_fd;
+  jmethodID detach_fd;
+};
+
+/* The java.lang.String class.  */
+static jclass java_string_class;
+
+/* Fields and methods associated with the Cursor class.  */
+static struct android_cursor_class cursor_class;
+
+/* Fields and methods associated with the EmacsDirectoryEntry
+   class.  */
+static struct emacs_directory_entry_class entry_class;
+
+/* Fields and methods associated with the ParcelFileDescriptor
+   class.  */
+static struct android_parcel_file_descriptor_class fd_class;
+
+/* Initialize `cursor_class' using the given JNI environment ENV.
+   Calling this function is not necessary on Android 4.4 and
+   earlier.  */
+
+static void
+android_init_cursor_class (JNIEnv *env)
+{
+  jclass old;
+
+  cursor_class.class
+    = (*env)->FindClass (env, "android/database/Cursor");
+  eassert (cursor_class.class);
+
+  old = cursor_class.class;
+  cursor_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!cursor_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)           \
+  cursor_class.c_name                                  \
+    = (*env)->GetMethodID (env, cursor_class.class,    \
+                          name, signature);            \
+  assert (cursor_class.c_name);
+  FIND_METHOD (close, "close", "()V");
+#undef FIND_METHOD
+}
+
+/* Initialize `entry_class' using the given JNI environment ENV.
+   Calling this function is not necessary on Android 4.4 and
+   earlier.  */
+
+static void
+android_init_entry_class (JNIEnv *env)
+{
+  jclass old;
+
+  entry_class.class
+    = (*env)->FindClass (env, "org/gnu/emacs/EmacsDirectoryEntry");
+  eassert (entry_class.class);
+
+  old = entry_class.class;
+  entry_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!entry_class.class)
+    emacs_abort ();
+
+  entry_class.d_type = (*env)->GetFieldID (env, entry_class.class,
+                                          "d_type", "I");
+  entry_class.d_name = (*env)->GetFieldID (env, entry_class.class,
+                                          "d_name",
+                                          "Ljava/lang/String;");
+  assert (entry_class.d_type && entry_class.d_name);
+}
+
+
+/* Initialize `fd_class' using the given JNI environment ENV.  Calling
+   this function is not necessary on Android 4.4 and earlier.  */
+
+static void
+android_init_fd_class (JNIEnv *env)
+{
+  jclass old;
+
+  fd_class.class
+    = (*env)->FindClass (env, "android/os/ParcelFileDescriptor");
+  eassert (fd_class.class);
+
+  old = fd_class.class;
+  fd_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!fd_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)           \
+  fd_class.c_name                                      \
+    = (*env)->GetMethodID (env, fd_class.class,                \
+                          name, signature);            \
+  assert (fd_class.c_name);
+  FIND_METHOD (close, "close", "()V");
+  FIND_METHOD (get_fd, "getFd", "()I");
+  FIND_METHOD (detach_fd, "detachFd", "()I");
+#undef FIND_METHOD
+}
+
+
+
+/* Delete redundant instances of `.' and `..' from NAME in-place.
+   NAME must be *LENGTH long, excluding a mandatory trailing NULL
+   byte.
+
+   Transform each directory component in NAME to avoid instances
+   of the `.' and `..' directories.  For example, turn:
+
+     a/../b/c/.
+
+   into
+
+     b/c/
+
+   and return NULL, writing the new length of NAME into *LENGTH.
+
+   If there are more `..' components in NAME than there are normal
+   file name components, return NAME incremented to the position after
+   the first `..' component that cannot be transformed.  For example,
+   if NAME is
+
+     a/../../a
+
+   value will be
+
+     a
+
+   If NAME is a directory separator and LENGTH is 1, return without
+   modifying NAME.  In any other case, omit any leading directory
+   separator when writing to NAME.  This is useful when a vnode that
+   can only be opened as a directory is desired, as this status is
+   made clear by suffixing the file name with a trailing
+   directory separator.  */
+
+static char *
+android_vfs_canonicalize_name (char *name, size_t *length)
+{
+  size_t nellipsis, i;
+  char *last_component, *prev_component, *fill, *orig_name;
+  size_t size;
+
+  /* Special case described in the last paragraph of the comment
+     above.  */
+
+  size = *length;
+  orig_name = name;
+
+  if (*name == '/' && size == 1)
+    return NULL;
+  else if (*name == '/')
+    size -= 1;
+
+  nellipsis = 0; /* Number of ellipsis encountered within the current
+                   file name component, or -1.  */
+  prev_component = NULL; /* Pointer to the separator character of
+                           the component immediately before the
+                           component currently being written.  */
+  last_component = name; /* Pointer to the separator character of
+                           the component currently being read.  */
+  fill = name; /* Pointer to the next character that will be written
+                 within NAME.  */
+
+  /* Adjust name to skip the leading directory separator.  But only
+     after fill is set.  */
+  if (*name == '/')
+    name++;
+
+  for (i = 0; i < size; ++i)
+    {
+      switch (name[i])
+       {
+       case '/':
+         /* See if the previous component was `..' or `.'.
+
+            If it is .., and if no previous directory separator was
+            encountered, return or look up a vnode representing the
+            parent.  */
+
+         if (nellipsis == 2)
+           {
+             /* .. */
+
+             if (!prev_component)
+               goto parent_vnode;
+
+             /* Return to the last component.  */
+             fill = prev_component;
+
+             /* Restore last_component to prev_component, and
+                prev_component back to the component before that.  */
+             last_component = prev_component;
+
+             if (last_component != name)
+               prev_component = memrchr (name, '/',
+                                         last_component - name - 1);
+             else
+               prev_component = NULL;
+
+             /* prev_component may now be NULL.  If last_component is
+                the same as NAME, then fill has really been returned
+                to the beginning of the string, so leave it be.  But
+                if it's something else, then it must be the first
+                separator character in the string, so set
+                prev_component to NAME itself.  */
+
+             if (!prev_component && last_component != name)
+               prev_component = name;
+           }
+         else if (nellipsis == 1)
+           /* If it's ., return to this component.  */
+           fill = last_component;
+         else
+           {
+             /* Record the position of the last directory separator,
+                so NAME can be overwritten from there onwards if `..'
+                or `.' are encountered.  */
+             prev_component = last_component;
+             last_component = fill;
+           }
+
+         /* Allow tracking ellipses again.  */
+         nellipsis = 0;
+         break;
+
+       case '.':
+         if (nellipsis != -1)
+           nellipsis++;
+         break;
+
+       default:
+         nellipsis = -1;
+         break;
+       }
+
+      /* Now copy this character over from NAME.  */
+      *fill++ = name[i];
+    }
+
+  /* See if the previous component was `..' or `.'.
+
+     If it is .., and if no previous directory separator was
+     encountered, return or look up a vnode representing the
+     parent.  */
+
+  if (nellipsis == 2)
+    {
+      /* .. */
+
+      if (!prev_component)
+       /* Look up the rest of the vnode in its parent.  */
+       goto parent_vnode;
+
+      /* Return to the last component.  */
+      fill = prev_component;
+      nellipsis = -2;
+    }
+  else if (nellipsis == 1)
+    {
+      /* If it's ., return to this component.  */
+      fill = last_component;
+      nellipsis = -2;
+    }
+
+  /* Now, if there's enough room and an ellipsis file name was the
+     last component of END, append a trailing `/' before NULL
+     terminating it, indicating that the file name must be a
+     directory.  */
+
+  if (fill + 1 < name + size && nellipsis == -2)
+    *fill++ = '/';
+
+  /* NULL terminate fill.  */
+  *fill = '\0';
+  *length = fill - orig_name;
+  return NULL;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+  return name + i;
+}
+
+
+
+/* Unix vnode implementation.  These VFS nodes directly wrap around
+   the Unix filesystem, with the exception of the root vnode.  */
+
+struct android_unix_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* Length of the name without a trailing null byte.  */
+  size_t name_length;
+
+  /* Name of the vnode.  */
+  char *name;
+};
+
+struct android_unix_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The directory stream.  */
+  DIR *directory;
+};
+
+/* The vnode representing the root filesystem.  */
+static struct android_unix_vnode root_vnode;
+
+static struct android_vnode *android_unix_name (struct android_vnode *,
+                                               char *, size_t);
+static int android_unix_open (struct android_vnode *, int,
+                             mode_t, bool, int *, AAsset **);
+static void android_unix_close (struct android_vnode *);
+static int android_unix_unlink (struct android_vnode *);
+static int android_unix_symlink (const char *, struct android_vnode *);
+static int android_unix_rmdir (struct android_vnode *);
+static int android_unix_rename (struct android_vnode *,
+                               struct android_vnode *, bool);
+static int android_unix_stat (struct android_vnode *, struct stat *);
+static int android_unix_access (struct android_vnode *, int);
+static int android_unix_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_unix_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with Unix filesystem VFS
+   nodes.  */
+
+static struct android_vops unix_vfs_ops =
+  {
+    android_unix_name,
+    android_unix_open,
+    android_unix_close,
+    android_unix_unlink,
+    android_unix_symlink,
+    android_unix_rmdir,
+    android_unix_rename,
+    android_unix_stat,
+    android_unix_access,
+    android_unix_mkdir,
+    android_unix_opendir,
+  };
+
+static struct android_vnode *
+android_unix_name (struct android_vnode *vnode, char *name,
+                  size_t length)
+{
+  struct android_unix_vnode *vp, *input, temp;
+  char *fill, *remainder;
+  size_t j;
+
+  /* Canonicalize NAME.  */
+  input = (struct android_unix_vnode *) vnode;
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the parent
+     vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* Create a new unix vnode.  */
+  vp = xmalloc (sizeof *vp);
+
+  /* If name is empty, duplicate the current vnode.  */
+
+  if (length < 1)
+    {
+      memcpy (vp, vnode, sizeof *vp);
+      vp->name = xstrdup (vp->name);
+      return &vp->vnode;
+    }
+
+  /* Otherwise, fill in the vnode.  */
+
+  vp->vnode.ops = &unix_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_UNIX;
+  vp->vnode.flags = 0;
+
+  /* Generate the new name of the vnode.  Remove any trailing slash
+     from vp->name.  */
+
+  vp->name_length = input->name_length + length;
+  vp->name = xmalloc (vp->name_length + 2);
+
+  /* Copy the parent name over.  */
+  fill = mempcpy (vp->name, input->name, input->name_length);
+
+  /* Check if it contains a trailing slash.  input->name cannot be
+     empty, as the root vnode's name is `/'.  */
+
+  if (fill[-1] != '/' && *name != '/')
+    /* If not, append a trailing slash and adjust vp->name_length
+       correspondingly.  */
+    *fill++ = '/', vp->name_length++;
+  else if (fill[-1] == '/' && *name == '/')
+    /* If name has a leading slash and fill does too, move fill
+       backwards so that name's slash will override that of fill.  */
+    fill--, vp->name_length--;
+
+  /* Now copy NAME.  */
+  fill = mempcpy (fill, name, length);
+
+  /* And NULL terminate fill.  */
+  *fill = '\0';
+  return &vp->vnode;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (input->name_length == 1)
+    /* This is the vnode representing the root directory; just look
+       within itself... */
+    vnode = &root_vnode.vnode;
+  else
+    {
+      /* Create a temporary asset vnode within the parent and use it
+         instead.  First, establish the length of vp->name before its
+         last component.  */
+
+      for (j = input->name_length - 1; j; --j)
+       {
+         if (input->name[j - 1] == '/')
+           break;
+       }
+
+      /* There must be at least one leading directory separator in an
+        asset vnode's `name' field.  */
+
+      if (!j)
+       abort ();
+
+      /* j is now the length of the string minus the size of its last
+        component.  Create a temporary vnode with that as its
+        name.  */
+
+      temp.vnode.ops = &unix_vfs_ops;
+      temp.vnode.type = ANDROID_VNODE_UNIX;
+      temp.vnode.flags = 0;
+      temp.name_length = j;
+      temp.name = xmalloc (j + 1);
+      fill = mempcpy (temp.name, input->name, j);
+      *fill = '\0';
+
+      /* Search for the remainder of NAME relative to its parent.  */
+      vnode = android_unix_name (&temp.vnode, remainder,
+                                strlen (remainder));
+      xfree (temp.name);
+      return vnode;
+    }
+
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+/* Create a Unix vnode representing the given file NAME.  Use this
+   function to create vnodes that aren't rooted in the root VFS
+   node.  */
+
+static struct android_vnode *
+android_unix_vnode (const char *name)
+{
+  struct android_unix_vnode *vp;
+
+  vp = xmalloc (sizeof *vp);
+  vp->vnode.ops = &unix_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_UNIX;
+  vp->vnode.flags = 0;
+  vp->name_length = strlen (name);
+  vp->name = xstrdup (name);
+  return &vp->vnode;
+}
+
+static int
+android_unix_open (struct android_vnode *vnode, int flags,
+                  mode_t mode, bool asset_p, int *fd,
+                  AAsset **asset)
+{
+  struct android_unix_vnode *vp;
+  int fds;
+
+  vp = (struct android_unix_vnode *) vnode;
+  fds = open (vp->name, flags, mode);
+
+  if (fds < 0)
+    return -1;
+
+  *fd = fds;
+  return 0;
+}
+
+static void
+android_unix_close (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+  int save_errno;
+
+  save_errno = errno;
+  vp = (struct android_unix_vnode *) vnode;
+  xfree (vp->name);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_unix_unlink (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return unlink (vp->name);
+}
+
+static int
+android_unix_symlink (const char *target, struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return symlink (target, vp->name);
+}
+
+static int
+android_unix_rmdir (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return rmdir (vp->name);
+}
+
+static int
+android_unix_rename (struct android_vnode *src,
+                    struct android_vnode *dst,
+                    bool keep_existing)
+{
+  struct android_unix_vnode *vp, *dest;
+
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  vp = (struct android_unix_vnode *) src;
+  dest = (struct android_unix_vnode *) dst;
+
+  return (keep_existing
+         ? renameat_noreplace (AT_FDCWD, vp->name,
+                               AT_FDCWD, dest->name)
+         : rename (vp->name, dest->name));
+}
+
+static int
+android_unix_stat (struct android_vnode *vnode, struct stat *statb)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return stat (vp->name, statb);
+}
+
+static int
+android_unix_access (struct android_vnode *vnode, int mode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return access (vp->name, mode);
+}
+
+static int
+android_unix_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return mkdir (vp->name, mode);
+}
+
+static struct dirent *
+android_unix_readdir (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  return readdir (dir->directory);
+}
+
+static void
+android_unix_closedir (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  closedir (dir->directory);
+  xfree (vdir);
+}
+
+static int
+android_unix_dirfd (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  return dirfd (dir->directory);
+}
+
+static struct android_vdir *
+android_unix_opendir (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+  struct android_unix_vdir *dir;
+  DIR *directory;
+
+  /* Try to opendir the vnode.  */
+  vp = (struct android_unix_vnode *) vnode;
+  directory = opendir (vp->name);
+
+  if (!directory)
+    return NULL;
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_unix_readdir;
+  dir->vdir.closedir = android_unix_closedir;
+  dir->vdir.dirfd = android_unix_dirfd;
+  dir->directory = directory;
+  return &dir->vdir;
+}
+
+
+
+/* Asset directory handling functions.  ``directory-tree'' is a file in
+   the root of the assets directory describing its contents.
+
+   See lib-src/asset-directory-tool for more details.  */
+
+/* The Android directory tree.  */
+static const char *directory_tree;
+
+/* The size of the directory tree.  */
+static size_t directory_tree_size;
+
+/* The asset manager being used.  */
+static AAssetManager *asset_manager;
+
+/* Read an unaligned (32-bit) long from the address POINTER.  */
+
+static unsigned int
+android_extract_long (char *pointer)
+{
+  unsigned int number;
+
+  memcpy (&number, pointer, sizeof number);
+  return number;
+}
+
+/* Scan to the file FILE in the asset directory tree.  Return a
+   pointer to the end of that file (immediately before any children)
+   in the directory tree, or NULL if that file does not exist.
+
+   If returning non-NULL, also return the offset to the end of the
+   last subdirectory or file in *LIMIT_RETURN.  LIMIT_RETURN may be
+   NULL.
+
+   FILE must have less than 11 levels of nesting.  If it ends with a
+   trailing slash, then NULL will be returned if it is not actually a
+   directory.  */
+
+static const char *
+android_scan_directory_tree (char *file, size_t *limit_return)
+{
+  char *token, *saveptr, *copy, *copy1, *start, *max, *limit;
+  size_t token_length, ntokens, i;
+  char *tokens[10];
+
+  USE_SAFE_ALLOCA;
+
+  /* Skip past the 5 byte header.  */
+  start = (char *) directory_tree + 5;
+
+  /* Figure out the current limit.  */
+  limit = (char *) directory_tree + directory_tree_size;
+
+  /* Now, split `file' into tokens, with the delimiter being the file
+     name separator.  Look for the file and seek past it.  */
+
+  ntokens = 0;
+  saveptr = NULL;
+  copy = copy1 = xstrdup (file);
+  memset (tokens, 0, sizeof tokens);
+
+  while ((token = strtok_r (copy, "/", &saveptr)))
+    {
+      copy = NULL;
+
+      /* Make sure ntokens is within bounds.  */
+      if (ntokens == ARRAYELTS (tokens))
+       {
+         xfree (copy1);
+         goto fail;
+       }
+
+      tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1);
+      memcpy (tokens[ntokens], token, strlen (token) + 1);
+      ntokens++;
+    }
+
+  /* Free the copy created for strtok_r.  */
+  xfree (copy1);
+
+  /* If there are no tokens, just return the start of the directory
+     tree.  */
+
+  if (!ntokens)
+    {
+      SAFE_FREE ();
+
+      /* Return the size of the directory tree as the limit.
+         Do not subtract the initial header bytes, as the limit
+         is an offset from the start of the file.  */
+
+      if (limit_return)
+       *limit_return = directory_tree_size;
+
+      return start;
+    }
+
+  /* Loop through tokens, indexing the directory tree each time.  */
+
+  for (i = 0; i < ntokens; ++i)
+    {
+      token = tokens[i];
+
+      /* Figure out how many bytes to compare.  */
+      token_length = strlen (token);
+
+    again:
+
+      /* If this would be past the directory, return NULL.  */
+      if (start + token_length > limit)
+       goto fail;
+
+      /* Now compare the file name.  */
+      if (!memcmp (start, token, token_length))
+       {
+         /* They probably match.  Find the NULL byte.  It must be
+            either one byte past start + token_length, with the last
+            byte a trailing slash (indicating that it is a
+            directory), or just start + token_length.  Return 4 bytes
+            past the next NULL byte.  */
+
+         max = memchr (start, 0, limit - start);
+
+         if (max != start + token_length
+             && !(max == start + token_length + 1
+                  && *(max - 1) == '/'))
+           goto false_positive;
+
+         /* Return it if it exists and is in range, and this is the
+            last token.  Otherwise, set it as start and the limit as
+            start + the offset and continue the loop.  */
+
+         if (max && max + 5 <= limit)
+           {
+             if (i < ntokens - 1)
+               {
+                 start = max + 5;
+                 limit = ((char *) directory_tree
+                          + android_extract_long (max + 1));
+
+                 /* Make sure limit is still in range.  */
+                 if (limit > directory_tree + directory_tree_size
+                     || start > directory_tree + directory_tree_size)
+                   goto fail;
+
+                 continue;
+               }
+
+             /* Now see if max is not a directory and file is.  If
+                file is a directory, then return NULL.  */
+             if (*(max - 1) != '/' && file[strlen (file) - 1] == '/')
+               max = NULL;
+             else
+               {
+                 /* Figure out the limit.  */
+                 if (limit_return)
+                   *limit_return = android_extract_long (max + 1);
+
+                 /* Go to the end of this file.  */
+                 max += 5;
+               }
+
+             SAFE_FREE ();
+             return max;
+           }
+
+         /* Return NULL otherwise.  */
+         __android_log_print (ANDROID_LOG_WARN, __func__,
+                              "could not scan to end of directory tree"
+                              ": %s", file);
+         goto fail;
+       }
+
+    false_positive:
+
+      /* No match was found.  Set start to the next sibling and try
+        again.  */
+
+      start = memchr (start, 0, limit - start);
+
+      if (!start || start + 5 > limit)
+       goto fail;
+
+      start = ((char *) directory_tree
+              + android_extract_long (start + 1));
+
+      /* Make sure start is still in bounds.  */
+
+      if (start > limit)
+       goto fail;
+
+      /* Continue the loop.  */
+      goto again;
+    }
+
+ fail:
+  SAFE_FREE ();
+  return NULL;
+}
+
+/* Return whether or not the directory tree entry DIR is a
+   directory.
+
+   DIR should be a value returned by
+   `android_scan_directory_tree'.  */
+
+static bool
+android_is_directory (const char *dir)
+{
+  /* If the directory is the directory tree, then it is a
+     directory.  */
+  if (dir == directory_tree + 5)
+    return true;
+
+  /* Otherwise, look 5 bytes behind.  If it is `/', then it is a
+     directory.  */
+  return (dir - 6 >= directory_tree
+         && *(dir - 6) == '/');
+}
+
+/* Initialize asset retrieval.  ENV should be a JNI environment for
+   the Emacs thread, and MANAGER should be a local reference to a Java
+   asset manager object created for the Emacs service context.  */
+
+static void
+android_init_assets (JNIEnv *env, jobject manager)
+{
+  AAsset *asset;
+
+  /* Set the asset manager.  */
+  asset_manager = AAssetManager_fromJava (env, manager);
+
+  /* Initialize the directory tree.  */
+  asset = AAssetManager_open (asset_manager, "directory-tree",
+                             AASSET_MODE_BUFFER);
+
+  if (!asset)
+    {
+      __android_log_print (ANDROID_LOG_FATAL, __func__,
+                          "Failed to open directory tree");
+      emacs_abort ();
+    }
+
+  directory_tree = AAsset_getBuffer (asset);
+
+  if (!directory_tree)
+    emacs_abort ();
+
+  /* Now figure out how big the directory tree is, and compare the
+     first few bytes.  */
+  directory_tree_size = AAsset_getLength (asset);
+  if (directory_tree_size < 5
+      || memcmp (directory_tree, "EMACS", 5))
+    {
+      __android_log_print (ANDROID_LOG_FATAL, __func__,
+                          "Directory tree has bad magic");
+      emacs_abort ();
+    }
+
+  /* Hold a VM reference to the asset manager to prevent the native
+     object from being deleted.  */
+  (*env)->NewGlobalRef (env, manager);
+
+  /* Abort if there's no more memory for the global reference.  */
+  if ((*env)->ExceptionCheck (env))
+    abort ();
+}
+
+
+
+/* Asset-to-file descriptor conversion.  */
+
+/* Pointer to the `ASharedMemory_create' function which is loaded
+   dynamically.  */
+static int (*asharedmemory_create) (const char *, size_t);
+
+/* Do the same as android_hack_asset_fd, but use an unlinked temporary
+   file to cater to old Android kernels where ashmem files are not
+   readable.  */
+
+static int
+android_hack_asset_fd_fallback (AAsset *asset)
+{
+  int fd;
+  char filename[PATH_MAX];
+  size_t size;
+  void *mem;
+
+  /* Assets must be small enough to fit in size_t, if off_t is
+     larger.  */
+  size = AAsset_getLength (asset);
+
+  /* Get an unlinked file descriptor from a file in the cache
+     directory, which is guaranteed to only be written to by Emacs.
+     Creating an ashmem file descriptor and reading from it doesn't
+     work on these old Android versions.  */
+
+  snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d",
+           android_cache_dir, getpid ());
+  fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
+            S_IRUSR | S_IWUSR);
+
+  if (fd < 0)
+    return -1;
+
+  if (unlink (filename))
+    goto fail;
+
+  if (ftruncate (fd, size))
+    goto fail;
+
+  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "mmap: %s", strerror (errno));
+      goto fail;
+    }
+
+  if (AAsset_read (asset, mem, size) != size)
+    {
+      /* Too little was read.  Close the file descriptor and
+        report an error.  */
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "AAsset_read: %s", strerror (errno));
+      goto fail;
+    }
+
+  munmap (mem, size);
+  return fd;
+
+ fail:
+  close (fd);
+  return -1;
+}
+
+/* Return whether or not shared memory file descriptors can also be
+   read from, and are thus suitable for creating asset files.
+
+   This does not work on some ancient Android systems running old
+   versions of the kernel.  */
+
+static bool
+android_detect_ashmem (void)
+{
+  int fd, rc;
+  void *mem;
+  char test_buffer[10];
+
+  memcpy (test_buffer, "abcdefghi", 10);
+
+  /* Create the file descriptor to be used for the test.  */
+
+  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
+     prefer that over using ASharedMemory.  */
+
+  if (android_get_current_api_level () <= 28)
+    {
+      fd = open ("/dev/ashmem", O_RDWR);
+
+      if (fd < 0)
+       return false;
+
+      /* An empty name means the memory area will exist until the file
+        descriptor is closed, because no other process can
+        attach.  */
+      rc = ioctl (fd, ASHMEM_SET_NAME, "");
+
+      if (rc < 0)
+       {
+         close (fd);
+         return false;
+       }
+
+      rc = ioctl (fd, ASHMEM_SET_SIZE, sizeof test_buffer);
+
+      if (rc < 0)
+       {
+         close (fd);
+         return false;
+       }
+    }
+  else
+    {
+      /* On the other hand, SELinux restrictions on Android 29 and
+        later require that Emacs use a system service to obtain
+        shared memory.  Load this dynamically, as this service is not
+        available on all versions of the NDK.  */
+
+      if (!asharedmemory_create)
+       {
+         *(void **) (&asharedmemory_create)
+           = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
+
+         if (!asharedmemory_create)
+           {
+             __android_log_print (ANDROID_LOG_FATAL, __func__,
+                                  "dlsym: %s\n",
+                                  strerror (errno));
+             emacs_abort ();
+           }
+       }
+
+      fd = (*asharedmemory_create) ("", sizeof test_buffer);
+
+      if (fd < 0)
+       return false;
+    }
+
+  /* Now map the resource and write the test contents.  */
+
+  mem = mmap (NULL, sizeof test_buffer, PROT_WRITE,
+             MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      close (fd);
+      return false;
+    }
+
+  /* Copy over the test contents.  */
+  memcpy (mem, test_buffer, sizeof test_buffer);
+
+  /* Return anyway even if munmap fails.  */
+  munmap (mem, sizeof test_buffer);
+
+  /* Try to read the content back into test_buffer.  If this does not
+     compare equal to the original string, or the read fails, then
+     ashmem descriptors are not readable on this system.  */
+
+  if ((read (fd, test_buffer, sizeof test_buffer)
+       != sizeof test_buffer)
+      || memcmp (test_buffer, "abcdefghi", sizeof test_buffer))
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "/dev/ashmem does not produce real"
+                          " temporary files on this system, so"
+                          " Emacs will fall back to creating"
+                          " unlinked temporary files.");
+      close (fd);
+      return false;
+    }
+
+  close (fd);
+  return true;
+}
+
+/* Get a file descriptor backed by a temporary in-memory file for the
+   given asset.  */
+
+static int
+android_hack_asset_fd (AAsset *asset)
+{
+  static bool ashmem_readable_p;
+  static bool ashmem_initialized;
+  int fd, rc;
+  unsigned char *mem;
+  size_t size;
+
+  /* The first time this function is called, try to determine whether
+     or not ashmem file descriptors can be read from.  */
+
+  if (!ashmem_initialized)
+    ashmem_readable_p
+      = android_detect_ashmem ();
+  ashmem_initialized = true;
+
+  /* If it isn't, fall back.  */
+
+  if (!ashmem_readable_p)
+    return android_hack_asset_fd_fallback (asset);
+
+  /* Assets must be small enough to fit in size_t, if off_t is
+     larger.  */
+  size = AAsset_getLength (asset);
+
+  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
+     prefer that over using ASharedMemory.  */
+
+  if (android_get_current_api_level () <= 28)
+    {
+      fd = open ("/dev/ashmem", O_RDWR);
+
+      if (fd < 0)
+       return -1;
+
+      /* An empty name means the memory area will exist until the file
+        descriptor is closed, because no other process can
+        attach.  */
+      rc = ioctl (fd, ASHMEM_SET_NAME, "");
+
+      if (rc < 0)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "ioctl ASHMEM_SET_NAME: %s",
+                              strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      rc = ioctl (fd, ASHMEM_SET_SIZE, size);
+
+      if (rc < 0)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "ioctl ASHMEM_SET_SIZE: %s",
+                              strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      if (!size)
+       return fd;
+
+      /* Now map the resource.  */
+      mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+      if (mem == MAP_FAILED)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "mmap: %s", strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      if (AAsset_read (asset, mem, size) != size)
+       {
+         /* Too little was read.  Close the file descriptor and
+            report an error.  */
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "AAsset_read: %s", strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      /* Return anyway even if munmap fails.  */
+      munmap (mem, size);
+      return fd;
+    }
+
+  /* On the other hand, SELinux restrictions on Android 29 and later
+     require that Emacs use a system service to obtain shared memory.
+     Load this dynamically, as this service is not available on all
+     versions of the NDK.  */
+
+  if (!asharedmemory_create)
+    {
+      *(void **) (&asharedmemory_create)
+       = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
+
+      if (!asharedmemory_create)
+       {
+         __android_log_print (ANDROID_LOG_FATAL, __func__,
+                              "dlsym: %s\n",
+                              strerror (errno));
+         emacs_abort ();
+       }
+    }
+
+  fd = (*asharedmemory_create) ("", size);
+
+  if (fd < 0)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "ASharedMemory_create: %s",
+                          strerror (errno));
+      return -1;
+    }
+
+  /* Now map the resource.  */
+  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "mmap: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  if (AAsset_read (asset, mem, size) != size)
+    {
+      /* Too little was read.  Close the file descriptor and
+        report an error.  */
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "AAsset_read: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  /* Return anyway even if munmap fails.  */
+  munmap (mem, size);
+  return fd;
+}
+
+
+
+/* ``Asset file system'' vnode implementation.  These vnodes map to
+   asset files within the application package, provided by the Android
+   ``asset manager''.  */
+
+struct android_afs_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* Length of the name without a trailing null byte.  */
+  size_t name_length;
+
+  /* Name of the vnode.  */
+  char *name;
+};
+
+struct android_afs_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_afs_vdirs'.  */
+  struct android_afs_vdir *next;
+
+  /* Pointer to the directory in directory_tree.  */
+  char *asset_dir;
+
+  /* And the end of the files in asset_dir.  */
+  char *asset_limit;
+
+  /* Path to the directory relative to /.  */
+  char *asset_file;
+
+  /* File descriptor representing this directory stream, or NULL.  */
+  int fd;
+};
+
+struct android_afs_open_fd
+{
+  /* The next table entry.  */
+  struct android_afs_open_fd *next;
+
+  /* The open file descriptor.  */
+  int fd;
+
+  /* The stat buffer associated with this entry.  */
+  struct stat statb;
+};
+
+static struct android_vnode *android_afs_name (struct android_vnode *,
+                                              char *, size_t);
+static int android_afs_open (struct android_vnode *, int,
+                            mode_t, bool, int *, AAsset **);
+static void android_afs_close (struct android_vnode *);
+static int android_afs_unlink (struct android_vnode *);
+static int android_afs_symlink (const char *, struct android_vnode *);
+static int android_afs_rmdir (struct android_vnode *);
+static int android_afs_rename (struct android_vnode *,
+                              struct android_vnode *, bool);
+static int android_afs_stat (struct android_vnode *, struct stat *);
+static int android_afs_access (struct android_vnode *, int);
+static int android_afs_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_afs_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with asset VFS nodes.  */
+
+static struct android_vops afs_vfs_ops =
+  {
+    android_afs_name,
+    android_afs_open,
+    android_afs_close,
+    android_afs_unlink,
+    android_afs_symlink,
+    android_afs_rmdir,
+    android_afs_rename,
+    android_afs_stat,
+    android_afs_access,
+    android_afs_mkdir,
+    android_afs_opendir,
+  };
+
+/* Chain consisting of all open asset directory streams.  */
+static struct android_afs_vdir *all_afs_vdirs;
+
+/* List linking open file descriptors to asset information.  This
+   assumes Emacs does not use dup on regular files.  */
+static struct android_afs_open_fd *afs_file_descriptors;
+
+static struct android_vnode *
+android_afs_name (struct android_vnode *vnode, char *name,
+                 size_t length)
+{
+  size_t j;
+  char *remainder, *fill;
+  struct android_afs_vnode *vp, *input;
+  struct android_afs_vnode temp;
+
+  input = (struct android_afs_vnode *) vnode;
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the parent
+     vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* Allocate a new vnode.  */
+  vp = xmalloc (sizeof *vp);
+
+  /* See the specified name is empty.  */
+
+  if (length < 1)
+    {
+      memcpy (vp, vnode, sizeof *vp);
+      vp->name = xstrdup (vp->name);
+      return &vp->vnode;
+    }
+
+  /* Recompute length.  */
+  vp->vnode.ops = &afs_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_AFS;
+  vp->vnode.flags = 0;
+
+  /* Generate the new name of the vnode.  Remove any trailing slash
+     from vp->name.  */
+
+  vp->name_length = input->name_length + length;
+  vp->name = xmalloc (vp->name_length + 2);
+
+  /* Copy the parent name over.  */
+  fill = mempcpy (vp->name, input->name, input->name_length);
+
+  /* Check if it contains a trailing slash.  input->name cannot be
+     empty, as the root vnode's name is `/'.  */
+
+  if (fill[-1] != '/' && *name != '/')
+    /* If not, append a trailing slash and adjust vp->name_length
+       correspondingly.  */
+    *fill++ = '/', vp->name_length++;
+  else if (fill[-1] == '/' && *name == '/')
+    /* If name has a leading slash and fill does too, move fill
+       backwards so that name's slash will override that of fill.  */
+    fill--, vp->name_length--;
+
+  /* Now copy NAME.  */
+  fill = mempcpy (fill, name, length);
+
+  /* And NULL terminate fill.  */
+  *fill = '\0';
+  return &vp->vnode;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (input->name_length == 1)
+    /* This is the vnode representing the /assets directory... */
+    vnode = &root_vnode.vnode;
+  else
+    {
+      /* Create a temporary asset vnode within the parent and use it
+         instead.  First, establish the length of vp->name before its
+         last component.  */
+
+      for (j = input->name_length - 1; j; --j)
+       {
+         if (input->name[j - 1] == '/')
+           break;
+       }
+
+      /* There must be at least one leading directory separator in an
+        asset vnode's `name' field.  */
+
+      if (!j)
+       abort ();
+
+      /* j is now the length of the string minus the size of its last
+        component.  Create a temporary vnode with that as its
+        name.  */
+
+      temp.vnode.ops = &afs_vfs_ops;
+      temp.vnode.type = ANDROID_VNODE_AFS;
+      temp.vnode.flags = 0;
+      temp.name_length = j;
+      temp.name = xmalloc (j + 1);
+      fill = mempcpy (temp.name, input->name, j);
+      *fill = '\0';
+
+      /* Search for the remainder of NAME relative to its parent.  */
+      vnode = android_afs_name (&temp.vnode, remainder,
+                               strlen (remainder));
+      xfree (temp.name);
+      return vnode;
+    }
+
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+/* Find the vnode designated by the normalized NAME relative to the
+   root of the asset file system.  NAME may be modified, and must be
+   LENGTH bytes long, excluding its terminating NULL byte.  */
+
+static struct android_vnode *
+android_afs_initial (char *name, size_t length)
+{
+  struct android_afs_vnode temp;
+
+  /* Create a temporary vnode at the root of the asset file
+     system.  */
+
+  temp.vnode.ops = &afs_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_AFS;
+  temp.vnode.flags = 0;
+  temp.name_length = 1;
+  temp.name = "/";
+
+  /* Try to name this vnode.  If NAME is empty, it will be duplicated
+     instead.  */
+  return android_afs_name (&temp.vnode, name, length);
+}
+
+/* Make FD close-on-exec.  If any system call fails, do not abort, but
+   log a warning to the system log.  */
+
+static void
+android_close_on_exec (int fd)
+{
+  int flags, rc;
+
+  flags = fcntl (fd, F_GETFD);
+
+  if (flags < 0)
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "fcntl: %s", strerror (errno));
+      return;
+    }
+
+  rc = fcntl (fd, F_SETFD, flags | O_CLOEXEC);
+
+  if (rc < 0)
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "fcntl: %s", strerror (errno));
+      return;
+    }
+}
+
+static int
+android_afs_open (struct android_vnode *vnode, int flags,
+                 mode_t mode, bool asset_p, int *fd_return,
+                 AAsset **asset_return)
+{
+  AAsset *asset;
+  struct android_afs_vnode *vp;
+  const char *asset_dir;
+  int fd;
+  struct android_afs_open_fd *info;
+
+  vp = (struct android_afs_vnode *) vnode;
+
+  /* Return suitable error indications for unsupported file
+     operations.  */
+
+  if ((flags & O_WRONLY) || (flags & O_RDWR))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  if (flags & O_DIRECTORY)
+    {
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Now try to open this asset.  Asset manager APIs expect there to
+     be no trailing directory separator.  */
+  asset = AAssetManager_open (asset_manager, vp->name + 1,
+                             AASSET_MODE_STREAMING);
+
+  /* If it can't be opened, return an error indication.  */
+
+  if (!asset)
+    {
+      /* Scan the directory tree for this file.  */
+      asset_dir = android_scan_directory_tree (vp->name, NULL);
+
+      /* Default errno to ENOTENT.  */
+      errno = ENOENT;
+
+      /* Maybe the caller wants to open a directory vnode as a
+        file?  */
+
+      if (asset_dir && android_is_directory (asset_dir))
+       /* In that case, set errno to ENOSYS.  */
+       errno = ENOSYS;
+
+      return -1;
+    }
+
+  /* An asset has been opened.  If the caller wants a file descriptor,
+     a temporary one must be created and the file contents read
+     inside.  */
+
+  if (!asset_p)
+    {
+      /* Create a shared memory file descriptor containing the asset
+        contents.
+
+         The documentation misleads people into thinking that
+         AAsset_openFileDescriptor does precisely this.  However, it
+         instead returns an offset into any uncompressed assets in the
+         ZIP archive.  This cannot be found in its documentation.  */
+
+      fd = android_hack_asset_fd (asset);
+
+      if (fd == -1)
+       {
+         AAsset_close (asset);
+         errno = EIO;
+         return -1;
+       }
+
+      /* If O_CLOEXEC is specified, make the file descriptor close on
+        exec too.  */
+
+      if (flags & O_CLOEXEC)
+       android_close_on_exec (fd);
+
+      /* Keep a record linking ``hacked'' file descriptors with
+        their file status.  */
+      info = xzalloc (sizeof *info);
+      info->fd   = fd;
+      info->next = afs_file_descriptors;
+
+      /* Fill in some information that will be reported to
+        callers of android_fstat, among others.  */
+      info->statb.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+
+      /* Owned by root.  */
+      info->statb.st_uid = 0;
+      info->statb.st_gid = 0;
+
+      /* Concoct a nonexistent device and an inode number.  */
+      info->statb.st_dev = -1;
+      info->statb.st_ino = 0;
+
+      /* Size of the file.  */
+      info->statb.st_size = AAsset_getLength (asset);
+
+      /* Chain info onto afs_file_descriptors.  */
+      afs_file_descriptors = info;
+
+      AAsset_close (asset);
+
+      /* Return the file descriptor.  */
+      *fd_return = fd;
+      return 0;
+    }
+
+  /* Return the asset itself.  */
+  *asset_return = asset;
+  return 1;
+}
+
+static void
+android_afs_close (struct android_vnode *vnode)
+{
+  struct android_afs_vnode *vp;
+  int save_errno;
+
+  save_errno = errno;
+  vp = (struct android_afs_vnode *) vnode;
+  xfree (vp->name);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_afs_unlink (struct android_vnode *vnode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* If the vnode already exists, return EROFS.  Else, return
+     ENOENT.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    errno = EROFS;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_afs_symlink (const char *linkname, struct android_vnode *vnode)
+{
+  struct android_afs_vnode *vp;
+
+  /* If this vnode already exists, return EEXIST.  */
+  vp = (struct android_afs_vnode *) vnode;
+
+  if (android_scan_directory_tree (vp->name, NULL))
+    {
+      errno = EEXIST;
+      return -1;
+    }
+
+  /* Symlinks aren't supported on this (read-only) ``file system'',
+     so return -1 with EROFS.  */
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_afs_rmdir (struct android_vnode *vnode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* If the vnode already exists and is a directory, return EROFS.
+     Else, return ENOTDIR or ENOENT.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir && android_is_directory (dir))
+    errno = EROFS;
+  else if (dir)
+    errno = ENOTDIR;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_afs_rename (struct android_vnode *src, struct android_vnode *dst,
+                   bool keep_existing)
+{
+  /* If src and dst are different kinds of vnodes, return EXDEV.
+     Else, return EROFS.  */
+
+  errno = EROFS;
+  if (src->type != dst->type)
+    errno = EXDEV;
+
+  return -1;
+}
+
+static int
+android_afs_stat (struct android_vnode *vnode, struct stat *statb)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+  AAsset *asset_desc;
+
+  /* Scan for the vnode to see whether or not it exists.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (!dir)
+    {
+      /* Return ENOENT; whether the lookup failed because directory
+        components within vp->path weren't really directories is not
+        important to Emacs's error reporting.  */
+      errno = ENOENT;
+      return -1;
+    }
+
+  if (android_is_directory (dir))
+    {
+      memset (statb, 0, sizeof *statb);
+
+      /* Fill in the stat buffer.  */
+      statb->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
+
+      /* Concoct a nonexistent device and an inode number.  */
+      statb->st_dev = -1;
+      statb->st_ino = 0;
+      return 0;
+    }
+
+  /* AASSET_MODE_STREAMING is fastest here.  */
+  asset_desc = AAssetManager_open (asset_manager, vp->name + 1,
+                                  AASSET_MODE_STREAMING);
+
+  if (!asset_desc)
+    {
+      /* If the asset exists in the directory tree but can't be
+        located by the asset manager, report OOM.  */
+      errno = ENOMEM;
+      return 1;
+    }
+
+  memset (statb, 0, sizeof *statb);
+
+  /* Fill in the stat buffer.  */
+  statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+  statb->st_dev = -1;
+  statb->st_ino = 0;
+  statb->st_size = AAsset_getLength (asset_desc);
+
+  /* Close the asset.  */
+  AAsset_close (asset_desc);
+  return 0;
+}
+
+static int
+android_afs_access (struct android_vnode *vnode, int mode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Scan for the vnode to see whether or not it exists.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    {
+      /* It exists.  If MODE contains W_OK or X_OK, return
+        EACCESS.  */
+
+      if (mode & (W_OK | X_OK))
+       {
+         errno = EACCES;
+         return -1;
+       }
+
+      /* If vp->name is a directory and DIR isn't, return ENOTDIR.  */
+
+      if (vp->name[vp->name_length] == '/'
+         && !android_is_directory (dir))
+       {
+         errno = ENOTDIR;
+         return -1;
+       }
+
+      return 0;
+    }
+
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_afs_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  struct android_afs_vnode *vp;
+  const char *dir;
+
+  /* If the vnode already exists, return EEXIST in lieu of EROFS.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    errno = EEXIST;
+  else
+    errno = EROFS;
+
+  return -1;
+}
+
+static struct dirent *
+android_afs_readdir (struct android_vdir *vdir)
+{
+  static struct dirent dirent;
+  const char *last;
+  struct android_afs_vdir *dir;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* There are no more files to read.  */
+  if (dir->asset_dir >= dir->asset_limit)
+    return NULL;
+
+  /* Otherwise, scan forward looking for the next NULL byte.  */
+  last = memchr (dir->asset_dir, 0,
+                dir->asset_limit - dir->asset_dir);
+
+  /* No more NULL bytes remain.  */
+  if (!last)
+    return NULL;
+
+  /* Forward last past the NULL byte.  */
+  last++;
+
+  /* Make sure it is still within the directory tree.  */
+  if (last >= directory_tree + directory_tree_size)
+    return NULL;
+
+  /* Now, fill in the dirent with the name.  */
+  memset (&dirent, 0, sizeof dirent);
+  dirent.d_ino = 0;
+  dirent.d_off = 0;
+  dirent.d_reclen = sizeof dirent;
+
+  /* Note that dir->asset_dir is actually a NULL terminated
+     string.  */
+  memcpy (dirent.d_name, dir->asset_dir,
+         MIN (sizeof dirent.d_name,
+              last - dir->asset_dir));
+  dirent.d_name[sizeof dirent.d_name - 1] = '\0';
+
+  /* Strip off the trailing slash, if any.  */
+  if (dirent.d_name[MIN (sizeof dirent.d_name,
+                        last - dir->asset_dir)
+                   - 2] == '/')
+    dirent.d_name[MIN (sizeof dirent.d_name,
+                      last - dir->asset_dir)
+                 - 2] = '\0';
+
+  /* If this is not a directory, return DT_REG.  Otherwise, return
+     DT_DIR.  */
+
+  if (last - 2 >= directory_tree && last[-2] == '/')
+    dirent.d_type = DT_DIR;
+  else
+    dirent.d_type = DT_REG;
+
+  /* Forward dir->asset_dir to the file past last.  */
+  dir->asset_dir = ((char *) directory_tree
+                   + android_extract_long ((char *) last));
+
+  return &dirent;
+}
+
+static void
+android_afs_closedir (struct android_vdir *vdir)
+{
+  struct android_afs_vdir *dir, **next, *tem;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  xfree (dir->asset_file);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_afs_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  /* Free the directory itself.  */
+
+  xfree (dir);
+}
+
+static int
+android_afs_dirfd (struct android_vdir *vdir)
+{
+  struct android_afs_vdir *dir;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* Since `android_afs_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_afs_opendir (struct android_vnode *vnode)
+{
+  char *asset_dir;
+  struct android_afs_vdir *dir;
+  struct android_afs_vnode *vp;
+  size_t limit;
+
+  vp = (struct android_afs_vnode *) vnode;
+
+  /* Scan for the asset directory by vp->name.  */
+
+  asset_dir
+    = (char *) android_scan_directory_tree (vp->name, &limit);
+
+  if (!asset_dir)
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* Verify that asset_dir is indeed a directory.  */
+
+  if (!android_is_directory (asset_dir))
+    {
+      errno = ENOTDIR;
+      return NULL;
+    }
+
+  /* Fill in the directory stream.  */
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_afs_readdir;
+  dir->vdir.closedir = android_afs_closedir;
+  dir->vdir.dirfd = android_afs_dirfd;
+  dir->asset_dir = asset_dir;
+  dir->asset_limit = (char *) directory_tree + limit;
+  dir->fd = -1;
+  dir->asset_file = xzalloc (vp->name_length + 2);
+  strcpy (dir->asset_file, vp->name);
+
+  /* Make sure dir->asset_file is terminated with /.  */
+  if (dir->asset_file[vp->name_length - 1] != '/')
+    dir->asset_file[vp->name_length] = '/';
+
+  /* Make sure dir->asset_limit is within bounds.  It is a limit,
+     and as such can be exactly one byte past directory_tree.  */
+  if (dir->asset_limit > directory_tree + directory_tree_size)
+    {
+      xfree (dir);
+      xfree (dir->asset_file);
+      errno = EACCES;
+      return NULL;
+    }
+
+  dir->next = all_afs_vdirs;
+  all_afs_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Return the file name corresponding to DIRFD if it is a
+   ``directory'' file descriptor returned by `android_afs_dirfd' or
+   NULL otherwise.  These file names are relative to the `/assets'
+   directory, but with a leading separator character.  */
+
+static char *
+android_afs_get_directory_name (int dirfd)
+{
+  struct android_afs_vdir *dir;
+
+  for (dir = all_afs_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir->asset_file;
+    }
+
+  return NULL;
+}
+
+
+
+struct android_content_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_content_vdirs'.  */
+  struct android_content_vdir *next;
+
+  /* Pointer to the next file to return.  */
+  const char **next_name;
+
+  /* Temporary file descriptor used to identify this directory to
+     at-funcs, or -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_authority_initial (char *, size_t);
+static struct android_vnode *android_saf_root_initial (char *, size_t);
+
+/* Content provider meta-interface.  This implements a vnode at
+   /content, which is a directory itself containing two additional
+   directories.
+
+   /content/storage only exists on Android 5.0 and later, and contains
+   a list of each directory tree Emacs has been granted permanent
+   access to through the Storage Access Framework.
+
+   /content/by-authority exists on Android 4.4 and later; it contains
+   no directories, but provides a `name' function that converts
+   children into content URIs.  */
+
+static struct android_vnode *android_content_name (struct android_vnode *,
+                                                  char *, size_t);
+static int android_content_open (struct android_vnode *, int,
+                                mode_t, bool, int *, AAsset **);
+static void android_content_close (struct android_vnode *);
+static int android_content_unlink (struct android_vnode *);
+static int android_content_symlink (const char *, struct android_vnode *);
+static int android_content_rmdir (struct android_vnode *);
+static int android_content_rename (struct android_vnode *,
+                                  struct android_vnode *, bool);
+static int android_content_stat (struct android_vnode *, struct stat *);
+static int android_content_access (struct android_vnode *, int);
+static int android_content_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_content_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the content VFS node.  */
+
+static struct android_vops content_vfs_ops =
+  {
+    android_content_name,
+    android_content_open,
+    android_content_close,
+    android_content_unlink,
+    android_content_symlink,
+    android_content_rmdir,
+    android_content_rename,
+    android_content_stat,
+    android_content_access,
+    android_content_mkdir,
+    android_content_opendir,
+  };
+
+/* Table of directories contained within a top-level vnode.  */
+
+static const char *content_directory_contents[] =
+  {
+    "storage", "by-authority",
+  };
+
+/* Chain consisting of all open content directory streams.  */
+static struct android_content_vdir *all_content_vdirs;
+
+static struct android_vnode *
+android_content_name (struct android_vnode *vnode, char *name,
+                     size_t length)
+{
+  char *remainder;
+  struct android_vnode *vp;
+  char *component_end;
+  struct android_special_vnode *special;
+  size_t i;
+  int api;
+
+  static struct android_special_vnode content_vnodes[] = {
+    { "storage", 7, android_saf_root_initial,        },
+    { "by-authority", 12, android_authority_initial, },
+  };
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+      return vp;
+    }
+
+  api = android_get_current_api_level ();
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+  else
+    /* Move past the separator character.  */
+    component_end++;
+
+  /* Now, find out if the first component is a special vnode; if so,
+     call its root lookup function with the rest of NAME there.  */
+
+  if (api < 19)
+    i = 2;
+  else if (api < 21)
+    i = 1;
+  else
+    i = 0;
+
+  for (; i < ARRAYELTS (content_vnodes); ++i)
+    {
+      special = &content_vnodes[i];
+
+      if (component_end - name == special->length
+         && !memcmp (special->name, name, special->length))
+       return (*special->initial) (component_end,
+                                   length - special->length);
+
+      /* Detect the case where a special is named with a trailing
+        directory separator.  */
+
+      if (component_end - name == special->length + 1
+         && !memcmp (special->name, name, special->length)
+         && name[special->length] == '/')
+       /* Make sure to include the directory separator.  */
+       return (*special->initial) (component_end - 1,
+                                   length - special->length);
+    }
+
+  errno = ENOENT;
+  return NULL;
+
+ parent_vnode:
+  /* The parent of this vnode is always the root filesystem.  */
+  vp = &root_vnode.vnode;
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+static int
+android_content_open (struct android_vnode *vnode, int flags,
+                     mode_t mode, bool asset_p, int *fd,
+                     AAsset **asset)
+{
+  /* Don't allow opening this special directory.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_content_close (struct android_vnode *vnode)
+{
+  int save_errno;
+
+  save_errno = errno;
+  xfree (vnode);
+  errno = save_errno;
+}
+
+static int
+android_content_unlink (struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_rename (struct android_vnode *src,
+                       struct android_vnode *dst,
+                       bool keep_existing)
+{
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* Otherwise, return ENOSYS.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_stat (struct android_vnode *vnode,
+                     struct stat *statb)
+{
+  memset (statb, 0, sizeof *statb);
+
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  statb->st_ino = 0;
+  statb->st_dev = -2;
+  statb->st_mode = S_IFDIR | S_IRUSR;
+  return 0;
+}
+
+static int
+android_content_access (struct android_vnode *vnode, int mode)
+{
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Return EROFS if the caller is trying to check for write access to
+     this vnode.  */
+
+  if (mode != F_OK && (mode & (W_OK | X_OK)))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+android_content_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EEXIST;
+  return 0;
+}
+
+static struct dirent *
+android_content_readdir (struct android_vdir *vdir)
+{
+  static struct dirent dirent;
+  struct android_content_vdir *dir;
+  const char *name;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* There are no more files to be read.  */
+  if (dir->next_name == (content_directory_contents
+                        + ARRAYELTS (content_directory_contents)))
+    return NULL;
+
+  /* Get the next child.  */
+  name = *dir->next_name++;
+
+  /* Now, fill in the dirent with the name.  */
+  memset (&dirent, 0, sizeof dirent);
+  dirent.d_ino = 0;
+  dirent.d_off = 0;
+  dirent.d_reclen = sizeof dirent;
+  dirent.d_type = DT_DIR;
+  strcpy (dirent.d_name, name);
+  return &dirent;
+}
+
+static void
+android_content_closedir (struct android_vdir *vdir)
+{
+  struct android_content_vdir *dir, **next, *tem;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_content_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  xfree (dir);
+}
+
+static int
+android_content_dirfd (struct android_vdir *vdir)
+{
+  struct android_content_vdir *dir;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* Since `android_content_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_content_opendir (struct android_vnode *vnode)
+{
+  struct android_content_vdir *dir;
+  int api;
+
+  /* Allocate the virtual directory.  */
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_content_readdir;
+  dir->vdir.closedir = android_content_closedir;
+  dir->vdir.dirfd = android_content_dirfd;
+  dir->fd = -1;
+
+  /* Fill in the directory contents.  */
+  dir->next_name = content_directory_contents;
+  api = android_get_current_api_level ();
+
+  /* Android 4.4 and earlier don't support /content/storage.  */
+
+  if (api < 21)
+    dir->next_name++;
+
+  /* Android 4.3 and earlier don't support /content/by-authority.  */
+
+  if (api < 19)
+    dir->next_name++;
+
+  /* Link this stream onto the list of all content directory
+     streams.  */
+  dir->next = all_content_vdirs;
+  all_content_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Return the file name corresponding to DIRFD if it is a
+   ``directory'' file descriptor returned by `android_content_dirfd'
+   or NULL otherwise.  */
+
+static char *
+android_content_get_directory_name (int dirfd)
+{
+  struct android_content_vdir *dir;
+
+  for (dir = all_content_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return "/content";
+    }
+
+  return NULL;
+}
+
+/* Find the vnode designated by the normalized NAME relative to the
+   root of the content file system.  NAME may be modified, and must be
+   LENGTH bytes long, excluding its terminating NULL byte.  */
+
+static struct android_vnode *
+android_content_initial (char *name, size_t length)
+{
+  struct android_vnode temp;
+
+  /* Create a temporary vnode at the root of the asset file
+     system.  */
+
+  temp.ops = &content_vfs_ops;
+  temp.type = ANDROID_VNODE_CONTENT;
+  temp.flags = 0;
+
+  /* Try to name this vnode.  If NAME is empty, it will be duplicated
+     instead.  */
+  return android_content_name (&temp, name, length);
+}
+
+
+
+/* Content URI management functions.  */
+
+/* Return the content URI corresponding to a `/content/by-authority'
+   file name, or NULL if it is invalid for some reason.  FILENAME
+   should be relative to /content/by-authority, with no leading
+   directory separator character.
+
+   This function is not reentrant.  */
+
+static const char *
+android_get_content_name (const char *filename)
+{
+  static char buffer[PATH_MAX + 1], *fill;
+
+  /* Make sure FILENAME isn't obviously invalid: it must contain an
+     authority name and a file name component.  */
+
+  fill = strchr (filename, '/');
+  if (!fill || *(fill + 1) == '\0')
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* FILENAME must also not be a directory.  */
+
+  if (filename[strlen (filename)] == '/')
+    {
+      errno = ENOTDIR;
+      return NULL;
+    }
+
+  snprintf (buffer, PATH_MAX + 1, "content://%s", filename);
+  return buffer;
+}
+
+/* Return whether or not the specified URI is an accessible content
+   URI.  MODE specifies what to check.  */
+
+static bool
+android_check_content_access (const char *uri, int mode)
+{
+  jobject string;
+  size_t length;
+  jboolean rc;
+
+  length = strlen (uri);
+
+  string = (*android_java_env)->NewByteArray (android_java_env,
+                                             length);
+  android_exception_check ();
+
+  (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                          string, 0, length,
+                                          (jbyte *) uri);
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              emacs_service,
+                                              service_class.check_content_uri,
+                                              string,
+                                              (jboolean) ((mode & R_OK)
+                                                          != 0),
+                                              (jboolean) ((mode & W_OK)
+                                                          != 0));
+  android_exception_check_1 (string);
+  ANDROID_DELETE_LOCAL_REF (string);
+
+  return rc;
+}
+
+
+
+/* Content authority-based vnode implementation.
+
+   /contents/by-authority is a simple vnode implementation that converts
+   components to content:// URIs.
+
+   It does not canonicalize file names by removing parent directory
+   separators, as these characters can appear in legitimate content
+   file names.  */
+
+struct android_authority_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* URI associated with this vnode, or NULL if this is the root of
+     the content authority tree.  */
+  char *uri;
+};
+
+static struct android_vnode *android_authority_name (struct android_vnode *,
+                                                    char *, size_t);
+static int android_authority_open (struct android_vnode *, int,
+                                  mode_t, bool, int *, AAsset **);
+static void android_authority_close (struct android_vnode *);
+static int android_authority_unlink (struct android_vnode *);
+static int android_authority_symlink (const char *, struct android_vnode *);
+static int android_authority_rmdir (struct android_vnode *);
+static int android_authority_rename (struct android_vnode *,
+                                    struct android_vnode *, bool);
+static int android_authority_stat (struct android_vnode *, struct stat *);
+static int android_authority_access (struct android_vnode *, int);
+static int android_authority_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_authority_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the content VFS node.  */
+
+static struct android_vops authority_vfs_ops =
+  {
+    android_authority_name,
+    android_authority_open,
+    android_authority_close,
+    android_authority_unlink,
+    android_authority_symlink,
+    android_authority_rmdir,
+    android_authority_rename,
+    android_authority_stat,
+    android_authority_access,
+    android_authority_mkdir,
+    android_authority_opendir,
+  };
+
+static struct android_vnode *
+android_authority_name (struct android_vnode *vnode, char *name,
+                       size_t length)
+{
+  struct android_authority_vnode *vp;
+  const char *uri_name;
+
+  if (!android_init_gui)
+    {
+      errno = EIO;
+      return NULL;
+    }
+
+  /* If NAME is empty or consists of a single directory separator
+     _and_ VP->uri is NULL, return a copy of VNODE.  */
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (length < 1 || (*name == '/' && length == 1 && !vp->uri))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      if (vp->uri)
+       vp->uri = xstrdup (vp->uri);
+
+      return &vp->vnode;
+    }
+
+  /* Else, if VP->uri is NULL, then it is the root of the by-authority
+     tree.  If NAME starts with a directory separator character,
+     remove it.  */
+
+  if (!vp->uri)
+    {
+      if (*name == '/')
+       name++, length -= 1;
+
+      uri_name = android_get_content_name (name);
+      if (!uri_name)
+       goto error;
+
+      /* Now fill in the vnode.  */
+      vp = xmalloc (sizeof *vp);
+      vp->vnode.ops = &authority_vfs_ops;
+      vp->vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
+      vp->vnode.flags = 0;
+      vp->uri = xstrdup (uri_name);
+      return &vp->vnode;
+    }
+
+  /* Content files can't have children.  */
+  errno = ENOENT;
+ error:
+  return NULL;
+}
+
+static int
+android_authority_open (struct android_vnode *vnode, int flags,
+                       mode_t mode, bool asset_p, int *fd_return,
+                       AAsset **asset)
+{
+  struct android_authority_vnode *vp;
+  size_t length;
+  jobject string;
+  int fd;
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (vp->uri == NULL)
+    {
+      /* This is the `by-authority' directory itself, which can't be
+        opened.  */
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Allocate a buffer to hold the file name.  */
+  length = strlen (vp->uri);
+  string = (*android_java_env)->NewByteArray (android_java_env,
+                                             length);
+  if (!string)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      errno = ENOMEM;
+      return -1;
+    }
+
+  /* Copy the URI into this byte array.  */
+  (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                          string, 0, length,
+                                          (jbyte *) vp->uri);
+
+  /* Try to open the file descriptor.  */
+
+  fd
+    = (*android_java_env)->CallIntMethod (android_java_env,
+                                         emacs_service,
+                                         service_class.open_content_uri,
+                                         string,
+                                         (jboolean) ((mode & O_WRONLY
+                                                      || mode & O_RDWR)
+                                                     != 0),
+                                         (jboolean) !(mode & O_WRONLY),
+                                         (jboolean) ((mode & O_TRUNC)
+                                                     != 0));
+
+  if ((*android_java_env)->ExceptionCheck (android_java_env))
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      errno = ENOMEM;
+      ANDROID_DELETE_LOCAL_REF (string);
+      return -1;
+    }
+
+  /* If fd is -1, just assume that the file does not exist,
+     and return -1 with errno set to ENOENT.  */
+
+  if (fd == -1)
+    {
+      errno = ENOENT;
+      goto skip;
+    }
+
+  if (mode & O_CLOEXEC)
+    android_close_on_exec (fd);
+
+ skip:
+  ANDROID_DELETE_LOCAL_REF (string);
+
+  if (fd == -1)
+    return -1;
+
+  *fd_return = fd;
+  return 0;
+}
+
+static void
+android_authority_close (struct android_vnode *vnode)
+{
+  struct android_authority_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_authority_vnode *) vnode;
+  save_errno = errno;
+  xfree (vp->uri);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_authority_unlink (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_symlink (const char *target,
+                          struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_rmdir (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_rename (struct android_vnode *src,
+                         struct android_vnode *dst,
+                         bool keep_existing)
+{
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* Otherwise, return ENOSYS.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_authority_stat (struct android_vnode *vnode,
+                       struct stat *statb)
+{
+  int rc, fd, save_errno;
+  struct android_authority_vnode *vp;
+
+  /* If this is a vnode representing `by-authority', return some
+     information about this directory.  */
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (!vp->uri)
+    {
+      memset (statb, 0, sizeof *statb);
+      statb->st_uid = getuid ();
+      statb->st_gid = getgid ();
+      statb->st_ino = 0;
+      statb->st_dev = -3;
+      statb->st_mode = S_IFDIR | S_IRUSR;
+      return 0;
+    }
+
+  /* Try to open the file and call fstat.  */
+  rc = (*vnode->ops->open) (vnode, O_RDONLY, 0, false, &fd, NULL);
+
+  if (rc < 0)
+    return -1;
+
+  /* If rc is 1, then an asset file descriptor has been returned.
+     This is impossible, so assert that it doesn't transpire.  */
+  assert (rc != 1);
+
+  /* Now, try to stat the file.  */
+  rc = fstat (fd, statb);
+  save_errno = errno;
+
+  /* Close the file descriptor.  */
+  close (fd);
+
+  /* Restore errno.  */
+  errno = save_errno;
+  return rc;
+}
+
+static int
+android_authority_access (struct android_vnode *vnode, int mode)
+{
+  struct android_authority_vnode *vp;
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  if (!vp->uri)
+    {
+      /* Return EACCES if the caller is trying to check for write
+        access to `by-authority'.  */
+
+      if (mode != F_OK && (mode & (W_OK | X_OK)))
+       {
+         errno = EACCES;
+         return -1;
+       }
+
+      return 0;
+    }
+
+  return (android_check_content_access (vp->uri, mode)
+         ? 0 : -1);
+}
+
+static int
+android_authority_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EACCES;
+  return -1;
+}
+
+static struct android_vdir *
+android_authority_opendir (struct android_vnode *vnode)
+{
+  struct android_authority_vnode *vp;
+
+  /* Forbid listing the `by-authority' directory.  */
+  vp = (struct android_authority_vnode *) vnode;
+  errno = vp->uri ? ENOTDIR : EACCES;
+  return NULL;
+}
+
+/* Find the vnode designated by NAME relative to the root of the
+   by-authority directory.
+
+   If NAME is empty or a single leading separator character, return
+   a vnode representing the by-authority directory itself.
+
+   Otherwise, represent the remainder of NAME as a URI (without
+   normalizing it) and return a vnode corresponding to that.
+
+   Value may also be NULL with errno set if the designated vnode is
+   not available, such as when Android windowing has not been
+   initialized.  */
+
+static struct android_vnode *
+android_authority_initial (char *name, size_t length)
+{
+  struct android_authority_vnode temp;
+
+  temp.vnode.ops = &authority_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
+  temp.vnode.flags = 0;
+  temp.uri = NULL;
+
+  return android_authority_name (&temp.vnode, name, length);
+}
+
+
+
+/* SAF ``root'' vnode implementation.
+
+   The Storage Access Framework is a system service that manages a
+   registry of document providers, which are a type of file system
+   server.
+
+   Normally, document providers can only provide individual files
+   through preestablished ``content URIs''.  Android 5.0 and later add
+   to that ``tree URIs'', which designate entire file system trees.
+
+   Authorization to access document trees and content URIs is granted
+   transiently by default when an Intent is received, but Emacs can
+   also receive persistent authorization for a given document tree.
+
+   /content/storage returns a list of directories, each representing a
+   single authority providing at least one tree URI Emacs holds
+   persistent authorization for.
+
+   Each one of those directories then contains one document tree that
+   Emacs is authorized to access.  */
+
+struct android_saf_root_vnode
+{
+  /* The vnode data.  */
+  struct android_vnode vnode;
+
+  /* The name of the document authority this directory represents, or
+     NULL.  */
+  char *authority;
+};
+
+struct android_saf_root_vdir
+{
+  /* The directory stream function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_saf_root_vdirs'.  */
+  struct android_saf_root_vdir *next;
+
+  /* Array of strings, one for each directory to return.  */
+  jobjectArray array;
+
+  /* Name of the authority this directory lists, or NULL.  */
+  char *authority;
+
+  /* Length of that array, and the current within it.  */
+  jsize length, i;
+
+  /* ``Directory'' file descriptor associated with this stream, or
+     -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_root_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_root_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static void android_saf_root_close (struct android_vnode *);
+static int android_saf_root_unlink (struct android_vnode *);
+static int android_saf_root_symlink (const char *, struct android_vnode *);
+static int android_saf_root_rmdir (struct android_vnode *);
+static int android_saf_root_rename (struct android_vnode *,
+                                   struct android_vnode *, bool);
+static int android_saf_root_stat (struct android_vnode *, struct stat *);
+static int android_saf_root_access (struct android_vnode *, int);
+static int android_saf_root_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_saf_root_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the SAF root VFS node.  */
+
+static struct android_vops saf_root_vfs_ops =
+  {
+    android_saf_root_name,
+    android_saf_root_open,
+    android_saf_root_close,
+    android_saf_root_unlink,
+    android_saf_root_symlink,
+    android_saf_root_rmdir,
+    android_saf_root_rename,
+    android_saf_root_stat,
+    android_saf_root_access,
+    android_saf_root_mkdir,
+    android_saf_root_opendir,
+  };
+
+/* Chain containing all SAF root directories.  */
+static struct android_saf_root_vdir *all_saf_root_vdirs;
+
+/* Defined in the next page.  */
+static struct android_vnode *android_saf_tree_from_name (char *, const char *,
+                                                        const char *);
+
+static struct android_vnode *
+android_saf_root_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  char *remainder, *component_end;
+  struct android_saf_root_vnode *vp;
+  struct android_vnode *new;
+  char component[PATH_MAX];
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      if (vp->authority)
+       vp->authority = xstrdup (vp->authority);
+
+      return &vp->vnode;
+    }
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+
+  if (component_end - name >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return NULL;
+    }
+
+  /* Copy the component over.  */
+  memcpy (component, name, component_end - name);
+  component[component_end - name] = '\0';
+
+  /* Create a SAF document vnode for this tree if it represents an
+     authority.  */
+
+  if (vp->authority)
+    return android_saf_tree_from_name (component_end, component,
+                                      vp->authority);
+
+  /* Otherwise, find the first component of NAME and create a vnode
+     representing it as an authority.  */
+
+  /* Create the vnode.  */
+  vp = xmalloc (sizeof *vp);
+  vp->vnode.ops = &saf_root_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_SAF_ROOT;
+  vp->vnode.flags = 0;
+  vp->authority = xstrdup (component);
+
+  /* If there is more of this component to be named, name it through
+     the new vnode.  */
+
+  if (component_end != name + length)
+    {
+      new = (*vp->vnode.ops->name) (&vp->vnode, component_end,
+                                   length - (component_end - name));
+      (*vp->vnode.ops->close) (&vp->vnode);
+
+      return new;
+    }
+
+  return &vp->vnode;
+
+ parent_vnode:
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (!vp->authority)
+    /* Look this file name up relative to the root of the contents
+       directory.  */
+    return android_content_initial (remainder, strlen (remainder));
+  else
+    /* Look this file name up relative to the root of the storage
+       directory.  */
+    return android_saf_root_initial (remainder, strlen (remainder));
+}
+
+static int
+android_saf_root_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd_return,
+                      AAsset **asset)
+{
+  /* /content/storage or one of its authority children cannot be
+     opened, as they are virtual directories.  */
+
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_saf_root_close (struct android_vnode *vnode)
+{
+  struct android_saf_root_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_saf_root_vnode *) vnode;
+  save_errno = errno;
+  xfree (vp->authority);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_saf_root_unlink (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_symlink (const char *target,
+                         struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_rmdir (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_rename (struct android_vnode *src,
+                        struct android_vnode *dst,
+                        bool keep_existing)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_stat (struct android_vnode *vnode,
+                      struct stat *statb)
+{
+  /* Make up some imaginary statistics for this vnode.  */
+
+  memset (statb, 0, sizeof *statb);
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  statb->st_ino = 0;
+  statb->st_dev = -4;
+  statb->st_mode = S_IFDIR | S_IRUSR;
+  return 0;
+}
+
+static int
+android_saf_root_access (struct android_vnode *vnode, int mode)
+{
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Now, don't allow writing or executing this directory.  */
+
+  if (mode != F_OK && (mode & (W_OK | X_OK)))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+android_saf_root_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static struct dirent *
+android_saf_root_readdir (struct android_vdir *vdir)
+{
+  static struct dirent *dirent;
+  jobject string;
+  const char *chars;
+  size_t length, size;
+  struct android_saf_root_vdir *dir;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  if (dir->i == dir->length)
+    {
+      /* At the end of the stream.  Free dirent and return NULL.  */
+
+      xfree (dirent);
+      dirent = NULL;
+      return NULL;
+    }
+
+  /* Get this string.  */
+  string = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                      dir->array, dir->i++);
+  android_exception_check ();
+  chars = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                 (jstring) string,
+                                                 NULL);
+  android_exception_check_nonnull ((void *) chars, string);
+
+  /* Figure out how large it is, and then resize dirent to fit.  */
+  length = strlen (chars) + 1;
+  size   = offsetof (struct dirent, d_name) + length;
+  dirent = xrealloc (dirent, size);
+
+  /* Clear dirent.  */
+  memset (dirent, 0, size);
+
+  /* Fill in the generic directory information and copy the string
+     over.  */
+  dirent->d_ino = 0;
+  dirent->d_off = 0;
+  dirent->d_reclen = size;
+  dirent->d_type = DT_DIR;
+  strcpy (dirent->d_name, chars);
+
+  /* Release the string data and the local reference to STRING.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) string, chars);
+  ANDROID_DELETE_LOCAL_REF (string);
+  return dirent;
+}
+
+static void
+android_saf_root_closedir (struct android_vdir *vdir)
+{
+  struct android_saf_root_vdir *dir, **next, *tem;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Delete the local reference to the file name array.  */
+  ANDROID_DELETE_LOCAL_REF (dir->array);
+
+  /* Free the authority name if set.  */
+  xfree (dir->authority);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_saf_root_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  /* Free the directory itself.  */
+  xfree (dir);
+}
+
+static int
+android_saf_root_dirfd (struct android_vdir *vdir)
+{
+  struct android_saf_root_vdir *dir;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  /* Since `android_saf_root_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_saf_root_opendir (struct android_vnode *vnode)
+{
+  struct android_saf_root_vnode *vp;
+  jobjectArray array;
+  jmethodID method;
+  jbyteArray authority;
+  struct android_saf_root_vdir *dir;
+  size_t length;
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  if (vp->authority)
+    {
+      /* Build a string containing the authority.  */
+      length = strlen (vp->authority);
+      authority = (*android_java_env)->NewByteArray (android_java_env,
+                                                    length);
+      android_exception_check ();
+
+      /* Copy the authority name to that byte array.  */
+      (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                              authority, 0, length,
+                                              (jbyte *) vp->authority);
+
+      /* Acquire a list of every tree provided by this authority.  */
+
+      method = service_class.get_document_trees;
+      array
+       = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                          emacs_service,
+                                                          service_class.class,
+                                                          method, authority);
+      android_exception_check_1 (authority);
+      ANDROID_DELETE_LOCAL_REF (authority);
+
+      /* Ascertain the length of the array.  If it is empty or NULL,
+         return ENOENT.  */
+
+      if (!array)
+       {
+         errno = ENOENT;
+         return NULL;
+       }
+
+      length = (*android_java_env)->GetArrayLength (android_java_env, array);
+
+      if (!length)
+       {
+         ANDROID_DELETE_LOCAL_REF (array);
+         errno = ENOENT;
+         return NULL;
+       }
+
+      /* Now allocate the directory stream.  It will retain a local
+        reference to the array, and should thus only be used within the
+        same JNI local reference frame.  */
+
+      dir = xmalloc (sizeof *dir);
+      dir->vdir.readdir = android_saf_root_readdir;
+      dir->vdir.closedir = android_saf_root_closedir;
+      dir->vdir.dirfd = android_saf_root_dirfd;
+      dir->fd = -1;
+      dir->array = array;
+      dir->length = length;
+      dir->i = 0;
+      dir->authority = xstrdup (vp->authority);
+
+      /* Link this stream onto the list of all SAF root directory
+        streams.  */
+      dir->next = all_saf_root_vdirs;
+      all_saf_root_vdirs = dir;
+      return &dir->vdir;
+    }
+
+  /* Acquire a list of every document authority.  */
+
+  method = service_class.get_document_authorities;
+  array = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                          emacs_service,
+                                                          service_class.class,
+                                                          method);
+  android_exception_check ();
+
+  if (!array)
+    emacs_abort ();
+
+  /* Now allocate the directory stream.  It will retain a local
+     reference to the array, and should thus only be used within the
+     same JNI local reference frame.  */
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_saf_root_readdir;
+  dir->vdir.closedir = android_saf_root_closedir;
+  dir->vdir.dirfd = android_saf_root_dirfd;
+  dir->fd = -1;
+  dir->array = array;
+  dir->length = (*android_java_env)->GetArrayLength (android_java_env,
+                                                    array);
+  dir->i = 0;
+  dir->authority = NULL;
+
+  /* Link this stream onto the list of all SAF root directory
+     streams.  */
+  dir->next = all_saf_root_vdirs;
+  all_saf_root_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Find the vnode designated by NAME relative to the root of the
+   storage directory.
+
+   If NAME is empty or a single leading separator character, return a
+   vnode representing the storage directory itself.
+
+   If NAME actually resides in a parent directory, look for it within
+   the vnode representing the content directory.  */
+
+static struct android_vnode *
+android_saf_root_initial (char *name, size_t length)
+{
+  struct android_saf_root_vnode temp;
+
+  temp.vnode.ops = &saf_root_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_SAF_ROOT;
+  temp.vnode.flags = 0;
+  temp.authority = NULL;
+
+  return android_saf_root_name (&temp.vnode, name, length);
+}
+
+/* Return any open SAF root directory stream for which dirfd has
+   returned the file descriptor DIRFD.  Return NULL otherwise.  */
+
+static struct android_saf_root_vdir *
+android_saf_root_get_directory (int dirfd)
+{
+  struct android_saf_root_vdir *dir;
+
+  for (dir = all_saf_root_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir;
+    }
+
+  return NULL;
+}
+
+
+
+/* Functions common to both SAF directory and file nodes.  */
+
+/* Return file status for the document designated by ID_NAME within
+   the document tree identified by URI_NAME.
+
+   If the file status is available, place it within *STATB and return
+   0.  If not, return -1 and set errno to EPERM.  */
+
+static int
+android_saf_stat (const char *uri_name, const char *id_name,
+                 struct stat *statb)
+{
+  jmethodID method;
+  jstring uri, id;
+  jobject status;
+  jlong mode, size, mtim, *longs;
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
+  android_exception_check ();
+
+  if (id_name)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             id_name);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to retrieve the file status.  */
+  method = service_class.stat_document;
+  status = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                           emacs_service,
+                                                           service_class.class,
+                                                           method, uri, id);
+
+  /* Check for exceptions and release unneeded local references.  */
+
+  if (id)
+    {
+      android_exception_check_2 (uri, id);
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else
+    android_exception_check_1 (uri);
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  /* Check for failure.  */
+
+  if (!status)
+    {
+      errno = EPERM;
+      return -1;
+    }
+
+  /* Read the file status from the array returned.  */
+
+  longs = (*android_java_env)->GetLongArrayElements (android_java_env,
+                                                    status, NULL);
+  android_exception_check_nonnull (longs, status);
+  mode = longs[0];
+  size = longs[1];
+  mtim = longs[2];
+  (*android_java_env)->ReleaseLongArrayElements (android_java_env, status,
+                                                longs, JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (status);
+
+  /* Fill in STATB with this information.  */
+  memset (statb, 0, sizeof *statb);
+  statb->st_size = MAX (0, MIN (TYPE_MAXIMUM (off_t), size));
+  statb->st_mode = mode;
+  statb->st_mtim.tv_sec = mtim / 1000;
+  statb->st_mtim.tv_nsec = (mtim % 1000) * 1000000;
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  return 0;
+}
+
+/* Detect if Emacs has access to the document designated by the the
+   documen ID ID_NAME within the tree URI_NAME.  If ID_NAME is NULL,
+   use the document ID in URI_NAME itself.
+
+   If WRITABLE, also check that the file is writable, which is true
+   if it is either a directory or its flags contains
+   FLAG_SUPPORTS_WRITE.
+
+   Value is 0 if the file is accessible, and -1 with errno set
+   appropriately if not.  */
+
+static int
+android_saf_access (const char *uri_name, const char *id_name,
+                   bool writable)
+{
+  jmethodID method;
+  jstring uri, id;
+  jint rc;
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
+  android_exception_check ();
+
+  if (id_name)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             id_name);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to retrieve the file status.  */
+  method = service_class.access_document;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method, uri, id,
+                                                    (jboolean) writable);
+
+  /* Check for exceptions and release unneeded local references.  */
+
+  if (id)
+    {
+      android_exception_check_2 (uri, id);
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else
+    android_exception_check_1 (uri);
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  switch (rc)
+    {
+    case -1:
+      /* -1 means it doesn't exist.  */
+      errno = ENOENT;
+      return -1;
+
+    case -2:
+      /* -2 means access has been denied.  */
+      errno = EACCES;
+      return -1;
+
+    case -3:
+      /* -3 refers to an internal error.  */
+      errno = EIO;
+      return -1;
+    }
+
+  /* Return success.  */
+  return 0;
+}
+
+
+
+/* SAF directory vnode.  A file within a SAF directory tree is
+   identified by the URI of the directory tree itself, an opaque
+   ``file identifier'' value, and a display name.  This information is
+   recorded in each vnode representing either a directory or a file
+   itself.  */
+
+struct android_saf_tree_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* The URI of the directory tree represented.  This is Java string
+     data in ``modified UTF format'', which is essentially a modified
+     UTF-8 format capable of storing NULL bytes while also utilizing
+     NULL termination.  */
+  const char *tree_uri;
+
+  /* The ID of the document tree designated by TREE_URI.  */
+  char *tree_id;
+
+  /* The document ID of the directory represented, or NULL if this is
+     the root directory of the tree.  */
+  char *document_id;
+
+  /* The file name of this tree vnode.  This is a ``path'' to the
+     file, where each directory component consists of the display name
+     of a directory leading up to a file within, terminated with a
+     directory separator character.  */
+  char *name;
+};
+
+struct android_saf_tree_vdir
+{
+  /* The virtual directory stream function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory in `all_saf_tree_vdirs'.  */
+  struct android_saf_tree_vdir *next;
+
+  /* Name of this directory relative to the root file system.  */
+  char *name;
+
+  /* Local reference to the cursor representing the directory
+     stream.  */
+  jobject cursor;
+
+  /* The ``directory'' file descriptor used to identify this directory
+     stream, or -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_tree_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_tree_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static void android_saf_tree_close (struct android_vnode *);
+static int android_saf_tree_unlink (struct android_vnode *);
+static int android_saf_tree_symlink (const char *, struct android_vnode *);
+static int android_saf_tree_rmdir (struct android_vnode *);
+static int android_saf_tree_rename (struct android_vnode *,
+                                   struct android_vnode *, bool);
+static int android_saf_tree_stat (struct android_vnode *, struct stat *);
+static int android_saf_tree_access (struct android_vnode *, int);
+static int android_saf_tree_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_saf_tree_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF tree VFS nodes.  */
+
+static struct android_vops saf_tree_vfs_ops =
+  {
+    android_saf_tree_name,
+    android_saf_tree_open,
+    android_saf_tree_close,
+    android_saf_tree_unlink,
+    android_saf_tree_symlink,
+    android_saf_tree_rmdir,
+    android_saf_tree_rename,
+    android_saf_tree_stat,
+    android_saf_tree_access,
+    android_saf_tree_mkdir,
+    android_saf_tree_opendir,
+  };
+
+/* Vector of VFS operations associated with SAF file VFS nodes.
+   Defined later in the next page.  */
+static struct android_vops saf_file_vfs_ops;
+
+/* Vector of VFS operations associated with SAF ``new'' VFS nodes.
+   Defined two pages below.  */
+static struct android_vops saf_new_vfs_ops;
+
+/* Chain of all open SAF directory streams.  */
+static struct android_saf_tree_vdir *all_saf_tree_vdirs;
+
+/* Find the document ID of the file within TREE_URI designated by
+   NAME.
+
+   NAME is a ``file name'' comprised of the display names of
+   individual files.  Each constituent component prior to the last
+   must name a directory file within TREE_URI.
+
+   Upon success, return 0 or 1 (contingent upon whether or not the
+   last component within NAME is a directory) and place the document
+   ID of the named file in ID.
+
+   If the designated file doesn't exist, but the penultimate component
+   within NAME does and is also a directory, return -2 and place the
+   document ID of that directory within *ID.
+
+   If the designated file can't be located, return -1.  */
+
+static int
+android_document_id_from_name (const char *tree_uri, char *name,
+                              char **id)
+{
+  jobjectArray result;
+  jstring uri;
+  jbyteArray java_name;
+  size_t length;
+  jint rc;
+  jmethodID method;
+  const char *doc_id;
+
+  /* First, create the array that will hold the result.  */
+  result = (*android_java_env)->NewObjectArray (android_java_env, 1,
+                                               java_string_class,
+                                               NULL);
+  android_exception_check ();
+
+  /* Next, create the string for the tree URI and name.  */
+  length = strlen (name);
+  java_name = (*android_java_env)->NewByteArray (android_java_env, length);
+  android_exception_check_1 (result);
+  (*android_java_env)->SetByteArrayRegion (android_java_env, java_name,
+                                          0, length, (jbyte *) name);
+  uri = (*android_java_env)->NewStringUTF (android_java_env, tree_uri);
+  android_exception_check_2 (result, java_name);
+
+  /* Now, call documentIdFromName.  */
+  method = service_class.document_id_from_name;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    uri, java_name,
+                                                    result);
+  android_exception_check_3 (result, uri, java_name);
+  ANDROID_DELETE_LOCAL_REF (uri);
+  ANDROID_DELETE_LOCAL_REF (java_name);
+
+  /* If rc indicates failure, don't try to copy from result.  */
+
+  if (rc == -1)
+    {
+      ANDROID_DELETE_LOCAL_REF (result);
+      goto finish;
+    }
+
+  eassert (rc == -2 || rc >= 0);
+
+  /* Otherwise, obtain the contents of the string returned in Java
+     ``UTF-8'' encoding.  */
+  uri = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                   result, 0);
+  android_exception_check_nonnull (uri, result);
+  ANDROID_DELETE_LOCAL_REF (result);
+
+  doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                  uri, NULL);
+  android_exception_check_nonnull ((void *) doc_id, uri);
+
+  /* Make *ID its copy.  */
+  *id = xstrdup (doc_id);
+
+  /* And release it.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) uri, doc_id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+ finish:
+  return rc;
+}
+
+static struct android_vnode *
+android_saf_tree_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  char *remainder;
+  int rc;
+  struct android_saf_tree_vnode *vp, *new;
+  size_t vp_length;
+  char *filename, *fill, *doc_id, *end;
+  struct android_saf_root_vnode root;
+  struct android_saf_tree_vnode tree;
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+
+      if (vp->document_id)
+       vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* Now, search for the document ID of the file designated by NAME
+     relative to this vnode.  */
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+  vp_length = strlen (vp->name);
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Concatenate VP->name with NAME.  Leave one byte at the end for an
+     extra trailing directory separator.  */
+
+  filename = xmalloc (vp_length + length + 2);
+  fill = stpcpy (filename, vp->name);
+  fill = stpcpy (fill, name);
+
+  /* And search for a document ID in the result.  */
+  rc = android_document_id_from_name (vp->tree_uri, name,
+                                     &doc_id);
+
+  if (rc < 0)
+    {
+      if (rc == -2)
+       {
+         /* This is a vnode representing a nonexistent file in a real
+            directory, so create a vnode whose sole use is to create
+            the file.  */
+
+         new = xmalloc (sizeof *new);
+         new->vnode.ops = &saf_new_vfs_ops;
+         new->vnode.type = ANDROID_VNODE_SAF_NEW;
+         new->vnode.flags = 0;
+
+         /* Here, doc_id is actually the ID of the penultimate
+            component in NAME.  */
+
+         new->document_id = doc_id;
+         new->tree_uri = xstrdup (vp->tree_uri);
+         new->tree_id = xstrdup (vp->tree_id);
+         new->name = filename;
+         return &new->vnode;
+       }
+
+      /* The document ID can't be found.  */
+      xfree (filename);
+      errno = ENOENT;
+      return NULL;
+    }
+
+  if (!rc)
+    {
+      /* rc set to 0 means that NAME is a regular file.  Detect if
+         NAME is supposed to be a directory; if it is, set errno to
+         ENODIR.  */
+
+      if (name[length - 1] == '/')
+       {
+         xfree (filename);
+         xfree (doc_id);
+         errno = ENOTDIR;
+         return NULL;
+       }
+    }
+
+  /* So this is either a directory or really a file.  Fortunately,
+     directory and file vnodes share everything in common except for a
+     few file operations, so create a new directory vnode with the new
+     file name and return it.  */
+
+  new = xmalloc (sizeof *new);
+  new->vnode.ops = (rc ? &saf_tree_vfs_ops
+                   : &saf_file_vfs_ops);
+  new->vnode.type = (rc ? ANDROID_VNODE_SAF_TREE
+                    : ANDROID_VNODE_SAF_FILE);
+  new->vnode.flags = 0;
+
+  if (rc)
+    {
+      /* If fill[-1] is not a directory separator character, append
+        one to the end of filename.  */
+
+      if (fill[-1] != '/')
+       {
+         *fill++ = '/';
+         *fill   = '\0';
+       }
+    }
+
+  new->document_id = doc_id;
+  new->tree_uri = xstrdup (vp->tree_uri);
+  new->tree_id = xstrdup (vp->tree_id);
+  new->name = filename;
+  return &new->vnode;
+
+ parent_vnode:
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (!vp->document_id)
+    {
+      /* VP->document_id is NULL, meaning this is the root of this
+        directory tree.  The parent vnode is an SAF root vnode with
+        VP->tree_uri's authority.  */
+
+      root.vnode.ops = &saf_root_vfs_ops;
+      root.vnode.type = ANDROID_VNODE_SAF_ROOT;
+      root.vnode.flags = 0;
+
+      /* Find the authority from the URI.  */
+
+      fill = (char *) vp->tree_uri;
+
+      if (strncmp (fill, "content://", 10))
+       emacs_abort ();
+
+      /* Skip the content header.  */
+      fill += sizeof "content://" - 1;
+
+      /* The authority segment of the URI is between here and the
+        next slash.  */
+
+      end = strchr (fill, '/');
+
+      if (!end)
+       emacs_abort ();
+
+      root.authority = xmalloc (end - fill + 1);
+      memcpy (root.authority, fill, end - fill);
+      root.authority[end - fill] = '\0';
+
+      /* Now search using this vnode.  */
+      vnode = (*root.vnode.ops->name) (&root.vnode, remainder,
+                                      strlen (remainder));
+      xfree (root.authority);
+      return vnode;
+    }
+
+  /* Otherwise, strip off the last directory component.  */
+
+  fill = strrchr (vp->name, '/');
+  if (!fill)
+    emacs_abort ();
+
+  /* Create a new vnode at the top of the directory tree, and search
+     for remainder from there.  */
+
+  tree.vnode.ops = &saf_tree_vfs_ops;
+  tree.vnode.type = ANDROID_VNODE_SAF_TREE;
+  tree.vnode.flags = 0;
+  tree.document_id = NULL;
+  tree.name = "/";
+  tree.tree_uri = vp->tree_uri;
+  tree.tree_id = vp->tree_id;
+
+  length   = strlen (remainder + (*remainder == '/'));
+  filename = xmalloc (fill - vp->name + length + 2);
+  fill = mempcpy (filename, vp->name,
+                 /* Include the separator character (*FILL) within
+                    this copy.  */
+                 fill - vp->name + 1);
+  /* Skip a leading separator in REMAINDER.  */
+  strcpy (fill, remainder + (*remainder == '/'));
+
+  /* Use this filename to find a vnode relative to the start of this
+     tree.  */
+
+  vnode = android_saf_tree_name (&tree.vnode, filename,
+                                strlen (filename));
+  xfree (filename);
+  return vnode;
+}
+
+static int
+android_saf_tree_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd,
+                      AAsset **asset)
+{
+  /* Don't allow opening this special directory.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_saf_tree_close (struct android_vnode *vnode)
+{
+  struct android_saf_tree_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  save_errno = errno;
+  xfree ((void *) vp->tree_uri);
+  xfree (vp->tree_id);
+  xfree (vp->name);
+  xfree (vp->document_id);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_saf_tree_unlink (struct android_vnode *vnode)
+{
+  errno = EISDIR;
+  return -1;
+}
+
+static int
+android_saf_tree_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = EPERM;
+  return -1;
+}
+
+static int
+android_saf_tree_rmdir (struct android_vnode *vnode)
+{
+  /* TODO */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_saf_tree_rename (struct android_vnode *src,
+                        struct android_vnode *dst,
+                        bool keep_existing)
+{
+  /* TODO */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_saf_tree_stat (struct android_vnode *vnode,
+                      struct stat *statb)
+{
+  struct android_saf_tree_vnode *vp;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  return android_saf_stat (vp->tree_uri, vp->document_id,
+                          statb);
+}
+
+static int
+android_saf_tree_access (struct android_vnode *vnode, int mode)
+{
+  struct android_saf_tree_vnode *vp;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  return android_saf_access (vp->tree_uri, vp->document_id,
+                            mode & W_OK);
+}
+
+static int
+android_saf_tree_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  /* Since tree vnodes represent files that already exist, return
+     EEXIST.  */
+  errno = EEXIST;
+  return -1;
+}
+
+/* Open a database Cursor containing each directory entry within the
+   supplied SAF tree vnode VP.
+
+   Value is NULL upon failure, a local reference to the Cursor object
+   otherwise.  */
+
+static jobject
+android_saf_tree_opendir_1 (struct android_saf_tree_vnode *vp)
+{
+  jobject uri, id, cursor;
+  jmethodID method;
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check ();
+
+  if (vp->document_id)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             vp->document_id);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to open the cursor.  */
+  method = service_class.open_document_directory;
+  cursor
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, uri, id);
+
+  if (id)
+    {
+      android_exception_check_2 (id, uri);
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else
+    android_exception_check_1 (uri);
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  /* Return the resulting cursor.  */
+  return cursor;
+}
+
+static struct dirent *
+android_saf_tree_readdir (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir;
+  static struct dirent *dirent;
+  jobject entry, d_name;
+  jint d_type;
+  jmethodID method;
+  size_t length, size;
+  const char *chars;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* Try to read one entry from the cursor.  */
+  method = service_class.read_directory_entry;
+  entry
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, dir->cursor);
+  android_exception_check ();
+
+  /* If ENTRY is NULL, we're at the end of the directory.  */
+
+  if (!entry)
+    {
+      xfree (entry);
+      entry = NULL;
+      return NULL;
+    }
+
+  /* Load both fields from ENTRY.  */
+  d_name = (*android_java_env)->GetObjectField (android_java_env, entry,
+                                               entry_class.d_name);
+  if (!d_name)
+    {
+      /* If an error transpires, d_name is set to NULL.  */
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (entry);
+
+      /* XXX: what would be a better error indication? */
+      errno = EIO;
+      return NULL;
+    }
+
+  /* d_type is 1 if this is a directory, and 0 if it's a regular
+     file.  */
+  d_type = (*android_java_env)->GetIntField (android_java_env, entry,
+                                            entry_class.d_type);
+  ANDROID_DELETE_LOCAL_REF (entry);
+
+  /* Copy the name of the directory over.  */
+  chars = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                 (jstring) d_name,
+                                                 NULL);
+  android_exception_check_nonnull ((void *) chars, d_name);
+
+  /* Figure out how large it is, and then resize dirent to fit.  */
+  length = strlen (chars) + 1;
+  size   = offsetof (struct dirent, d_name) + length;
+  dirent = xrealloc (dirent, size);
+
+  /* Clear dirent.  */
+  memset (dirent, 0, size);
+
+  /* Fill in the generic directory information and copy the string
+     over.  */
+  dirent->d_ino = 0;
+  dirent->d_off = 0;
+  dirent->d_reclen = size;
+  dirent->d_type = d_type ? DT_DIR : DT_UNKNOWN;
+  strcpy (dirent->d_name, chars);
+
+  /* Release the string data and the local reference to STRING.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) d_name,
+                                             chars);
+  ANDROID_DELETE_LOCAL_REF (d_name);
+  return dirent;
+}
+
+static void
+android_saf_tree_closedir (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir, **next, *tem;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* dir->name is allocated by asprintf, which uses regular
+     malloc.  */
+  free (dir->name);
+
+  /* Yes, DIR->cursor is a local reference.  */
+  ANDROID_DELETE_LOCAL_REF (dir->cursor);
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_saf_tree_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  xfree (dir);
+}
+
+static int
+android_saf_tree_dirfd (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* Since `android_saf_tree_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_saf_tree_opendir (struct android_vnode *vnode)
+{
+  struct android_saf_tree_vnode *vp;
+  struct android_saf_tree_vdir *dir;
+  char *fill, *end;
+  jobject cursor;
+  char component[PATH_MAX];
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* First, fill the directory stream with the right functions and
+     file name.  */
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_saf_tree_readdir;
+  dir->vdir.closedir = android_saf_tree_closedir;
+  dir->vdir.dirfd = android_saf_tree_dirfd;
+
+  /* Find the authority from the URI.  */
+
+  fill = (char *) vp->tree_uri;
+
+  if (strncmp (fill, "content://", 10))
+    emacs_abort ();
+
+  /* Skip the content header.  */
+  fill += sizeof "content://" - 1;
+
+  /* The authority segment of the URI is between here and the
+     next slash.  */
+
+  end = strchr (fill, '/');
+
+  if (!end)
+    emacs_abort ();
+
+  if (end - fill >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      xfree (dir);
+      return NULL;
+    }
+
+  /* Copy the authority over.  */
+
+  memcpy (component, fill, end - fill);
+  component[end - fill] = '\0';
+
+  if (asprintf (&dir->name, "/content/storage/%s/%s%s",
+               component, vp->tree_id, vp->name) < 0)
+    {
+      /* Out of memory.  */
+      xfree (dir);
+      memory_full (0);
+    }
+
+  /* Now open a cursor that iterates through each file in this
+     directory.  */
+
+  cursor = android_saf_tree_opendir_1 (vp);
+
+  if (!cursor)
+    {
+      xfree (dir);
+      xfree (dir->name);
+      errno = ENOENT;
+      return NULL;
+    }
+
+  dir->cursor = cursor;
+  dir->fd = -1;
+  dir->next = all_saf_tree_vdirs;
+  all_saf_tree_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Create a vnode designating the file NAME within a directory tree
+   whose identifier is TREE.  As with all other `name' functions, NAME
+   may be modified.
+
+   AUTHORITY is the name of the content provider authority that is
+   offering TREE.
+
+   Value is NULL if no document tree or provider by those names
+   exists, or some other error takes place (for example, if TREE and
+   AUTHORITY aren't encoded correctly.)  */
+
+static struct android_vnode *
+android_saf_tree_from_name (char *name, const char *tree,
+                           const char *authority)
+{
+  struct android_saf_tree_vnode root;
+  jobject tree_string, authority_string, result;
+  jmethodID method;
+  const char *uri;
+  struct android_vnode *vp;
+
+  /* Assume that TREE and NAME are in ``modified UTF-8 format''.  */
+  tree_string = (*android_java_env)->NewStringUTF (android_java_env,
+                                                  tree);
+  android_exception_check ();
+
+  authority_string
+    = (*android_java_env)->NewStringUTF (android_java_env,
+                                        authority);
+  android_exception_check_1 (tree_string);
+
+  /* Now create the URI and detect if Emacs has the rights to access
+     it.  */
+
+  method = service_class.get_tree_uri;
+  result
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, tree_string,
+                                                      authority_string);
+  android_exception_check_2 (tree_string, authority_string);
+  ANDROID_DELETE_LOCAL_REF (tree_string);
+  ANDROID_DELETE_LOCAL_REF (authority_string);
+
+  /* If it doesn't, return NULL and set errno to ENOENT.  */
+
+  if (!result)
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* Otherwise, decode this string.  */
+  uri = (*android_java_env)->GetStringUTFChars (android_java_env, result,
+                                               NULL);
+  android_exception_check_nonnull ((void *) uri, result);
+
+  /* Fill in root.tree_uri with values that represent the root of this
+     document tree.  */
+
+  root.vnode.ops = &saf_tree_vfs_ops;
+  root.vnode.type = ANDROID_VNODE_SAF_TREE;
+  root.vnode.flags = 0;
+  root.tree_uri = uri;
+  root.tree_id = (char *) tree;
+  root.document_id = NULL;
+  root.name = "/";
+
+  vp = (*root.vnode.ops->name) (&root.vnode, name, strlen (name));
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) result, uri);
+  ANDROID_DELETE_LOCAL_REF (result);
+  return vp;
+}
+
+/* Return any open SAF tree directory stream for which dirfd has
+   returned the file descriptor DIRFD.  Return NULL otherwise.  */
+
+static struct android_saf_tree_vdir *
+android_saf_tree_get_directory (int dirfd)
+{
+  struct android_saf_tree_vdir *dir;
+
+  for (dir = all_saf_tree_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir;
+    }
+
+  return NULL;
+}
+
+
+
+/* SAF file vnode.  The information used to uniquely identify a file
+   is identical to that used to identify an SAF directory, but the
+   vnode operations are different.  */
+
+/* Define `struct android_saf_file_vnode' to be identical to a file
+   vnode.  */
+
+#define android_saf_file_vnode android_saf_tree_vnode
+
+/* Structure describing an open ParcelFileDescriptor.  */
+
+struct android_parcel_fd
+{
+  /* The next open parcel file descriptor.  */
+  struct android_parcel_fd *next;
+
+  /* Global reference to this parcel file descriptor.  */
+  jobject descriptor;
+
+  /* The modification time of this parcel file descriptor, or
+     `invalid_timespec'.  */
+  struct timespec mtime;
+
+  /* The file descriptor itself.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_file_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_file_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static int android_saf_file_unlink (struct android_vnode *);
+static int android_saf_file_rmdir (struct android_vnode *);
+static struct android_vdir *android_saf_file_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF tree VFS nodes.  */
+
+static struct android_vops saf_file_vfs_ops =
+  {
+    android_saf_file_name,
+    android_saf_file_open,
+    android_saf_tree_close,
+    android_saf_file_unlink,
+    android_saf_tree_symlink,
+    android_saf_file_rmdir,
+    android_saf_tree_rename,
+    android_saf_tree_stat,
+    android_saf_tree_access,
+    android_saf_tree_mkdir,
+    android_saf_file_opendir,
+  };
+
+/* Chain of all parcel file descriptors currently open.  */
+static struct android_parcel_fd *open_parcel_fds;
+
+static struct android_vnode *
+android_saf_file_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  struct android_saf_file_vnode *vp;
+
+  /* If LENGTH is empty, make a copy of this vnode and return it.  */
+
+  if (length < 1)
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+      vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* A file vnode has no children of its own.  */
+  errno = ENOTDIR;
+  return NULL;
+}
+
+static int
+android_saf_file_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd_return,
+                      AAsset **asset)
+{
+  struct android_saf_file_vnode *vp;
+  jobject uri, id, descriptor;
+  jmethodID method;
+  jboolean trunc, write;
+  jint fd;
+  struct android_parcel_fd *info;
+  struct stat statb;
+
+  /* Build strings for both the URI and ID.  */
+
+  vp = (struct android_saf_file_vnode *) vnode;
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check ();
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         vp->document_id);
+  android_exception_check_1 (uri);
+
+  /* Open a parcel file descriptor according to flags.  */
+
+  method = service_class.open_document;
+  trunc  = flags & O_TRUNC;
+  write  = ((flags & O_RDWR) == O_RDWR || (flags & O_WRONLY));
+  descriptor
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, uri, id,
+                                                      write, trunc);
+  android_exception_check_2 (uri, id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+  ANDROID_DELETE_LOCAL_REF (id);
+
+  if (!descriptor)
+    {
+      /* Assume that permission has been denied if DESCRIPTOR cannot
+        be opened.  */
+      errno = EPERM;
+      return -1;
+    }
+
+  /* Allocate a record for this file descriptor.  Parcel file
+     descriptors should be closed using their own `close' function,
+     which takes care of notifying the source that it has been
+     closed.  */
+  info = xmalloc (sizeof *info);
+
+  /* Now obtain the file descriptor.  */
+  fd = (*android_java_env)->CallIntMethod (android_java_env,
+                                          descriptor,
+                                          fd_class.get_fd);
+  android_exception_check_1 (descriptor);
+
+  /* Create a global reference to descriptor.  */
+  info->descriptor
+    = (*android_java_env)->NewGlobalRef (android_java_env,
+                                        descriptor);
+
+  if (!info->descriptor)
+    {
+      /* If the global reference can't be created, delete
+        descriptor.  */
+      (*android_java_env)->ExceptionClear (android_java_env);
+      (*android_java_env)->CallVoidMethod (android_java_env,
+                                          descriptor,
+                                          fd_class.close);
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (descriptor);
+
+      /* Free INFO.  */
+      xfree (info);
+
+      /* Set errno to EMFILE and return.  */
+      errno = EMFILE;
+      return -1;
+    }
+
+  /* Delete the local ref to DESCRIPTOR.  */
+  ANDROID_DELETE_LOCAL_REF (descriptor);
+
+  /* Try to retrieve the modification time of this file from the
+     content provider.  */
+
+  if (!android_saf_stat (vp->tree_uri, vp->document_id,
+                        &statb))
+    info->mtime = statb.st_mtim;
+  else
+    info->mtime = invalid_timespec ();
+
+  /* Set info->fd and chain it onto the list.  */
+  info->fd = fd;
+  info->next = open_parcel_fds;
+  open_parcel_fds = info;
+
+  /* Return the file descriptor.  */
+  *fd_return = fd;
+  return 0;
+}
+
+static int
+android_saf_file_unlink (struct android_vnode *vnode)
+{
+  /* TODO */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_saf_file_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOTDIR;
+  return -1;
+}
+
+static struct android_vdir *
+android_saf_file_opendir (struct android_vnode *vnode)
+{
+  errno = ENOTDIR;
+  return NULL;
+}
+
+/* Close FD if it's a parcel file descriptor and return true.
+   If FD isn't, return false.
+
+   Such file descriptors need to be closed using a function
+   written in Java, to tell the sender that it has been
+   closed.  */
+
+static bool
+android_close_parcel_fd (int fd)
+{
+  struct android_parcel_fd *tem, **next, *temp;
+
+  for (next = &open_parcel_fds; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         (*android_java_env)->CallVoidMethod (android_java_env,
+                                              tem->descriptor,
+                                              fd_class.close);
+
+         /* Ignore exceptions for the same reason EINTR errors from
+            `close' should be ignored.  */
+         (*android_java_env)->ExceptionClear (android_java_env);
+         (*android_java_env)->DeleteGlobalRef (android_java_env,
+                                               tem->descriptor);
+
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+
+         return true;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+  return false;
+}
+
+
+
+/* SAF ``new'' vnodes.  These nodes share their data structures
+   with tree and file vnodes, but represent files that don't actually
+   exist within a directory.  In them, the document ID represents not
+   the file designated by the vnode itself, but rather its parent
+   directory.
+
+   The only vops defined serve to create directories or files, at
+   which point the vnode becomes invalid.  */
+
+#define android_saf_new_vnode android_saf_tree_vnode
+
+static struct android_vnode *android_saf_new_name (struct android_vnode *,
+                                                  char *, size_t);
+static int android_saf_new_open (struct android_vnode *, int,
+                                mode_t, bool, int *, AAsset **);
+static int android_saf_new_unlink (struct android_vnode *);
+static int android_saf_new_symlink (const char *, struct android_vnode *);
+static int android_saf_new_rmdir (struct android_vnode *);
+static int android_saf_new_rename (struct android_vnode *,
+                                  struct android_vnode *, bool);
+static int android_saf_new_stat (struct android_vnode *, struct stat *);
+static int android_saf_new_access (struct android_vnode *, int);
+static int android_saf_new_mkdir (struct android_vnode *, mode_t);
+static struct android_vdir *android_saf_new_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF new VFS nodes.  */
+
+static struct android_vops saf_new_vfs_ops =
+  {
+    android_saf_new_name,
+    android_saf_new_open,
+    android_saf_tree_close,
+    android_saf_new_unlink,
+    android_saf_new_symlink,
+    android_saf_new_rmdir,
+    android_saf_new_rename,
+    android_saf_new_stat,
+    android_saf_new_access,
+    android_saf_new_mkdir,
+    android_saf_new_opendir,
+  };
+
+static struct android_vnode *
+android_saf_new_name (struct android_vnode *vnode, char *name,
+                     size_t length)
+{
+  struct android_saf_new_vnode *vp;
+
+  /* If LENGTH is empty, make a copy of this vnode and return it.  */
+
+  if (length < 1)
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+      vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* A nonexistent vnode has no children of its own.  */
+  errno = ENOTDIR;
+  return NULL;
+}
+
+static int
+android_saf_new_open (struct android_vnode *vnode, int flags,
+                     mode_t mode, bool asset_p, int *fd_return,
+                     AAsset **asset)
+{
+  struct android_saf_new_vnode *vp;
+  char *end;
+  jstring name, id, uri, new_id;
+  const char *new_doc_id;
+  jmethodID method;
+
+  /* If creating a file wasn't intended, return ENOENT.  */
+
+  if (!(flags & O_CREAT))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  /* If vp->name indicates that it's a directory, return ENOENT.  */
+
+  vp = (struct android_saf_new_vnode *) vnode;
+  end = strrchr (vp->name, '/');
+
+  /* VP->name must contain at least one directory separator.  */
+  eassert (end);
+
+  if (end[1] == '\0')
+    {
+      errno = ENOENT;
+      return -1;
+    }  
+
+  /* Otherwise, try to create a new document.  First, build strings
+     for the name, ID and document URI.  */
+
+  name = (*android_java_env)->NewStringUTF (android_java_env,
+                                           end + 1);
+  android_exception_check ();
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         vp->document_id);
+  android_exception_check_1 (name);
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check_2 (name, id);
+
+  /* Next, try to create a new document and retrieve its ID.  */
+
+  method = service_class.create_document;
+  new_id = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                           emacs_service,
+                                                           service_class.class,
+                                                           method, uri, id,
+                                                           name);
+  android_exception_check_3 (name, id, uri);
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (name);
+  ANDROID_DELETE_LOCAL_REF (id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  if (!new_id)
+    {
+      /* The file couldn't be created for some reason.  */
+      errno = EIO;
+      return -1;
+    }
+
+  /* Now, free VP->document_id and replace it with the service
+     document ID.  */
+
+  new_doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      new_id, NULL);
+  android_exception_check_nonnull ((void *) new_doc_id, new_id);
+
+  xfree (vp->document_id);
+  vp->document_id = xstrdup (new_doc_id);
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             new_id, new_doc_id);
+  ANDROID_DELETE_LOCAL_REF (new_id);
+
+  /* Finally, transform this vnode into a file vnode and call its
+     `open' function.  */
+  vp->vnode.type = ANDROID_VNODE_SAF_FILE;
+  vp->vnode.ops = &saf_file_vfs_ops;
+  return (*vp->vnode.ops->open) (vnode, flags, mode, asset_p,
+                                fd_return, asset);
+}
+
+static int
+android_saf_new_unlink (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = EPERM;
+  return -1;
+}
+
+static int
+android_saf_new_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_rename (struct android_vnode *src,
+                       struct android_vnode *dst,
+                       bool keep_existing)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_stat (struct android_vnode *vnode,
+                     struct stat *statb)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_access (struct android_vnode *vnode, int mode)
+{
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    errno = EINVAL;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_saf_new_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  /* TODO */
+  errno = ENOSYS;
+  return -1;
+}
+
+static struct android_vdir *
+android_saf_new_opendir (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return NULL;
+}
+
+
+
+/* Root vnode.  This vnode represents the root inode, and is a regular
+   Unix vnode with modifications to `name' that make it return asset
+   vnodes.  */
+
+static struct android_vnode *android_root_name (struct android_vnode *,
+                                               char *, size_t);
+
+/* Vector of VFS operations associated with Unix root filesystem VFS
+   nodes.  */
+
+static struct android_vops root_vfs_ops =
+  {
+    android_root_name,
+    android_unix_open,
+    android_unix_close,
+    android_unix_unlink,
+    android_unix_symlink,
+    android_unix_rmdir,
+    android_unix_rename,
+    android_unix_stat,
+    android_unix_access,
+    android_unix_mkdir,
+    android_unix_opendir,
+  };
+
+/* Array of special named vnodes.  */
+
+static struct android_special_vnode special_vnodes[] =
+  {
+    { "assets",  6, android_afs_initial,       },
+    { "content", 7, android_content_initial,   },
+  };
+
+static struct android_vnode *
+android_root_name (struct android_vnode *vnode, char *name,
+                  size_t length)
+{
+  char *component_end;
+  struct android_special_vnode *special;
+  size_t i;
+
+  /* Skip any leading separator in NAME.  */
+
+  if (*name == '/')
+    name++, length--;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+  else
+    /* Move past the spearator character.  */
+    component_end++;
+
+  /* Now, find out if the first component is a special vnode; if so,
+     call its root lookup function with the rest of NAME there.  */
+
+  for (i = 0; i < ARRAYELTS (special_vnodes); ++i)
+    {
+      special = &special_vnodes[i];
+
+      if (component_end - name == special->length
+         && !memcmp (special->name, name, special->length))
+       return (*special->initial) (component_end,
+                                   length - special->length);
+
+      /* Detect the case where a special is named with a trailing
+        directory separator.  */
+
+      if (component_end - name == special->length + 1
+         && !memcmp (special->name, name, special->length)
+         && name[special->length] == '/')
+       /* Make sure to include the directory separator.  */
+       return (*special->initial) (component_end - 1,
+                                   length - special->length);
+    }
+
+  /* Otherwise, continue searching for a vnode normally.  */
+  return android_unix_name (vnode, name, length);
+}
+
+
+
+/* File system lookup.  */
+
+/* Look up the vnode that designates NAME, a file name that is at
+   least N bytes.
+
+   NAME may be either an absolute file name or a name relative to the
+   current working directory.  It must not be longer than PATH_MAX
+   bytes.
+
+   Value is NULL upon failure with errno set accordingly, or the
+   vnode.  */
+
+static struct android_vnode *
+android_name_file (const char *name)
+{
+  char buffer[PATH_MAX + 1], *head;
+  const char *end;
+  size_t len;
+  int nslash, c;
+  struct android_vnode *vp;
+
+  len = strlen (name);
+  if (len > PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return NULL;
+    }
+
+  /* Now, try to ``normalize'' the file name by removing consecutive
+     slash characters while copying it to BUFFER.  */
+
+  head = buffer;
+  nslash = 0;
+  for (end = name + len; name < end; ++name)
+    {
+      c = *name;
+
+      switch (c)
+       {
+       case '/':
+         /* This is a directory separator character.  Two consecutive
+            separator characters should be replaced by a single
+            character; more than three in a row means that the
+            section of the file name before the last slash character
+            should be discarded.  */
+
+         if (!nslash)
+           *head++ = '/';
+
+         nslash++;
+
+         if (nslash >= 3)
+           /* Return to the root directory.  */
+           head = buffer, *head++ = '/', nslash = 0;
+         break;
+
+       default:
+         /* Otherwise, copy the file name over.  */
+         nslash = 0;
+         *head++ = *name;
+         break;
+       }
+    }
+
+  /* Terminate the file name.  */
+  *head = '\0';
+
+  /* If HEAD is a relative file name, it can't reside inside the
+     virtual mounts; create a Unix vnode instead.  */
+
+  if (head == buffer || buffer[0] != '/')
+    return android_unix_vnode (buffer);
+
+  /* Start looking from the root vnode.  */
+  vp = &root_vnode.vnode;
+
+  /* If buffer is empty, this will create a duplicate of the root
+     vnode.  */
+  return (*vp->ops->name) (vp, buffer + 1, head - buffer - 1);
+}
+
+
+
+/* Initialize the virtual filesystem layer.  Load the directory tree
+   from the given asset MANAGER (which should be a local reference
+   within ENV) that will be used to access assets in the future, and
+   create the root vnode.
+
+   ENV should be a JNI environment valid for future calls to VFS
+   functions.  */
+
+void
+android_vfs_init (JNIEnv *env, jobject manager)
+{
+  jclass old;
+
+  android_init_assets (env, manager);
+
+  /* Create the root vnode, which is used to locate all other
+     vnodes.  */
+  root_vnode.vnode.ops = &root_vfs_ops;
+  root_vnode.vnode.type = ANDROID_VNODE_UNIX;
+  root_vnode.vnode.flags = 0;
+  root_vnode.name_length = 1;
+  root_vnode.name = "/";
+
+  /* Initialize some required classes.  */
+  java_string_class = (*env)->FindClass (env, "java/lang/String");
+  assert (java_string_class);
+
+  old = java_string_class;
+  java_string_class = (jclass) (*env)->NewGlobalRef (env,
+                                                    java_string_class);
+  assert (java_string_class);
+  (*env)->DeleteLocalRef (env, old);
+
+  /* And initialize those used on Android 5.0 and later.  */
+
+  if (android_get_current_api_level () < 21)
+    return;
+
+  android_init_cursor_class (env);
+  android_init_entry_class (env);
+  android_init_fd_class (env);
+}
+
+/* The replacement functions that follow have several major
+   drawbacks:
+
+   The first is that CWD relative file names will always be Unix
+   vnodes, and looking up their parents will always return another
+   Unix vnode.  For example, with the working directory set to
+   /sdcard:
+
+     ../content/storage
+
+   will find /sdcard/../content/storage on the Unix filesystem,
+   opposed to /content/storage within the ``content'' VFS.
+
+   Emacs only uses file names expanded through `expand-file-name', so
+   this is unproblematic in practice.
+
+   The second is that `..' components do not usually check that their
+   preceding component is a directory.  This is a side effect of their
+   removal from file names as part of a pre-processing step before
+   they are opened.  So, even if:
+
+     /sdcard/foo.txt
+
+   is a file, opening the directory:
+
+     /sdcard/foo.txt/..
+
+   will be successful.
+
+   The third is that the handling of `..' components relative to
+   another vnode hasn't been tested and is only assumed to work
+   because the code has been written.  It does not pose a practical
+   problem, however, as Emacs only names files starting from the root
+   vnode.
+
+   The fourth is that errno values from vnode operations don't always
+   reflect what the Unix system calls they emulate can return: for
+   example, `open' may return EIO, while trying to `mkdir' within
+   /content will return ENOENT instead of EROFS.  This is a
+   consequence of how accessing a non-existent file may fail at vnode
+   lookup, instead of when a vop is used.  This problem hasn't made a
+   sufficient nuisance of itself to justify its fix yet.
+
+   The fifth is that trailing directory separators may be lost when
+   naming files relative to another vnode, as a consequence of an
+   optimization used to avoid allocating too much stack or heap
+   space.
+
+   The sixth is that flags and other argument checking is nowhere near
+   exhaustive on vnode types other than Unix vnodes.
+
+   And the final drawback is that directories cannot be directly
+   opened.  Instead, `dirfd' must be called on a directory stream used
+   by `openat'.
+
+   Caveat emptor! */
+
+/* Open the VFS node designated by NAME, taking into account FLAGS and
+   MODE, both of which mean the same as they do in a call to `open'.
+
+   Value is -1 upon failure with errno set accordingly, and a file
+   descriptor otherwise.  */
+
+int
+android_open (const char *name, int flags, mode_t mode)
+{
+  struct android_vnode *vp;
+  int fd, rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->open) (vp, flags, mode, false, &fd, NULL);
+  (*vp->ops->close) (vp);
+
+  if (rc < 0)
+    return -1;
+
+  /* If rc is 1, then an asset file descriptor has been returned.
+     This is impossible, so assert that it doesn't transpire.  */
+  assert (rc != 1);
+  return fd;
+}
+
+/* Unlink the VFS node designated by the specified FILE.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_unlink (const char *name)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->unlink) (vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Symlink the VFS node designated by LINKPATH to TARGET.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_symlink (const char *target, const char *linkpath)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (linkpath);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->symlink) (target, vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Remove the empty directory at the VFS node designated by NAME.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_rmdir (const char *name)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->rmdir) (vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Create a directory at the VFS node designated by NAME and the given
+   access MODE.  Value is -1 upon failure with errno set, 0
+   otherwise.  */
+
+int
+android_mkdir (const char *name, mode_t mode)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->mkdir) (vp, mode);
+  (*vp->ops->close) (vp);
+  return rc; 
+}
+
+/* Rename the vnode designated by SRC to the vnode designated by DST.
+   If DST already exists, return -1 and set errno to EEXIST.
+
+   SRCFD and DSTFD should be AT_FDCWD, or else value is -1 and errno
+   is ENOSYS.
+
+   If the filesystem or vnodes containing either DST or SRC does not
+   support rename operations that also check for a preexisting
+   destination, return -1 and set errno to ENOSYS.
+
+   Otherwise, value and errno are identical to that of Unix
+   `rename' with the same arguments.  */
+
+int
+android_renameat_noreplace (int srcfd, const char *src,
+                           int dstfd, const char *dst)
+{
+  struct android_vnode *vp, *vdst;
+  int rc;
+
+  if (srcfd != AT_FDCWD || dstfd != AT_FDCWD)
+    {
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Find vnodes for both src and dst.  */
+
+  vp = android_name_file (src);
+  if (!vp)
+    goto error;
+
+  vdst = android_name_file (dst);
+  if (!vdst)
+    goto error1;
+
+  /* Now try to rename vp to vdst.  */
+  rc = (*vp->ops->rename) (vp, vdst, true);
+  (*vp->ops->close) (vp);
+  (*vp->ops->close) (vdst);
+  return rc;
+
+ error1:
+  (*vp->ops->close) (vp);
+ error:
+  return -1;
+}
+
+/* Like `android_renameat_noreplace', but don't check for DST's
+   existence and don't accept placeholder SRCFD and DSTFD
+   arguments.  */
+
+int
+android_rename (const char *src, const char *dst)
+{
+  struct android_vnode *vp, *vdst;
+  int rc;
+
+  /* Find vnodes for both src and dst.  */
+
+  vp = android_name_file (src);
+  if (!vp)
+    goto error;
+
+  vdst = android_name_file (dst);
+  if (!vdst)
+    goto error1;
+
+  /* Now try to rename vp to vdst.  */
+  rc = (*vp->ops->rename) (vp, vdst, false);
+  (*vp->ops->close) (vp);
+  (*vp->ops->close) (vdst);
+  return rc;
+
+ error1:
+  (*vp->ops->close) (vp);
+ error:
+  return -1;
+}
+
+
+
+/* fstat, fstatat, faccessat, close/fclose etc.  These functions are
+   somewhat tricky to wrap: they (at least partially) operate on file
+   descriptors, which sometimes provide a base directory for the
+   filesystem operations they perform.  VFS nodes aren't mapped to
+   file descriptors opened through them, which makes this troublesome.
+
+   openat is not wrapped at all; uses are defined out when Emacs is
+   being built for Android.  The other functions fall back to directly
+   making Unix system calls when their base directory arguments are
+   not AT_FDCWD and no directory stream returned from
+   `android_opendir' ever returned that file descriptor, which is
+   enough to satisfy Emacs's current requirements for those functions
+   when a directory file descriptor is supplied.
+
+   fclose and close are finally wrapped because they need to erase
+   information used to link file descriptors with file statistics from
+   their origins; fstat is also wrapped to take this information into
+   account, so that it can return correct file statistics for asset
+   directory files.  */
+
+/* Like fstat.  However, look up the asset corresponding to the file
+   descriptor.  If it exists, return the right information.  */
+
+int
+android_fstat (int fd, struct stat *statb)
+{
+  struct android_afs_open_fd *tem;
+  struct android_parcel_fd *parcel_fd;
+  int rc;
+
+  for (tem = afs_file_descriptors; tem; tem = tem->next)
+    {
+      if (tem->fd == fd)
+       {         
+         memcpy (statb, &tem->statb, sizeof *statb);
+         return 0;
+       }
+    }
+
+  rc = fstat (fd, statb);
+
+  /* Now look for a matching parcel file descriptor and use its
+     mtime if available.  */
+
+  parcel_fd = open_parcel_fds;
+  for (; parcel_fd; parcel_fd = parcel_fd->next)
+    {
+      if (parcel_fd->fd == fd
+         && timespec_valid_p (parcel_fd->mtime))
+       {
+         statb->st_mtim = parcel_fd->mtime;
+         break;
+       }
+    }
+
+  return rc;
+}
+
+/* If DIRFD is a file descriptor returned by `android_readdir' for a
+   non-Unix file stream, return FILENAME relative to the file name of
+   the directory represented by that stream within BUFFER, a buffer
+   SIZE bytes long.
+
+   Value is 0 if a file name is returned, 1 otherwise.  */
+
+static int
+android_fstatat_1 (int dirfd, const char *filename,
+                  char *restrict buffer, size_t size)
+{
+  char *dir_name;
+  struct android_saf_root_vdir *vdir;
+  struct android_saf_tree_vdir *vdir1;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open asset directory stream.  */
+
+  dir_name = android_afs_get_directory_name (dirfd);
+
+  if (dir_name)
+    {
+      /* Look for PATHNAME relative to this directory within an asset
+        vnode.  */
+      snprintf (buffer, size, "/assets%s%s", dir_name,
+               filename);
+      return 0;
+    }
+
+  /* Do the same, but for /content directories instead.  */
+
+  dir_name = android_content_get_directory_name (dirfd);
+
+  if (dir_name)
+    {
+      /* Look for PATHNAME relative to this directory within an asset
+        vnode.  */
+      snprintf (buffer, size, "%s/%s", dir_name,
+               filename);
+      return 0;
+    }
+
+  /* And for /content/storage.  */
+
+  vdir = android_saf_root_get_directory (dirfd);
+
+  if (vdir)
+    {
+      if (vdir->authority)
+       snprintf (buffer, size, "/content/storage/%s/%s",
+                 vdir->authority, filename);
+      else
+       snprintf (buffer, size, "/content/storage/%s",
+                 filename);
+
+      return 0;
+    }
+
+  /* /content/storage/foo/... */
+
+  vdir1 = android_saf_tree_get_directory (dirfd);
+
+  if (vdir1)
+    {
+      snprintf (buffer, size, "%s%s", vdir1->name, filename);
+      return 0;
+    }
+
+  return 1;
+}
+
+/* If DIRFD is AT_FDCWD or a file descriptor returned by
+   `android_dirfd', or PATHNAME is an absolute file name, return the
+   file status of the VFS node designated by PATHNAME relative to the
+   VFS node corresponding to DIRFD, or relative to the current working
+   directory if DIRFD is AT_FDCWD.
+
+   Otherwise, call `fstatat' with DIRFD, PATHNAME, STATBUF and
+   FLAGS.  */
+
+int
+android_fstatat (int dirfd, const char *restrict pathname,
+                struct stat *restrict statbuf, int flags)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  int rc;
+
+  /* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
+     never known to Emacs or AT_FDCWD when it originates from a VFS
+     node representing a filesystem that supports symlinks.  */
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to fstatat.  */
+  return fstatat (dirfd, pathname, statbuf, flags);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->stat) (vp, statbuf);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `android_fstatat', but check file accessibility instead of
+   status.  */
+
+int
+android_faccessat (int dirfd, const char *restrict pathname,
+                  int mode, int flags)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  int rc;
+
+  /* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
+     never known to Emacs or AT_FDCWD when it originates from a VFS
+     node representing a filesystem that supports symlinks.  */
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to faccessat.  */
+  return faccessat (dirfd, pathname, mode, flags);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->access) (vp, mode);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `fdopen', but if FD is a parcel file descriptor, ``detach'' it
+   from the original.
+
+   This is necessary because ownership over parcel file descriptors is
+   retained by the ParcelFileDescriptor objects that return them,
+   while file streams also require ownership over file descriptors
+   they are created on behalf of.
+
+   Detaching the parcel file descriptor linked to FD consequentially
+   prevents the owner from being notified when it is eventually
+   closed, but for now that hasn't been demonstrated to be problematic
+   yet, as Emacs doesn't write to file streams.  */
+
+FILE *
+android_fdopen (int fd, const char *mode)
+{
+  struct android_parcel_fd *tem, **next, *temp;
+  int new_fd;
+
+  for (next = &open_parcel_fds; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         new_fd
+           = (*android_java_env)->CallIntMethod (android_java_env,
+                                                 tem->descriptor,
+                                                 fd_class.detach_fd);
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+         android_exception_check ();
+
+         /* Assert that FD (returned from `getFd') is identical to
+            the file descriptor returned by `detachFd'.  */
+
+         if (fd != new_fd)
+           emacs_abort ();
+
+         goto open_file;
+       }
+    }
+
+ open_file:
+  return fdopen (fd, mode);
+}
+
+/* Like close.  However, remove the file descriptor from the asset
+   table as well.  */
+
+int
+android_close (int fd)
+{
+  struct android_afs_open_fd *tem, **next, *temp;
+
+  if (android_close_parcel_fd (fd))
+    return 0;
+
+  for (next = &afs_file_descriptors; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+
+         break;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+  return close (fd);
+}
+
+/* Like fclose.  However, remove any information associated with
+   FILE's file descriptor from the asset table as well.  */
+
+int
+android_fclose (FILE *stream)
+{
+  int fd;
+  struct android_afs_open_fd *tem, **next, *temp;
+
+  fd = fileno (stream);
+
+  if (fd == -1)
+    goto skip;
+
+  for (next = &afs_file_descriptors; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         temp = tem->next;
+         xfree (*next);
+         *next = temp;
+
+         break;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+ skip:
+  return fclose (stream);
+}
+
+
+
+/* External asset management interface.  By using functions here
+   to read and write from files, Emacs can avoid opening a
+   shared memory file descriptor for each ``asset'' file.  */
+
+/* Like android_open.  However, return a structure that can
+   either directly hold an AAsset or a file descriptor.
+
+   Value is the structure upon success.  Upon failure, value
+   consists of an uninitialized file descriptor, but its asset
+   field is set to -1, and errno is set accordingly.  */
+
+struct android_fd_or_asset
+android_open_asset (const char *filename, int oflag, mode_t mode)
+{
+  struct android_fd_or_asset fd;
+  AAsset *asset;
+  int rc;
+  struct android_vnode *vp;
+
+  /* Now name this file.  */
+  vp = android_name_file (filename);
+  if (!vp)
+    goto failure;
+
+  rc = (*vp->ops->open) (vp, oflag, mode, true, &fd.fd,
+                        &asset);
+  (*vp->ops->close) (vp);
+
+  /* Upon failure, return fd with its asset field set to (void *)
+     -1.  */
+
+  if (rc < 0)
+    {
+    failure:
+      fd.asset = (void *) -1;
+      fd.fd = -1;
+      return fd;
+    }
+
+  if (rc == 1)
+    {
+      /* An asset file was returned.  Return the structure containing
+        an asset.  */
+      fd.asset = asset;
+      fd.fd = -1;
+      return fd;
+    }
+
+  /* Otherwise, a file descriptor has been returned.  Set fd.asset to
+     NULL, signifying that it is a file descriptor.  */
+  fd.asset = NULL;
+  return fd;
+}
+
+/* Like android_close.  However, it takes a ``file descriptor''
+   opened using android_open_asset.  */
+
+int
+android_close_asset (struct android_fd_or_asset asset)
+{
+  if (!asset.asset)
+    return android_close (asset.fd);
+
+  AAsset_close (asset.asset);
+  return 0;
+}
+
+/* Like `emacs_read_quit'.  However, it handles file descriptors
+   opened using `android_open_asset' as well.  */
+
+ssize_t
+android_asset_read_quit (struct android_fd_or_asset asset,
+                        void *buffer, size_t size)
+{
+  if (!asset.asset)
+    return emacs_read_quit (asset.fd, buffer, size);
+
+  /* It doesn't seem possible to quit from inside AAsset_read,
+     sadly.  */
+  return AAsset_read (asset.asset, buffer, size);
+}
+
+/* Like `read'.  However, it handles file descriptors opened
+   using `android_open_asset' as well.  */
+
+ssize_t
+android_asset_read (struct android_fd_or_asset asset,
+                   void *buffer, size_t size)
+{
+  if (!asset.asset)
+    return read (asset.fd, buffer, size);
+
+  /* It doesn't seem possible to quit from inside AAsset_read,
+     sadly.  */
+  return AAsset_read (asset.asset, buffer, size);
+}
+
+/* Like `lseek', but it handles ``file descriptors'' opened with
+   android_open_asset.  */
+
+off_t
+android_asset_lseek (struct android_fd_or_asset asset, off_t off,
+                    int whence)
+{
+  if (!asset.asset)
+    return lseek (asset.fd, off, whence);
+
+  return AAsset_seek (asset.asset, off, whence);
+}
+
+/* Like `fstat'.  */
+
+int
+android_asset_fstat (struct android_fd_or_asset asset,
+                    struct stat *statb)
+{
+  if (!asset.asset)
+    return android_fstat (asset.fd, statb);
+
+  /* Clear statb.  */
+  memset (statb, 0, sizeof *statb);
+
+  /* Set the mode.  */
+  statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+
+  /* Concoct a nonexistent device and an inode number.  */
+  statb->st_dev = -1;
+  statb->st_ino = 0;
+
+  /* Owned by root.  */
+  statb->st_uid = 0;
+  statb->st_gid = 0;
+
+  /* Size of the file.  */
+  statb->st_size = AAsset_getLength (asset.asset);
+  return 0;
+}
+
+
+
+/* Directory listing emulation.  */
+
+/* Open a directory stream from the VFS node designated by NAME.
+   Value is NULL upon failure with errno set accordingly.
+
+   The directory stream returned holds local references to JNI objects
+   and shouldn't be used after the current local reference frame is
+   popped.  */
+
+struct android_vdir *
+android_opendir (const char *name)
+{
+  struct android_vnode *vp;
+  struct android_vdir *dir;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return NULL;
+
+  dir = (*vp->ops->opendir) (vp);
+  (*vp->ops->close) (vp);
+  return dir;
+}
+
+/* Like dirfd.  However, value is not a real directory file descriptor
+   if DIR is an asset directory.  */
+
+int
+android_dirfd (struct android_vdir *dirp)
+{
+  return (*dirp->dirfd) (dirp);
+}
+
+/* Like readdir, but for VFS directory streams instead.  */
+
+struct dirent *
+android_readdir (struct android_vdir *dirp)
+{
+  return (*dirp->readdir) (dirp);
+}
+
+/* Like closedir, but for VFS directory streams instead.  */
+
+void
+android_closedir (struct android_vdir *dirp)
+{
+  return (*dirp->closedir) (dirp);
+}
diff --git a/src/callproc.c b/src/callproc.c
index ee5195385de..0645c2c3e18 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -214,7 +214,7 @@ record_kill_process (struct Lisp_Process *p, Lisp_Object 
tempfile)
 static void
 delete_temp_file (Lisp_Object name)
 {
-  unlink (SSDATA (name));
+  emacs_unlink (SSDATA (name));
 }
 
 static void
diff --git a/src/charset.c b/src/charset.c
index c532f79d282..d5e42d038df 100644
--- a/src/charset.c
+++ b/src/charset.c
@@ -488,7 +488,7 @@ load_charset_map_from_file (struct charset *charset, 
Lisp_Object mapfile,
   specbind (Qfile_name_handler_alist, Qnil);
   fd = openp (Vcharset_map_path, mapfile, suffixes, NULL, Qnil, false, false,
              NULL);
-  fp = fd < 0 ? 0 : fdopen (fd, "r");
+  fp = fd < 0 ? 0 : emacs_fdopen (fd, "r");
   if (!fp)
     {
       int open_errno = errno;
diff --git a/src/dired.c b/src/dired.c
index 93487d552e2..f2a123dc168 100644
--- a/src/dired.c
+++ b/src/dired.c
@@ -54,7 +54,7 @@ typedef DIR emacs_dir;
 
 /* The Android emulation of dirent stuff is required to be able to
    list the /assets special directory.  */
-typedef struct android_dir emacs_dir;
+typedef struct android_vdir emacs_dir;
 #define emacs_readdir android_readdir
 #define emacs_closedir android_closedir
 #endif
diff --git a/src/emacs.c b/src/emacs.c
index d75a83ab9d8..72f75d7890d 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -3012,7 +3012,7 @@ killed.  */
     {
       Lisp_Object listfile;
       listfile = Fexpand_file_name (Vauto_save_list_file_name, Qnil);
-      unlink (SSDATA (listfile));
+      emacs_unlink (SSDATA (listfile));
     }
 
 #ifdef HAVE_NATIVE_COMP
diff --git a/src/fileio.c b/src/fileio.c
index d24f25c2e06..5ce933ec45b 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -182,15 +182,12 @@ static bool e_write (int, Lisp_Object, ptrdiff_t, 
ptrdiff_t,
 
 
 
-/* Check that ENCODED does not lie on any special directory whose
-   contents are read only.  Signal a `file-error' if it does.
-
-   If WRITE, then don't check that the file lies on `/content' on
-   Android.  This special exception allows writing to content
-   provider-supplied files.  */
+/* Establish that ENCODED is not contained within a special directory
+   whose contents are not eligible for Unix VFS operations.  Signal a
+   `file-error' with REASON if it does.  */
 
 static void
-check_mutable_filename (Lisp_Object encoded, bool write)
+check_vfs_filename (Lisp_Object encoded, const char *reason)
 {
 #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
   const char *name;
@@ -198,17 +195,10 @@ check_mutable_filename (Lisp_Object encoded, bool write)
   name = SSDATA (encoded);
 
   if (android_is_special_directory (name, "/assets"))
-    xsignal2 (Qfile_error,
-             build_string ("File lies on read-only directory"),
-             encoded);
-
-  if (write)
-    return;
+    xsignal2 (Qfile_error, build_string (reason), encoded);
 
   if (android_is_special_directory (name, "/content"))
-    xsignal2 (Qfile_error,
-             build_string ("File lies on read-only directory"),
-             encoded);
+    xsignal2 (Qfile_error, build_string (reason), encoded);
 #endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
 }
 
@@ -2287,7 +2277,6 @@ permissions.  */)
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
-  check_mutable_filename (encoded_newname, true);
 
 #ifdef WINDOWSNT
   if (NILP (ok_if_already_exists)
@@ -2553,7 +2542,7 @@ DEFUN ("make-directory-internal", 
Fmake_directory_internal,
 
   dir = SSDATA (encoded_dir);
 
-  if (mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
+  if (emacs_mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
     report_file_error ("Creating directory", directory);
 
   return Qnil;
@@ -2572,9 +2561,7 @@ DEFUN ("delete-directory-internal", 
Fdelete_directory_internal,
   encoded_dir = ENCODE_FILE (directory);
   dir = SSDATA (encoded_dir);
 
-  check_mutable_filename (encoded_dir, false);
-
-  if (rmdir (dir) != 0)
+  if (emacs_rmdir (dir) != 0)
     report_file_error ("Removing directory", directory);
 
   return Qnil;
@@ -2613,9 +2600,9 @@ With a prefix argument, TRASH is nil.  */)
     return call1 (Qmove_file_to_trash, filename);
 
   encoded_file = ENCODE_FILE (filename);
-  check_mutable_filename (encoded_file, false);
 
-  if (unlink (SSDATA (encoded_file)) != 0 && errno != ENOENT)
+  if (emacs_unlink (SSDATA (encoded_file)) != 0
+      && errno != ENOENT)
     report_file_error ("Removing old name", filename);
   return Qnil;
 }
@@ -2771,8 +2758,6 @@ This is what happens in interactive use with M-x.  */)
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
-  check_mutable_filename (encoded_file, false);
-  check_mutable_filename (encoded_newname, false);
 
   bool plain_rename = (case_only_rename
                       || (!NILP (ok_if_already_exists)
@@ -2780,8 +2765,10 @@ This is what happens in interactive use with M-x.  */)
   int rename_errno UNINIT;
   if (!plain_rename)
     {
-      if (renameat_noreplace (AT_FDCWD, SSDATA (encoded_file),
-                             AT_FDCWD, SSDATA (encoded_newname))
+      if (emacs_renameat_noreplace (AT_FDCWD,
+                                   SSDATA (encoded_file),
+                                   AT_FDCWD,
+                                   SSDATA (encoded_newname))
          == 0)
        return Qnil;
 
@@ -2803,7 +2790,8 @@ This is what happens in interactive use with M-x.  */)
 
   if (plain_rename)
     {
-      if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
+      if (emacs_rename (SSDATA (encoded_file),
+                       SSDATA (encoded_newname)) == 0)
        return Qnil;
       rename_errno = errno;
       /* Don't prompt again.  */
@@ -2884,8 +2872,10 @@ This is what happens in interactive use with M-x.  */)
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
-  check_mutable_filename (encoded_file, false);
-  check_mutable_filename (encoded_newname, false);
+  check_vfs_filename (encoded_file, "Trying to create hard link to "
+                     "file within special directory");
+  check_vfs_filename (encoded_newname, "Trying to create hard link"
+                     " within special directory");
 
   if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
     return Qnil;
@@ -2896,7 +2886,7 @@ This is what happens in interactive use with M-x.  */)
          || FIXNUMP (ok_if_already_exists))
        barf_or_query_if_file_exists (newname, true, "make it a new name",
                                      FIXNUMP (ok_if_already_exists), false);
-      unlink (SSDATA (newname));
+      emacs_unlink (SSDATA (newname));
       if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
        return Qnil;
     }
@@ -2939,10 +2929,9 @@ This happens for interactive use with M-x.  */)
 
   encoded_target = ENCODE_FILE (target);
   encoded_linkname = ENCODE_FILE (linkname);
-  check_mutable_filename (encoded_target, false);
-  check_mutable_filename (encoded_linkname, false);
 
-  if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+  if (emacs_symlink (SSDATA (encoded_target),
+                    SSDATA (encoded_linkname)) == 0)
     return Qnil;
 
   if (errno == ENOSYS)
@@ -2955,8 +2944,9 @@ This happens for interactive use with M-x.  */)
          || FIXNUMP (ok_if_already_exists))
        barf_or_query_if_file_exists (linkname, true, "make it a link",
                                      FIXNUMP (ok_if_already_exists), false);
-      unlink (SSDATA (encoded_linkname));
-      if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+      emacs_unlink (SSDATA (encoded_linkname));
+      if (emacs_symlink (SSDATA (encoded_target),
+                        SSDATA (encoded_linkname)) == 0)
        return Qnil;
     }
 
@@ -3328,27 +3318,12 @@ file_accessible_directory_p (Lisp_Object file)
         special cases "/" and "//", and it's a safe optimization
         here.  After appending '.', append another '/' to work around
         a macOS bug (Bug#30350).  */
-#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
-      if (!strncmp ("/assets/", data,
-                   sizeof "/assets" - 1))
-       {
-         static char const appended[] = "/";
-         char *buf = SAFE_ALLOCA (len + sizeof appended);
-         memcpy (buf, data, len);
-         strcpy (buf + len, &appended[data[len - 1] == '/']);
-         dir = buf;
-       }
-      else
-       {
-#endif
-         static char const appended[] = "/./";
-         char *buf = SAFE_ALLOCA (len + sizeof appended);
-         memcpy (buf, data, len);
-         strcpy (buf + len, &appended[data[len - 1] == '/']);
-         dir = buf;
-#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
-       }
-#endif
+
+      static char const appended[] = "/./";
+      char *buf = SAFE_ALLOCA (len + sizeof appended);
+      memcpy (buf, data, len);
+      strcpy (buf + len, &appended[data[len - 1] == '/']);
+      dir = buf;
     }
 
   ok = file_access_p (dir, F_OK);
@@ -3682,7 +3657,8 @@ command from GNU Coreutils.  */)
     return call4 (handler, Qset_file_modes, absname, mode, flag);
 
   encoded = ENCODE_FILE (absname);
-  check_mutable_filename (encoded, false);
+  check_vfs_filename (encoded, "Trying to change access modes of file"
+                     " within special directory");
   char *fname = SSDATA (encoded);
   mode_t imode = XFIXNUM (mode) & 07777;
   if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
@@ -3755,7 +3731,8 @@ TIMESTAMP is in the format of `current-time'. */)
     return call4 (handler, Qset_file_times, absname, timestamp, flag);
 
   Lisp_Object encoded_absname = ENCODE_FILE (absname);
-  check_mutable_filename (encoded_absname, false);
+  check_vfs_filename (encoded_absname, "Trying to set access times of"
+                     " file within special directory");
 
   if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0)
     {
@@ -5456,7 +5433,6 @@ write_region (Lisp_Object start, Lisp_Object end, 
Lisp_Object filename,
     }
 
   encoded_filename = ENCODE_FILE (filename);
-  check_mutable_filename (encoded_filename, false);
 
   fn = SSDATA (encoded_filename);
   open_flags = O_WRONLY | O_CREAT;
diff --git a/src/filelock.c b/src/filelock.c
index cbbcc016b27..fea0044e219 100644
--- a/src/filelock.c
+++ b/src/filelock.c
@@ -228,7 +228,7 @@ get_boot_time (void)
        {
          get_boot_time_1 (SSDATA (filename), 1);
          if (delete_flag)
-           unlink (SSDATA (filename));
+           emacs_unlink (SSDATA (filename));
        }
     }
 
@@ -323,11 +323,12 @@ rename_lock_file (char const *old, char const *new, bool 
force)
     {
       struct stat st;
 
-      int r = renameat_noreplace (AT_FDCWD, old, AT_FDCWD, new);
+      int r = emacs_renameat_noreplace (AT_FDCWD, old,
+                                       AT_FDCWD, new);
       if (! (r < 0 && errno == ENOSYS))
        return r;
       if (link (old, new) == 0)
-       return unlink (old) == 0 || errno == ENOENT ? 0 : -1;
+       return emacs_unlink (old) == 0 || errno == ENOENT ? 0 : -1;
       if (errno != ENOSYS && errno != LINKS_MIGHT_NOT_WORK)
        return -1;
 
@@ -347,7 +348,7 @@ rename_lock_file (char const *old, char const *new, bool 
force)
        return -1;
     }
 
-  return rename (old, new);
+  return emacs_rename (old, new);
 #endif
 }
 
@@ -365,13 +366,13 @@ create_lock_file (char *lfname, char *lock_info_str, bool 
force)
      pretending that 'symlink' does not work.  */
   int err = ENOSYS;
 #else
-  int err = symlink (lock_info_str, lfname) == 0 ? 0 : errno;
+  int err = emacs_symlink (lock_info_str, lfname) == 0 ? 0 : errno;
 #endif
 
   if (err == EEXIST && force)
     {
-      unlink (lfname);
-      err = symlink (lock_info_str, lfname) == 0 ? 0 : errno;
+      emacs_unlink (lfname);
+      err = emacs_symlink (lock_info_str, lfname) == 0 ? 0 : errno;
     }
 
   if (err == ENOSYS || err == LINKS_MIGHT_NOT_WORK || err == ENAMETOOLONG)
@@ -409,7 +410,7 @@ create_lock_file (char *lfname, char *lock_info_str, bool 
force)
          if (!err && rename_lock_file (nonce, lfname, force) != 0)
            err = errno;
          if (err)
-           unlink (nonce);
+           emacs_unlink (nonce);
        }
 
       SAFE_FREE ();
@@ -622,7 +623,7 @@ current_lock_owner (lock_info_type *owner, Lisp_Object 
lfname)
       /* The owner process is dead or has a strange pid, so try to
          zap the lockfile.  */
       else
-        return unlink (SSDATA (lfname)) < 0 ? errno : 0;
+        return emacs_unlink (SSDATA (lfname)) < 0 ? errno : 0;
     }
   else
     { /* If we wanted to support the check for stale locks on remote machines,
@@ -770,7 +771,8 @@ unlock_file (Lisp_Object fn)
   int err = current_lock_owner (0, lfname);
   if (! (err == 0 || err == ANOTHER_OWNS_IT
         || (err == I_OWN_IT
-            && (unlink (SSDATA (lfname)) == 0 || (err = errno) == ENOENT))))
+            && (emacs_unlink (SSDATA (lfname)) == 0
+                || (err = errno) == ENOENT))))
     report_file_errno ("Unlocking file", fn, err);
 
   return Qnil;
diff --git a/src/image.c b/src/image.c
index 7501838d8c4..8a8f18754ac 100644
--- a/src/image.c
+++ b/src/image.c
@@ -4234,7 +4234,7 @@ static char *
 slurp_file (image_fd fd, ptrdiff_t *size)
 {
 #if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
-  FILE *fp = fdopen (fd, "rb");
+  FILE *fp = emacs_fdopen (fd, "rb");
 
   char *buf = NULL;
   struct stat st;
@@ -8021,7 +8021,7 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
        }
 
       /* Open the image file.  */
-      fp = fdopen (fd, "rb");
+      fp = emacs_fdopen (fd, "rb");
       if (!fp)
        {
          image_error ("Cannot open image file `%s'", file);
@@ -8750,7 +8750,7 @@ jpeg_load_body (struct frame *f, struct image *img,
          return 0;
        }
 
-      fp = fdopen (fd, "rb");
+      fp = emacs_fdopen (fd, "rb");
       if (fp == NULL)
        {
          image_error ("Cannot open `%s'", file);
diff --git a/src/keyboard.c b/src/keyboard.c
index ee42b821dfa..445f39ea140 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -11740,9 +11740,9 @@ This may include sensitive information such as 
passwords.  */)
       encfile = ENCODE_FILE (file);
       fd = emacs_open (SSDATA (encfile), O_WRONLY | O_CREAT | O_EXCL, 0600);
       if (fd < 0 && errno == EEXIST
-         && (unlink (SSDATA (encfile)) == 0 || errno == ENOENT))
+         && (emacs_unlink (SSDATA (encfile)) == 0 || errno == ENOENT))
        fd = emacs_open (SSDATA (encfile), O_WRONLY | O_CREAT | O_EXCL, 0600);
-      dribble = fd < 0 ? 0 : fdopen (fd, "w");
+      dribble = fd < 0 ? 0 : emacs_fdopen (fd, "w");
       if (dribble == 0)
        report_file_error ("Opening dribble", file);
     }
diff --git a/src/lisp.h b/src/lisp.h
index 4be0002e9fc..5802984a5e7 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -5086,7 +5086,15 @@ extern int emacs_open (const char *, int, int);
 extern int emacs_open_noquit (const char *, int, int);
 extern int emacs_pipe (int[2]);
 extern int emacs_close (int);
+extern FILE *emacs_fdopen (int, const char *);
 extern int emacs_fclose (FILE *);
+extern int emacs_unlink (const char *);
+extern int emacs_symlink (const char *, const char *);
+extern int emacs_rmdir (const char *);
+extern int emacs_mkdir (const char *, mode_t);
+extern int emacs_renameat_noreplace (int, const char *, int,
+                                    const char *);
+extern int emacs_rename (const char *, const char *);
 extern ptrdiff_t emacs_read (int, void *, ptrdiff_t);
 extern ptrdiff_t emacs_read_quit (int, void *, ptrdiff_t);
 extern ptrdiff_t emacs_write (int, void const *, ptrdiff_t);
diff --git a/src/lread.c b/src/lread.c
index 11bfc52a83f..251da5670d0 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -1702,7 +1702,7 @@ Return t if the file exists and loads successfully.  */)
       stream = emacs_fopen (SSDATA (efound), fmode);
 #else
 #if !defined USE_ANDROID_ASSETS
-      stream = fdopen (fd, fmode);
+      stream = emacs_fdopen (fd, fmode);
 #else
       /* Android systems use special file descriptors which can point
         into compressed data and double as file streams.  FMODE is
diff --git a/src/process.c b/src/process.c
index 8b7b8d8566e..90885ad318a 100644
--- a/src/process.c
+++ b/src/process.c
@@ -7472,6 +7472,16 @@ handle_child_signal (int sig)
            {
              changed = true;
              if (STRINGP (XCDR (head)))
+               /* handle_child_signal is called in an async signal
+                  handler but needs to unlink temporary files which
+                  might've been created in an Android content
+                  provider.
+
+                  emacs_unlink is not async signal safe because
+                  deleting files from content providers must proceed
+                  through Java code.  Consequentially, if XCDR (head)
+                  lies on a content provider it will not be removed,
+                  which is a bug.  */
                unlink (SSDATA (XCDR (head)));
              XSETCAR (tail, Qnil);
            }
diff --git a/src/sysdep.c b/src/sysdep.c
index bec2c00d3e5..88938d15b91 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -260,7 +260,7 @@ init_standard_fds (void)
   /* Set buferr if possible on platforms defining _PC_PIPE_BUF, as
      they support the notion of atomic writes to pipes.  */
   #ifdef _PC_PIPE_BUF
-    buferr = fdopen (STDERR_FILENO, "w");
+    buferr = emacs_fdopen (STDERR_FILENO, "w");
     if (buferr)
       setvbuf (buferr, NULL, _IOLBF, 0);
   #endif
@@ -2545,7 +2545,7 @@ emacs_fopen (char const *file, char const *mode)
       }
 
   fd = emacs_open (file, omode | oflags | bflag, 0666);
-  return fd < 0 ? 0 : fdopen (fd, mode);
+  return fd < 0 ? 0 : emacs_fdopen (fd, mode);
 }
 
 /* Create a pipe for Emacs use.  */
@@ -2627,6 +2627,19 @@ emacs_close (int fd)
     }
 }
 
+/* Wrapper around fdopen.  On Android, this calls `android_fclose' to
+   clear information associated with FD if necessary.  */
+
+FILE *
+emacs_fdopen (int fd, const char *mode)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return fdopen (fd, mode);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_fdopen (fd, mode);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
 /* Wrapper around fclose.  On Android, this calls `android_fclose' to
    clear information associated with the FILE's file descriptor if
    necessary.  */
@@ -2641,6 +2654,71 @@ emacs_fclose (FILE *stream)
 #endif
 }
 
+/* Wrappers around unlink, symlink, rename, renameat_noreplace, and
+   rmdir.  These operations handle asset and content directories on
+   Android.  */
+
+int
+emacs_unlink (const char *name)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return unlink (name);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_unlink (name);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_symlink (const char *target, const char *linkname)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return symlink (target, linkname);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_symlink (target, linkname);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_rmdir (const char *dirname)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return rmdir (dirname);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_rmdir (dirname);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_mkdir (const char *dirname, mode_t mode)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return mkdir (dirname, mode);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_mkdir (dirname, mode);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_renameat_noreplace (int srcfd, const char *src,
+                         int dstfd, const char *dst)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return renameat_noreplace (srcfd, src, dstfd, dst);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_renameat_noreplace (srcfd, src, dstfd, dst);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_rename (const char *src, const char *dst)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return rename (src, dst);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_rename (src, dst);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
 /* Maximum number of bytes to read or write in a single system call.
    This works around a serious bug in Linux kernels before 2.6.16; see
    <https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=612839>.
diff --git a/src/term.c b/src/term.c
index 4de57ca1afe..9bcb2cb1386 100644
--- a/src/term.c
+++ b/src/term.c
@@ -2415,7 +2415,7 @@ frame's terminal). */)
 #else  /* !MSDOS */
       fd = emacs_open (t->display_info.tty->name, O_RDWR | O_NOCTTY, 0);
       t->display_info.tty->input = t->display_info.tty->output
-       = fd < 0 ? 0 : fdopen (fd, "w+");
+       = fd < 0 ? 0 : emacs_fdopen (fd, "w+");
 
       if (! t->display_info.tty->input)
        {
@@ -4128,7 +4128,7 @@ init_tty (const char *name, const char *terminal_type, 
bool must_succeed)
     tty->input = tty->output
       = ((fd < 0 || ! isatty (fd))
         ? NULL
-        : fdopen (fd, "w+"));
+        : emacs_fdopen (fd, "w+"));
 
     if (! tty->input)
       {



reply via email to

[Prev in Thread] Current Thread [Next in Thread]