diff options
| author | Po Lu | 2024-03-11 21:40:47 +0800 |
|---|---|---|
| committer | Po Lu | 2024-03-11 21:41:14 +0800 |
| commit | a7a37341cad230448e487d0ffa343eeeb8a66a65 (patch) | |
| tree | ab06b5826c5ae7bdd5b3dcc85b3ecba8dbee8c84 /java | |
| parent | 75cfc6c73faa1561018b1212156964a7919c69fe (diff) | |
| download | emacs-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.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 | |||
| 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; | |||
| 24 | import android.app.NotificationChannel; | 24 | import android.app.NotificationChannel; |
| 25 | import android.app.PendingIntent; | 25 | import android.app.PendingIntent; |
| 26 | 26 | ||
| 27 | import android.content.BroadcastReceiver; | ||
| 27 | import android.content.Context; | 28 | import android.content.Context; |
| 28 | import android.content.Intent; | 29 | import android.content.Intent; |
| 29 | 30 | ||
| 31 | import android.net.Uri; | ||
| 32 | |||
| 30 | import android.os.Build; | 33 | import android.os.Build; |
| 31 | 34 | ||
| 32 | import android.widget.RemoteViews; | 35 | import android.widget.RemoteViews; |
| @@ -44,6 +47,16 @@ import android.widget.RemoteViews; | |||
| 44 | 47 | ||
| 45 | public final class EmacsDesktopNotification | 48 | public 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); |