diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 4aa4464315..2be9639942 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -21,6 +21,9 @@ package eu.faircode.email; import android.Manifest; import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.ComponentName; import android.content.ContentResolver; @@ -36,12 +39,14 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; import android.provider.ContactsContract; +import android.provider.Settings; import android.text.Editable; import android.text.Html; import android.text.Layout; @@ -195,6 +200,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0); + boolean channel = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O); grpAddress.setVisibility(show_addresses ? View.VISIBLE : View.GONE); ivSearchContact.setVisibility(show_addresses && search && BuildConfig.DEBUG ? View.VISIBLE : View.GONE); - ivAddContact.setVisibility(show_addresses && contacts && message.from != null && message.from.length > 0 ? View.VISIBLE : View.GONE); + ivNotifyContact.setVisibility(show_addresses && channel && from ? View.VISIBLE : View.GONE); + ivAddContact.setVisibility(show_addresses && contacts && from ? View.VISIBLE : View.GONE); grpHeaders.setVisibility(show_headers ? View.VISIBLE : View.GONE); if (show_headers && message.headers == null) { @@ -980,6 +994,8 @@ public class AdapterMessage extends RecyclerView.Adapter> notifying = new HashMap<>(); - Map channelSubTitle = new HashMap<>(); - Map> channelMessages = new HashMap<>(); + Map> groupNotifying = new HashMap<>(); + Map> groupMessages = new HashMap<>(); // Previous for (String key : prefs.getAll().keySet()) if (key.startsWith("notifying:")) { - String channelName = key.split(":")[1]; - notifying.put(channelName, new ArrayList()); + String group = key.substring(key.indexOf(":") + 1); + groupNotifying.put(group, new ArrayList()); for (String id : prefs.getString(key, null).split(",")) - notifying.get(channelName).add(Long.parseLong(id)); + groupNotifying.get(group).add(Long.parseLong(id)); + + Log.i("Notifying " + group + "=" + TextUtils.join(",", groupNotifying.get(group))); editor.remove(key); } // Current for (TupleMessageEx message : messages) { - String channelName = EntityAccount.getNotificationChannelName(message.accountNotify ? message.account : 0); - String subTitle = (message.accountNotify ? message.accountName : null); - channelSubTitle.put(channelName, subTitle); - if (!channelMessages.containsKey(channelName)) { - channelMessages.put(channelName, new ArrayList()); - if (!notifying.containsKey(channelName)) - notifying.put(channelName, new ArrayList()); + String group = Long.toString(message.accountNotify ? message.account : 0); + + if (!groupMessages.containsKey(group)) { + groupMessages.put(group, new ArrayList()); + if (!groupNotifying.containsKey(group)) + groupNotifying.put(group, new ArrayList()); } - channelMessages.get(channelName).add(message); + + groupMessages.get(group).add(message); } // Difference - for (String channelName : notifying.keySet()) { - List notifications = getNotificationUnseen( - context, channelName, channelSubTitle.get(channelName), channelMessages.get(channelName)); + for (String group : groupNotifying.keySet()) { + List notifications = getNotificationUnseen(context, group, groupMessages.get(group)); List all = new ArrayList<>(); List add = new ArrayList<>(); - List remove = notifying.get(channelName); + List remove = groupNotifying.get(group); for (Notification notification : notifications) { Long id = notification.extras.getLong("id", 0); if (id != 0) { @@ -1505,53 +1506,46 @@ class Core { if (id < 0) headers++; - Log.i("Notify channel=" + channelName + " count=" + notifications.size() + + Log.i("Notify group=" + group + " count=" + notifications.size() + " added=" + add.size() + " removed=" + remove.size() + " headers=" + headers); if (notifications.size() == 0 || (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && headers > 0)) - nm.cancel("unseen:0", 1); + nm.cancel("unseen." + group + ":0", 1); for (Long id : remove) - nm.cancel("unseen:" + Math.abs(id), 1); + nm.cancel("unseen." + group + ":" + Math.abs(id), 1); for (Notification notification : notifications) { long id = notification.extras.getLong("id", 0); if ((id == 0 && add.size() + remove.size() > 0) || add.contains(id)) - nm.notify("unseen:" + Math.abs(id), 1, notification); + nm.notify("unseen." + group + ":" + Math.abs(id), 1, notification); } if (all.size() > 0) - editor.putString("notifying:" + channelName, TextUtils.join(",", all)); + editor.putString("notifying:" + group, TextUtils.join(",", all)); } editor.apply(); } - private static List getNotificationUnseen( - Context context, - String channelName, String subTitle, - List messages) { + private static List getNotificationUnseen(Context context, String group, List messages) { List notifications = new ArrayList<>(); + // https://developer.android.com/training/notify-user/group if (messages == null || messages.size() == 0) return notifications; boolean pro = Helper.isPro(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - // https://developer.android.com/training/notify-user/group - String group = channelName; - - String title = context.getResources().getQuantityString( - R.plurals.title_notification_unseen, messages.size(), messages.size()); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // Get contact info Map messageContact = new HashMap<>(); for (TupleMessageEx message : messages) messageContact.put(message, ContactInfo.get(context, message.from, false)); - // Build pending intent + // Build pending intents Intent summary = new Intent(context, ServiceUI.class); summary.setAction("summary"); PendingIntent piSummary = PendingIntent.getService(context, ServiceUI.PI_SUMMARY, summary, PendingIntent.FLAG_UPDATE_CURRENT); @@ -1560,42 +1554,29 @@ class Core { clear.setAction("clear"); PendingIntent piClear = PendingIntent.getService(context, ServiceUI.PI_CLEAR, clear, PendingIntent.FLAG_UPDATE_CURRENT); - // Build public notification - NotificationCompat.Builder pbuilder = new NotificationCompat.Builder(context, channelName); + // Build title + String title = context.getResources().getQuantityString( + R.plurals.title_notification_unseen, messages.size(), messages.size()); - pbuilder + // Build notification + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "notification"); + builder .setSmallIcon(R.drawable.baseline_email_white_24) .setContentTitle(title) .setContentIntent(piSummary) .setNumber(messages.size()) .setShowWhen(false) .setDeleteIntent(piClear) - .setPriority(Notification.PRIORITY_DEFAULT) - .setCategory(Notification.CATEGORY_STATUS) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - - if (!TextUtils.isEmpty(subTitle)) - pbuilder.setSubText(subTitle); - - // Build notification - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelName); - - builder - .setSmallIcon(R.drawable.baseline_email_white_24) - .setContentTitle(context.getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size())) - .setContentIntent(piSummary) - .setNumber(messages.size()) - .setShowWhen(false) - .setDeleteIntent(piClear) - .setPriority(Notification.PRIORITY_DEFAULT) - .setCategory(Notification.CATEGORY_STATUS) - .setVisibility(Notification.VISIBILITY_PRIVATE) - .setPublicVersion(pbuilder.build()) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_STATUS) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setGroup(group) .setGroupSummary(true); - if (!TextUtils.isEmpty(subTitle)) - builder.setSubText(subTitle); + Notification pub = builder.build(); + builder + .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) + .setPublicVersion(pub); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { boolean light = prefs.getBoolean("light", false); @@ -1636,9 +1617,11 @@ class Core { for (TupleMessageEx message : messages) { ContactInfo info = messageContact.get(message); + // Build arguments Bundle args = new Bundle(); args.putLong("id", message.content ? message.id : -message.id); + // Build pending intents Intent thread = new Intent(context, ActivityView.class); thread.setAction("thread:" + message.thread); thread.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -1663,6 +1646,7 @@ class Core { trash.setAction("trash:" + message.id); PendingIntent piTrash = PendingIntent.getService(context, ServiceUI.PI_TRASH, trash, PendingIntent.FLAG_UPDATE_CURRENT); + // Build actions NotificationCompat.Action.Builder actionSeen = new NotificationCompat.Action.Builder( R.drawable.baseline_visibility_24, context.getString(R.string.title_action_seen), @@ -1678,13 +1662,26 @@ class Core { context.getString(R.string.title_action_trash), piTrash); - NotificationCompat.Builder mbuilder; - mbuilder = new NotificationCompat.Builder(context, channelName); + // Get channel name + String channelName = null; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O && + message.from != null && message.from.length > 0) { + InternetAddress from = (InternetAddress) message.from[0]; + NotificationChannel channel = nm.getNotificationChannel("notification." + from.getAddress().toLowerCase()); + if (channel != null && channel.getImportance() != NotificationManager.IMPORTANCE_NONE) + channelName = channel.getId(); + } + if (channelName == null) + channelName = EntityAccount.getNotificationChannelName(message.accountNotify ? message.account : 0); + // Get folder name String folderName = message.folderDisplay == null ? Helper.localizeFolderName(context, message.folderName) : message.folderDisplay; + NotificationCompat.Builder mbuilder; + mbuilder = new NotificationCompat.Builder(context, channelName); + mbuilder .addExtras(args) .setSmallIcon(R.drawable.baseline_email_white_24) @@ -1693,12 +1690,12 @@ class Core { .setContentIntent(piContent) .setWhen(message.received) .setDeleteIntent(piDelete) - .setPriority(Notification.PRIORITY_DEFAULT) - .setOnlyAlertOnce(true) - .setCategory(Notification.CATEGORY_MESSAGE) - .setVisibility(Notification.VISIBILITY_PRIVATE) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_MESSAGE) + .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) .setGroup(group) .setGroupSummary(false) + .setOnlyAlertOnce(true) .addAction(actionSeen.build()) .addAction(actionArchive.build()) .addAction(actionTrash.build()); @@ -1826,9 +1823,9 @@ class Core { .setContentIntent(pi) .setAutoCancel(false) .setShowWhen(true) - .setPriority(Notification.PRIORITY_MAX) + .setPriority(NotificationCompat.PRIORITY_MAX) .setOnlyAlertOnce(true) - .setCategory(Notification.CATEGORY_ERROR) + .setCategory(NotificationCompat.CATEGORY_ERROR) .setVisibility(NotificationCompat.VISIBILITY_SECRET); builder.setStyle(new NotificationCompat.BigTextStyle() diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index 174558c9fa..90600fb921 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -102,11 +102,11 @@ public class EntityAccount implements Serializable { @RequiresApi(api = Build.VERSION_CODES.O) void createNotificationChannel(Context context) { NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel notification = new NotificationChannel( + NotificationChannel channel = new NotificationChannel( getNotificationChannelName(id), name, NotificationManager.IMPORTANCE_HIGH); - notification.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - nm.createNotificationChannel(notification); + channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + nm.createNotificationChannel(channel); } @RequiresApi(api = Build.VERSION_CODES.O) diff --git a/app/src/main/java/eu/faircode/email/ServiceExternal.java b/app/src/main/java/eu/faircode/email/ServiceExternal.java index 4be5b9a499..a3bd753460 100644 --- a/app/src/main/java/eu/faircode/email/ServiceExternal.java +++ b/app/src/main/java/eu/faircode/email/ServiceExternal.java @@ -1,6 +1,5 @@ package eu.faircode.email; -import android.app.Notification; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; @@ -69,8 +68,8 @@ public class ServiceExternal extends Service { .setContentTitle(getString(R.string.tile_synchronize)) .setAutoCancel(false) .setShowWhen(false) - .setPriority(Notification.PRIORITY_MIN) - .setCategory(Notification.CATEGORY_STATUS) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setCategory(NotificationCompat.CATEGORY_STATUS) .setVisibility(NotificationCompat.VISIBILITY_SECRET); return builder; diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 65d0003ba6..f59536e927 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -19,7 +19,6 @@ package eu.faircode.email; Copyright 2018-2019 by Marcel Bokhorst (M66B) */ -import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; @@ -120,8 +119,8 @@ public class ServiceSend extends LifecycleService { .setContentTitle(getString(R.string.title_notification_sending)) .setAutoCancel(false) .setShowWhen(false) - .setPriority(Notification.PRIORITY_MIN) - .setCategory(Notification.CATEGORY_STATUS) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setCategory(NotificationCompat.CATEGORY_STATUS) .setVisibility(NotificationCompat.VISIBILITY_SECRET); if (lastUnsent > 0) diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 9438b592ed..062ba3363b 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -20,7 +20,6 @@ package eu.faircode.email; */ import android.app.AlarmManager; -import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -227,8 +226,8 @@ public class ServiceSynchronize extends LifecycleService { .setContentIntent(pi) .setAutoCancel(false) .setShowWhen(false) - .setPriority(Notification.PRIORITY_MIN) - .setCategory(Notification.CATEGORY_STATUS) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setCategory(NotificationCompat.CATEGORY_STATUS) .setVisibility(NotificationCompat.VISIBILITY_SECRET); if (lastStats.operations > 0) diff --git a/app/src/main/res/layout/item_message_compact.xml b/app/src/main/res/layout/item_message_compact.xml index b18769b3f1..2ab7d16af2 100644 --- a/app/src/main/res/layout/item_message_compact.xml +++ b/app/src/main/res/layout/item_message_compact.xml @@ -358,6 +358,16 @@ android:layout_marginEnd="6dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:src="@drawable/baseline_search_24" + app:layout_constraintEnd_toStartOf="@+id/ivNotifyContact" + app:layout_constraintTop_toBottomOf="@id/ivExpanderAddress" /> + + diff --git a/app/src/main/res/layout/item_message_normal.xml b/app/src/main/res/layout/item_message_normal.xml index f8e9026229..8b8721b4d6 100644 --- a/app/src/main/res/layout/item_message_normal.xml +++ b/app/src/main/res/layout/item_message_normal.xml @@ -350,6 +350,16 @@ android:layout_marginEnd="6dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:src="@drawable/baseline_search_24" + app:layout_constraintEnd_toStartOf="@+id/ivNotifyContact" + app:layout_constraintTop_toBottomOf="@id/ivExpanderAddress" /> + +