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-cancel @var{on-cancel}
@item :actions @var{actions}
@item :resident @var{resident}
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}
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
notifications displayed with the defined @var{group}, except under
Android 7.1 and earlier.

View file

@ -1967,4 +1967,29 @@ In addition, arbitrary runtime exceptions (such as
else
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");
FIND_METHOD (request_storage_access,
"requestStorageAccess", "()V");
FIND_METHOD (cancel_notification,
"cancelNotification", "(Ljava/lang/String;)V");
#undef FIND_METHOD
}

View file

@ -302,6 +302,7 @@ struct android_emacs_service
jmethodID valid_authority;
jmethodID external_storage_available;
jmethodID request_storage_access;
jmethodID cancel_notification;
};
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,
REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, ACTION_CB and CANCEL_CB.
Return an identifier for the resulting notification. */
REPLACES_ID, GROUP, ICON, URGENCY, ACTIONS, RESIDENT, ACTION_CB and
CANCEL_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 actions,
Lisp_Object action_cb,
Lisp_Object resident, Lisp_Object action_cb,
Lisp_Object cancel_cb)
{
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 (!NILP (action_cb) || !NILP (cancel_cb))
Fputhash (build_string (identifier), Fcons (action_cb, cancel_cb),
if (!NILP (action_cb) || !NILP (cancel_cb) || !NILP (resident))
Fputhash (build_string (identifier), list3 (action_cb, cancel_cb,
resident),
notification_table);
/* Return the ID. */
@ -755,12 +756,12 @@ ARGS must contain keywords followed by values. Each of the following
keywords is understood:
:title The notification title.
:body The notification body.
: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
: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 ...)
@ -770,6 +771,8 @@ keywords is understood:
its existence is implied, and its TITLE is ignored.
No more than three actions can be defined, not
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.
The notification id and the key of the action are
provided as arguments to the function.
@ -811,7 +814,7 @@ 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, resident;
Lisp_Object icon;
Lisp_Object key, value, actions, action_cb, cancel_cb;
ptrdiff_t i;
@ -821,7 +824,7 @@ usage: (android-notifications-notify &rest ARGS) */)
/* Clear each variable above. */
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. */
@ -849,6 +852,8 @@ usage: (android-notifications-notify &rest ARGS) */)
icon = value;
else if (EQ (key, QCactions))
actions = value;
else if (EQ (key, QCresident))
resident = value;
else if (EQ (key, QCon_action))
action_cb = value;
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,
group, icon, urgency,
actions, action_cb,
cancel_cb));
actions, resident,
action_cb, cancel_cb));
}
/* Run callbacks in response to a notification being deleted.
@ -899,7 +904,7 @@ android_notification_deleted (struct android_notification_event *event,
if (!NILP (item))
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)
{
ie->kind = NOTIFICATION_EVENT;
@ -919,6 +924,8 @@ android_notification_action (struct android_notification_event *event,
{
Lisp_Object item, tag;
intmax_t id;
jstring tag_object;
jmethodID method;
tag = build_string (event->tag);
item = Fgethash (tag, notification_table, Qnil);
@ -929,6 +936,29 @@ android_notification_action (struct android_notification_event *event,
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);
}
}
@ -971,6 +1001,7 @@ syms_of_androidselect (void)
DEFSYM (QCurgency, ":urgency");
DEFSYM (QCicon, ":icon");
DEFSYM (QCactions, ":actions");
DEFSYM (QCresident, ":resident");
DEFSYM (QCon_action, ":on-action");
DEFSYM (QCon_cancel, ":on-cancel");