]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix compatibility issues with Android clipboards
authorPo Lu <luangruo@yahoo.com>
Wed, 1 May 2024 03:45:53 +0000 (11:45 +0800)
committerEshel Yaron <me@eshelyaron.com>
Mon, 6 May 2024 16:30:28 +0000 (18:30 +0200)
* java/org/gnu/emacs/EmacsClipboard.java (getClipboardData):
Return an AssetFileDescriptor.

* java/org/gnu/emacs/EmacsContextMenu.java (onMenuItemClick):
Typo corrections in commentary.

* java/org/gnu/emacs/EmacsOpenActivity.java (onCreate): Raise
minimum version on which to read file descriptors from
ParcelFileDescriptor objects to Honeycomb.

* java/org/gnu/emacs/EmacsSdk11Clipboard.java
(getClipboardData): Return the asset file descriptor.

* java/org/gnu/emacs/EmacsSdk8Clipboard.java (getClipboardData):
Adjust return type to match.

* src/android.h (struct android_parcel_file_descriptor_class):
Move from androidselect.c.

* src/androidselect.c (fd_class): Export function.
(android_init_emacs_clipboard): Adjust signature of
getClipboardData.
(android_init_asset_file_descriptor, close_asset_fd)
(extract_fd_offsets): New functions.
(Fandroid_get_clipboard_data): Extract file descriptor and
offset from the AssetFileDescriptor here, rather than in
getClipboardData.
(init_androidselect): Call android_init_asset_file_descriptor.

* src/androidvfs.c (android_init_fd_class): Export and enable
calling this function more than once.

(cherry picked from commit 2451456695d0e03b89365cbbe64effb2f99af2d5)

java/org/gnu/emacs/EmacsClipboard.java
java/org/gnu/emacs/EmacsContextMenu.java
java/org/gnu/emacs/EmacsOpenActivity.java
java/org/gnu/emacs/EmacsSdk11Clipboard.java
java/org/gnu/emacs/EmacsSdk8Clipboard.java
src/android.h
src/androidselect.c
src/androidvfs.c

index 9db436ca1e2c92e131735ed34ab23592b25f6fbb..f27d96129ef754d18f7d0d2e05c0f92aa9682a7d 100644 (file)
@@ -19,6 +19,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 package org.gnu.emacs;
 
+import android.content.res.AssetFileDescriptor;
 import android.os.Build;
 
 /* This class provides helper code for accessing the clipboard,
@@ -32,7 +33,7 @@ public abstract class EmacsClipboard
   public abstract byte[] getClipboard ();
 
   public abstract byte[][] getClipboardTargets ();
-  public abstract long[] getClipboardData (byte[] target);
+  public abstract AssetFileDescriptor getClipboardData (byte[] target);
 
   /* Create the correct kind of clipboard for this system.  */
 
index 2bbf2a313d655c0dd7249cb3a63ebce1c11d169f..0f52d45455f7a72bd8149df8ef3ea6ab73e905bf 100644 (file)
@@ -108,8 +108,8 @@ public final class EmacsContextMenu
             will normally confuse Emacs into thinking that the
             context menu has been dismissed.  Wrong!
 
-            Setting this flag makes EmacsActivity to only handle
-            SubMenuBuilder being closed, which always means the menu
+            Setting this flag prompts EmacsActivity to only handle
+            SubMenuBuilders being closed, which always means the menu
             has actually been dismissed.
 
             However, these extraneous events aren't sent on devices
index 327a53bc417bf668b17eaed0b00f79c7579ab3e3..cdc68aea2bf6088b84af035f46246942251a8045 100644 (file)
@@ -19,29 +19,23 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 package org.gnu.emacs;
 
