Notification sound per email address

This commit is contained in:
M66B 2019-03-07 12:29:03 +00:00
parent 6d81aed83a
commit 9786067060
8 changed files with 131 additions and 81 deletions

View File

@ -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<AdapterMessage.ViewHold
private ImageView ivExpanderAddress;
private TextView tvFromEx;
private ImageView ivSearchContact;
private ImageView ivNotifyContact;
private ImageView ivAddContact;
private TextView tvTo;
private TextView tvReplyTo;
@ -270,6 +276,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ivExpanderAddress = itemView.findViewById(R.id.ivExpanderAddress);
tvFromEx = itemView.findViewById(R.id.tvFromEx);
ivSearchContact = itemView.findViewById(R.id.ivSearchContact);
ivNotifyContact = itemView.findViewById(R.id.ivNotifyContact);
ivAddContact = itemView.findViewById(R.id.ivAddContact);
tvTo = itemView.findViewById(R.id.tvTo);
tvReplyTo = itemView.findViewById(R.id.tvReplyTo);
@ -363,6 +370,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ivExpanderAddress.setOnClickListener(this);
ivSearchContact.setOnClickListener(this);
ivNotifyContact.setOnClickListener(this);
ivAddContact.setOnClickListener(this);
btnDownloadAttachments.setOnClickListener(this);
@ -385,6 +393,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ivFlagged.setOnClickListener(null);
ivExpanderAddress.setOnClickListener(null);
ivSearchContact.setOnClickListener(null);
ivNotifyContact.setOnClickListener(null);
ivAddContact.setOnClickListener(null);
btnDownloadAttachments.setOnClickListener(null);
btnSaveAttachments.setOnClickListener(null);
@ -440,6 +449,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpDay.setVisibility(View.GONE);
grpAddress.setVisibility(View.GONE);
ivSearchContact.setVisibility(View.GONE);
ivNotifyContact.setVisibility(View.GONE);
ivAddContact.setVisibility(View.GONE);
grpHeaders.setVisibility(View.GONE);
grpAttachments.setVisibility(View.GONE);
@ -678,6 +688,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpExpanded.setVisibility(View.GONE);
ivSearchContact.setVisibility(View.GONE);
ivNotifyContact.setVisibility(View.GONE);
ivAddContact.setVisibility(View.GONE);
tvFlags.setVisibility(View.GONE);
tvKeywords.setVisibility(View.GONE);
@ -723,9 +734,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpExpanded.setVisibility(View.VISIBLE);
boolean from = (message.from != null && message.from.length > 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<AdapterMessage.ViewHold
onToggleFlag(message);
else if (view.getId() == R.id.ivSearchContact)
onSearchContact(message);
else if (view.getId() == R.id.ivNotifyContact)
onNotifyContact(message);
else if (view.getId() == R.id.ivAddContact)
onAddContact(message);
else if (viewType == ViewType.THREAD) {
@ -1119,6 +1135,26 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:search");
}
@TargetApi(Build.VERSION_CODES.O)
private void onNotifyContact(TupleMessageEx message) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
InternetAddress from = (InternetAddress) message.from[0];
String channelName = "notification." + from.getAddress().toLowerCase();
NotificationChannel channel = new NotificationChannel(
channelName, from.getAddress(),
NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(from.getPersonal());
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(channel);
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
.putExtra(Settings.EXTRA_CHANNEL_ID, channelName);
context.startActivity(intent);
}
private void onAddContact(TupleMessageEx message) {
for (Address address : message.from) {
InternetAddress ia = (InternetAddress) address;

View File

@ -1,6 +1,7 @@
package eu.faircode.email;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@ -1448,43 +1449,43 @@ class Core {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Map<String, List<Long>> notifying = new HashMap<>();
Map<String, String> channelSubTitle = new HashMap<>();
Map<String, List<TupleMessageEx>> channelMessages = new HashMap<>();
Map<String, List<Long>> groupNotifying = new HashMap<>();
Map<String, List<TupleMessageEx>> groupMessages = new HashMap<>();
// Previous
for (String key : prefs.getAll().keySet())
if (key.startsWith("notifying:")) {
String channelName = key.split(":")[1];
notifying.put(channelName, new ArrayList<Long>());
String group = key.substring(key.indexOf(":") + 1);
groupNotifying.put(group, new ArrayList<Long>());
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<TupleMessageEx>());
if (!notifying.containsKey(channelName))
notifying.put(channelName, new ArrayList<Long>());
String group = Long.toString(message.accountNotify ? message.account : 0);
if (!groupMessages.containsKey(group)) {
groupMessages.put(group, new ArrayList<TupleMessageEx>());
if (!groupNotifying.containsKey(group))
groupNotifying.put(group, new ArrayList<Long>());
}
channelMessages.get(channelName).add(message);
groupMessages.get(group).add(message);
}
// Difference
for (String channelName : notifying.keySet()) {
List<Notification> notifications = getNotificationUnseen(
context, channelName, channelSubTitle.get(channelName), channelMessages.get(channelName));
for (String group : groupNotifying.keySet()) {
List<Notification> notifications = getNotificationUnseen(context, group, groupMessages.get(group));
List<String> all = new ArrayList<>();
List<Long> add = new ArrayList<>();
List<Long> remove = notifying.get(channelName);
List<Long> 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<Notification> getNotificationUnseen(
Context context,
String channelName, String subTitle,
List<TupleMessageEx> messages) {
private static List<Notification> getNotificationUnseen(Context context, String group, List<TupleMessageEx> messages) {
List<Notification> 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<TupleMessageEx, ContactInfo> 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()

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -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)

View File

@ -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" />
<ImageView
android:id="@+id/ivNotifyContact"
android:layout_width="21dp"
android:layout_height="21dp"
android:layout_marginEnd="6dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_notifications_24"
app:layout_constraintEnd_toStartOf="@+id/ivAddContact"
app:layout_constraintTop_toBottomOf="@id/ivExpanderAddress" />

View File

@ -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" />
<ImageView
android:id="@+id/ivNotifyContact"
android:layout_width="21dp"
android:layout_height="21dp"
android:layout_marginEnd="6dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_notifications_24"
app:layout_constraintEnd_toStartOf="@+id/ivAddContact"
app:layout_constraintTop_toBottomOf="@id/ivExpanderAddress" />