summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPo Lu <luangruo@yahoo.com>2024-05-01 11:45:53 +0800
committerPo Lu <luangruo@yahoo.com>2024-05-01 11:46:31 +0800
commit2451456695d0e03b89365cbbe64effb2f99af2d5 (patch)
treeb6bcd3e227454b436ab4ee0fad447d946b5179cf
parent294335b2304028cc97aca036bd37adf2f8e1c508 (diff)
downloademacs-2451456695d0e03b89365cbbe64effb2f99af2d5.tar.gz
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.
-rw-r--r--java/org/gnu/emacs/EmacsClipboard.java3
-rw-r--r--java/org/gnu/emacs/EmacsContextMenu.java4
-rw-r--r--java/org/gnu/emacs/EmacsOpenActivity.java39
-rw-r--r--java/org/gnu/emacs/EmacsSdk11Clipboard.java50
-rw-r--r--java/org/gnu/emacs/EmacsSdk8Clipboard.java11
-rw-r--r--src/android.h16
-rw-r--r--src/androidselect.c216
-rw-r--r--src/androidvfs.c26
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 <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. */
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 <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
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 <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;
}
+
+
+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;
}