-/* This class makes the Emacs server work reasonably on Android.
+/* Opening external documents on Android.
 
-   There is no way to make the Unix socket publicly available on
-   Android.
+   This activity is registered as an application capable of opening text
+   files and files in several other formats that Emacs understands, and
+   assumes responsibility for deriving file names from the files
+   provided to `onCreate', potentially copying them to temporary
+   directories in the process, and invoking `emacsclient' with suitable
+   arguments to open the same.  In this respect, it fills the role of
+   `etc/emacs.desktop' on XDG systems.
 
-   Instead, this activity tries to connect to the Emacs server, to
-   make it open files the system asks Emacs to open, and to emulate
-   some reasonable behavior when Emacs has not yet started.
+   It is also registered as a handler for mailto URIs, in which capacity
+   it constructs invocations of `emacsclient' so as to start
+   `message-mailto' with their contents and attachments, much like
+   `etc/emacs-mail.desktop'.
 
-   First, Emacs registers itself as an application that can open text
-   and image files.
-
-   Then, when the user is asked to open a file and selects ``Emacs''
-   as the application that will open the file, the system pops up a
-   window, this activity, and calls the `onCreate' function.
-
-   `onCreate' then tries very to find the file name of the file that
-   was selected, and give it to emacsclient.
-
-   If emacsclient successfully opens the file, then this activity
-   starts EmacsActivity (to bring it on to the screen); otherwise, it
-   displays the output of emacsclient or any error message that occurs
-   and exits.  */
+   As with all other activities, it is registered in the package
+   manifest file.  */
 
 import android.app.AlertDialog;
 import android.app.Activity;
@@ -628,11 +622,12 @@ public final class EmacsOpenActivity extends Activity
 
            if (scheme.equals ("content")
                /* Retrieving the native file descriptor of a
-                  ParcelFileDescriptor requires Honeycomb, and
+                  ParcelFileDescriptor requires Honeycomb MR1, and
                   proceeding without this capability is pointless on
                   systems before KitKat, since Emacs doesn't support
                   opening content files on those.  */
-               && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+               && (Build.VERSION.SDK_INT
+                   >= Build.VERSION_CODES.HONEYCOMB_MR1))
              {
                /* This is one of the annoying Android ``content''
                   URIs.  Most of the time, there is actually an
index 850bb6c8deb59e27f1483125b3dc13e3fd5ec1aa..71381b0f114e9b1ddc9e518a3f729bf0bec10776 100644 (file)
@@ -207,8 +207,9 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
   /* Return the clipboard data for the given target, or NULL if it
      does not exist.
 
-     Value is normally an array of three longs: the file descriptor,
-     the start offset of the data, and its length; length may be
+     Value is normally an asset file descriptor, which in turn holds
+     three important values: the file descriptor, the start offset of
+     the data, and its length; length may be
      AssetFileDescriptor.UNKNOWN_LENGTH, meaning that the data extends
      from that offset to the end of the file.
 
@@ -217,15 +218,13 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
      solely of a URI.  */
 
   @Override
-  public long[]
+  public AssetFileDescriptor
   getClipboardData (byte[] target)
   {
     ClipData data;
     String mimeType;
-    int fd;
     AssetFileDescriptor assetFd;
     Uri uri;
-    long[] value;
 
     /* Decode the target given by Emacs.  */
     try
@@ -245,8 +244,6 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
     if (data == null || data.getItemCount () < 1)
       return null;
 
-    fd = -1;
-
     try
       {
        uri = data.getItemAt (0).getUri ();
@@ -257,52 +254,15 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
        /* Now open the file descriptor.  */
        assetFd = resolver.openTypedAssetFileDescriptor (uri, mimeType,
                                                         null);
-
-       /* Duplicate the file descriptor.  */
-       fd = assetFd.getParcelFileDescriptor ().getFd ();
-       fd = EmacsNative.dup (fd);
-
-       /* Return the relevant information.  */
-       value = new long[] { fd, assetFd.getStartOffset (),
-                            assetFd.getLength (), };
-
-       /* Close the original offset.  */
-       assetFd.close ();
+       return assetFd;
       }
     catch (SecurityException e)
       {
-       /* Guarantee a file descriptor duplicated or detached is
-          ultimately closed if an error arises.  */
-
-       if (fd != -1)
-         EmacsNative.close (fd);
-
        return null;
       }
     catch (FileNotFoundException e)
       {
-       /* Guarantee a file descriptor duplicated or detached is
-          ultimately closed if an error arises.  */
-
-       if (fd != -1)
-         EmacsNative.close (fd);
-
        return null;
       }
-    catch (IOException e)
-      {
-       /* Guarantee a file descriptor duplicated or detached is
-          ultimately closed if an error arises.  */
-
-       if (fd != -1)
-         EmacsNative.close (fd);
-
-       return null;
-      }
-
-    /* Don't return value if the file descriptor couldn't be
-       created.  */
-
-    return fd != -1 ? value : null;
   }
 };
