diff options
| author | Po Lu | 2024-03-11 21:40:47 +0800 |
|---|---|---|
| committer | Po Lu | 2024-03-11 21:41:14 +0800 |
| commit | a7a37341cad230448e487d0ffa343eeeb8a66a65 (patch) | |
| tree | ab06b5826c5ae7bdd5b3dcc85b3ecba8dbee8c84 /src | |
| parent | 75cfc6c73faa1561018b1212156964a7919c69fe (diff) | |
| download | emacs-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.c | 161 | ||||
| -rw-r--r-- | src/android.h | 7 | ||||
| -rw-r--r-- | src/androidgui.h | 29 | ||||
| -rw-r--r-- | src/androidselect.c | 210 | ||||
| -rw-r--r-- | src/androidterm.c | 22 | ||||
| -rw-r--r-- | src/androidterm.h | 6 | ||||
| -rw-r--r-- | src/androidvfs.c | 2 | ||||
| -rw-r--r-- | src/keyboard.c | 10 | ||||
| -rw-r--r-- | src/termhooks.h | 4 |
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 | ||
| 2460 | JNIEXPORT jboolean JNICALL | 2460 | JNIEXPORT jlong JNICALL |
| 2461 | NATIVE_NAME (sendDndDrag) (JNIEnv *env, jobject object, | 2461 | NATIVE_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 | ||
| 2480 | JNIEXPORT jboolean JNICALL | 2480 | JNIEXPORT jlong JNICALL |
| 2481 | NATIVE_NAME (sendDndUri) (JNIEnv *env, jobject object, | 2481 | NATIVE_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 | ||
| 2517 | JNIEXPORT jboolean JNICALL | 2517 | JNIEXPORT jlong JNICALL |
| 2518 | NATIVE_NAME (sendDndText) (JNIEnv *env, jobject object, | 2518 | NATIVE_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 | ||
| 2554 | JNIEXPORT jlong JNICALL | ||
| 2555 | NATIVE_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 | |||
| 2585 | JNIEXPORT jlong JNICALL | ||
| 2586 | NATIVE_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 | |||
| 2554 | JNIEXPORT jboolean JNICALL | 2633 | JNIEXPORT jboolean JNICALL |
| 2555 | NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env, | 2634 | NATIVE_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 | |||
| 6395 | void | ||
| 6396 | android_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 | |||
| 6432 | void | ||
| 6433 | android_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); | |||
| 118 | extern void android_exception_check_2 (jobject, jobject); | 118 | extern void android_exception_check_2 (jobject, jobject); |
| 119 | extern void android_exception_check_3 (jobject, jobject, jobject); | 119 | extern void android_exception_check_3 (jobject, jobject, jobject); |
| 120 | extern void android_exception_check_4 (jobject, jobject, jobject, jobject); | 120 | extern void android_exception_check_4 (jobject, jobject, jobject, jobject); |
| 121 | extern void android_exception_check_5 (jobject, jobject, jobject, jobject, | ||
| 122 | jobject); | ||
| 123 | extern void android_exception_check_6 (jobject, jobject, jobject, jobject, | ||
| 124 | jobject, jobject); | ||
| 121 | extern void android_exception_check_nonnull (void *, jobject); | 125 | extern void android_exception_check_nonnull (void *, jobject); |
| 122 | extern void android_exception_check_nonnull_1 (void *, jobject, jobject); | 126 | extern void android_exception_check_nonnull_1 (void *, jobject, jobject); |
| 123 | 127 | ||
| @@ -306,6 +310,9 @@ extern JNIEnv *android_java_env; | |||
| 306 | extern JavaVM *android_jvm; | 310 | extern JavaVM *android_jvm; |
| 307 | #endif /* THREADS_ENABLED */ | 311 | #endif /* THREADS_ENABLED */ |
| 308 | 312 | ||
| 313 | /* The Java String class. */ | ||
| 314 | extern jclass java_string_class; | ||
| 315 | |||
| 309 | /* The EmacsService object. */ | 316 | /* The EmacsService object. */ |
| 310 | extern jobject emacs_service; | 317 | extern 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 | ||
| 256 | struct android_any_event | 258 | struct android_any_event |
| @@ -535,6 +537,29 @@ struct android_dnd_event | |||
| 535 | size_t length; | 537 | size_t length; |
| 536 | }; | 538 | }; |
| 537 | 539 | ||
| 540 | struct 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 | |||
| 538 | union android_event | 563 | union 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 | ||
| 576 | enum | 605 | enum |
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. */ |
| 491 | static struct android_emacs_desktop_notification notification_class; | 492 | static struct android_emacs_desktop_notification notification_class; |
| 492 | 493 | ||
| 494 | /* Hash table pairing notification identifiers with callbacks. */ | ||
| 495 | static 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 | ||
| 568 | static intmax_t | 573 | static intmax_t |
| 569 | android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, | 574 | android_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, | |||
| 659 | ARGS must contain keywords followed by values. Each of the following | 754 | ARGS must contain keywords followed by values. Each of the following |
| 660 | keywords is understood: | 755 | keywords 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 | ||
| 671 | The notification group is ignored on Android 7.1 and earlier versions | 780 | The notification group is ignored on Android 7.1 and earlier versions |
| 672 | of Android. Outside such older systems, it identifies a category that | 781 | of 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 | |||
| 686 | transparent background. If no icon is provided (or the icon is absent | 795 | transparent background. If no icon is provided (or the icon is absent |
| 687 | from this system), it defaults to "ic_dialog_alert". | 796 | from this system), it defaults to "ic_dialog_alert". |
| 688 | 797 | ||
| 798 | Actions specified with :actions cannot be displayed on Android 4.0 and | ||
| 799 | earlier versions of the system. | ||
| 800 | |||
| 689 | When the system is running Android 13 or later, notifications sent | 801 | When the system is running Android 13 or later, notifications sent |
| 690 | will be silently disregarded unless permission to display | 802 | will be silently disregarded unless permission to display |
| 691 | notifications is expressly granted from the "App Info" settings panel | 803 | notifications 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 | |||
| 889 | void | ||
| 890 | android_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 | |||
| 916 | void | ||
| 917 | android_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 (¬ification_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 | ||
| 4745 | static unsigned short * | 4765 | static unsigned short * |
| 4746 | android_copy_java_string (JNIEnv *env, jstring string, size_t *length) | 4766 | android_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 | ||
| 29 | struct android_bitmap_record | 30 | struct 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 | ||
| 468 | extern void android_notification_deleted (struct android_notification_event *, | ||
| 469 | struct input_event *); | ||
| 470 | extern void android_notification_action (struct android_notification_event *, | ||
| 471 | struct input_event *, Lisp_Object); | ||
| 472 | |||
| 467 | extern void init_androidselect (void); | 473 | extern void init_androidselect (void); |
| 468 | extern void syms_of_androidselect (void); | 474 | extern 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. */ |
| 295 | static jclass java_string_class; | 295 | jclass java_string_class; |
| 296 | 296 | ||
| 297 | /* Fields and methods associated with the Cursor class. */ | 297 | /* Fields and methods associated with the Cursor class. */ |
| 298 | static struct android_cursor_class cursor_class; | 298 | static 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. */ |