aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorPo Lu2024-03-11 21:40:47 +0800
committerPo Lu2024-03-11 21:41:14 +0800
commita7a37341cad230448e487d0ffa343eeeb8a66a65 (patch)
treeab06b5826c5ae7bdd5b3dcc85b3ecba8dbee8c84 /java
parent75cfc6c73faa1561018b1212156964a7919c69fe (diff)
downloademacs-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 'java')
-rw-r--r--java/org/gnu/emacs/EmacsActivity.java21
-rw-r--r--java/org/gnu/emacs/EmacsDesktopNotification.java162
-rw-r--r--java/org/gnu/emacs/EmacsNative.java6
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
453 syncFullscreenWith (window); 453 syncFullscreenWith (window);
454 } 454 }
455 455
456 @Override
457 public final void
458 onNewIntent (Intent intent)
459 {
460 String tag, action;
461
462 /* This function is called when EmacsActivity is relaunched from a
463 notification. */
464
465 if (intent == null || EmacsService.SERVICE == null)
466 return;
467
468 tag = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_TAG);
469 action
470 = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_ACTION);
471
472 if (tag == null || action == null)
473 return;
474
475 EmacsNative.sendNotificationAction (tag, action);
476 }
456 477
457 478
458 @Override 479 @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;
24import android.app.NotificationChannel; 24import android.app.NotificationChannel;
25import android.app.PendingIntent; 25import android.app.PendingIntent;
26 26
27import android.content.BroadcastReceiver;
27import android.content.Context; 28import android.content.Context;
28import android.content.Intent; 29import android.content.Intent;
29 30
31import android.net.Uri;
32
30import android.os.Build; 33import android.os.Build;
31 34
32import android.widget.RemoteViews; 35import android.widget.RemoteViews;
@@ -44,6 +47,16 @@ import android.widget.RemoteViews;
44 47
45public final class EmacsDesktopNotification 48public final class EmacsDesktopNotification
46{ 49{
50 /* Intent tag for notification action data. */
51 public static final String NOTIFICATION_ACTION = "emacs:notification_action";
52
53 /* Intent tag for notification IDs. */
54 public static final String NOTIFICATION_TAG = "emacs:notification_tag";
55
56 /* Action ID assigned to the broadcast receiver which should be
57 notified of any notification's being dismissed. */
58 public static final String NOTIFICATION_DISMISSED = "org.gnu.emacs.DISMISSED";
59
47 /* The content of this desktop notification. */ 60 /* The content of this desktop notification. */
48 public final String content; 61 public final String content;
49 62
@@ -66,10 +79,15 @@ public final class EmacsDesktopNotification
66 /* The importance of this notification's group. */ 79 /* The importance of this notification's group. */
67 public final int importance; 80 public final int importance;
68 81
82 /* Array of actions and their user-facing text to be offered by this
83 notification. */
84 public final String[] actions, titles;
85
69 public 86 public
70 EmacsDesktopNotification (String title, String content, 87 EmacsDesktopNotification (String title, String content,
71 String group, String tag, int icon, 88 String group, String tag, int icon,
72 int importance) 89 int importance,
90 String[] actions, String[] titles)
73 { 91 {
74 this.content = content; 92 this.content = content;
75 this.title = title; 93 this.title = title;
@@ -77,12 +95,68 @@ public final class EmacsDesktopNotification
77 this.tag = tag; 95 this.tag = tag;
78 this.icon = icon; 96 this.icon = icon;
79 this.importance = importance; 97 this.importance = importance;
98 this.actions = actions;
99 this.titles = titles;
80 } 100 }
81 101
82 102
83 103
84 /* Functions for displaying desktop notifications. */ 104 /* Functions for displaying desktop notifications. */
85 105
106 /* Insert each action in actions and titles into the notification
107 builder BUILDER, with pending intents created with CONTEXT holding
108 suitable metadata. */
109
110 @SuppressWarnings ("deprecation")
111 private void
112 insertActions (Context context, Notification.Builder builder)
113 {
114 int i;
115 PendingIntent pending;
116 Intent intent;
117 Notification.Action.Builder action;
118
119 if (actions == null)
120 return;
121
122 for (i = 0; i < actions.length; ++i)
123 {
124 /* Actions named default should not be displayed. */
125 if (actions[i].equals ("default"))
126 continue;
127
128 intent = new Intent (context, EmacsActivity.class);
129 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
130
131 /* Pending intents are specific to combinations of class, action
132 and data, but not information provided as extras. In order
133 that its target may be invoked with the action and tag set
134 below, generate a URL from those two elements and specify it
135 as the intent data, which ensures that the intent allocated
136 fully reflects the duo. */
137
138 intent.setData (new Uri.Builder ().scheme ("action")
139 .appendPath (tag).appendPath (actions[i])
140 .build ());
141 intent.putExtra (NOTIFICATION_ACTION, actions[i]);
142 intent.putExtra (NOTIFICATION_TAG, tag);
143
144 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
145 pending = PendingIntent.getActivity (context, 0, intent,
146 PendingIntent.FLAG_IMMUTABLE);
147 else
148 pending = PendingIntent.getActivity (context, 0, intent, 0);
149
150 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
151 {
152 action = new Notification.Action.Builder (0, titles[i], pending);
153 builder.addAction (action.build ());
154 }
155 else
156 builder.addAction (0, titles[i], pending);
157 }
158 }
159
86 /* Internal helper for `display' executed on the main thread. */ 160 /* Internal helper for `display' executed on the main thread. */
87 161
88 @SuppressWarnings ("deprecation") /* Notification.Builder (Context). */ 162 @SuppressWarnings ("deprecation") /* Notification.Builder (Context). */
@@ -97,6 +171,7 @@ public final class EmacsDesktopNotification
97 Intent intent; 171 Intent intent;
98 PendingIntent pending; 172 PendingIntent pending;
99 int priority; 173 int priority;
174 Notification.Builder builder;
100 175
101 tem = context.getSystemService (Context.NOTIFICATION_SERVICE); 176 tem = context.getSystemService (Context.NOTIFICATION_SERVICE);
102 manager = (NotificationManager) tem; 177 manager = (NotificationManager) tem;
@@ -108,13 +183,16 @@ public final class EmacsDesktopNotification
108 (such as its importance) will be overridden. */ 183 (such as its importance) will be overridden. */
109 channel = new NotificationChannel (group, group, importance); 184 channel = new NotificationChannel (group, group, importance);
110 manager.createNotificationChannel (channel); 185 manager.createNotificationChannel (channel);
186 builder = new Notification.Builder (context, group);
111 187
112 /* Create a notification object and display it. */ 188 /* Create and configure a notification object and display
113 notification = (new Notification.Builder (context, group) 189 it. */
114 .setContentTitle (title) 190
115 .setContentText (content) 191 builder.setContentTitle (title);
116 .setSmallIcon (icon) 192 builder.setContentText (content);
117 .build ()); 193 builder.setSmallIcon (icon);
194 insertActions (context, builder);
195 notification = builder.build ();
118 } 196 }
119 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) 197 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
120 { 198 {
@@ -138,12 +216,16 @@ public final class EmacsDesktopNotification
138 break; 216 break;
139 } 217 }
140 218
141 notification = (new Notification.Builder (context) 219 builder = new Notification.Builder (context);
142 .setContentTitle (title) 220 builder.setContentTitle (title);
143 .setContentText (content) 221 builder.setContentText (content);
144 .setSmallIcon (icon) 222 builder.setSmallIcon (icon);
145 .setPriority (priority) 223 builder.setPriority (priority);
146 .build ()); 224
225 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
226 insertActions (context, builder);
227
228 notification = builder.build ();
147 229
148 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) 230 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN)
149 notification.priority = priority; 231 notification.priority = priority;
@@ -170,6 +252,12 @@ public final class EmacsDesktopNotification
170 252
171 intent = new Intent (context, EmacsActivity.class); 253 intent = new Intent (context, EmacsActivity.class);
172 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); 254 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
255 intent.setData (new Uri.Builder ()
256 .scheme ("action")
257 .appendPath (tag)
258 .build ());
259 intent.putExtra (NOTIFICATION_ACTION, "default");
260 intent.putExtra (NOTIFICATION_TAG, tag);
173 261
174 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 262 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
175 pending = PendingIntent.getActivity (context, 0, intent, 263 pending = PendingIntent.getActivity (context, 0, intent,
@@ -179,6 +267,27 @@ public final class EmacsDesktopNotification
179 267
180 notification.contentIntent = pending; 268 notification.contentIntent = pending;
181 269
270 /* Provide a cancellation intent to respond to notification
271 dismissals. */
272
273 intent = new Intent (context, CancellationReceiver.class);
274 intent.setAction (NOTIFICATION_DISMISSED);
275 intent.setPackage ("org.gnu.emacs");
276 intent.setData (new Uri.Builder ()
277 .scheme ("action")
278 .appendPath (tag)
279 .build ());
280 intent.putExtra (NOTIFICATION_TAG, tag);
281
282 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
283 pending = PendingIntent.getBroadcast (context, 0, intent,
284 (PendingIntent.FLAG_IMMUTABLE
285 | PendingIntent.FLAG_ONE_SHOT));
286 else
287 pending = PendingIntent.getBroadcast (context, 0, intent,
288 PendingIntent.FLAG_ONE_SHOT);
289
290 notification.deleteIntent = pending;
182 manager.notify (tag, 2, notification); 291 manager.notify (tag, 2, notification);
183 } 292 }
184 293
@@ -199,4 +308,31 @@ public final class EmacsDesktopNotification
199 } 308 }
200 }); 309 });
201 } 310 }
311
312
313
314 /* Broadcast receiver. This is something of a system-wide callback
315 arranged to be invoked whenever a notification posted by Emacs is
316 dismissed, in order to relay news of its dismissal to
317 androidselect.c and run or remove callbacks as appropriate. */
318
319 public static class CancellationReceiver extends BroadcastReceiver
320 {
321 @Override
322 public void
323 onReceive (Context context, Intent intent)
324 {
325 String tag, action;
326
327 if (intent == null || EmacsService.SERVICE == null)
328 return;
329
330 tag = intent.getStringExtra (NOTIFICATION_TAG);
331
332 if (tag == null)
333 return;
334
335 EmacsNative.sendNotificationDeleted (tag);
336 }
337 };
202}; 338};
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
196 public static native long sendDndText (short window, int x, int y, 196 public static native long sendDndText (short window, int x, int y,
197 String text); 197 String text);
198 198
199 /* Send an ANDROID_NOTIFICATION_CANCELED event. */
200 public static native void sendNotificationDeleted (String tag);
201
202 /* Send an ANDROID_NOTIFICATION_ACTION event. */
203 public static native void sendNotificationAction (String tag, String action);
204
199 /* Return the file name associated with the specified file 205 /* Return the file name associated with the specified file
200 descriptor, or NULL if there is none. */ 206 descriptor, or NULL if there is none. */
201 public static native byte[] getProcName (int fd); 207 public static native byte[] getProcName (int fd);