mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-29 08:31:35 -08:00
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.
This commit is contained in:
parent
75cfc6c73f
commit
a7a37341ca
13 changed files with 608 additions and 35 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue