Notification groups, trash action, small improvements

Fixes #126
pull/145/head
M66B 6 years ago
parent b7a5432de0
commit 6039fc1383

@ -436,7 +436,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
private void checkIntent(Intent intent) { private void checkIntent(Intent intent) {
Log.i(Helper.TAG, "View intent=" + intent + " action=" + intent.getAction()); Log.i(Helper.TAG, "View intent=" + intent + " action=" + intent.getAction());
String action = intent.getAction(); String action = intent.getAction();
if ("unseen".equals(action)) { if ("notification".equals(action)) {
intent.setAction(null); intent.setAction(null);
setIntent(intent); setIntent(intent);

@ -188,12 +188,6 @@ public interface DaoMessage {
" AND NOT ui_found" /* keep found messages */) " AND NOT ui_found" /* keep found messages */)
List<Long> getUids(long folder, long received); List<Long> getUids(long folder, long received);
@Query("SELECT message.* FROM message" +
" JOIN folder ON folder.id = message.folder" +
" WHERE NOT message.ui_seen" +
" AND folder.unified")
List<EntityMessage> getUnseenUnifiedMessages();
@Insert @Insert
long insertMessage(EntityMessage message); long insertMessage(EntityMessage message);

@ -120,7 +120,6 @@ public class ServiceSynchronize extends LifecycleService {
private ServiceManager serviceManager = new ServiceManager(); private ServiceManager serviceManager = new ServiceManager();
private static final int NOTIFICATION_SYNCHRONIZE = 1; private static final int NOTIFICATION_SYNCHRONIZE = 1;
private static final int NOTIFICATION_UNSEEN = 2;
private static final int CONNECT_BACKOFF_START = 8; // seconds private static final int CONNECT_BACKOFF_START = 8; // seconds
private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min) private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min)
@ -130,6 +129,10 @@ public class ServiceSynchronize extends LifecycleService {
private static final int MESSAGE_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes private static final int MESSAGE_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
private static final int ATTACHMENT_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes private static final int ATTACHMENT_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
static final int PI_UNSEEN = 1;
static final int PI_SEEN = 2;
static final int PI_TRASH = 3;
static final String ACTION_SYNCHRONIZE_FOLDER = BuildConfig.APPLICATION_ID + ".SYNCHRONIZE_FOLDER"; static final String ACTION_SYNCHRONIZE_FOLDER = BuildConfig.APPLICATION_ID + ".SYNCHRONIZE_FOLDER";
static final String ACTION_PROCESS_OPERATIONS = BuildConfig.APPLICATION_ID + ".PROCESS_OPERATIONS"; static final String ACTION_PROCESS_OPERATIONS = BuildConfig.APPLICATION_ID + ".PROCESS_OPERATIONS";
@ -159,20 +162,40 @@ public class ServiceSynchronize extends LifecycleService {
}); });
db.message().liveUnseenUnified().observe(this, new Observer<List<EntityMessage>>() { db.message().liveUnseenUnified().observe(this, new Observer<List<EntityMessage>>() {
private int prev_unseen = -1; private List<Integer> notifying = new ArrayList<>();
@Override @Override
public void onChanged(List<EntityMessage> messages) { public void onChanged(List<EntityMessage> messages) {
NotificationManager nm = getSystemService(NotificationManager.class); NotificationManager nm = getSystemService(NotificationManager.class);
if (messages.size() > 0) { List<Notification> notifications = getNotificationUnseen(messages);
if (messages.size() > prev_unseen) {
nm.cancel(NOTIFICATION_UNSEEN); List<Integer> all = new ArrayList<>();
nm.notify(NOTIFICATION_UNSEEN, getNotificationUnseen(messages).build()); List<Integer> added = new ArrayList<>();
List<Integer> removed = new ArrayList<>(notifying);
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if (id > 0) {
all.add(id);
if (removed.contains(id))
removed.remove(id);
else
added.add(id);
}
}
if (notifications.size() == 0)
nm.cancel("unseen", 0);
for (Integer id : removed)
nm.cancel("unseen", id);
for (Notification notification : notifications) {
Integer id = (int) notification.extras.getLong("id", 0);
if ((id == 0 && added.size() + removed.size() > 0) || added.contains(id))
nm.notify("unseen", id, notification);
} }
} else
nm.cancel(NOTIFICATION_UNSEEN);
prev_unseen = messages.size(); notifying = all;
} }
}); });
} }
@ -199,10 +222,11 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, "Service command intent=" + intent); Log.i(Helper.TAG, "Service command intent=" + intent);
super.onStartCommand(intent, flags, startId); super.onStartCommand(intent, flags, startId);
if (intent != null) if (intent != null) {
if ("reload".equals(intent.getAction())) String action = intent.getAction();
if ("reload".equals(action))
serviceManager.restart(); serviceManager.restart();
else if ("unseen".equals(intent.getAction())) { else if ("until".equals(action)) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("time", new Date().getTime()); args.putLong("time", new Date().getTime());
@ -232,19 +256,31 @@ public class ServiceSynchronize extends LifecycleService {
} }
}.load(this, args); }.load(this, args);
} else if ("seen".equals(intent.getAction())) { } else if (action != null &&
(action.startsWith("seen:") || action.startsWith("trash:"))) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", Long.parseLong(action.split(":")[1]));
args.putString("action", action.split(":")[0]);
new SimpleTask<Void>() { new SimpleTask<Void>() {
@Override @Override
protected Void onLoad(Context context, Bundle args) { protected Void onLoad(Context context, Bundle args) {
long id = args.getLong("id");
String action = args.getString("action");
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
try { try {
db.beginTransaction(); db.beginTransaction();
for (EntityMessage message : db.message().getUnseenUnifiedMessages()) { EntityMessage message = db.message().getMessage(id);
if ("seen".equals(action)) {
db.message().setMessageUiSeen(message.id, true); db.message().setMessageUiSeen(message.id, true);
EntityOperation.queue(db, message, EntityOperation.SEEN, true); EntityOperation.queue(db, message, EntityOperation.SEEN, true);
} else if ("trash".equals(action)) {
db.message().setMessageUiHide(message.id, true);
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
if (trash != null)
EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id);
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
@ -263,6 +299,7 @@ public class ServiceSynchronize extends LifecycleService {
} }
}.load(this, args); }.load(this, args);
} }
}
return START_STICKY; return START_STICKY;
} }
@ -301,49 +338,53 @@ public class ServiceSynchronize extends LifecycleService {
return builder; return builder;
} }
private Notification.Builder getNotificationUnseen(List<EntityMessage> messages) { private List<Notification> getNotificationUnseen(List<EntityMessage> messages) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); // https://developer.android.com/training/notify-user/group
List<Notification> notifications = new ArrayList<>();
// Build pending intent
Intent intent = new Intent(this, ActivityView.class);
intent.setAction("unseen");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntent.getActivity(
this, ActivityView.REQUEST_UNSEEN, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent delete = new Intent(this, ServiceSynchronize.class); if (messages.size() == 0)
delete.setAction("unseen"); return notifications;
PendingIntent pid = PendingIntent.getService(this, 1, delete, PendingIntent.FLAG_UPDATE_CURRENT);
Intent seen = new Intent(this, ServiceSynchronize.class); boolean pro = Helper.isPro(this);
seen.setAction("seen"); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
PendingIntent pis = PendingIntent.getService(this, 2, seen, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action.Builder actionBuilder = new Notification.Action.Builder( // Build pending intent
Icon.createWithResource(this, R.drawable.baseline_mail_outline_24), Intent view = new Intent(this, ActivityView.class);
getString(R.string.title_seen), view.setAction("notification");
pis); view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent piView = PendingIntent.getActivity(
this, ActivityView.REQUEST_UNSEEN, view, PendingIntent.FLAG_UPDATE_CURRENT);
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Intent until = new Intent(this, ServiceSynchronize.class);
until.setAction("until");
PendingIntent piUntil = PendingIntent.getService(
this, PI_UNSEEN, until, PendingIntent.FLAG_UPDATE_CURRENT);
// Build notification // Build notification
Notification.Builder builder; Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
builder = new Notification.Builder(this, "notification");
else
builder = new Notification.Builder(this); builder = new Notification.Builder(this);
else
builder = new Notification.Builder(this, "notification");
builder builder
.setSmallIcon(R.drawable.baseline_mail_24) .setSmallIcon(R.drawable.baseline_mail_24)
.setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size())) .setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size()))
.setContentIntent(pi) .setContentText("")
.setSound(uri) .setContentIntent(piView)
.setNumber(messages.size())
.setShowWhen(false) .setShowWhen(false)
.setPriority(Notification.PRIORITY_DEFAULT) .setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_STATUS) .setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PUBLIC) .setVisibility(Notification.VISIBILITY_PRIVATE)
.setDeleteIntent(pid) .setDeleteIntent(piUntil)
.addAction(actionBuilder.build()); .setGroup(BuildConfig.APPLICATION_ID)
.setGroupSummary(true);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
builder.setSound(null);
else
builder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O && if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O &&
prefs.getBoolean("light", false)) { prefs.getBoolean("light", false)) {
@ -351,7 +392,7 @@ public class ServiceSynchronize extends LifecycleService {
builder.setLights(0xff00ff00, 1000, 1000); builder.setLights(0xff00ff00, 1000, 1000);
} }
if (Helper.isPro(this)) { if (pro) {
DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (EntityMessage message : messages) { for (EntityMessage message : messages) {
@ -365,7 +406,64 @@ public class ServiceSynchronize extends LifecycleService {
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(sb.toString()))); builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(sb.toString())));
} }
return builder; notifications.add(builder.build());
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
for (EntityMessage message : messages) {
Bundle args = new Bundle();
args.putLong("id", message.id);
Intent seen = new Intent(this, ServiceSynchronize.class);
seen.setAction("seen:" + message.id);
PendingIntent piSeen = PendingIntent.getService(this, PI_SEEN, seen, PendingIntent.FLAG_UPDATE_CURRENT);
Intent trash = new Intent(this, ServiceSynchronize.class);
trash.setAction("trash:" + message.id);
PendingIntent piTrash = PendingIntent.getService(this, PI_TRASH, trash, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action.Builder actionSeen = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.baseline_visibility_24),
getString(R.string.title_seen),
piSeen);
Notification.Action.Builder actionTrash = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.baseline_delete_24),
getString(R.string.title_trash),
piTrash);
Notification.Builder mbuilder;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
mbuilder = new Notification.Builder(this);
else
mbuilder = new Notification.Builder(this, "notification");
mbuilder
.addExtras(args)
.setSmallIcon(R.drawable.baseline_mail_24)
.setContentTitle(MessageHelper.getFormattedAddresses(message.from, true))
.setContentIntent(piView)
.setSound(uri)
.setWhen(message.sent == null ? message.received : message.sent)
.setPriority(Notification.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setGroup(BuildConfig.APPLICATION_ID)
.setGroupSummary(false)
.addAction(actionSeen.build())
.addAction(actionTrash.build());
if (pro)
if (!TextUtils.isEmpty(message.subject))
mbuilder.setContentText(message.subject);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
notifications.add(mbuilder.build());
}
return notifications;
} }
private Notification.Builder getNotificationError(String action, Throwable ex) { private Notification.Builder getNotificationError(String action, Throwable ex) {
@ -1399,7 +1497,7 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " add=" + imessages.length); Log.i(Helper.TAG, folder.name + " add=" + imessages.length);
for (int i = imessages.length - 1; i >= 0; i -= SYNC_BATCH_SIZE) { for (int i = imessages.length - 1; i >= 0; i -= SYNC_BATCH_SIZE) {
int from = Math.max(0, i - SYNC_BATCH_SIZE + 1); int from = Math.max(0, i - SYNC_BATCH_SIZE + 1);
Log.i(Helper.TAG, folder.name + " update " + from + " .. " + i); //Log.i(Helper.TAG, folder.name + " update " + from + " .. " + i);
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
@ -1411,8 +1509,10 @@ public class ServiceSynchronize extends LifecycleService {
if (message == null) if (message == null)
full.add(imessage); full.add(imessage);
} }
Log.i(Helper.TAG, folder.name + " fetch headers=" + full.size()); long headers = SystemClock.elapsedRealtime();
ifolder.fetch(full.toArray(new Message[0]), fp); ifolder.fetch(full.toArray(new Message[0]), fp);
Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() +
" " + (SystemClock.elapsedRealtime() - fetch) + " ms");
for (int j = isub.length - 1; j >= 0; j--) for (int j = isub.length - 1; j >= 0; j--)
try { try {
@ -1439,14 +1539,14 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " download=" + imessages.length); Log.i(Helper.TAG, folder.name + " download=" + imessages.length);
for (int i = imessages.length - 1; i >= 0; i -= DOWNLOAD_BATCH_SIZE) { for (int i = imessages.length - 1; i >= 0; i -= DOWNLOAD_BATCH_SIZE) {
int from = Math.max(0, i - DOWNLOAD_BATCH_SIZE + 1); int from = Math.max(0, i - DOWNLOAD_BATCH_SIZE + 1);
Log.i(Helper.TAG, folder.name + " download " + from + " .. " + i); //Log.i(Helper.TAG, folder.name + " download " + from + " .. " + i);
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
// Fetch on demand // Fetch on demand
for (int j = isub.length - 1; j >= 0; j--) for (int j = isub.length - 1; j >= 0; j--)
try { try {
Log.i(Helper.TAG, folder.name + " download index=" + (from + j) + " id=" + ids[from + j]); //Log.i(Helper.TAG, folder.name + " download index=" + (from + j) + " id=" + ids[from + j]);
if (ids[from + j] != null) if (ids[from + j] != null)
downloadMessage(this, folder, ifolder, (IMAPMessage) isub[j], ids[from + j]); downloadMessage(this, folder, ifolder, (IMAPMessage) isub[j], ids[from + j]);
} catch (FolderClosedException ex) { } catch (FolderClosedException ex) {

Loading…
Cancel
Save