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.
This commit is contained in:
Po Lu 2024-05-01 11:45:53 +08:00
parent 294335b230
commit 2451456695
8 changed files with 251 additions and 114 deletions

View file

@ -19,6 +19,7 @@
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. */

View file

@ -108,8 +108,8 @@ private static final class Item implements MenuItem.OnMenuItemClickListener
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

View file

@ -19,29 +19,23 @@
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 @@ else if (scheme != null
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

View 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;
}
};

View file

@ -25,6 +25,8 @@
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;

View file

@ -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. */

View 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;
}
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);
/* Extract the file descriptor from the AssetFileDescriptor
object. */
ref = SPECPDL_INDEX ();
record_unwind_protect_ptr (close_asset_fd, &afd);
/* 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];
(*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;

View 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;
}