summaryrefslogtreecommitdiff
path: root/src/androidselect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/androidselect.c')
-rw-r--r--src/androidselect.c321
1 files changed, 283 insertions, 38 deletions
diff --git a/src/androidselect.c b/src/androidselect.c
index 3ba3058aeb9..2f6114d0fcb 100644
--- a/src/androidselect.c
+++ b/src/androidselect.c
@@ -30,6 +30,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "coding.h"
#include "android.h"
#include "androidterm.h"
+#include "termhooks.h"
/* Selection support on Android is confined to copying and pasting of
plain text and MIME data from the clipboard. There is no primary
@@ -237,15 +238,21 @@ DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p,
return rc ? Qt : Qnil;
}
-DEFUN ("android-browse-url", Fandroid_browse_url,
- Sandroid_browse_url, 1, 2, 0,
- doc: /* Open URL in an external application. URL should be a
-URL-encoded URL with a scheme specified unless SEND is non-nil.
-Signal an error upon failure.
+DEFUN ("android-browse-url-internal", Fandroid_browse_url_internal,
+ Sandroid_browse_url_internal, 1, 2, 0,
+ doc: /* Open URL in an external application.
+
+URL should be a URL-encoded URL with a scheme specified unless SEND is
+non-nil. Signal an error upon failure.
If SEND is nil, start a program that is able to display the URL, such
as a web browser. Otherwise, try to share URL using programs such as
-email clients. */)
+email clients.
+
+If URL is a file URI, convert it into a `content' address accessible to
+other programs. Files inside the /content or /assets directories cannot
+be opened through such addresses, which this function does not provide
+for. Use `android-browse-url' instead. */)
(Lisp_Object url, Lisp_Object send)
{
Lisp_Object value;
@@ -446,7 +453,7 @@ does not have any corresponding data. In that case, use
{
rc = emacs_read_quit (fd, start, BUFSIZ);
- if (!INT_ADD_OK (rc, length, &length)
+ if (ckd_add (&length, length, rc)
|| PTRDIFF_MAX - length < BUFSIZ)
memory_full (PTRDIFF_MAX);
@@ -484,6 +491,9 @@ struct android_emacs_desktop_notification
/* Methods provided by the EmacsDesktopNotification class. */
static struct android_emacs_desktop_notification notification_class;
+/* Hash table pairing notification identifiers with callbacks. */
+static Lisp_Object notification_table;
+
/* Initialize virtual function IDs and class pointers tied to the
EmacsDesktopNotification class. */
@@ -515,7 +525,8 @@ android_init_emacs_desktop_notification (void)
FIND_METHOD (init, "<init>", "(Ljava/lang/String;"
"Ljava/lang/String;Ljava/lang/String;"
- "Ljava/lang/String;II)V");
+ "Ljava/lang/String;II[Ljava/lang/String;"
+ "[Ljava/lang/String;J)V");
FIND_METHOD (display, "display", "()V");
#undef FIND_METHOD
}
@@ -556,25 +567,34 @@ android_locate_icon (const char *name)
}
/* Display a desktop notification with the provided TITLE, BODY,
- REPLACES_ID, GROUP, ICON, and URGENCY. Return an identifier for
- the resulting notification. */
+ REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, TIMEOUT, RESIDENT,
+ ACTION_CB and CLOSE_CB. Return an identifier for the resulting
+ notification. */
static intmax_t
android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
Lisp_Object replaces_id,
Lisp_Object group, Lisp_Object icon,
- Lisp_Object urgency)
+ Lisp_Object urgency, Lisp_Object actions,
+ Lisp_Object timeout, Lisp_Object resident,
+ Lisp_Object action_cb, Lisp_Object close_cb)
{
static intmax_t counter;
intmax_t id;
jstring title1, body1, group1, identifier1;
jint type, icon1;
jobject notification;
+ jobjectArray action_keys, action_titles;
char identifier[INT_STRLEN_BOUND (int)
+ INT_STRLEN_BOUND (long int)
+ INT_STRLEN_BOUND (intmax_t)
+ sizeof "..."];
struct timespec boot_time;
+ Lisp_Object key, value, tem;
+ jint nitems, i;
+ jstring item;
+ Lisp_Object length;
+ jlong timeout_val;
if (EQ (urgency, Qlow))
type = 2; /* IMPORTANCE_LOW */
@@ -585,10 +605,50 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
else
signal_error ("Invalid notification importance given", urgency);
+ /* Decode the timeout. */
+
+ timeout_val = 0;
+
+ if (!NILP (timeout))
+ {
+ CHECK_INTEGER (timeout);
+
+ if (!integer_to_intmax (timeout, &id)
+ || id > TYPE_MAXIMUM (jlong)
+ || id < TYPE_MINIMUM (jlong))
+ signal_error ("Invalid timeout", timeout);
+
+ if (id > 0)
+ timeout_val = id;
+ }
+
+ nitems = 0;
+
+ /* If ACTIONS is provided, split it into two arrays of Java strings
+ holding keys and titles. */
+
+ if (!NILP (actions))
+ {
+ /* Count the number of items to be inserted. */
+
+ length = Flength (actions);
+ if (!TYPE_RANGED_FIXNUMP (jint, length))
+ error ("Action list too long");
+ nitems = XFIXNAT (length);
+ if (nitems & 1)
+ error ("Length of action list is invalid");
+ nitems /= 2;
+
+ /* Verify that the list consists exclusively of strings. */
+ tem = actions;
+ FOR_EACH_TAIL (tem)
+ CHECK_STRING (XCAR (tem));
+ }
+
if (NILP (replaces_id))
{
/* Generate a new identifier. */
- INT_ADD_WRAPV (counter, 1, &counter);
+ ckd_add (&counter, counter, 1);
id = counter;
}
else
@@ -620,20 +680,71 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
= (*android_java_env)->NewStringUTF (android_java_env, identifier);
android_exception_check_3 (title1, body1, group1);
+ /* Create the arrays for action identifiers and titles if
+ provided. */
+
+ if (nitems)
+ {
+ action_keys = (*android_java_env)->NewObjectArray (android_java_env,
+ nitems,
+ java_string_class,
+ NULL);
+ android_exception_check_4 (title, body1, group1, identifier1);
+ action_titles = (*android_java_env)->NewObjectArray (android_java_env,
+ nitems,
+ java_string_class,
+ NULL);
+ android_exception_check_5 (title, body1, group1, identifier1,
+ action_keys);
+
+ for (i = 0; i < nitems; ++i)
+ {
+ key = XCAR (actions);
+ value = XCAR (XCDR (actions));
+ actions = XCDR (XCDR (actions));
+
+ /* Create a string for this action. */
+ item = android_build_string (key, body1, group1, identifier1,
+ action_keys, action_titles, NULL);
+ (*android_java_env)->SetObjectArrayElement (android_java_env,
+ action_keys, i,
+ item);
+ ANDROID_DELETE_LOCAL_REF (item);
+
+ /* Create a string for this title. */
+ item = android_build_string (value, body1, group1, identifier1,
+ action_keys, action_titles, NULL);
+ (*android_java_env)->SetObjectArrayElement (android_java_env,
+ action_titles, i,
+ item);
+ ANDROID_DELETE_LOCAL_REF (item);
+ }
+ }
+ else
+ {
+ action_keys = NULL;
+ action_titles = NULL;
+ }
+
/* Create the notification. */
notification
= (*android_java_env)->NewObject (android_java_env,
notification_class.class,
notification_class.init,
title1, body1, group1,
- identifier1, icon1, type);
- android_exception_check_4 (title1, body1, group1, identifier1);
+ identifier1, icon1, type,
+ action_keys, action_titles,
+ timeout_val);
+ android_exception_check_6 (title1, body1, group1, identifier1,
+ action_titles, action_keys);
/* Delete unused local references. */
ANDROID_DELETE_LOCAL_REF (title1);
ANDROID_DELETE_LOCAL_REF (body1);
ANDROID_DELETE_LOCAL_REF (group1);
ANDROID_DELETE_LOCAL_REF (identifier1);
+ ANDROID_DELETE_LOCAL_REF (action_keys);
+ ANDROID_DELETE_LOCAL_REF (action_titles);
/* Display the notification. */
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
@@ -643,6 +754,13 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
android_exception_check_1 (notification);
ANDROID_DELETE_LOCAL_REF (notification);
+ /* If callbacks are provided, save them into notification_table. */
+
+ if (!NILP (action_cb) || !NILP (close_cb) || !NILP (resident))
+ Fputhash (build_string (identifier), list3 (action_cb, close_cb,
+ resident),
+ notification_table);
+
/* Return the ID. */
return id;
}
@@ -653,21 +771,46 @@ DEFUN ("android-notifications-notify", Fandroid_notifications_notify,
ARGS must contain keywords followed by values. Each of the following
keywords is understood:
- :title The notification title.
- :body The notification body.
- :replaces-id The ID of a previous notification to supersede.
- :group The notification group, or nil.
- :urgency One of the symbols `low', `normal' or `critical',
- defining the importance of the notification group.
- :icon The name of a drawable resource to display as the
- notification's icon.
-
-The notification group is ignored on Android 7.1 and earlier versions
-of Android. Outside such older systems, it identifies a category that
-will be displayed in the system Settings menu, and the urgency
-provided always extends to affect all notifications displayed within
-that category. If the group is not provided, it defaults to the
-string "Desktop Notifications".
+ :title The notification title.
+ :body The notification body.
+ :replaces-id The ID of a previous notification to supersede.
+ :group The notification group, or nil.
+ :urgency One of the symbols `low', `normal' or `critical',
+ defining the importance of the notification group.
+ :icon The name of a drawable resource to display as the
+ notification's icon.
+ :actions A list of actions of the form:
+ (KEY TITLE KEY TITLE ...)
+ where KEY and TITLE are both strings.
+ The action for which CALLBACK is called when the
+ notification itself is selected is named "default",
+ its existence is implied, and its TITLE is ignored.
+ No more than three actions defined here will be
+ displayed, not counting any with "default" as its
+ key.
+ :timeout Number of miliseconds from the display of the
+ notification at which it will be automatically
+ dismissed, or a value of zero or smaller if it
+ is to remain until user action is taken to dismiss
+ it.
+ :resident When set the notification will not be automatically
+ dismissed when it or an action is selected.
+ :on-action Function to call when an action is invoked.
+ The notification id and the key of the action are
+ provided as arguments to the function.
+ :on-close Function to call if the notification is dismissed,
+ with the notification id and the symbol `undefined'
+ for arguments.
+
+The notification group and timeout are ignored on Android 7.1 and
+earlier versions of Android. On more recent versions, the group
+identifies a category that will be displayed in the system Settings
+menu, and the urgency provided always extends to affect all
+notifications displayed within that category, though it may be ignored
+if higher than any previously-specified urgency or if the user have
+already configured a different urgency for this category from Settings.
+If the group is not provided, it defaults to the string "Desktop
+Notifications" with the urgency suffixed.
Each caller should strive to provide one unchanging combination of
notification group and urgency for each kind of notification it sends,
@@ -677,8 +820,11 @@ first notification sent to its notification group.
The provided icon should be the name of a "drawable resource" present
within the "android.R.drawable" class designating an icon with a
-transparent background. If no icon is provided (or the icon is absent
-from this system), it defaults to "ic_dialog_alert".
+transparent background. Should no icon be provided (or the icon is
+absent from this system), it defaults to "ic_dialog_alert".
+
+Actions specified with :actions cannot be displayed on Android 4.0 and
+earlier versions of the system.
When the system is running Android 13 or later, notifications sent
will be silently disregarded unless permission to display
@@ -693,16 +839,18 @@ this function.
usage: (android-notifications-notify &rest ARGS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
- Lisp_Object title, body, replaces_id, group, urgency;
+ Lisp_Object title, body, replaces_id, group, urgency, timeout, resident;
Lisp_Object icon;
- Lisp_Object key, value;
+ Lisp_Object key, value, actions, action_cb, close_cb;
ptrdiff_t i;
+ AUTO_STRING (default_icon, "ic_dialog_alert");
if (!android_init_gui)
error ("No Android display connection!");
/* Clear each variable above. */
- title = body = replaces_id = group = icon = urgency = Qnil;
+ title = body = replaces_id = group = icon = urgency = actions = Qnil;
+ timeout = resident = action_cb = close_cb = Qnil;
/* If NARGS is odd, error. */
@@ -728,6 +876,16 @@ usage: (android-notifications-notify &rest ARGS) */)
urgency = value;
else if (EQ (key, QCicon))
icon = value;
+ else if (EQ (key, QCactions))
+ actions = value;
+ else if (EQ (key, QCtimeout))
+ timeout = value;
+ else if (EQ (key, QCresident))
+ resident = value;
+ else if (EQ (key, QCon_action))
+ action_cb = value;
+ else if (EQ (key, QCon_close))
+ close_cb = value;
}
/* Demand at least TITLE and BODY be present. */
@@ -744,15 +902,94 @@ usage: (android-notifications-notify &rest ARGS) */)
urgency = Qlow;
if (NILP (group))
- group = build_string ("Desktop Notifications");
+ {
+ AUTO_STRING (format, "Desktop Notifications (%s importance)");
+ group = CALLN (Fformat, format, urgency);
+ }
if (NILP (icon))
- icon = build_string ("ic_dialog_alert");
+ icon = default_icon;
else
CHECK_STRING (icon);
return make_int (android_notifications_notify_1 (title, body, replaces_id,
- group, icon, urgency));
+ group, icon, urgency,
+ actions, timeout, resident,
+ action_cb, close_cb));
+}
+
+/* Run callbacks in response to a notification being deleted.
+ Save any input generated for the keyboard within *IE.
+ EVENT should be the notification deletion event. */
+
+void
+android_notification_deleted (struct android_notification_event *event,
+ struct input_event *ie)
+{
+ Lisp_Object item, tag;
+ intmax_t id;
+
+ tag = build_string (event->tag);
+ item = Fgethash (tag, notification_table, Qnil);
+
+ if (!NILP (item))
+ Fremhash (tag, notification_table);
+
+ if (CONSP (item) && FUNCTIONP (XCAR (XCDR (item)))
+ && sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0)
+ {
+ ie->kind = NOTIFICATION_EVENT;
+ ie->arg = list3 (XCAR (XCDR (item)), make_int (id),
+ Qundefined);
+ }
+}
+
+/* Run callbacks in response to one of a notification's actions being
+ invoked, saving any input generated for the keyboard within *IE.
+ EVENT should be the notification deletion event, and ACTION the
+ action key. */
+
+void
+android_notification_action (struct android_notification_event *event,
+ struct input_event *ie, Lisp_Object action)
+{
+ Lisp_Object item, tag;
+ intmax_t id;
+ jstring tag_object;
+ jmethodID method;
+
+ tag = build_string (event->tag);
+ item = Fgethash (tag, notification_table, Qnil);
+
+ if (CONSP (item) && FUNCTIONP (XCAR (item))
+ && sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0)
+ {
+ ie->kind = NOTIFICATION_EVENT;
+ ie->arg = list3 (XCAR (item), make_int (id), action);
+ }
+
+ /* Test whether ITEM is resident. Non-resident notifications must be
+ removed when activated. */
+
+ if (!CONSP (item) || NILP (XCAR (XCDR (XCDR (item)))))
+ {
+ method = service_class.cancel_notification;
+ tag_object
+ = (*android_java_env)->NewStringUTF (android_java_env,
+ event->tag);
+ android_exception_check ();
+
+ (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+ emacs_service,
+ service_class.class,
+ method, tag_object);
+ android_exception_check_1 (tag_object);
+ ANDROID_DELETE_LOCAL_REF (tag_object);
+
+ /* Remove the notification from the callback table. */
+ if (!NILP (item))
+ Fremhash (tag, notification_table);
+ }
}
@@ -794,6 +1031,11 @@ syms_of_androidselect (void)
DEFSYM (QCgroup, ":group");
DEFSYM (QCurgency, ":urgency");
DEFSYM (QCicon, ":icon");
+ DEFSYM (QCactions, ":actions");
+ DEFSYM (QCtimeout, ":timeout");
+ DEFSYM (QCresident, ":resident");
+ DEFSYM (QCon_action, ":on-action");
+ DEFSYM (QCon_close, ":on-close");
DEFSYM (Qlow, "low");
DEFSYM (Qnormal, "normal");
@@ -803,9 +1045,12 @@ syms_of_androidselect (void)
defsubr (&Sandroid_set_clipboard);
defsubr (&Sandroid_get_clipboard);
defsubr (&Sandroid_clipboard_exists_p);
- defsubr (&Sandroid_browse_url);
+ defsubr (&Sandroid_browse_url_internal);
defsubr (&Sandroid_get_clipboard_targets);
defsubr (&Sandroid_get_clipboard_data);
defsubr (&Sandroid_notifications_notify);
+
+ notification_table = CALLN (Fmake_hash_table, QCtest, Qequal);
+ staticpro (&notification_table);
}