emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android 3b07a4b3158 1/2: Implement `yank-media' on Android


From: Po Lu
Subject: feature/android 3b07a4b3158 1/2: Implement `yank-media' on Android
Date: Wed, 5 Apr 2023 21:56:55 -0400 (EDT)

branch: feature/android
commit 3b07a4b3158d024c6eb19ce0e7c67b799ae0d1fc
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Implement `yank-media' on Android
    
    * doc/emacs/android.texi (Android Windowing): Update selection
    restrictions.
    * java/org/gnu/emacs/EmacsClipboard.java (EmacsClipboard): New
    functions `getClipboardTargets' and `getClipboardData'.
    * java/org/gnu/emacs/EmacsSdk11Clipboard.java
    (EmacsSdk11Clipboard, getClipboardTargets, getClipboardData):
    Implement.
    * java/org/gnu/emacs/EmacsSdk8Clipboard.java: Stub out new
    functions.
    
    * lisp/term/android-win.el (android-get-clipboard-1): Implement
    MIME type targets.
    * src/android.c (android_exception_check)
    (android_exception_check_1, android_exception_check_2): Fix
    punctuation in warning message.
    (android_exception_check_nonnull_1): New function.
    * src/android.h: Update prototypes.
    * src/androidselect.c (struct android_emacs_clipboard): New
    methods.
    (android_init_emacs_clipboard): Initialize new methods.
    (Fandroid_get_clipboard_targets, android_xfree_inside)
    (Fandroid_get_clipboard_data): New functions.
    (syms_of_androidselect): Define new subrs.
---
 doc/emacs/android.texi                      |   2 +-
 java/org/gnu/emacs/EmacsClipboard.java      |   3 +
 java/org/gnu/emacs/EmacsSdk11Clipboard.java | 130 ++++++++++++++++
 java/org/gnu/emacs/EmacsSdk8Clipboard.java  |  29 ++++
 lisp/term/android-win.el                    |  20 ++-
 src/android.c                               |  27 +++-
 src/android.h                               |   1 +
 src/androidselect.c                         | 221 +++++++++++++++++++++++++++-
 8 files changed, 424 insertions(+), 9 deletions(-)

diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index 97f72e98cee..67faea7f76d 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -476,7 +476,7 @@ Android, it can also be @code{fullscreen}.
   Emacs does not implement all selection related features supported
 under the X Window System on Android.  For example, only the
 @code{CLIPBOARD} and @code{PRIMARY} selections (@pxref{Cut and Paste})
-are supported, and plain text is the only supported data type.
+are supported, and Emacs is only able to set selections to plain text.
 
   In addition, the Android system itself places certain restrictions
 on what selection data Emacs can access:
diff --git a/java/org/gnu/emacs/EmacsClipboard.java 
b/java/org/gnu/emacs/EmacsClipboard.java
index cd6bcebfe0e..5cd48af6e3a 100644
--- a/java/org/gnu/emacs/EmacsClipboard.java
+++ b/java/org/gnu/emacs/EmacsClipboard.java
@@ -31,6 +31,9 @@ public abstract class EmacsClipboard
   public abstract boolean clipboardExists ();
   public abstract byte[] getClipboard ();
 
+  public abstract byte[][] getClipboardTargets ();
+  public abstract long[] getClipboardData (byte[] target);
+
   /* Create the correct kind of clipboard for this system.  */
 
   public static EmacsClipboard
diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java 
b/java/org/gnu/emacs/EmacsSdk11Clipboard.java
index a05184513cd..4959ec36eed 100644
--- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java
+++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java
@@ -21,12 +21,20 @@ package org.gnu.emacs;
 
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.ContentResolver;
 import android.content.ClipData;