index 418f55c12c12615d2c144581785dfb030fc730c0..3d0504b192458a7b39f2ee083695f2f8e4ee83b9 100644 (file)
@@ -25,6 +25,8 @@ package org.gnu.emacs;
 import android.text.*;
 
 import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+
 import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
@@ -129,9 +131,10 @@ public final class EmacsSdk8Clipboard extends EmacsClipboard
   /* Return the clipboard data for the given target, or NULL if it
      does not exist.
 
-     Value is normally an array of three longs: the file descriptor,
-     the start offset of the data, and its length; length may be
-     AssetFileDescriptor.UNKOWN_LENGTH, meaning that the data extends
+     Value is normally an asset file descriptor, which in turn holds
+     three important values: the file descriptor, the start offset of
+     the data, and its length; length may be
+     AssetFileDescriptor.UNKNOWN_LENGTH, meaning that the data extends
      from that offset to the end of the file.
 
      Do not use this function to open text targets; use `getClipboard'
@@ -139,7 +142,7 @@ public final class EmacsSdk8Clipboard extends EmacsClipboard
      solely of a URI.  */
 
   @Override
-  public long[]
+  public AssetFileDescriptor
   getClipboardData (byte[] target)
   {
     return null;
index 19adfa38087ec429ba4b3c79d1e54dfa38a08107..7074ca2630c303e0e7faa38229948e6ed1dbc885 100644 (file)
@@ -53,6 +53,22 @@ extern char *android_user_full_name (struct passwd *);
 
 \f
 
+/* 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 ParcelFileDescriptor class.  */
+extern struct android_parcel_file_descriptor_class fd_class;
+
+extern void android_init_fd_class (JNIEnv *);
+
 /* File I/O operations.  Many of these are defined in
    androidvfs.c.  */
 
index 2f6114d0fcbacc1e6d660d72cccfd97b5f13f51d..04d04d326d92820865b5d6700172a5f6b4b837e0 100644 (file)
@@ -21,6 +21,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <assert.h>
 #include <minmax.h>
 #include <unistd.h>
+#include <dlfcn.h>
 
 #include <boot-time.h>
 #include <sys/types.h>
@@ -100,7 +101,7 @@ android_init_emacs_clipboard (void)
   FIND_METHOD (get_clipboard_targets, "getClipboardTargets",
               "()[[B");
   FIND_METHOD (get_clipboard_data, "getClipboardData",
-              "([B)[J");
+              "([B)Landroid/content/res/AssetFileDescriptor;");
 
   clipboard_class.make_clipboard
     = (*android_java_env)->GetStaticMethodID (android_java_env,
@@ -340,6 +341,62 @@ data type available from the clipboard.  */)
   return Qnil;
 }
 
+\f
+
+struct android_asset_file_descriptor
+{
+  jclass class;
+  jmethodID close;
+  jmethodID get_length;
+  jmethodID get_start_offset;
+  jmethodID get_file_descriptor;
+  jmethodID get_parcel_file_descriptor;
+  jmethodID get_fd;
+};
+
+/* Methods associated with the AssetFileDescriptor class.  */
+static struct android_asset_file_descriptor asset_fd_class;
+
+/* Initialize virtual function IDs and class pointers in connection with
+   the AssetFileDescriptor class.  */
+
+static void
+android_init_asset_file_descriptor (void)
+{
+  jclass old;
+
+  asset_fd_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "android/content/res/"
+                                     "AssetFileDescriptor");
+  eassert (asset_fd_class.class);
+
+  old = asset_fd_class.class;
+  asset_fd_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!asset_fd_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  asset_fd_class.c_name                                                \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       asset_fd_class.class,   \
+                                       name, signature);       \
+  eassert (asset_fd_class.c_name);
+
+  FIND_METHOD (close, "close", "()V");
+  FIND_METHOD (get_length, "getLength", "()J");
+  FIND_METHOD (get_start_offset, "getStartOffset", "()J");
+  FIND_METHOD (get_file_descriptor, "getFileDescriptor",
+              "()Ljava/io/FileDescriptor;");
+  FIND_METHOD (get_parcel_file_descriptor, "getParcelFileDescriptor",
+              "()Landroid/os/ParcelFileDescriptor;");
+#undef FIND_METHOD
+}
+
 /* Free the memory inside PTR, a pointer to a char pointer.  */
 
 static void
