diff options
author | Po Lu <luangruo@yahoo.com> | 2024-03-11 21:40:47 +0800 |
---|---|---|
committer | Po Lu <luangruo@yahoo.com> | 2024-03-11 21:41:14 +0800 |
commit | a7a37341cad230448e487d0ffa343eeeb8a66a65 (patch) | |
tree | ab06b5826c5ae7bdd5b3dcc85b3ecba8dbee8c84 /java | |
parent | 75cfc6c73faa1561018b1212156964a7919c69fe (diff) | |
download | emacs-a7a37341cad230448e487d0ffa343eeeb8a66a65.tar.gz |
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 'java')
-rw-r--r-- | java/org/gnu/emacs/EmacsActivity.java | 21 | ||||
-rw-r--r-- | java/org/gnu/emacs/EmacsDesktopNotification.java | 162 | ||||
-rw-r--r-- | java/org/gnu/emacs/EmacsNative.java | 6 |
3 files changed, 176 insertions, 13 deletions
diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 66a1e41d84c..06b9c0f005d 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -453,6 +453,27 @@ public class EmacsActivity extends Activity syncFullscreenWith (window); } + @Override + public final void + onNewIntent (Intent intent) + { + String tag, action; + + /* This function is called when EmacsActivity is relaunched from a + notification. */ + + if (intent == null || EmacsService.SERVICE == null) + return; + + tag = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_TAG); + action + = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_ACTION); + + if (tag == null || action == null) + return; + + EmacsNative.sendNotificationAction (tag, action); + } @Override diff --git a/java/org/gnu/emacs/EmacsDesktopNotification.java b/java/org/gnu/emacs/EmacsDesktopNotification.java index fb35e3fea1f..f52c3d9d4fb 100644 --- a/java/org/gnu/emacs/EmacsDesktopNotification.java +++ b/java/org/gnu/emacs/EmacsDesktopNotification.java @@ -24,9 +24,12 @@ import android.app.NotificationManager; import android.app.NotificationChannel; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.net.Uri; + import android.os.Build; import android.widget.RemoteViews; @@ -44,6 +47,16 @@ import android.widget.RemoteViews; public final class EmacsDesktopNotification { + /* Intent tag for notification action data. */ + public static final String NOTIFICATION_ACTION = "emacs:notification_action"; + + /* Intent tag for notification IDs. */ + public static final String NOTIFICATION_TAG = "emacs:notification_tag"; + + /* Action ID assigned to the broadcast receiver which should be + notified of any notification's being dismissed. */ + public static final String NOTIFICATION_DISMISSED = "org.gnu.emacs.DISMISSED"; + /* The content of this desktop notification. */ public final String content; @@ -66,10 +79,15 @@ public final class EmacsDesktopNotification /* The importance of this notification's group. */ public final int importance; + /* Array of actions and their user-facing text to be offered by this + notification. */ + public final String[] actions, titles; + public EmacsDesktopNotification (String title, String content, String group, String tag, int icon, - int importance) + int importance, + String[] actions, String[] titles) { this.content = content; this.title = title; @@ -77,12 +95,68 @@ public final class EmacsDesktopNotification this.tag = tag; this.icon = icon; this.importance = importance; + this.actions = actions; + this.titles = titles; } /* Functions for displaying desktop notifications. */ + /* Insert each action in actions and titles into the notification + builder BUILDER, with pending intents created with CONTEXT holding + suitable metadata. */ + + @SuppressWarnings ("deprecation") + private void + insertActions (Context context, Notification.Builder builder) + { + int i; + PendingIntent pending; + Intent intent; + Notification.Action.Builder action; + + if (actions == null) + return; + + for (i = 0; i < actions.length; ++i) + { + /* Actions named default should not be displayed. */ + if (actions[i].equals ("default")) + continue; + + intent = new Intent (context, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + + /* Pending intents are specific to combinations of class, action + and data, but not information provided as extras. In order + that its target may be invoked with the action and tag set + below, generate a URL from those two elements and specify it + as the intent data, which ensures that the intent allocated + fully reflects the duo. */ + + intent.setData (new Uri.Builder ().scheme ("action") + .appendPath (tag).appendPath (actions[i]) + .build ()); + intent.putExtra (NOTIFICATION_ACTION, actions[i]); + intent.putExtra (NOTIFICATION_TAG, tag); + + 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); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + action = new Notification.Action.Builder (0, titles[i], pending); + builder.addAction (action.build ()); + } + else + builder.addAction (0, titles[i], pending); + } + } + /* Internal helper for `display' executed on the main thread. */ @SuppressWarnings ("deprecation") /* Notification.Builder (Context). */ @@ -97,6 +171,7 @@ public final class EmacsDesktopNotification Intent intent; PendingIntent pending; int priority; + Notification.Builder builder; tem = context.getSystemService (Context.NOTIFICATION_SERVICE); manager = (NotificationManager) tem; @@ -108,13 +183,16 @@ public final class EmacsDesktopNotification (such as its importance) will be overridden. */ channel = new NotificationChannel (group, group, importance); manager.createNotificationChannel (channel); + builder = new Notification.Builder (context, group); - /* Create a notification object and display it. */ - notification = (new Notification.Builder (context, group) - .setContentTitle (title) - .setContentText (content) - .setSmallIcon (icon) - .build ()); + /* Create and configure a notification object and display + it. */ + + builder.setContentTitle (title); + builder.setContentText (content); + builder.setSmallIcon (icon); + insertActions (context, builder); + notification = builder.build (); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { @@ -138,12 +216,16 @@ public final class EmacsDesktopNotification break; } - notification = (new Notification.Builder (context) - .setContentTitle (title) - .setContentText (content) - .setSmallIcon (icon) - .setPriority (priority) - .build ()); + builder = new Notification.Builder (context); + builder.setContentTitle (title); + builder.setContentText (content); + builder.setSmallIcon (icon); + builder.setPriority (priority); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + insertActions (context, builder); + + notification = builder.build (); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) notification.priority = priority; @@ -170,6 +252,12 @@ public final class EmacsDesktopNotification intent = new Intent (context, EmacsActivity.class); intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setData (new Uri.Builder () + .scheme ("action") + .appendPath (tag) + .build ()); + intent.putExtra (NOTIFICATION_ACTION, "default"); + intent.putExtra (NOTIFICATION_TAG, tag); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) pending = PendingIntent.getActivity (context, 0, intent, @@ -179,6 +267,27 @@ public final class EmacsDesktopNotification notification.contentIntent = pending; + /* Provide a cancellation intent to respond to notification + dismissals. */ + + intent = new Intent (context, CancellationReceiver.class); + intent.setAction (NOTIFICATION_DISMISSED); + intent.setPackage ("org.gnu.emacs"); + intent.setData (new Uri.Builder () + .scheme ("action") + .appendPath (tag) + .build ()); + intent.putExtra (NOTIFICATION_TAG, tag); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + pending = PendingIntent.getBroadcast (context, 0, intent, + (PendingIntent.FLAG_IMMUTABLE + | PendingIntent.FLAG_ONE_SHOT)); + else + pending = PendingIntent.getBroadcast (context, 0, intent, + PendingIntent.FLAG_ONE_SHOT); + + notification.deleteIntent = pending; manager.notify (tag, 2, notification); } @@ -199,4 +308,31 @@ public final class EmacsDesktopNotification } }); } + + + + /* Broadcast receiver. This is something of a system-wide callback + arranged to be invoked whenever a notification posted by Emacs is + dismissed, in order to relay news of its dismissal to + androidselect.c and run or remove callbacks as appropriate. */ + + public static class CancellationReceiver extends BroadcastReceiver + { + @Override + public void + onReceive (Context context, Intent intent) + { + String tag, action; + + if (intent == null || EmacsService.SERVICE == null) + return; + + tag = intent.getStringExtra (NOTIFICATION_TAG); + + if (tag == null) + return; + + EmacsNative.sendNotificationDeleted (tag); + } + }; }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index cd0e70923d1..6845f833908 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -196,6 +196,12 @@ public final class EmacsNative public static native long sendDndText (short window, int x, int y, String text); + /* Send an ANDROID_NOTIFICATION_CANCELED event. */ + public static native void sendNotificationDeleted (String tag); + + /* Send an ANDROID_NOTIFICATION_ACTION event. */ + public static native void sendNotificationAction (String tag, String action); + /* Return the file name associated with the specified file descriptor, or NULL if there is none. */ public static native byte[] getProcName (int fd); |