+import android.content.ClipDescription;
+
+import android.content.res.AssetFileDescriptor;
+
+import android.net.Uri;
 
 import android.util.Log;
 
 import android.os.Build;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 
 /* This class implements EmacsClipboard for Android 3.0 and later
@@ -40,6 +48,7 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
   private boolean ownsClipboard;
   private int clipboardChangedCount;
   private int monitoredClipboardChangedCount;
+  private ContentResolver resolver;
 
   public
   EmacsSdk11Clipboard ()
@@ -51,6 +60,11 @@ public final class EmacsSdk11Clipboard extends EmacsClipboard
 
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
       manager.addPrimaryClipChangedListener (this);
+
+    /* Now obtain the content resolver used to open file
+       descriptors.  */
+
+    resolver = EmacsService.SERVICE.getContentResolver ();
   }
 
   @Override
@@ -157,4 +171,120 @@ public final class EmacsSdk11Clipboard extends 
EmacsClipboard
 
     return null;
   }
+
+  /* Return an array of targets currently provided by the
+     clipboard, or NULL if there are none.  */
+
+  @Override
+  public byte[][]
+  getClipboardTargets ()
+  {
+    ClipData clip;
+    ClipDescription description;
+    byte[][] typeArray;
+    int i;
+
+    /* N.B. that Android calls the clipboard the ``primary clip''; it
+       is not related to the X primary selection.  */
+    clip = manager.getPrimaryClip ();
+    description = clip.getDescription ();
+    i = description.getMimeTypeCount ();
+    typeArray = new byte[i][i];
+
+    try
+      {
+       for (i = 0; i < description.getMimeTypeCount (); ++i)
+         typeArray[i] = description.getMimeType (i).getBytes ("UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    return typeArray;
+  }
+
+  /* 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
+     from that offset to the end of the file.
+
+     Do not use this function to open text targets; use `getClipboard'
+     for that instead, as it will handle selection data consisting
+     solely of a URI.  */
+
+  @Override
+  public long[]
+  getClipboardData (byte[] target)
+  {
+    ClipData data;
+    String mimeType;
+    int fd;
+    AssetFileDescriptor assetFd;
+    Uri uri;
+    long[] value;
+
+    /* Decode the target given by Emacs.  */
+    try
+      {
+       mimeType = new String (target, "UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    Log.d (TAG, "getClipboardData: "+ mimeType);
+
+    /* Now obtain the clipboard data and the data corresponding to
+       that MIME type.  */
+
+    data = manager.getPrimaryClip ();
+
+    if (data.getItemCount () < 1)
+      return null;
+
+    try
+      {
+       uri = data.getItemAt (0).getUri ();
+
+       if (uri == null)
+         return null;
+
+       Log.d (TAG, "getClipboardData: "+ uri);
+
+       /* 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 ();
+
+       Log.d (TAG, "getClipboardData: "+ value);
+      }
+    catch (FileNotFoundException e)
+      {
+       return null;
+      }
+    catch (IOException e)
+      {
+       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 5a40128b0ac..9622641810f 100644
--- a/java/org/gnu/emacs/EmacsSdk8Clipboard.java
+++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java
@@ -115,4 +115,33 @@ public final class EmacsSdk8Clipboard extends 
EmacsClipboard
 
     return null;
   }
+
+  /* Return an array of targets currently provided by the
+     clipboard, or NULL if there are none.  */
+
+  @Override
+  public byte[][]
+  getClipboardTargets ()
+  {
+    return null;
+  }
+
+  /* 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
+     from that offset to the end of the file.
+
+     Do not use this function to open text targets; use `getClipboard'
+     for that instead, as it will handle selection data consisting
+     solely of a URI.  */
+
+  @Override
+  public long[]
+  getClipboardData (byte[] target)
+  {
+    return null;
+  }
 };
diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el
index c7610ae2ca3..d425ea401a9 100644
--- a/lisp/term/android-win.el
+++ b/lisp/term/android-win.el
@@ -70,6 +70,8 @@ DISPLAY is ignored on Android."
 (declare-function android-get-clipboard "androidselect.c")
 (declare-function android-set-clipboard "androidselect.c")
 (declare-function android-clipboard-owner-p "androidselect.c")
+(declare-function android-get-clipboard-targets "androidselect.c")
+(declare-function android-get-clipboard-data "androidselect.c")
 
 (defvar android-primary-selection nil
   "The last string placed in the primary selection.
@@ -80,13 +82,25 @@ emulates one inside Lisp.")
 
 (defun android-get-clipboard-1 (data-type)
   "Return the clipboard data.
-DATA-TYPE is a selection conversion target; only STRING and
-TARGETS are supported."
+DATA-TYPE is a selection conversion target.  `STRING' means to
+return the contents of the clipboard as a string.  `TARGETS'
+means to return supported data types as a vector.
+
+Interpret any other symbol as a MIME type, and return its
+corresponding data."
   (or (and (eq data-type 'STRING)
            (android-get-clipboard))
       (and (eq data-type 'TARGETS)
            (android-clipboard-exists-p)
-           [TARGETS STRING])))
+           (vconcat [TARGETS STRING]
+                    (let ((i nil))
+                      (dolist (type (android-get-clipboard-targets))
+                        ;; Don't report plain text as a valid target.
+                        (unless (equal type "text/plain")
+                          (push (intern type) i)))
+                      (nreverse i))))
+      (and (symbolp data-type)
+           (android-get-clipboard-data (symbol-name data-type)))))
 
 (defun android-get-primary (data-type)
   "Return the last string placed in the primary selection, or nil.
diff --git a/src/android.c b/src/android.c
index 1da8bec316e..7852590acf4 100644
--- a/src/android.c
+++ b/src/android.c
@@ -5530,7 +5530,7 @@ android_exception_check (void)
   if ((*android_java_env)->ExceptionCheck (android_java_env))
     {
       __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "Possible out of memory error."
+                          "Possible out of memory error. "
                           " The Java exception follows:  ");
       /* Describe exactly what went wrong.  */
       (*android_java_env)->ExceptionDescribe (android_java_env);
@@ -5549,7 +5549,7 @@ android_exception_check_1 (jobject object)
   if ((*android_java_env)->ExceptionCheck (android_java_env))
     {
       __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "Possible out of memory error."
+                          "Possible out of memory error. "
                           " The Java exception follows:  ");
       /* Describe exactly what went wrong.  */
       (*android_java_env)->ExceptionDescribe (android_java_env);
@@ -5568,7 +5568,7 @@ android_exception_check_2 (jobject object, jobject 
object1)
   if ((*android_java_env)->ExceptionCheck (android_java_env))
     {
       __android_log_print (ANDROID_LOG_WARN, __func__,
-                          "Possible out of memory error."
+                          "Possible out of memory error. "
                           " The Java exception follows:  ");
       /* Describe exactly what went wrong.  */
       (*android_java_env)->ExceptionDescribe (android_java_env);
@@ -5600,6 +5600,27 @@ android_exception_check_nonnull (void *object, jobject 
object1)
   memory_full (0);
 }
 
+/* Check for JNI problems based on the value of OBJECT.
+
+   Signal out of memory if OBJECT is NULL.  OBJECT1 and OBJECT2 mean
+   the same as in `android_exception_check_2'.  */
+
+void
+android_exception_check_nonnull_1 (void *object, jobject object1,
+                                  jobject object2)
+{
+  if (object)
+    return;
+
+  if (object1)
+    ANDROID_DELETE_LOCAL_REF (object1);
+
+  if (object2)
+    ANDROID_DELETE_LOCAL_REF (object2);
+
+  memory_full (0);
+}
+
 
 
 /* Native image transforms.  */
diff --git a/src/android.h b/src/android.h
index 03592bd955d..24666aaf989 100644
--- a/src/android.h
+++ b/src/android.h
@@ -88,6 +88,7 @@ extern void android_exception_check (void);
 extern void android_exception_check_1 (jobject);
 extern void android_exception_check_2 (jobject, jobject);
 extern void android_exception_check_nonnull (void *, jobject);
+extern void android_exception_check_nonnull_1 (void *, jobject, jobject);
 
 extern void android_get_keysym_name (int, char *, size_t);
 extern void android_wait_event (void);
diff --git a/src/androidselect.c b/src/androidselect.c
index dfbe0240941..54c712ca93b 100644
--- a/src/androidselect.c
+++ b/src/androidselect.c
@@ -19,6 +19,8 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
 #include <assert.h>
+#include <minmax.h>
+#include <unistd.h>
 
 #include "lisp.h"
 #include "blockinput.h"
@@ -27,12 +29,15 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "androidterm.h"
 
 /* Selection support on Android is confined to copying and pasting of
-   plain text from the clipboard.  There is no primary selection.
+   plain text and MIME data from the clipboard.  There is no primary
+   selection.
 
    While newer versions of Android are supposed to have the necessary
    interfaces for transferring other kinds of selection data, doing so
    is too complicated, and involves registering ``content providers''
-   and all kinds of other stuff.  */
+   and all kinds of other stuff; for this reason, Emacs does not
+   support setting the clipboard contents to anything other than plain
+   text.  */
 
 
 
@@ -46,6 +51,8 @@ struct android_emacs_clipboard
   jmethodID clipboard_exists;
   jmethodID get_clipboard;
   jmethodID make_clipboard;
+  jmethodID get_clipboard_targets;
+  jmethodID get_clipboard_data;
 };
 
 /* Methods associated with the EmacsClipboard class.  */
@@ -86,6 +93,10 @@ android_init_emacs_clipboard (void)
   FIND_METHOD (owns_clipboard, "ownsClipboard", "()I");
   FIND_METHOD (clipboard_exists, "clipboardExists", "()Z");
   FIND_METHOD (get_clipboard, "getClipboard", "()[B");
+  FIND_METHOD (get_clipboard_targets, "getClipboardTargets",
+              "()[[B");
+  FIND_METHOD (get_clipboard_data, "getClipboardData",
+              "([B)[J");
 
   clipboard_class.make_clipboard
     = (*android_java_env)->GetStaticMethodID (android_java_env,
@@ -244,6 +255,210 @@ URL with a scheme specified.  Signal an error upon 
failure.  */)
 
 
 
+/* MIME clipboard support.  This provides support for reading MIME
+   data (but not text) from the clipboard.  */
+
+DEFUN ("android-get-clipboard-targets", Fandroid_get_clipboard_targets,
+       Sandroid_get_clipboard_targets, 0, 0, 0,
+       doc: /* Return a list of data types in the clipboard.
+Value is a list of MIME types as strings, each defining a single extra
+data type available from the clipboard.  */)
+  (void)
+{
+  jarray bytes_array;
+  jbyteArray bytes;
+  jmethodID method;
+  size_t length, length1, i;
+  jbyte *data;
+  Lisp_Object targets, tem;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  targets = Qnil;
+  block_input ();
+  method = clipboard_class.get_clipboard_targets;
+  bytes_array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                      clipboard, method);
+  android_exception_check ();
+
+  if (!bytes_array)
+    goto fail;
+
+  length = (*android_java_env)->GetArrayLength (android_java_env,
+                                               bytes_array);
+  for (i = 0; i < length; ++i)
+    {
+      /* Retireve the MIME type.  */
+      bytes
+       = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                     bytes_array, i);
+      android_exception_check_nonnull (bytes, bytes_array);
+
+      /* Cons it onto the list of targets.  */
+      length1 = (*android_java_env)->GetArrayLength (android_java_env,
+                                                    bytes);
+      data = (*android_java_env)->GetByteArrayElements (android_java_env,
+                                                       bytes, NULL);
+      android_exception_check_nonnull_1 (data, bytes, bytes_array);
+
+      /* Decode the string.  */
+      tem = make_unibyte_string ((char *) data, length1);
+      tem = code_convert_string_norecord (tem, Qutf_8, Qnil);
+      targets = Fcons (tem, targets);
+
+      /* Delete the retrieved data.  */
+      (*android_java_env)->ReleaseByteArrayElements (android_java_env,
+                                                    bytes, data,
+                                                    JNI_ABORT);
+      ANDROID_DELETE_LOCAL_REF (bytes);
+    }
+  unblock_input ();
+
+  ANDROID_DELETE_LOCAL_REF (bytes_array);
+  return Fnreverse (targets);
+
+ fail:
+  unblock_input ();
+  return Qnil;
+}
+
+/* Free the memory inside PTR, a pointer to a char pointer.  */
+
+static void
+android_xfree_inside (void *ptr)
+{
+  xfree (*(char **) ptr);
+}
+
+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.
+Value is a unibyte string containing the entire contents of the
+clipboard, after its owner has converted the data to the given
+MIME type.  Value is nil if the conversion fails, or if the data
+is not present.
+
+Value is also nil if the clipboard data consists of a single URL which
+does not have any corresponding data.  In that case, use
+`android-get-clipboard' instead.  */)
+  (Lisp_Object type)
+{
+  jlongArray array;
+  jbyteArray bytes;
+  jmethodID method;
+  int fd;
+  ptrdiff_t rc;
+  jlong offset, length, *longs;
+  specpdl_ref ref;
+  char *buffer, *start;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  /* Encode the string as UTF-8.  */
+  CHECK_STRING (type);
+  type = ENCODE_UTF_8 (type);
+
+  /* Then give it to the selection code.  */
+  block_input ();
+  bytes = (*android_java_env)->NewByteArray (android_java_env,
+                                            SBYTES (type));
+  (*android_java_env)->SetByteArrayRegion (android_java_env, bytes,
+                                          0, SBYTES (type),
+                                          (jbyte *) SDATA (type));
+  android_exception_check ();
+
+  method = clipboard_class.get_clipboard_data;
+  array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                clipboard, method,
+                                                bytes);
+  android_exception_check_1 (bytes);
+  ANDROID_DELETE_LOCAL_REF (bytes);
+
+  if (!array)
+    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];
+
+  (*android_java_env)->ReleaseLongArrayElements (android_java_env,
+                                                array, longs,
+                                                JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (array);
+  unblock_input ();
+
+  /* Now begin reading from longs[0].  */
+  ref = SPECPDL_INDEX ();
+  record_unwind_protect_int (close_file_unwind, fd);
+
+  if (length != -1)
+    {
+      buffer = xmalloc (MIN (length, PTRDIFF_MAX));
+      record_unwind_protect_ptr (xfree, buffer);
+
+      rc = emacs_read_quit (fd, buffer,
+                           MIN (length, PTRDIFF_MAX));
+
+      /* Return nil upon an IO problem.  */
+      if (rc < 0)
+       return unbind_to (ref, Qnil);
+
+      /* Return the data as a unibyte string.  */
+      return unbind_to (ref, make_unibyte_string (buffer, rc));
+    }
+
+  /* Otherwise, read BUFSIZ bytes at a time.  */
+  buffer = xmalloc (BUFSIZ);
+  length = 0;
+  start = buffer;
+
+  record_unwind_protect_ptr (android_xfree_inside, &buffer);
+
+  /* Seek to the start of the data.  */
+
+  if (offset)
+    {
+      if (lseek (fd, offset, SEEK_SET) < 0)
+       return unbind_to (ref, Qnil);
+    }
+
+  while (true)
+    {
+      rc = emacs_read_quit (fd, start, BUFSIZ);
+
+      if (!INT_ADD_OK (rc, length, &length)
+         || PTRDIFF_MAX - length < BUFSIZ)
+       memory_full (PTRDIFF_MAX);
+
+      if (rc < 0)
+       return unbind_to (ref, Qnil);
+
+      if (rc < BUFSIZ)
+       break;
+
+      buffer = xrealloc (buffer, length + BUFSIZ);
+      start = buffer + length;
+    }
+
+  return unbind_to (ref, make_unibyte_string (buffer, rc));
+
+ fail:
+  unblock_input ();
+  return Qnil;
+}
+
+
+
 void
 init_androidselect (void)
 {
@@ -279,4 +494,6 @@ syms_of_androidselect (void)
   defsubr (&Sandroid_get_clipboard);
   defsubr (&Sandroid_clipboard_exists_p);
   defsubr (&Sandroid_browse_url);
+  defsubr (&Sandroid_get_clipboard_targets);
+  defsubr (&Sandroid_get_clipboard_data);
 }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]