@@ -348,6 +405,125 @@ android_xfree_inside (void *ptr)
   xfree (*(char **) ptr);
 }
 
+/* Close the referent of, then delete, the local reference to an asset
+   file descriptor referenced by AFD.  */
+
+static void
+close_asset_fd (void *afd)
+{
+  jobject *afd_1;
+
+  afd_1 = afd;
+  (*android_java_env)->CallVoidMethod (android_java_env, *afd_1,
+                                      asset_fd_class.close);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (*afd_1);
+}
+
+/* Return the offset, file descriptor and length of the data contained
+   in the asset file descriptor AFD, in *FD, *OFFSET, and *LENGTH.
+   Value is 0 upon success, 1 otherwise.  */
+
+static int
+extract_fd_offsets (jobject afd, int *fd, jlong *offset, jlong *length)
+{
+  jobject java_fd;
+  void *handle;
+#if __ANDROID_API__ <= 11
+  static int (*jniGetFDFromFileDescriptor) (JNIEnv *, jobject);
+#endif /* __ANDROID_API__ <= 11 */
+  static int (*AFileDescriptor_getFd) (JNIEnv *, jobject);;
+  jmethodID method;
+
+  method  = asset_fd_class.get_start_offset;
+  *offset = (*android_java_env)->CallLongMethod (android_java_env,
+                                                afd, method);
+  android_exception_check ();
+  method  = asset_fd_class.get_length;
+  *length = (*android_java_env)->CallLongMethod (android_java_env,
+                                                afd, method);
+  android_exception_check ();
+
+#if __ANDROID_API__ <= 11
+  if (android_get_current_api_level () <= 11)
+    {
+      /* Load libnativehelper and link to a private interface that is
+        the only means of retrieving the file descriptor from an asset
+        file descriptor on these systems.  */
+
+      if (!jniGetFDFromFileDescriptor)
+       {
+         handle = dlopen ("libnativehelper.so",
+                          RTLD_LAZY | RTLD_GLOBAL);
+         if (!handle)
+           goto failure;
+         jniGetFdFromFileDescriptor = dlsym (handle,
+                                             "jniGetFDFromFileDescriptor");
+         if (!jniGetFdFromFileDescriptor)
+           goto failure;
+       }
+
+      method  = asset_fd_class.get_file_descriptor;
+      java_fd = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                      afd, method);
+      android_exception_check ();
+      *fd = (*jniGetFDFromFileDescriptor) (android_java_env, java_fd);
+      ANDROID_DELETE_LOCAL_REF (java_fd);
+
+      if (*fd >= 0)
+       return 0;
+    }
+  else
+#endif /* __ANDROID_API__ <= 11 */
+#if __ANDROID_API__ <= 30
+  if (android_get_current_api_level () <= 30)
+    {
+      /* Convert this AssetFileDescriptor into a ParcelFileDescriptor,
+        whose getFd method will return its native file descriptor.  */
+      method  = asset_fd_class.get_parcel_file_descriptor;
+      java_fd = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                      afd, method);
+      android_exception_check ();
+
+      /* Initialize fd_class if not already complete.  */
+      android_init_fd_class (android_java_env);
+      *fd = (*android_java_env)->CallIntMethod (android_java_env,
+                                               java_fd,
+                                               fd_class.get_fd);
+      if (*fd >= 0)
+       return 0;
+    }
+  else
+#endif /* __ANDROID_API__ <= 30 */
+    {
+      /* Load libnativehelper (now a public interface) and link to
+        AFileDescriptor_getFd.  */
+      if (!AFileDescriptor_getFd)
+       {
+         handle = dlopen ("libnativehelper.so",
+                          RTLD_LAZY | RTLD_GLOBAL);
+         if (!handle)
+           goto failure;
+         AFileDescriptor_getFd = dlsym (handle, "AFileDescriptor_getFd");
+         if (!AFileDescriptor_getFd)
+           goto failure;
+       }
+
+      method  = asset_fd_class.get_file_descriptor;
+      java_fd = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                      afd, method);
+      android_exception_check ();
+      *fd = (*AFileDescriptor_getFd) (android_java_env, java_fd);
+      ANDROID_DELETE_LOCAL_REF (java_fd);
+
+      if (*fd >= 0)
+       return 0;
+    }
+
+ failure:
+  return 1;
+}
+
 DEFUN ("android-get-clipboard-data", Fandroid_get_clipboard_data,
        Sandroid_get_clipboard_data, 1, 1, 0,
        doc: /* Return the clipboard data of the given MIME TYPE.
@@ -361,12 +537,12 @@ does not have any corresponding data.  In that case, use
 `android-get-clipboard' instead.  */)
   (Lisp_Object type)
 {
-  jlongArray array;
+  jobject afd;
   jbyteArray bytes;
   jmethodID method;
   int fd;
   ptrdiff_t rc;
-  jlong offset, length, *longs;
+  jlong offset, length;
   specpdl_ref ref;
   char *buffer, *start;
 
@@ -387,36 +563,25 @@ does not have any corresponding data.  In that case, use
   android_exception_check ();
 
   method = clipboard_class.get_clipboard_data;
-  array = (*android_java_env)->CallObjectMethod (android_java_env,
-                                                clipboard, method,
-                                                bytes);
+  afd = (*android_java_env)->CallObjectMethod (android_java_env,
+                                              clipboard, method,
+                                              bytes);
   android_exception_check_1 (bytes);
   ANDROID_DELETE_LOCAL_REF (bytes);
 
-  if (!array)
+  if (!afd)
     goto fail;
 
-  longs = (*android_java_env)->GetLongArrayElements (android_java_env,
-                                                    array, NULL);
-  android_exception_check_nonnull (longs, array);
-
-  /* longs[0] is the file descriptor.
-     longs[1] is an offset to apply to the file.
-     longs[2] is either -1, or the number of bytes to read from the
-     file.  */
-  fd = longs[0];
-  offset = longs[1];
-  length = longs[2];
+  /* Extract the file descriptor from the AssetFileDescriptor
+     object.  */
+  ref = SPECPDL_INDEX ();
+  record_unwind_protect_ptr (close_asset_fd, &afd);
 
-  (*android_java_env)->ReleaseLongArrayElements (android_java_env,
-                                                array, longs,
-                                                JNI_ABORT);
-  ANDROID_DELETE_LOCAL_REF (array);
+  if (extract_fd_offsets (afd, &fd, &offset, &length))
+    return unbind_to (ref, Qnil);
   unblock_input ();
 
-  /* Now begin reading from longs[0].  */
-  ref = SPECPDL_INDEX ();
-  record_unwind_protect_int (close_file_unwind, fd);
+  /* Now begin reading from fd.  */
 
   if (length != -1)
     {
@@ -1004,6 +1169,7 @@ init_androidselect (void)
     return;
 
   android_init_emacs_clipboard ();
+  android_init_asset_file_descriptor ();
   android_init_emacs_desktop_notification ();
 
   make_clipboard = clipboard_class.make_clipboard;
index c4b3dba4af07d54b45dbb1b498b68d45ab5a0606..38bec7d349a9b354cff805c73dd84ad00c35b521 100644 (file)
@@ -290,17 +290,6 @@ struct emacs_directory_entry_class
   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.  */
 jclass java_string_class;
 
@@ -313,7 +302,7 @@ 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;
+struct android_parcel_file_descriptor_class fd_class;
 
 /* Global references to several exception classes.  */
 static jclass file_not_found_exception, security_exception;
@@ -380,13 +369,18 @@ android_init_entry_class (JNIEnv *env)
 }
 
 
-/* Initialize `fd_class' using the given JNI environment ENV.  Calling
-   this function is not necessary on Android 4.4 and earlier.  */
+/* Initialize `fd_class' using the given JNI environment ENV.  Called on
+   API 12 (Android 3.1) and later by androidselect.c and on 5.0 and
+   later in this file.  */
 
-static void
+void
 android_init_fd_class (JNIEnv *env)
 {
   jclass old;
+  static bool fd_class_initialized;
+
+  if (fd_class_initialized)
+    return;
 
   fd_class.class
     = (*env)->FindClass (env, "android/os/ParcelFileDescriptor");
@@ -409,6 +403,8 @@ android_init_fd_class (JNIEnv *env)
   FIND_METHOD (get_fd, "getFd", "()I");
   FIND_METHOD (detach_fd, "detachFd", "()I");
 #undef FIND_METHOD
+
+  fd_class_initialized = true;
 }
 
 \f