From 167f0b08ce6cccc6035ae8290adf5fcf302fcd10 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 1 May 2024 11:45:53 +0800 Subject: [PATCH] Fix compatibility issues with Android clipboards * 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 | 3 +- java/org/gnu/emacs/EmacsContextMenu.java | 4 +- java/org/gnu/emacs/EmacsOpenActivity.java | 39 ++-- java/org/gnu/emacs/EmacsSdk11Clipboard.java | 50 +---- java/org/gnu/emacs/EmacsSdk8Clipboard.java | 11 +- src/android.h | 16 ++ src/androidselect.c | 216 +++++++++++++++++--- src/androidvfs.c | 26 +-- 8 files changed, 251 insertions(+), 114 deletions(-) diff --git a/java/org/gnu/emacs/EmacsClipboard.java b/java/org/gnu/emacs/EmacsClipboard.java index 9db436ca1e2..f27d96129ef 100644 --- a/java/org/gnu/emacs/EmacsClipboard.java +++ b/java/org/gnu/emacs/EmacsClipboard.java @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ 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. */ diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 2bbf2a313d6..0f52d45455f 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -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 diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 327a53bc417..cdc68aea2bf 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -19,29 +19,23 @@ along with GNU Emacs. If not, see . */ 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 diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java index 850bb6c8deb..71381b0f114 100644 --- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -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; } }; diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java index 418f55c12c1..3d0504b1924 100644 --- a/java/org/gnu/emacs/EmacsSdk8Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -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; diff --git a/src/android.h b/src/android.h index 19adfa38087..7074ca2630c 100644 --- a/src/android.h +++ b/src/android.h @@ -53,6 +53,22 @@ extern char *android_user_full_name (struct passwd *); +/* 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. */ diff --git a/src/androidselect.c b/src/androidselect.c index 2f6114d0fcb..04d04d326d9 100644 --- a/src/androidselect.c +++ b/src/androidselect.c @@ -21,6 +21,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include @@ -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; } + + +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; diff --git a/src/androidvfs.c b/src/androidvfs.c index c4b3dba4af0..38bec7d349a 100644 --- a/src/androidvfs.c +++ b/src/androidvfs.c @@ -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; } -- 2.39.5