emacs-diffs
[Top][All Lists]
Advanced

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

feature/android 7aa4ffddd84 2/3: Update Android port


From: Po Lu
Subject: feature/android 7aa4ffddd84 2/3: Update Android port
Date: Tue, 21 Feb 2023 06:09:11 -0500 (EST)

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

    Update Android port
    
    * doc/emacs/android.texi (Android Startup): Document `content'
    special directory.
    * java/debug.sh (is_root): Improve /bin/tee detection.
    * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
    function `dup'.
    * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity)
    (checkReadableOrCopy, onCreate): Create content directory names
    when the file is not readable.
    * java/org/gnu/emacs/EmacsService.java (EmacsService)
    (openContentUri, checkContentUri): New functions.
    * src/android.c (struct android_emacs_service): New methods.
    (android_content_name_p, android_get_content_name)
    (android_check_content_access): New function.
    (android_fstatat, android_open): Implement opening content URIs.
    (dup): Export to Java.
    (android_init_emacs_service): Initialize new methods.
    (android_faccessat): Implement content file names.
---
 doc/emacs/android.texi                    |  12 +-
 java/debug.sh                             |   2 +-
 java/org/gnu/emacs/EmacsNative.java       |   4 +
 java/org/gnu/emacs/EmacsOpenActivity.java |  42 +++++-
 java/org/gnu/emacs/EmacsService.java      | 119 +++++++++++++++++
 src/android.c                             | 213 +++++++++++++++++++++++++++++-
 6 files changed, 379 insertions(+), 13 deletions(-)

diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index d070199d325..f176d68ae67 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -129,9 +129,15 @@ file, it invokes @command{emacsclient} with the options
 and the name of the file being opened.  Then, upon success, the focus
 is transferred to any open Emacs frame.
 
-It is sadly impossible to open certain kinds of files which are
-provided by a ``content provider''.  When that is the case, a dialog
-is displayed with an explanation of the error.
+@cindex /content directory, android
+  Some files are given to Emacs as ``content identifiers'', which the
+system provides access to outside the normal filesystem APIs.  Emacs
+internally supports a temporary @file{/content} directory which is
+used 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 the file is copied to a temporary directory instead.
 
 @node Android File System
 @section What files Emacs can access under Android
diff --git a/java/debug.sh b/java/debug.sh
index 30e5a94eee5..f07bb98ed7d 100755
--- a/java/debug.sh
+++ b/java/debug.sh
@@ -281,7 +281,7 @@ else
     # Upload the specified gdbserver binary to the device.
     adb -s $device push "$gdbserver" "$gdbserver_bin"
 
-    if (adb -s $device pull /system/bin/tee /dev/null &> /dev/null); then
+    if adb -s $device shell ls /system/bin/tee; then
        # Copy it to the user directory.
        adb -s $device shell "$gdbserver_cat"
        adb -s $device shell "run-as $package chmod +x gdbserver"
diff --git a/java/org/gnu/emacs/EmacsNative.java 
b/java/org/gnu/emacs/EmacsNative.java
index ef1a0e75a5c..f0917a68120 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -31,6 +31,10 @@ public class EmacsNative
      initialization.  */
   private static final String[] libraryDeps;
 
+
+  /* Like `dup' in C.  */
+  public static native int dup (int fd);
+
   /* Obtain the fingerprint of this build of Emacs.  The fingerprint
      can be used to determine the dump file name.  */
   public static native String getFingerprint ();
diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java 
b/java/org/gnu/emacs/EmacsOpenActivity.java
index c8501d91025..87ce454a816 100644
--- a/java/org/gnu/emacs/EmacsOpenActivity.java
+++ b/java/org/gnu/emacs/EmacsOpenActivity.java
@@ -57,6 +57,8 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
+import android.util.Log;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -69,6 +71,8 @@ import java.io.UnsupportedEncodingException;
 public class EmacsOpenActivity extends Activity
   implements DialogInterface.OnClickListener
 {
+  private static final String TAG = "EmacsOpenActivity";
+
   private class EmacsClientThread extends Thread
   {
     private ProcessBuilder builder;
@@ -178,12 +182,16 @@ public class EmacsOpenActivity extends Activity
     dialog.show ();
   }
 
-  /* Check that the specified FILE is readable.  If it is not, then
-     copy the file in FD to a location in the system cache
-     directory and return the name of that file.  */
+  /* Check that the specified FILE is readable.  If Android 4.4 or
+     later is being used, return URI formatted into a `/content/' file
+     name.
+
+     If it is not, then copy the file in FD to a location in the
+     system cache directory and return the name of that file.  */
 
   private String
-  checkReadableOrCopy (String file, ParcelFileDescriptor fd)
+  checkReadableOrCopy (String file, ParcelFileDescriptor fd,
+                      Uri uri)
     throws IOException, FileNotFoundException
   {
     File inFile;
@@ -191,12 +199,34 @@ public class EmacsOpenActivity extends Activity
     InputStream stream;
     byte buffer[];
     int read;
+    String content;
+
+    Log.d (TAG, "checkReadableOrCopy: " + file);
 
     inFile = new File (file);
 
-    if (inFile.setReadable (true))
+    if (inFile.canRead ())
       return file;
 
+    Log.d (TAG, "checkReadableOrCopy: NO");
+
+    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 ();
+
+       Log.d (TAG, "checkReadableOrCopy: " + content);
+
+       return content;
+      }
+
     /* inFile is now the file being written to.  */
     inFile = new File (getCacheDir (), inFile.getName ());
     buffer = new byte[4098];
@@ -398,7 +428,7 @@ public class EmacsOpenActivity extends Activity
                    if (names != null)
                      fileName = new String (names, "UTF-8");
 
-                   fileName = checkReadableOrCopy (fileName, fd);
+                   fileName = checkReadableOrCopy (fileName, fd, uri);
                  }
                catch (FileNotFoundException exception)
                  {
diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
index ba6ec485d62..c9701ff2990 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -19,7 +19,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 package org.gnu.emacs;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
 import java.util.List;
 import java.util.ArrayList;
 
@@ -41,22 +44,31 @@ import android.app.Service;
 
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 
+import android.database.Cursor;
+import android.database.MatrixCursor;
+
+
 import android.net.Uri;
 
 import android.os.Build;
 import android.os.Looper;
 import android.os.IBinder;
 import android.os.Handler;
+import android.os.ParcelFileDescriptor;
 import android.os.Vibrator;
 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;
 
@@ -79,6 +91,7 @@ public class EmacsService extends Service
 
   private EmacsThread thread;
   private Handler handler;
+  private ContentResolver resolver;
 
   /* Keep this in synch with androidgui.h.  */
   public static final int IC_MODE_NULL   = 0;
@@ -193,6 +206,7 @@ public class EmacsService extends Service
     metrics = getResources ().getDisplayMetrics ();
     pixelDensityX = metrics.xdpi;
     pixelDensityY = metrics.ydpi;
+    resolver = getContentResolver ();
 
     try
       {
@@ -643,4 +657,109 @@ public class EmacsService extends Service
     window.view.setICMode (icMode);
     window.view.imManager.restartInput (window.view);
   }
+
+  /* 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.
+
+     Value is the resulting file descriptor or -1 upon failure.  */
+
+  public int
+  openContentUri (byte[] bytes, boolean writable, boolean readable,
+                 boolean truncate)
+  {
+    String name, mode;
+    ParcelFileDescriptor fd;
+    int i;
+
+    /* Figure out the file access mode.  */
+
+    mode = "";
+
+    if (readable)
+      mode += "r";
+
+    if (writable)
+      mode += "w";
+
+    if (truncate)
+      mode += "t";
+
+    /* Try to open an associated ParcelFileDescriptor.  */
+
+    try
+      {
+       /* The usual file name encoding question rears its ugly head
+          again.  */
+       name = new String (bytes, "UTF-8");
+       Log.d (TAG, "openContentUri: " + Uri.parse (name));
+
+       fd = resolver.openFileDescriptor (Uri.parse (name), mode);
+
+       /* Use detachFd on newer versions of Android or plain old
+          dup.  */
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
+         {
+           i = fd.detachFd ();
+           fd.close ();
+
+           return i;
+         }
+       else
+         {
+           i = EmacsNative.dup (fd.getFd ());
+           fd.close ();
+
+           return i;
+         }
+      }
+    catch (Exception exception)
+      {
+       return -1;
+      }
+  }
+
+  public boolean
+  checkContentUri (byte[] string, boolean readable, boolean writable)
+  {
+    String mode, name;
+    ParcelFileDescriptor fd;
+
+    /* Decode this into a URI.  */
+
+    try
+      {
+       /* 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)
+      {
+       name = null;
+       throw new RuntimeException (exception);
+      }
+
+    mode = "r";
+
+    if (writable)
+      mode += "w";
+
+    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;
+      }
+  }
 };
diff --git a/src/android.c b/src/android.c
index fc5e6d278ed..75a97f9db33 100644
--- a/src/android.c
+++ b/src/android.c
@@ -108,6 +108,8 @@ struct android_emacs_service
   jmethodID restart_emacs;
   jmethodID update_ic;
   jmethodID reset_ic;
+  jmethodID open_content_uri;
+  jmethodID check_content_uri;
 };
 
 struct android_emacs_pixmap
@@ -941,6 +943,102 @@ android_get_asset_name (const char *filename)
   return NULL;
 }
 
+/* Return whether or not the specified FILENAME actually resolves to a
+   content resolver URI.  */
+
+static bool
+android_content_name_p (const char *filename)
+{
+  return (!strcmp (filename, "/content")
+         || !strncmp (filename, "/content/",
+                      sizeof "/content/" - 1));
+}
+
+/* Return the content URI corresponding to a `/content' file name,
+   or NULL if it is not a content URI.
+
+   This function is not reentrant.  */
+
+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;
+
+  /* First handle content ``URIs'' without a provider.  */
+
+  if (!strcmp (filename, "/content")
+      || !strcmp (filename, "/content/"))
+    return "content://";
+
+  /* Next handle ordinary file names.  */
+
+  if (strncmp (filename, "/content/", sizeof "/content/" - 1))
+    return NULL;
+
+  /* Forward past the first directory specifying the schema.  */
+
+  copy = xstrdup (filename + sizeof "/content");
+  token = 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);
+      assert ((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 ();
+  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.  */
 
@@ -976,6 +1074,7 @@ android_fstatat (int dirfd, const char *restrict pathname,
   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.  */
@@ -1027,6 +1126,23 @@ android_fstatat (int dirfd, const char *restrict 
pathname,
       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);
 }
 
@@ -1316,6 +1432,8 @@ android_open (const char *filename, int oflag, int mode)
   AAsset *asset;
   int fd;
   off_t out_start, out_length;
+  size_t length;
+  jobject string;
 
   if (asset_manager && (name = android_get_asset_name (filename)))
     {
@@ -1329,7 +1447,7 @@ android_open (const char *filename, int oflag, int mode)
 
       if (oflag & O_DIRECTORY)
        {
-         errno = EINVAL;
+         errno = ENOTSUP;
          return -1;
        }
 
@@ -1396,7 +1514,7 @@ android_open (const char *filename, int oflag, int mode)
          /* 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;;
+           = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
 
          /* Owned by root.  */
          android_table[fd].statb.st_uid = 0;
@@ -1411,6 +1529,64 @@ android_open (const char *filename, int oflag, int mode)
       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 (mode & O_CLOEXEC)
+       android_close_on_exec (fd);
+
+      ANDROID_DELETE_LOCAL_REF (string);
+      return fd;
+    }
+
   return open (filename, oflag, mode);
 }
 
@@ -1488,6 +1664,12 @@ android_proc_name (int fd, char *buffer, size_t size)
 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
 #endif
 
+JNIEXPORT jint JNICALL
+NATIVE_NAME (dup) (JNIEnv *env, jobject object, jint fd)
+{
+  return dup (fd);
+}
+
 JNIEXPORT jstring JNICALL
 NATIVE_NAME (getFingerprint) (JNIEnv *env, jobject object)
 {
@@ -1795,6 +1977,10 @@ android_init_emacs_service (void)
               "(Lorg/gnu/emacs/EmacsWindow;IIII)V");
   FIND_METHOD (reset_ic, "resetIC",
               "(Lorg/gnu/emacs/EmacsWindow;I)V");
+  FIND_METHOD (open_content_uri, "openContentUri",
+              "([BZZZ)I");
+  FIND_METHOD (check_content_uri, "checkContentUri",
+              "([BZZ)Z");
 #undef FIND_METHOD
 }
 
@@ -4577,7 +4763,28 @@ android_faccessat (int dirfd, const char *pathname, int 
mode, int flags)
   if (dirfd == AT_FDCWD
       && asset_manager
       && (asset = android_get_asset_name (pathname)))
-    return !android_file_access_p (asset, mode);
+    {
+      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
   return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);



reply via email to

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