aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPo Lu2024-03-11 21:40:47 +0800
committerPo Lu2024-03-11 21:41:14 +0800
commita7a37341cad230448e487d0ffa343eeeb8a66a65 (patch)
treeab06b5826c5ae7bdd5b3dcc85b3ecba8dbee8c84 /src
parent75cfc6c73faa1561018b1212156964a7919c69fe (diff)
downloademacs-a7a37341cad230448e487d0ffa343eeeb8a66a65.tar.gz
emacs-a7a37341cad230448e487d0ffa343eeeb8a66a65.zip
Implement notification callbacks on Android
* doc/lispref/os.texi (Desktop Notifications): Document that :on-cancel, :on-action and :actions are now supported on Android. * java/org/gnu/emacs/EmacsActivity.java (onNewIntent): New function. * java/org/gnu/emacs/EmacsDesktopNotification.java (NOTIFICATION_ACTION, NOTIFICATION_TAG, NOTIFICATION_DISMISSED): New constants. <actions, titles>: New fields. (insertActions): New function. (display1, display): Insert actions on Jelly Bean and up, and arrange to be notified when the notification is dismissed. (CancellationReceiver): New class. * java/org/gnu/emacs/EmacsNative.java (sendNotificationDeleted) (sendNotificationAction): New functions. * src/android.c (sendDndDrag, sendDndUri, sendDndText): Correct return types. (sendNotificationDeleted, sendNotificationAction) (android_exception_check_5, android_exception_check_6): New functions. * src/android.h: * src/androidgui.h (struct android_notification_event): New structure. (union android_event): New member for notification events. * src/androidselect.c (android_init_emacs_desktop_notification): Update JNI signatures. (android_notifications_notify_1, Fandroid_notifications_notify): New arguments ACTIONS, ACTION_CB and CANCEL_CB. Convert and record them as appropriate. (android_notification_deleted, android_notification_action): New functions. (syms_of_androidselect): Prepare a hash table of outstanding notifications. <QCactions, QCon_action, QCon_cancel> New defsyms. * src/androidterm.c (handle_one_android_event) <ANDROID_NOTIFICATION_DELETED> <ANDROID_NOTIFICATION_ACTION>: Dispatch event contents to androidselect.c for processing. * src/androidterm.h: * src/androidvfs.c (java_string_class): Export. * src/keyboard.c (kbd_buffer_get_event) <NOTIFICATION_EVENT>: Call callback specified by the event. * src/termhooks.h (enum event_kind) [HAVE_ANDROID]: New enum NOTIFICATION_EVENT.
Diffstat (limited to 'src')
-rw-r--r--src/android.c161
-rw-r--r--src/android.h7
-rw-r--r--src/androidgui.h29
-rw-r--r--src/androidselect.c210
-rw-r--r--src/androidterm.c22
-rw-r--r--src/androidterm.h6
-rw-r--r--src/androidvfs.c2
-rw-r--r--src/keyboard.c10
-rw-r--r--src/termhooks.h4
9 files changed, 429 insertions, 22 deletions
diff --git a/src/android.c b/src/android.c
index d7bd06f1f34..125bb5209c3 100644
--- a/src/android.c
+++ b/src/android.c
@@ -2457,7 +2457,7 @@ NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
2457 return event_serial; 2457 return event_serial;
2458} 2458}
2459 2459
2460JNIEXPORT jboolean JNICALL 2460JNIEXPORT jlong JNICALL
2461NATIVE_NAME (sendDndDrag) (JNIEnv *env, jobject object, 2461NATIVE_NAME (sendDndDrag) (JNIEnv *env, jobject object,
2462 jshort window, jint x, jint y) 2462 jshort window, jint x, jint y)
2463{ 2463{
@@ -2477,7 +2477,7 @@ NATIVE_NAME (sendDndDrag) (JNIEnv *env, jobject object,
2477 return event_serial; 2477 return event_serial;
2478} 2478}
2479 2479
2480JNIEXPORT jboolean JNICALL 2480JNIEXPORT jlong JNICALL
2481NATIVE_NAME (sendDndUri) (JNIEnv *env, jobject object, 2481NATIVE_NAME (sendDndUri) (JNIEnv *env, jobject object,
2482 jshort window, jint x, jint y, 2482 jshort window, jint x, jint y,
2483 jstring string) 2483 jstring string)
@@ -2514,7 +2514,7 @@ NATIVE_NAME (sendDndUri) (JNIEnv *env, jobject object,
2514 return event_serial; 2514 return event_serial;
2515} 2515}
2516 2516
2517JNIEXPORT jboolean JNICALL 2517JNIEXPORT jlong JNICALL
2518NATIVE_NAME (sendDndText) (JNIEnv *env, jobject object, 2518NATIVE_NAME (sendDndText) (JNIEnv *env, jobject object,
2519 jshort window, jint x, jint y, 2519 jshort window, jint x, jint y,
2520 jstring string) 2520 jstring string)
@@ -2551,6 +2551,85 @@ NATIVE_NAME (sendDndText) (JNIEnv *env, jobject object,
2551 return event_serial; 2551 return event_serial;
2552} 2552}
2553 2553
2554JNIEXPORT jlong JNICALL
2555NATIVE_NAME (sendNotificationDeleted) (JNIEnv *env, jobject object,
2556 jstring tag)
2557{
2558 JNI_STACK_ALIGNMENT_PROLOGUE;
2559
2560 union android_event event;
2561 const char *characters;
2562
2563 event.notification.type = ANDROID_NOTIFICATION_DELETED;
2564 event.notification.serial = ++event_serial;
2565 event.notification.window = ANDROID_NONE;
2566
2567 /* TAG is guaranteed to be an ASCII string, of which the JNI character
2568 encoding is a superset. */
2569 characters = (*env)->GetStringUTFChars (env, tag, NULL);
2570 if (!characters)
2571 return 0;
2572
2573 event.notification.tag = strdup (characters);
2574 (*env)->ReleaseStringUTFChars (env, tag, characters);
2575 if (!event.notification.tag)
2576 return 0;
2577
2578 event.notification.action = NULL;
2579 event.notification.length = 0;
2580
2581 android_write_event (&event);
2582 return event_serial;
2583}
2584
2585JNIEXPORT jlong JNICALL
2586NATIVE_NAME (sendNotificationAction) (JNIEnv *env, jobject object,
2587 jstring tag, jstring action)
2588{
2589 JNI_STACK_ALIGNMENT_PROLOGUE;
2590
2591 union android_event event;
2592 const void *characters;
2593 jsize length;
2594 uint16_t *buffer;
2595
2596 event.notification.type = ANDROID_NOTIFICATION_ACTION;
2597 event.notification.serial = ++event_serial;
2598 event.notification.window = ANDROID_NONE;
2599
2600 /* TAG is guaranteed to be an ASCII string, of which the JNI character
2601 encoding is a superset. */
2602 characters = (*env)->GetStringUTFChars (env, tag, NULL);
2603 if (!characters)
2604 return 0;
2605
2606 event.notification.tag = strdup (characters);
2607 (*env)->ReleaseStringUTFChars (env, tag, characters);
2608 if (!event.notification.tag)
2609 return 0;
2610
2611 length = (*env)->GetStringLength (env, action);
2612 buffer = malloc (length * sizeof *buffer);
2613 characters = (*env)->GetStringChars (env, action, NULL);
2614
2615 if (!characters)
2616 {
2617 /* The JVM has run out of memory; return and let the out of memory
2618 error take its course. */
2619 xfree (event.notification.tag);
2620 return 0;
2621 }
2622
2623 memcpy (buffer, characters, length * sizeof *buffer);
2624 (*env)->ReleaseStringChars (env, action, characters);
2625
2626 event.notification.action = buffer;
2627 event.notification.length = length;
2628
2629 android_write_event (&event);
2630 return event_serial;
2631}
2632
2554JNIEXPORT jboolean JNICALL 2633JNIEXPORT jboolean JNICALL
2555NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env, 2634NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env,
2556 jobject object) 2635 jobject object)
@@ -6310,6 +6389,82 @@ android_exception_check_4 (jobject object, jobject object1,
6310 memory_full (0); 6389 memory_full (0);
6311} 6390}
6312 6391
6392/* Like android_exception_check_4, except it takes more than four local
6393 reference arguments. */
6394
6395void
6396android_exception_check_5 (jobject object, jobject object1,
6397 jobject object2, jobject object3,
6398 jobject object4)
6399{
6400 if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
6401 return;
6402
6403 __android_log_print (ANDROID_LOG_WARN, __func__,
6404 "Possible out of memory error. "
6405 " The Java exception follows: ");
6406 /* Describe exactly what went wrong. */
6407 (*android_java_env)->ExceptionDescribe (android_java_env);
6408 (*android_java_env)->ExceptionClear (android_java_env);
6409
6410 if (object)
6411 ANDROID_DELETE_LOCAL_REF (object);
6412
6413 if (object1)
6414 ANDROID_DELETE_LOCAL_REF (object1);
6415
6416 if (object2)
6417 ANDROID_DELETE_LOCAL_REF (object2);
6418
6419 if (object3)
6420 ANDROID_DELETE_LOCAL_REF (object3);
6421
6422 if (object4)
6423 ANDROID_DELETE_LOCAL_REF (object4);
6424
6425 memory_full (0);
6426}
6427
6428
6429/* Like android_exception_check_5, except it takes more than five local
6430 reference arguments. */
6431
6432void
6433android_exception_check_6 (jobject object, jobject object1,
6434 jobject object2, jobject object3,
6435 jobject object4, jobject object5)
6436{
6437 if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
6438 return;
6439
6440 __android_log_print (ANDROID_LOG_WARN, __func__,
6441 "Possible out of memory error. "
6442 " The Java exception follows: ");
6443 /* Describe exactly what went wrong. */
6444 (*android_java_env)->ExceptionDescribe (android_java_env);
6445 (*android_java_env)->ExceptionClear (android_java_env);
6446
6447 if (object)
6448 ANDROID_DELETE_LOCAL_REF (object);
6449
6450 if (object1)
6451 ANDROID_DELETE_LOCAL_REF (object1);
6452
6453 if (object2)
6454 ANDROID_DELETE_LOCAL_REF (object2);
6455
6456 if (object3)
6457 ANDROID_DELETE_LOCAL_REF (object3);
6458
6459 if (object4)
6460 ANDROID_DELETE_LOCAL_REF (object4);
6461
6462 if (object5)
6463 ANDROID_DELETE_LOCAL_REF (object5);
6464
6465 memory_full (0);
6466}
6467
6313/* Check for JNI problems based on the value of OBJECT. 6468/* Check for JNI problems based on the value of OBJECT.
6314 6469
6315 Signal out of memory if OBJECT is NULL. OBJECT1 means the 6470 Signal out of memory if OBJECT is NULL. OBJECT1 means the
diff --git a/src/android.h b/src/android.h
index e1834cebf68..ee634a3e76c 100644
--- a/src/android.h
+++ b/src/android.h
@@ -118,6 +118,10 @@ extern void android_exception_check_1 (jobject);
118extern void android_exception_check_2 (jobject, jobject); 118extern void android_exception_check_2 (jobject, jobject);
119extern void android_exception_check_3 (jobject, jobject, jobject); 119extern void android_exception_check_3 (jobject, jobject, jobject);
120extern void android_exception_check_4 (jobject, jobject, jobject, jobject); 120extern void android_exception_check_4 (jobject, jobject, jobject, jobject);
121extern void android_exception_check_5 (jobject, jobject, jobject, jobject,
122 jobject);
123extern void android_exception_check_6 (jobject, jobject, jobject, jobject,
124 jobject, jobject);
121extern void android_exception_check_nonnull (void *, jobject); 125extern void android_exception_check_nonnull (void *, jobject);
122extern void android_exception_check_nonnull_1 (void *, jobject, jobject); 126extern void android_exception_check_nonnull_1 (void *, jobject, jobject);
123 127
@@ -306,6 +310,9 @@ extern JNIEnv *android_java_env;
306extern JavaVM *android_jvm; 310extern JavaVM *android_jvm;
307#endif /* THREADS_ENABLED */ 311#endif /* THREADS_ENABLED */
308 312
313/* The Java String class. */
314extern jclass java_string_class;
315
309/* The EmacsService object. */ 316/* The EmacsService object. */
310extern jobject emacs_service; 317extern jobject emacs_service;
311 318
diff --git a/src/androidgui.h b/src/androidgui.h
index 73b60c483d3..d89aee51055 100644
--- a/src/androidgui.h
+++ b/src/androidgui.h
@@ -251,6 +251,8 @@ enum android_event_type
251 ANDROID_DND_DRAG_EVENT, 251 ANDROID_DND_DRAG_EVENT,
252 ANDROID_DND_URI_EVENT, 252 ANDROID_DND_URI_EVENT,
253 ANDROID_DND_TEXT_EVENT, 253 ANDROID_DND_TEXT_EVENT,
254 ANDROID_NOTIFICATION_DELETED,
255 ANDROID_NOTIFICATION_ACTION,
254 }; 256 };
255 257
256struct android_any_event 258struct android_any_event
@@ -535,6 +537,29 @@ struct android_dnd_event
535 size_t length; 537 size_t length;
536}; 538};
537 539
540struct android_notification_event
541{
542 /* Type of the event. */
543 enum android_event_type type;
544
545 /* The event serial. */
546 unsigned long serial;
547
548 /* The window that gave rise to the event (None). */
549 android_window window;
550
551 /* The identifier of the notification whose status changed.
552 Must be deallocated with `free'. */
553 char *tag;
554
555 /* The action that was activated, if any. Must be deallocated with
556 `free'. */
557 unsigned short *action;
558
559 /* Length of that data. */
560 size_t length;
561};
562
538union android_event 563union android_event
539{ 564{
540 enum android_event_type type; 565 enum android_event_type type;
@@ -571,6 +596,10 @@ union android_event
571 protocol, whereas there exist several competing X protocols 596 protocol, whereas there exist several competing X protocols
572 implemented in terms of X client messages. */ 597 implemented in terms of X client messages. */
573 struct android_dnd_event dnd; 598 struct android_dnd_event dnd;
599
600 /* X provides no equivalent interface for displaying
601 notifications. */
602 struct android_notification_event notification;
574}; 603};
575 604
576enum 605enum
diff --git a/src/androidselect.c b/src/androidselect.c
index 61f1c6045db..04f4cf1573f 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/>. */
30#include "coding.h" 30#include "coding.h"
31#include "android.h" 31#include "android.h"
32#include "androidterm.h" 32#include "androidterm.h"
33#include "termhooks.h"
33 34
34/* Selection support on Android is confined to copying and pasting of 35/* Selection support on Android is confined to copying and pasting of
35 plain text and MIME data from the clipboard. There is no primary 36 plain text and MIME data from the clipboard. There is no primary
@@ -490,6 +491,9 @@ struct android_emacs_desktop_notification
490/* Methods provided by the EmacsDesktopNotification class. */ 491/* Methods provided by the EmacsDesktopNotification class. */
491static struct android_emacs_desktop_notification notification_class; 492static struct android_emacs_desktop_notification notification_class;
492 493
494/* Hash table pairing notification identifiers with callbacks. */
495static Lisp_Object notification_table;
496
493/* Initialize virtual function IDs and class pointers tied to the 497/* Initialize virtual function IDs and class pointers tied to the
494 EmacsDesktopNotification class. */ 498 EmacsDesktopNotification class. */
495 499
@@ -521,7 +525,8 @@ android_init_emacs_desktop_notification (void)
521 525
522 FIND_METHOD (init, "<init>", "(Ljava/lang/String;" 526 FIND_METHOD (init, "<init>", "(Ljava/lang/String;"
523 "Ljava/lang/String;Ljava/lang/String;" 527 "Ljava/lang/String;Ljava/lang/String;"
524 "Ljava/lang/String;II)V"); 528 "Ljava/lang/String;II[Ljava/lang/String;"
529 "[Ljava/lang/String;)V");
525 FIND_METHOD (display, "display", "()V"); 530 FIND_METHOD (display, "display", "()V");
526#undef FIND_METHOD 531#undef FIND_METHOD
527} 532}
@@ -562,25 +567,32 @@ android_locate_icon (const char *name)
562} 567}
563 568
564/* Display a desktop notification with the provided TITLE, BODY, 569/* Display a desktop notification with the provided TITLE, BODY,
565 REPLACES_ID, GROUP, ICON, and URGENCY. Return an identifier for 570 REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, ACTION_CB and CANCEL_CB.
566 the resulting notification. */ 571 Return an identifier for the resulting notification. */
567 572
568static intmax_t 573static intmax_t
569android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, 574android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
570 Lisp_Object replaces_id, 575 Lisp_Object replaces_id,
571 Lisp_Object group, Lisp_Object icon, 576 Lisp_Object group, Lisp_Object icon,
572 Lisp_Object urgency) 577 Lisp_Object urgency, Lisp_Object actions,
578 Lisp_Object action_cb,
579 Lisp_Object cancel_cb)
573{ 580{
574 static intmax_t counter; 581 static intmax_t counter;
575 intmax_t id; 582 intmax_t id;
576 jstring title1, body1, group1, identifier1; 583 jstring title1, body1, group1, identifier1;
577 jint type, icon1; 584 jint type, icon1;
578 jobject notification; 585 jobject notification;
586 jobjectArray action_keys, action_titles;
579 char identifier[INT_STRLEN_BOUND (int) 587 char identifier[INT_STRLEN_BOUND (int)
580 + INT_STRLEN_BOUND (long int) 588 + INT_STRLEN_BOUND (long int)
581 + INT_STRLEN_BOUND (intmax_t) 589 + INT_STRLEN_BOUND (intmax_t)
582 + sizeof "..."]; 590 + sizeof "..."];
583 struct timespec boot_time; 591 struct timespec boot_time;
592 Lisp_Object key, value, tem;
593 jint nitems, i;
594 jstring item;
595 Lisp_Object length;
584 596
585 if (EQ (urgency, Qlow)) 597 if (EQ (urgency, Qlow))
586 type = 2; /* IMPORTANCE_LOW */ 598 type = 2; /* IMPORTANCE_LOW */
@@ -591,6 +603,29 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
591 else 603 else
592 signal_error ("Invalid notification importance given", urgency); 604 signal_error ("Invalid notification importance given", urgency);
593 605
606 nitems = 0;
607
608 /* If ACTIONS is provided, split it into two arrays of Java strings
609 holding keys and titles. */
610
611 if (!NILP (actions))
612 {
613 /* Count the number of items to be inserted. */
614
615 length = Flength (actions);
616 if (!TYPE_RANGED_FIXNUMP (jint, length))
617 error ("Action list too long");
618 nitems = XFIXNAT (length);
619 if (nitems & 1)
620 error ("Length of action list is invalid");
621 nitems /= 2;
622
623 /* Verify that the list consists exclusively of strings. */
624 tem = actions;
625 FOR_EACH_TAIL (tem)
626 CHECK_STRING (XCAR (tem));
627 }
628
594 if (NILP (replaces_id)) 629 if (NILP (replaces_id))
595 { 630 {
596 /* Generate a new identifier. */ 631 /* Generate a new identifier. */
@@ -626,14 +661,62 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
626 = (*android_java_env)->NewStringUTF (android_java_env, identifier); 661 = (*android_java_env)->NewStringUTF (android_java_env, identifier);
627 android_exception_check_3 (title1, body1, group1); 662 android_exception_check_3 (title1, body1, group1);
628 663
664 /* Create the arrays for action identifiers and titles if
665 provided. */
666
667 if (nitems)
668 {
669 action_keys = (*android_java_env)->NewObjectArray (android_java_env,
670 nitems,
671 java_string_class,
672 NULL);
673 android_exception_check_4 (title, body1, group1, identifier1);
674 action_titles = (*android_java_env)->NewObjectArray (android_java_env,
675 nitems,
676 java_string_class,
677 NULL);
678 android_exception_check_5 (title, body1, group1, identifier1,
679 action_keys);
680
681 for (i = 0; i < nitems; ++i)
682 {
683 key = XCAR (actions);
684 value = XCAR (XCDR (actions));
685 actions = XCDR (XCDR (actions));
686
687 /* Create a string for this action. */
688 item = android_build_string (key, body1, group1, identifier1,
689 action_keys, action_titles, NULL);
690 (*android_java_env)->SetObjectArrayElement (android_java_env,
691 action_keys, i,
692 item);
693 ANDROID_DELETE_LOCAL_REF (item);
694
695 /* Create a string for this title. */
696 item = android_build_string (value, body1, group1, identifier1,
697 action_keys, action_titles, NULL);
698 (*android_java_env)->SetObjectArrayElement (android_java_env,
699 action_titles, i,
700 item);
701 ANDROID_DELETE_LOCAL_REF (item);
702 }
703 }
704 else
705 {
706 action_keys = NULL;
707 action_titles = NULL;
708 }
709
629 /* Create the notification. */ 710 /* Create the notification. */
630 notification 711 notification
631 = (*android_java_env)->NewObject (android_java_env, 712 = (*android_java_env)->NewObject (android_java_env,
632 notification_class.class, 713 notification_class.class,
633 notification_class.init, 714 notification_class.init,
634 title1, body1, group1, 715 title1, body1, group1,
635 identifier1, icon1, type); 716 identifier1, icon1, type,
636 android_exception_check_4 (title1, body1, group1, identifier1); 717 action_keys, action_titles);
718 android_exception_check_6 (title1, body1, group1, identifier1,
719 action_titles, action_keys);
637 720
638 /* Delete unused local references. */ 721 /* Delete unused local references. */
639 ANDROID_DELETE_LOCAL_REF (title1); 722 ANDROID_DELETE_LOCAL_REF (title1);
@@ -641,6 +724,12 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
641 ANDROID_DELETE_LOCAL_REF (group1); 724 ANDROID_DELETE_LOCAL_REF (group1);
642 ANDROID_DELETE_LOCAL_REF (identifier1); 725 ANDROID_DELETE_LOCAL_REF (identifier1);
643 726
727 if (action_keys)
728 ANDROID_DELETE_LOCAL_REF (action_keys);
729
730 if (action_titles)
731 ANDROID_DELETE_LOCAL_REF (action_titles);
732
644 /* Display the notification. */ 733 /* Display the notification. */
645 (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, 734 (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
646 notification, 735 notification,
@@ -649,6 +738,12 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
649 android_exception_check_1 (notification); 738 android_exception_check_1 (notification);
650 ANDROID_DELETE_LOCAL_REF (notification); 739 ANDROID_DELETE_LOCAL_REF (notification);
651 740
741 /* If callbacks are provided, save them into notification_table. */
742
743 if (!NILP (action_cb) || !NILP (cancel_cb))
744 Fputhash (build_string (identifier), Fcons (action_cb, cancel_cb),
745 notification_table);
746
652 /* Return the ID. */ 747 /* Return the ID. */
653 return id; 748 return id;
654} 749}
@@ -659,14 +754,28 @@ DEFUN ("android-notifications-notify", Fandroid_notifications_notify,
659ARGS must contain keywords followed by values. Each of the following 754ARGS must contain keywords followed by values. Each of the following
660keywords is understood: 755keywords is understood:
661 756
662 :title The notification title. 757 :title The notification title.
663 :body The notification body. 758 :body The notification body.
664 :replaces-id The ID of a previous notification to supersede. 759 :replaces-id The ID of a previous notification to supersede.
665 :group The notification group, or nil. 760 :group The notification group, or nil.
666 :urgency One of the symbols `low', `normal' or `critical', 761 :urgency One of the symbols `low', `normal' or `critical',
667 defining the importance of the notification group. 762 defining the importance of the notification group.
668 :icon The name of a drawable resource to display as the 763 :icon The name of a drawable resource to display as the
669 notification's icon. 764 notification's icon.
765 :actions A list of actions of the form:
766 (KEY TITLE KEY TITLE ...)
767 where KEY and TITLE are both strings.
768 The action for which CALLBACK is called when the
769 notification itself is selected is named "default",
770 its existence is implied, and its TITLE is ignored.
771 No more than three actions can be defined, not
772 counting any action with "default" as its key.
773 :on-action Function to call when an action is invoked.
774 The notification id and the key of the action are
775 provided as arguments to the function.
776 :on-cancel Function to call if the notification is dismissed,
777 with the notification id and the symbol `undefined'
778 for arguments.
670 779
671The notification group is ignored on Android 7.1 and earlier versions 780The notification group is ignored on Android 7.1 and earlier versions
672of Android. Outside such older systems, it identifies a category that 781of Android. Outside such older systems, it identifies a category that
@@ -686,6 +795,9 @@ within the "android.R.drawable" class designating an icon with a
686transparent background. If no icon is provided (or the icon is absent 795transparent background. If no icon is provided (or the icon is absent
687from this system), it defaults to "ic_dialog_alert". 796from this system), it defaults to "ic_dialog_alert".
688 797
798Actions specified with :actions cannot be displayed on Android 4.0 and
799earlier versions of the system.
800
689When the system is running Android 13 or later, notifications sent 801When the system is running Android 13 or later, notifications sent
690will be silently disregarded unless permission to display 802will be silently disregarded unless permission to display
691notifications is expressly granted from the "App Info" settings panel 803notifications is expressly granted from the "App Info" settings panel
@@ -701,14 +813,15 @@ usage: (android-notifications-notify &rest ARGS) */)
701{ 813{
702 Lisp_Object title, body, replaces_id, group, urgency; 814 Lisp_Object title, body, replaces_id, group, urgency;
703 Lisp_Object icon; 815 Lisp_Object icon;
704 Lisp_Object key, value; 816 Lisp_Object key, value, actions, action_cb, cancel_cb;
705 ptrdiff_t i; 817 ptrdiff_t i;
706 818
707 if (!android_init_gui) 819 if (!android_init_gui)
708 error ("No Android display connection!"); 820 error ("No Android display connection!");
709 821
710 /* Clear each variable above. */ 822 /* Clear each variable above. */
711 title = body = replaces_id = group = icon = urgency = Qnil; 823 title = body = replaces_id = group = icon = urgency = actions = Qnil;
824 action_cb = cancel_cb = Qnil;
712 825
713 /* If NARGS is odd, error. */ 826 /* If NARGS is odd, error. */
714 827
@@ -734,6 +847,12 @@ usage: (android-notifications-notify &rest ARGS) */)
734 urgency = value; 847 urgency = value;
735 else if (EQ (key, QCicon)) 848 else if (EQ (key, QCicon))
736 icon = value; 849 icon = value;
850 else if (EQ (key, QCactions))
851 actions = value;
852 else if (EQ (key, QCon_action))
853 action_cb = value;
854 else if (EQ (key, QCon_cancel))
855 cancel_cb = value;
737 } 856 }
738 857
739 /* Demand at least TITLE and BODY be present. */ 858 /* Demand at least TITLE and BODY be present. */
@@ -758,7 +877,58 @@ usage: (android-notifications-notify &rest ARGS) */)
758 CHECK_STRING (icon); 877 CHECK_STRING (icon);
759 878
760 return make_int (android_notifications_notify_1 (title, body, replaces_id, 879 return make_int (android_notifications_notify_1 (title, body, replaces_id,
761 group, icon, urgency)); 880 group, icon, urgency,
881 actions, action_cb,
882 cancel_cb));
883}
884
885/* Run callbacks in response to a notification being deleted.
886 Save any input generated for the keyboard within *IE.
887 EVENT should be the notification deletion event. */
888
889void
890android_notification_deleted (struct android_notification_event *event,
891 struct input_event *ie)
892{
893 Lisp_Object item, tag;
894 intmax_t id;
895
896 tag = build_string (event->tag);
897 item = Fgethash (tag, notification_table, Qnil);
898
899 if (!NILP (item))
900 Fremhash (tag, notification_table);
901
902 if (CONSP (item) && FUNCTIONP (XCDR (item))
903 && sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0)
904 {
905 ie->kind = NOTIFICATION_EVENT;
906 ie->arg = list3 (XCDR (item), make_int (id),
907 Qundefined);
908 }
909}
910
911/* Run callbacks in response to one of a notification's actions being
912 invoked, saving any input generated for the keyboard within *IE.
913 EVENT should be the notification deletion event, and ACTION the
914 action key. */
915
916void
917android_notification_action (struct android_notification_event *event,
918 struct input_event *ie, Lisp_Object action)
919{
920 Lisp_Object item, tag;
921 intmax_t id;
922
923 tag = build_string (event->tag);
924 item = Fgethash (tag, notification_table, Qnil);
925
926 if (CONSP (item) && FUNCTIONP (XCAR (item))
927 && sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0)
928 {
929 ie->kind = NOTIFICATION_EVENT;
930 ie->arg = list3 (XCAR (item), make_int (id), action);
931 }
762} 932}
763 933
764 934
@@ -800,6 +970,9 @@ syms_of_androidselect (void)
800 DEFSYM (QCgroup, ":group"); 970 DEFSYM (QCgroup, ":group");
801 DEFSYM (QCurgency, ":urgency"); 971 DEFSYM (QCurgency, ":urgency");
802 DEFSYM (QCicon, ":icon"); 972 DEFSYM (QCicon, ":icon");
973 DEFSYM (QCactions, ":actions");
974 DEFSYM (QCon_action, ":on-action");
975 DEFSYM (QCon_cancel, ":on-cancel");
803 976
804 DEFSYM (Qlow, "low"); 977 DEFSYM (Qlow, "low");
805 DEFSYM (Qnormal, "normal"); 978 DEFSYM (Qnormal, "normal");
@@ -814,4 +987,7 @@ syms_of_androidselect (void)
814 defsubr (&Sandroid_get_clipboard_data); 987 defsubr (&Sandroid_get_clipboard_data);
815 988
816 defsubr (&Sandroid_notifications_notify); 989 defsubr (&Sandroid_notifications_notify);
990
991 notification_table = CALLN (Fmake_hash_table, QCtest, Qequal);
992 staticpro (&notification_table);
817} 993}
diff --git a/src/androidterm.c b/src/androidterm.c
index baf26abe322..f68f8a9ef62 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -1761,6 +1761,26 @@ handle_one_android_event (struct android_display_info *dpyinfo,
1761 free (event->dnd.uri_or_string); 1761 free (event->dnd.uri_or_string);
1762 goto OTHER; 1762 goto OTHER;
1763 1763
1764 case ANDROID_NOTIFICATION_DELETED:
1765 case ANDROID_NOTIFICATION_ACTION:
1766
1767 if (event->notification.type == ANDROID_NOTIFICATION_DELETED)
1768 android_notification_deleted (&event->notification, &inev.ie);
1769 else
1770 {
1771 Lisp_Object action;
1772
1773 action = android_decode_utf16 (event->notification.action,
1774 event->notification.length);
1775 android_notification_action (&event->notification, &inev.ie,
1776 action);
1777 }
1778
1779 /* Free dynamically allocated data. */
1780 free (event->notification.tag);
1781 free (event->notification.action);
1782 goto OTHER;
1783
1764 default: 1784 default:
1765 goto OTHER; 1785 goto OTHER;
1766 } 1786 }
@@ -4740,7 +4760,7 @@ android_sync_edit (void)
4740 4760
4741/* Return a copy of the specified Java string and its length in 4761/* Return a copy of the specified Java string and its length in
4742 *LENGTH. Use the JNI environment ENV. Value is NULL if copying 4762 *LENGTH. Use the JNI environment ENV. Value is NULL if copying
4743 *the string fails. */ 4763 the string fails. */
4744 4764
4745static unsigned short * 4765static unsigned short *
4746android_copy_java_string (JNIEnv *env, jstring string, size_t *length) 4766android_copy_java_string (JNIEnv *env, jstring string, size_t *length)
diff --git a/src/androidterm.h b/src/androidterm.h
index 41c93067e82..ca6929bef0e 100644
--- a/src/androidterm.h
+++ b/src/androidterm.h
@@ -25,6 +25,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
25#include "character.h" 25#include "character.h"
26#include "dispextern.h" 26#include "dispextern.h"
27#include "font.h" 27#include "font.h"
28#include "termhooks.h"
28 29
29struct android_bitmap_record 30struct android_bitmap_record
30{ 31{
@@ -464,6 +465,11 @@ extern void syms_of_sfntfont_android (void);
464 465
465#ifndef ANDROID_STUBIFY 466#ifndef ANDROID_STUBIFY
466 467
468extern void android_notification_deleted (struct android_notification_event *,
469 struct input_event *);
470extern void android_notification_action (struct android_notification_event *,
471 struct input_event *, Lisp_Object);
472
467extern void init_androidselect (void); 473extern void init_androidselect (void);
468extern void syms_of_androidselect (void); 474extern void syms_of_androidselect (void);
469 475
diff --git a/src/androidvfs.c b/src/androidvfs.c
index d618e351204..4bb652f3eb7 100644
--- a/src/androidvfs.c
+++ b/src/androidvfs.c
@@ -292,7 +292,7 @@ struct android_parcel_file_descriptor_class
292}; 292};
293 293
294/* The java.lang.String class. */ 294/* The java.lang.String class. */
295static jclass java_string_class; 295jclass java_string_class;
296 296
297/* Fields and methods associated with the Cursor class. */ 297/* Fields and methods associated with the Cursor class. */
298static struct android_cursor_class cursor_class; 298static struct android_cursor_class cursor_class;
diff --git a/src/keyboard.c b/src/keyboard.c
index 1ba74a59537..91faf4582fa 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -4187,6 +4187,16 @@ kbd_buffer_get_event (KBOARD **kbp,
4187 break; 4187 break;
4188 } 4188 }
4189 4189
4190#ifdef HAVE_ANDROID
4191 case NOTIFICATION_EVENT:
4192 {
4193 kbd_fetch_ptr = next_kbd_event (event);
4194 input_pending = readable_events (0);
4195 CALLN (Fapply, XCAR (event->ie.arg), XCDR (event->ie.arg));
4196 break;
4197 }
4198#endif /* HAVE_ANDROID */
4199
4190#ifdef HAVE_EXT_MENU_BAR 4200#ifdef HAVE_EXT_MENU_BAR
4191 case MENU_BAR_ACTIVATE_EVENT: 4201 case MENU_BAR_ACTIVATE_EVENT:
4192 { 4202 {
diff --git a/src/termhooks.h b/src/termhooks.h
index 8defebb20bd..d828c62ce33 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -343,6 +343,10 @@ enum event_kind
343 the notification that was clicked. */ 343 the notification that was clicked. */
344 , NOTIFICATION_CLICKED_EVENT 344 , NOTIFICATION_CLICKED_EVENT
345#endif /* HAVE_HAIKU */ 345#endif /* HAVE_HAIKU */
346#ifdef HAVE_ANDROID
347 /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */
348 , NOTIFICATION_EVENT
349#endif /* HAVE_ANDROID */
346}; 350};
347 351
348/* Bit width of an enum event_kind tag at the start of structs and unions. */ 352/* Bit width of an enum event_kind tag at the start of structs and unions. */