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:
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
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
private boolean ownsClipboard;
private int clipboardChangedCount;
private int monitoredClipboardChangedCount;
+ private ContentResolver resolver;
public
EmacsSdk11Clipboard ()
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
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;
+ }
};
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;
+ }
};
(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.
(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.
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);
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);
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);
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);
+}
+
\f
/* Native image transforms. */
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);
#include <config.h>
#include <assert.h>
+#include <minmax.h>
+#include <unistd.h>
#include "lisp.h"
#include "blockinput.h"
#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. */
\f
jmethodID clipboard_exists;
jmethodID get_clipboard;
jmethodID make_clipboard;
+ jmethodID get_clipboard_targets;
+ jmethodID get_clipboard_data;
};
/* Methods associated with the EmacsClipboard class. */
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,
\f
+/* 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;
+}
+
+\f
+
void
init_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);
}