From b1e498ac8c067b265c5f3de778e0a2722f7948a6 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 21 Aug 2023 09:36:52 +0800 Subject: [PATCH] Enable providing icons for Android desktop notifications * doc/lispref/os.texi (Desktop Notifications) : Mention the :icon parameter. * java/org/gnu/emacs/EmacsDesktopNotification.java (EmacsDesktopNotification) : New field. (): New argument ICON. Set this.icon to its value. (display1): Use provided icon and always supply a pending intent to open Emacs once the notification is clicked. * java/res/layout/sdk8_notifications_view.xml (sdk8_notifications_title, sdk8_notifications_content): Set foreground color to #000000. * src/androidselect.c (android_init_emacs_desktop_notification): Update signature of . (android_locate_icon): New function. (android_notifications_notify_1): New arg ICON. (Fandroid_notifications_notify): New parameter icon. (syms_of_androidselect): : New symbol. --- doc/lispref/os.texi | 10 +++ .../gnu/emacs/EmacsDesktopNotification.java | 33 ++++++--- java/res/layout/sdk8_notifications_view.xml | 2 + src/androidselect.c | 73 ++++++++++++++++--- 4 files changed, 98 insertions(+), 20 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index cf65380a3ac..8a3e5663468 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -3228,6 +3228,16 @@ all notifications displayed with the defined @var{group}. If @var{group} is nil or not present within @var{params}, it is replaced by the string @samp{"Desktop Notifications"}. + +@item :icon @var{icon} +This parameter controls the symbolic icon the notification will be +displayed with. Its value is a string designating an icon within the +@code{android.R.drawable} system package. See +@url{https://developer.android.com/reference/android/R.drawable,,R.drawable +| Android Developers} for a list of such icons. + +If it is not provided within @var{params} or @var{icon} does not +exist, it defaults to @samp{"ic_dialog_alert"}. @end table It returns a number identifying the notification, which may be diff --git a/java/org/gnu/emacs/EmacsDesktopNotification.java b/java/org/gnu/emacs/EmacsDesktopNotification.java index b73ef1022fa..c6ebbc6ae48 100644 --- a/java/org/gnu/emacs/EmacsDesktopNotification.java +++ b/java/org/gnu/emacs/EmacsDesktopNotification.java @@ -60,17 +60,22 @@ public final class EmacsDesktopNotification function. */ public final String tag; + /* The identifier of this notification's icon. */ + public final int icon; + /* The importance of this notification's group. */ public final int importance; public EmacsDesktopNotification (String title, String content, - String group, String tag, int importance) + String group, String tag, int icon, + int importance) { this.content = content; this.title = title; this.group = group; this.tag = tag; + this.icon = icon; this.importance = importance; } @@ -107,19 +112,19 @@ public final class EmacsDesktopNotification notification = (new Notification.Builder (context, group) .setContentTitle (title) .setContentText (content) - .setSmallIcon (R.drawable.emacs) + .setSmallIcon (icon) .build ()); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) notification = (new Notification.Builder (context) .setContentTitle (title) .setContentText (content) - .setSmallIcon (R.drawable.emacs) + .setSmallIcon (icon) .build ()); else { notification = new Notification (); - notification.icon = R.drawable.emacs; + notification.icon = icon; /* This remote widget tree is defined in java/res/layout/sdk8_notifications_view.xml. */ @@ -131,15 +136,21 @@ public final class EmacsDesktopNotification title); contentView.setTextViewText (R.id.sdk8_notifications_content, content); + } - /* A content intent must be provided on these old versions of - Android. */ + /* Provide a content intent which starts Emacs when the + notification is clicked. */ - intent = new Intent (context, EmacsActivity.class); - intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); - pending = PendingIntent.getActivity (context, 0, intent, 0); - notification.contentIntent = pending; - } + intent = new Intent (context, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) + pending = PendingIntent.getActivity (context, 0, intent, + PendingIntent.FLAG_IMMUTABLE); + else + pending = PendingIntent.getActivity (context, 0, intent, 0); + + notification.contentIntent = pending; manager.notify (tag, 2, notification); } diff --git a/java/res/layout/sdk8_notifications_view.xml b/java/res/layout/sdk8_notifications_view.xml index 2daa5beea86..0572f350cff 100644 --- a/java/res/layout/sdk8_notifications_view.xml +++ b/java/res/layout/sdk8_notifications_view.xml @@ -23,9 +23,11 @@ along with GNU Emacs. If not, see . --> android:layout_height="wrap_content" android:padding="8dp"> diff --git a/src/androidselect.c b/src/androidselect.c index 5551598032d..5735eda2dd5 100644 --- a/src/androidselect.c +++ b/src/androidselect.c @@ -515,24 +515,60 @@ android_init_emacs_desktop_notification (void) FIND_METHOD (init, "", "(Ljava/lang/String;" "Ljava/lang/String;Ljava/lang/String;" - "Ljava/lang/String;I)V"); + "Ljava/lang/String;II)V"); FIND_METHOD (display, "display", "()V"); #undef FIND_METHOD } +/* Return the numeric resource ID designating the icon within the + ``android.R.drawable'' package by the supplied NAME. + + If no icon is found, return that of + ``android.R.drawable.ic_dialog_alert''. */ + +static jint +android_locate_icon (const char *name) +{ + jclass drawable; + jfieldID field; + jint rc; + + if (android_verify_jni_string (name)) + /* If NAME isn't valid, return the default value. */ + return 17301543; /* android.R.drawable.ic_dialog_alert. */ + + drawable = (*android_java_env)->FindClass (android_java_env, + "android/R$drawable"); + android_exception_check (); + + field = (*android_java_env)->GetStaticFieldID (android_java_env, + drawable, name, "I"); + (*android_java_env)->ExceptionClear (android_java_env); + + if (!field) + rc = 17301543; /* android.R.drawable.ic_dialog_alert. */ + else + rc = (*android_java_env)->GetStaticIntField (android_java_env, + drawable, field); + + ANDROID_DELETE_LOCAL_REF (drawable); + return rc; +} + /* Display a desktop notification with the provided TITLE, BODY, - REPLACES_ID, GROUP and URGENCY. Return an identifier for the - resulting notification. */ + REPLACES_ID, GROUP, ICON, and URGENCY. 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 urgency) + Lisp_Object group, Lisp_Object icon, + Lisp_Object urgency) { static intmax_t counter; intmax_t id; jstring title1, body1, group1, identifier1; - jint type; + jint type, icon1; jobject notification; char identifier[INT_STRLEN_BOUND (int) + INT_STRLEN_BOUND (long int) @@ -562,6 +598,9 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, id = -1; /* Overflow. */ } + /* Locate the integer ID linked to ICON. */ + icon1 = android_locate_icon (SSDATA (icon)); + /* Generate a unique identifier for this notification. Because Android persists notifications past system shutdown, also include the boot time within IDENTIFIER. Scale it down to avoid being @@ -585,7 +624,7 @@ android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, notification_class.class, notification_class.init, title1, body1, group1, - identifier1, type); + identifier1, icon1, type); android_exception_check_4 (title1, body1, group1, identifier1); /* Delete unused local references. */ @@ -618,6 +657,8 @@ keywords is understood: :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 and urgency are ignored on Android 7.1 and earlier versions of Android. Outside such older systems, it @@ -626,6 +667,11 @@ menu. 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". +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". + When the system is running Android 13 or later, notifications sent will be silently disregarded unless permission to display notifications is expressly granted from the "App Info" settings panel @@ -640,6 +686,7 @@ usage: (android-notifications-notify &rest ARGS) */) (ptrdiff_t nargs, Lisp_Object *args) { Lisp_Object title, body, replaces_id, group, urgency; + Lisp_Object icon; Lisp_Object key, value; ptrdiff_t i; @@ -647,7 +694,7 @@ usage: (android-notifications-notify &rest ARGS) */) error ("No Android display connection!"); /* Clear each variable above. */ - title = body = replaces_id = group = urgency = Qnil; + title = body = replaces_id = group = icon = urgency = Qnil; /* If NARGS is odd, error. */ @@ -671,6 +718,8 @@ usage: (android-notifications-notify &rest ARGS) */) group = value; else if (EQ (key, QCurgency)) urgency = value; + else if (EQ (key, QCicon)) + icon = value; } /* Demand at least TITLE and BODY be present. */ @@ -687,10 +736,15 @@ usage: (android-notifications-notify &rest ARGS) */) urgency = Qlow; if (NILP (group)) - group = build_string ("Desktop Notifications"); + group = build_string ("Desktop Notifications"); + + if (NILP (icon)) + icon = build_string ("ic_dialog_alert"); + else + CHECK_STRING (icon); return make_int (android_notifications_notify_1 (title, body, replaces_id, - group, urgency)); + group, icon, urgency)); } @@ -731,6 +785,7 @@ syms_of_androidselect (void) DEFSYM (QCreplaces_id, ":replaces-id"); DEFSYM (QCgroup, ":group"); DEFSYM (QCurgency, ":urgency"); + DEFSYM (QCicon, ":icon"); DEFSYM (Qlow, "low"); DEFSYM (Qnormal, "normal"); -- 2.39.2