summaryrefslogtreecommitdiff
path: root/java/org/gnu/emacs/EmacsDesktopNotification.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/gnu/emacs/EmacsDesktopNotification.java')
-rw-r--r--java/org/gnu/emacs/EmacsDesktopNotification.java344
1 files changed, 344 insertions, 0 deletions
diff --git a/java/org/gnu/emacs/EmacsDesktopNotification.java b/java/org/gnu/emacs/EmacsDesktopNotification.java
new file mode 100644
index 00000000000..72569631a8c
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDesktopNotification.java
@@ -0,0 +1,344 @@
+/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+package org.gnu.emacs;
+
+import android.app.Notification;
+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;
+
+
+
+/* Structure designating a single desktop notification.
+
+ New versions of Android also organize notifications into individual
+ ``channels'', which are used to implement groups. Unlike on other
+ systems, notification importance is set for each group, not for
+ each individual notification. */
+
+
+
+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;
+
+ /* The title of this desktop notification. */
+ public final String title;
+
+ /* The notification group. */
+ public final String group;
+
+ /* String identifying this notification for future replacement.
+ Typically a string resembling ``XXXX.NNNN.YYYY'', where XXXX is
+ the system boot time, NNNN is the PID of this Emacs instance, and
+ YYYY is the counter value returned by the notifications display
+ 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;
+
+ /* Array of actions and their user-facing text to be offered by this
+ notification. */
+ public final String[] actions, titles;
+
+ /* Delay in miliseconds after which this notification should be
+ automatically dismissed. */
+ public final long delay;
+
+ public
+ EmacsDesktopNotification (String title, String content,
+ String group, String tag, int icon,
+ int importance,
+ String[] actions, String[] titles,
+ long delay)
+ {
+ this.content = content;
+ this.title = title;
+ this.group = group;
+ this.tag = tag;
+ this.icon = icon;
+ this.importance = importance;
+ this.actions = actions;
+ this.titles = titles;
+ this.delay = delay;
+ }
+
+
+
+ /* 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). */
+ private void
+ display1 (Context context)
+ {
+ NotificationManager manager;
+ NotificationChannel channel;
+ Notification notification;
+ Object tem;
+ RemoteViews contentView;
+ Intent intent;
+ PendingIntent pending;
+ int priority;
+ Notification.Builder builder;
+
+ tem = context.getSystemService (Context.NOTIFICATION_SERVICE);
+ manager = (NotificationManager) tem;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ {
+ /* Create the notification channel for this group. If a group
+ already exists with the same name, its linked attributes
+ (such as its importance) will be overridden. */
+ channel = new NotificationChannel (group, group, importance);
+ manager.createNotificationChannel (channel);
+ builder = new Notification.Builder (context, group);
+
+ /* Create and configure a notification object and display
+ it. */
+
+ builder.setContentTitle (title);
+ builder.setContentText (content);
+ builder.setSmallIcon (icon);
+ builder.setTimeoutAfter (delay);
+
+ insertActions (context, builder);
+ notification = builder.build ();
+ }
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+ {
+ /* Android 7.1 and earlier don't segregate notifications into
+ distinct categories, but permit an importance to be
+ assigned to each individual notification. */
+
+ builder = new Notification.Builder (context);
+ builder.setContentTitle (title);
+ builder.setContentText (content);
+ builder.setSmallIcon (icon);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ {
+ switch (importance)
+ {
+ case 2: /* IMPORTANCE_LOW */
+ default:
+ priority = Notification.PRIORITY_LOW;
+ break;
+
+ case 3: /* IMPORTANCE_DEFAULT */
+ priority = Notification.PRIORITY_DEFAULT;
+ break;
+
+ case 4: /* IMPORTANCE_HIGH */
+ priority = Notification.PRIORITY_HIGH;
+ break;
+ }
+
+ builder.setPriority (priority);
+ insertActions (context, builder);
+ notification = builder.build ();
+ }
+ else
+ notification = builder.getNotification ();
+ }
+ else
+ {
+ notification = new Notification ();
+ notification.icon = icon;
+
+ /* This remote widget tree is defined in
+ java/res/layout/sdk8_notifications_view.xml. */
+ notification.contentView
+ = contentView
+ = new RemoteViews ("org.gnu.emacs",
+ R.layout.sdk8_notifications_view);
+ contentView.setTextViewText (R.id.sdk8_notifications_title,
+ title);
+ contentView.setTextViewText (R.id.sdk8_notifications_content,
+ content);
+ }
+
+ /* 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);
+ 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,
+ PendingIntent.FLAG_IMMUTABLE);
+ else
+ pending = PendingIntent.getActivity (context, 0, intent, 0);
+
+ 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);
+ else
+ pending = PendingIntent.getBroadcast (context, 0, intent, 0);
+
+ notification.deleteIntent = pending;
+ manager.notify (tag, 2, notification);
+ }
+
+ /* Display this desktop notification.
+
+ Create a notification channel named GROUP or update its
+ importance if such a channel is already defined. */
+
+ public void
+ display ()
+ {
+ EmacsService.SERVICE.runOnUiThread (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ display1 (EmacsService.SERVICE);
+ }
+ });
+ }
+
+
+
+ /* 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);
+ }
+ };
+};