From 93104cff532f932bcea65d02a59c916767a31645 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 15 Oct 2023 13:10:34 +0800 Subject: [PATCH] Correctly receive files through Android DND * java/org/gnu/emacs/EmacsService.java (getUsefulContentResolver) (getContentResolverContext): New functions which return a content resolver from an EmacsActivity, if at all possible. (openContentUri, checkContentUri): Probe or open URIs through such content resolvers. Probe URIs by opening them if merely testing permissions fails, for DND URIs do not make checkCallingUriPermission return true. * java/org/gnu/emacs/EmacsWindow.java (onDragEvent): Address potential crash. * src/androidvfs.c (android_check_content_access): Circumvent JNI dynamic method dispatch. (android_authority_name): Guarantee NAME is never a directory. --- java/org/gnu/emacs/EmacsService.java | 89 +++++++++++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 9 ++- src/androidvfs.c | 20 +++++-- 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 6fa2ebb3fdb..1325cd85e9b 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -921,6 +921,48 @@ public final class EmacsService extends Service /* Content provider functions. */ + /* Return a ContentResolver capable of accessing as many files as + possible, namely the content resolver of the last selected + activity if available: only they posses the rights to access drag + and drop files. */ + + public ContentResolver + getUsefulContentResolver () + { + EmacsActivity activity; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + /* Since the system predates drag and drop, return this resolver + to avoid any unforseen difficulties. */ + return resolver; + + activity = EmacsActivity.lastFocusedActivity; + if (activity == null) + return resolver; + + return activity.getContentResolver (); + } + + /* Return a context whose ContentResolver is granted access to most + files, as in `getUsefulContentResolver'. */ + + public Context + getContentResolverContext () + { + EmacsActivity activity; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + /* Since the system predates drag and drop, return this resolver + to avoid any unforseen difficulties. */ + return this; + + activity = EmacsActivity.lastFocusedActivity; + if (activity == null) + return this; + + return activity; + } + /* 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. @@ -934,6 +976,9 @@ public final class EmacsService extends Service String name, mode; ParcelFileDescriptor fd; int i; + ContentResolver resolver; + + resolver = getUsefulContentResolver (); /* Figure out the file access mode. */ @@ -978,6 +1023,7 @@ public final class EmacsService extends Service } catch (Exception exception) { + exception.printStackTrace (); return -1; } } @@ -994,6 +1040,11 @@ public final class EmacsService extends Service ParcelFileDescriptor fd; Uri uri; int rc, flags; + Context context; + ContentResolver resolver; + ParcelFileDescriptor descriptor; + + context = getContentResolverContext (); uri = Uri.parse (name); flags = 0; @@ -1004,8 +1055,42 @@ public final class EmacsService extends Service if (writable) flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - rc = checkCallingUriPermission (uri, flags); - return rc == PackageManager.PERMISSION_GRANTED; + rc = context.checkCallingUriPermission (uri, flags); + + if (rc == PackageManager.PERMISSION_GRANTED) + return true; + + /* In the event checkCallingUriPermission fails and only read + permissions are being verified, attempt to query the URI. This + enables ascertaining whether drag and drop URIs can be + accessed, something otherwise not provided for. */ + + descriptor = null; + + try + { + resolver = context.getContentResolver (); + descriptor = resolver.openFileDescriptor (uri, "r"); + return true; + } + catch (Exception exception) + { + /* Ignored. */ + } + finally + { + try + { + if (descriptor != null) + descriptor.close (); + } + catch (IOException exception) + { + /* Ignored. */ + } + } + + return false; } /* Build a content file name for URI. diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 3d2d86624a7..386eaca8c41 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1601,7 +1601,7 @@ public final class EmacsWindow extends EmacsHandleObject { ClipData data; ClipDescription description; - int i, x, y; + int i, j, x, y, itemCount; String type; Uri uri; EmacsActivity activity; @@ -1626,11 +1626,12 @@ public final class EmacsWindow extends EmacsHandleObject data = event.getClipData (); description = data.getDescription (); + itemCount = data.getItemCount (); /* If there are insufficient items within the clip data, return false. */ - if (data.getItemCount () < 1) + if (itemCount < 1) return false; /* Search for plain text data within the clipboard. */ @@ -1662,12 +1663,14 @@ public final class EmacsWindow extends EmacsHandleObject { /* If the item dropped is a URI, send it to the main thread. */ + uri = data.getItemAt (0).getUri (); /* Attempt to acquire permissions for this URI; failing which, insert it as text instead. */ - if (uri.getScheme () != null + if (uri != null + && uri.getScheme () != null && uri.getScheme ().equals ("content") && (activity = EmacsActivity.lastFocusedActivity) != null) { diff --git a/src/androidvfs.c b/src/androidvfs.c index 94c5d35ed2c..f89a82cfcc6 100644 --- a/src/androidvfs.c +++ b/src/androidvfs.c @@ -2898,6 +2898,7 @@ android_check_content_access (const char *uri, int mode) { jobject string; jboolean rc, read, write; + jmethodID method; string = (*android_java_env)->NewStringUTF (android_java_env, uri); android_exception_check (); @@ -2907,11 +2908,13 @@ android_check_content_access (const char *uri, int mode) read = (bool) (mode & R_OK || (mode == F_OK)); write = (bool) (mode & W_OK); + method = service_class.check_content_uri; - rc = (*android_java_env)->CallBooleanMethod (android_java_env, - emacs_service, - service_class.check_content_uri, - string, read, write); + rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env, + emacs_service, + service_class.class, + method, string, read, + write); android_exception_check_1 (string); ANDROID_DELETE_LOCAL_REF (string); return rc; @@ -3013,6 +3016,15 @@ android_authority_name (struct android_vnode *vnode, char *name, if (*name == '/') name++, length -= 1; + /* If the provided URI is a directory, return NULL and set errno + to ENOTDIR. Content files are never directories. */ + + if (name[length - 1] == '/') + { + errno = ENOTDIR; + return NULL; + } + /* NAME must be a valid JNI string, so that it can be encoded properly. */ -- 2.39.2