1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-09 07:40:39 -08:00

Implement notification residency on Android

* doc/lispref/os.texi (Desktop Notifications): Document support
for `:resident'.

* java/org/gnu/emacs/EmacsService.java (cancelNotification):

* src/android.c (android_init_emacs_service):

* src/android.h (struct android_emacs_service): New function.

* src/androidselect.c (android_notifications_notify_1)
(Fandroid_notifications_notify): New parameter QCresident; save
it within notification lists.
(android_notification_deleted, android_notification_action):
Adjust for changes to the format of notification lists and
cancel non-resident notifications when an action is selected.
(syms_of_androidselect): <QCresident>: New symbol.
This commit is contained in:
Po Lu 2024-03-12 09:48:53 +08:00
parent bf38783c32
commit d7ded99608
5 changed files with 75 additions and 14 deletions

View file

@ -3244,11 +3244,13 @@ of parameters analogous to its namesake in
@item :on-action @var{on-action} @item :on-action @var{on-action}
@item :on-cancel @var{on-cancel} @item :on-cancel @var{on-cancel}
@item :actions @var{actions} @item :actions @var{actions}
@item :resident @var{resident}
These have the same meaning as they do when used in calls to These have the same meaning as they do when used in calls to
@code{notifications-notify}. @code{notifications-notify}, except that no more than three non-default
actions will be displayed.
@item :urgency @var{urgency} @item :urgency @var{urgency}
The set of values for @var{urgency} is the same as with The set of accepted values for @var{urgency} is the same as with
@code{notifications-notify}, but the urgency applies to all @code{notifications-notify}, but the urgency applies to all
notifications displayed with the defined @var{group}, except under notifications displayed with the defined @var{group}, except under
Android 7.1 and earlier. Android 7.1 and earlier.

View file

@ -1967,4 +1967,29 @@ public final class EmacsService extends Service
else else
requestStorageAccess30 (); requestStorageAccess30 ();
} }
/* Notification miscellany. */
/* Cancel any notification displayed with the tag TAG. */
public void
cancelNotification (final String string)
{
Object tem;
final NotificationManager manager;
tem = getSystemService (Context.NOTIFICATION_SERVICE);
manager = (NotificationManager) tem;
runOnUiThread (new Runnable () {
@Override
public void
run ()
{
manager.cancel (string, 2);
}
});
}
}; };

View file

@ -1688,6 +1688,8 @@ android_init_emacs_service (void)
"externalStorageAvailable", "()Z"); "externalStorageAvailable", "()Z");
FIND_METHOD (request_storage_access, FIND_METHOD (request_storage_access,
"requestStorageAccess", "()V"); "requestStorageAccess", "()V");
FIND_METHOD (cancel_notification,
"cancelNotification", "(Ljava/lang/String;)V");
#undef FIND_METHOD #undef FIND_METHOD
} }

View file

@ -302,6 +302,7 @@ struct android_emacs_service
jmethodID valid_authority; jmethodID valid_authority;
jmethodID external_storage_available; jmethodID external_storage_available;
jmethodID request_storage_access; jmethodID request_storage_access;
jmethodID cancel_notification;
}; };
extern JNIEnv *android_java_env; extern JNIEnv *android_java_env;

View file

@ -567,15 +567,15 @@ android_locate_icon (const char *name)
} }
/* Display a desktop notification with the provided TITLE, BODY, /* Display a desktop notification with the provided TITLE, BODY,
REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, ACTION_CB and CANCEL_CB. REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, RESIDENT, ACTION_CB and
Return an identifier for the resulting notification. */ CANCEL_CB. Return an identifier for the resulting notification. */
static intmax_t static intmax_t
android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
Lisp_Object replaces_id, Lisp_Object replaces_id,
Lisp_Object group, Lisp_Object icon, Lisp_Object group, Lisp_Object icon,
Lisp_Object urgency, Lisp_Object actions, Lisp_Object urgency, Lisp_Object actions,
Lisp_Object action_cb, Lisp_Object resident, Lisp_Object action_cb,
Lisp_Object cancel_cb) Lisp_Object cancel_cb)
{ {
static intmax_t counter; static intmax_t counter;
@ -740,8 +740,9 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
/* If callbacks are provided, save them into notification_table. */ /* If callbacks are provided, save them into notification_table. */
if (!NILP (action_cb) || !NILP (cancel_cb)) if (!NILP (action_cb) || !NILP (cancel_cb) || !NILP (resident))
Fputhash (build_string (identifier), Fcons (action_cb, cancel_cb), Fputhash (build_string (identifier), list3 (action_cb, cancel_cb,
resident),
notification_table); notification_table);
/* Return the ID. */ /* Return the ID. */
@ -755,12 +756,12 @@ ARGS must contain keywords followed by values. Each of the following
keywords is understood: keywords is understood:
:title The notification title. :title The notification title.
:body The notification body. :body The notification body.
:replaces-id The ID of a previous notification to supersede. :replaces-id The ID of a previous notification to supersede.
:group The notification group, or nil. :group The notification group, or nil.
:urgency One of the symbols `low', `normal' or `critical', :urgency One of the symbols `low', `normal' or `critical',
defining the importance of the notification group. defining the importance of the notification group.
:icon The name of a drawable resource to display as the :icon The name of a drawable resource to display as the
notification's icon. notification's icon.
:actions A list of actions of the form: :actions A list of actions of the form:
(KEY TITLE KEY TITLE ...) (KEY TITLE KEY TITLE ...)
@ -770,6 +771,8 @@ keywords is understood:
its existence is implied, and its TITLE is ignored. its existence is implied, and its TITLE is ignored.
No more than three actions can be defined, not No more than three actions can be defined, not
counting any action with "default" as its key. counting any action with "default" as its key.
: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. :on-action Function to call when an action is invoked.
The notification id and the key of the action are The notification id and the key of the action are
provided as arguments to the function. provided as arguments to the function.
@ -811,7 +814,7 @@ this function.
usage: (android-notifications-notify &rest ARGS) */) usage: (android-notifications-notify &rest ARGS) */)
(ptrdiff_t nargs, Lisp_Object *args) (ptrdiff_t nargs, Lisp_Object *args)
{ {
Lisp_Object title, body, replaces_id, group, urgency; Lisp_Object title, body, replaces_id, group, urgency, resident;
Lisp_Object icon; Lisp_Object icon;
Lisp_Object key, value, actions, action_cb, cancel_cb; Lisp_Object key, value, actions, action_cb, cancel_cb;
ptrdiff_t i; ptrdiff_t i;
@ -821,7 +824,7 @@ usage: (android-notifications-notify &rest ARGS) */)
/* Clear each variable above. */ /* Clear each variable above. */
title = body = replaces_id = group = icon = urgency = actions = Qnil; title = body = replaces_id = group = icon = urgency = actions = Qnil;
action_cb = cancel_cb = Qnil; resident = action_cb = cancel_cb = Qnil;
/* If NARGS is odd, error. */ /* If NARGS is odd, error. */
@ -849,6 +852,8 @@ usage: (android-notifications-notify &rest ARGS) */)
icon = value; icon = value;
else if (EQ (key, QCactions)) else if (EQ (key, QCactions))
actions = value; actions = value;
else if (EQ (key, QCresident))
resident = value;
else if (EQ (key, QCon_action)) else if (EQ (key, QCon_action))
action_cb = value; action_cb = value;
else if (EQ (key, QCon_cancel)) else if (EQ (key, QCon_cancel))
@ -878,8 +883,8 @@ usage: (android-notifications-notify &rest ARGS) */)
return make_int (android_notifications_notify_1 (title, body, replaces_id, return make_int (android_notifications_notify_1 (title, body, replaces_id,
group, icon, urgency, group, icon, urgency,
actions, action_cb, actions, resident,
cancel_cb)); action_cb, cancel_cb));
} }
/* Run callbacks in response to a notification being deleted. /* Run callbacks in response to a notification being deleted.
@ -899,7 +904,7 @@ android_notification_deleted (struct android_notification_event *event,
if (!NILP (item)) if (!NILP (item))
Fremhash (tag, notification_table); Fremhash (tag, notification_table);
if (CONSP (item) && FUNCTIONP (XCDR (item)) if (CONSP (item) && FUNCTIONP (XCAR (XCDR (item)))
&& sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0) && sscanf (event->tag, "%*d.%*ld.%jd", &id) > 0)
{ {
ie->kind = NOTIFICATION_EVENT; ie->kind = NOTIFICATION_EVENT;
@ -919,6 +924,8 @@ android_notification_action (struct android_notification_event *event,
{ {
Lisp_Object item, tag; Lisp_Object item, tag;
intmax_t id; intmax_t id;
jstring tag_object;
jmethodID method;
tag = build_string (event->tag); tag = build_string (event->tag);
item = Fgethash (tag, notification_table, Qnil); item = Fgethash (tag, notification_table, Qnil);
@ -929,6 +936,29 @@ android_notification_action (struct android_notification_event *event,
ie->kind = NOTIFICATION_EVENT; ie->kind = NOTIFICATION_EVENT;
ie->arg = list3 (XCAR (item), make_int (id), action); 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);
}
} }
@ -971,6 +1001,7 @@ syms_of_androidselect (void)
DEFSYM (QCurgency, ":urgency"); DEFSYM (QCurgency, ":urgency");
DEFSYM (QCicon, ":icon"); DEFSYM (QCicon, ":icon");
DEFSYM (QCactions, ":actions"); DEFSYM (QCactions, ":actions");
DEFSYM (QCresident, ":resident");
DEFSYM (QCon_action, ":on-action"); DEFSYM (QCon_action, ":on-action");
DEFSYM (QCon_cancel, ":on-cancel"); DEFSYM (QCon_cancel, ":on-cancel");