diff options
Diffstat (limited to 'src/androidselect.c')
-rw-r--r-- | src/androidselect.c | 321 |
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 (¬ification_table); } |