From 74e1f31acb078ec89ccb1e8689bd038ea349b37e Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 15 Sep 2017 08:39:28 -0500 Subject: [PATCH] Only display summary for two or more notifications --- app/src/main/java/org/tasks/Notifier.java | 113 +------------ .../tasks/notifications/NotificationDao.java | 4 +- .../notifications/NotificationManager.java | 153 ++++++++++++++++-- 3 files changed, 146 insertions(+), 124 deletions(-) diff --git a/app/src/main/java/org/tasks/Notifier.java b/app/src/main/java/org/tasks/Notifier.java index 0c9706fc6..747ae662e 100644 --- a/app/src/main/java/org/tasks/Notifier.java +++ b/app/src/main/java/org/tasks/Notifier.java @@ -4,11 +4,8 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.support.v4.app.NotificationCompat; -import android.text.TextUtils; -import com.google.common.base.Strings; import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.dao.TaskDao; @@ -17,18 +14,12 @@ import com.todoroo.astrid.reminders.ReminderService; import com.todoroo.astrid.voice.VoiceOutputAssistant; import org.tasks.injection.ForApplication; -import org.tasks.intents.TaskIntents; import org.tasks.jobs.JobQueueEntry; import org.tasks.notifications.AudioManager; import org.tasks.notifications.NotificationDao; import org.tasks.notifications.NotificationManager; import org.tasks.notifications.TelephonyManager; import org.tasks.preferences.Preferences; -import org.tasks.receivers.CompleteTaskReceiver; -import org.tasks.reminders.NotificationActivity; -import org.tasks.reminders.SnoozeActivity; -import org.tasks.reminders.SnoozeDialog; -import org.tasks.reminders.SnoozeOption; import org.tasks.ui.CheckBoxes; import java.util.Collections; @@ -43,8 +34,6 @@ import timber.log.Timber; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.google.common.collect.Lists.transform; -import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybean; -import static org.tasks.notifications.NotificationManager.GROUP_KEY; import static org.tasks.time.DateTimeUtils.currentTimeMillis; public class Notifier { @@ -128,105 +117,7 @@ public class Notifier { triggerNotifications(Collections.singletonList(notification), true); } - private NotificationCompat.Builder getTaskNotification(org.tasks.notifications.Notification notification) { - long id = notification.taskId; - int type = notification.type; - long when = notification.timestamp; - Task task = taskDao.fetch(id); - if (task == null) { - Timber.e("Could not find %s", id); - return null; - } - - // you're done, or not yours - don't sound, do delete - if (task.isCompleted() || task.isDeleted()) { - return null; - } - - // new task edit in progress - if (TextUtils.isEmpty(task.getTitle())) { - return null; - } - - // it's hidden - don't sound, don't delete - if (task.isHidden() && type == ReminderService.TYPE_RANDOM) { - return null; - } - // task due date was changed, but alarm wasn't rescheduled - boolean dueInFuture = task.hasDueTime() && task.getDueDate() > DateUtilities.now() || - !task.hasDueTime() && task.getDueDate() - DateUtilities.now() > DateUtilities.ONE_DAY; - if ((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) && - (!task.hasDueDate() || dueInFuture)) { - return null; - } - - // read properties - final String taskTitle = task.getTitle(); - final String taskDescription = task.getNotes(); - - // update last reminder time - task.setReminderLast(when); - taskDao.saveExisting(task); - - final String appName = context.getString(R.string.app_name); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT) - .setCategory(NotificationCompat.CATEGORY_REMINDER) - .setTicker(taskTitle) - .setContentTitle(taskTitle) - .setContentText(appName) - .setGroup(GROUP_KEY) - .setSmallIcon(R.drawable.ic_check_white_24dp) - .setWhen(when) - .setShowWhen(true) - .setPriority(NotificationCompat.PRIORITY_HIGH); - - if (atLeastJellybean()) { - builder.setContentIntent(PendingIntent.getActivity(context, (int) id, TaskIntents.getEditTaskIntent(context, null, id), PendingIntent.FLAG_UPDATE_CURRENT)); - } else { - final Intent intent = new Intent(context, NotificationActivity.class); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); - intent.setAction("NOTIFY" + id); //$NON-NLS-1$ - intent.putExtra(NotificationActivity.EXTRA_TASK_ID, id); - intent.putExtra(NotificationActivity.EXTRA_TITLE, taskTitle); - builder.setContentIntent(PendingIntent.getActivity(context, (int) id, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - } - - if (!Strings.isNullOrEmpty(taskDescription)) { - builder.setStyle(new NotificationCompat.BigTextStyle().bigText(taskDescription)); - } - Intent completeIntent = new Intent(context, CompleteTaskReceiver.class); - completeIntent.putExtra(CompleteTaskReceiver.TASK_ID, id); - PendingIntent completePendingIntent = PendingIntent.getBroadcast(context, (int) id, completeIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - NotificationCompat.Action completeAction = new NotificationCompat.Action.Builder( - R.drawable.ic_check_white_24dp, context.getResources().getString(R.string.rmd_NoA_done), completePendingIntent).build(); - - Intent snoozeIntent = new Intent(context, SnoozeActivity.class); - snoozeIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); - snoozeIntent.putExtra(SnoozeActivity.EXTRA_TASK_ID, id); - PendingIntent snoozePendingIntent = PendingIntent.getActivity(context, (int) id, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); - wearableExtender.addAction(completeAction); - for (final SnoozeOption snoozeOption : SnoozeDialog.getSnoozeOptions(preferences)) { - final long timestamp = snoozeOption.getDateTime().getMillis(); - Intent wearableIntent = new Intent(context, SnoozeActivity.class); - wearableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - wearableIntent.setAction(String.format("snooze-%s-%s", id, timestamp)); - wearableIntent.putExtra(SnoozeActivity.EXTRA_TASK_ID, id); - wearableIntent.putExtra(SnoozeActivity.EXTRA_SNOOZE_TIME, timestamp); - PendingIntent wearablePendingIntent = PendingIntent.getActivity(context, (int) id, wearableIntent, PendingIntent.FLAG_UPDATE_CURRENT); - wearableExtender.addAction(new NotificationCompat.Action.Builder( - R.drawable.ic_snooze_white_24dp, context.getString(snoozeOption.getResId()), wearablePendingIntent) - .build()); - } - - return builder.addAction(completeAction) - .addAction(R.drawable.ic_snooze_white_24dp, context.getResources().getString(R.string.rmd_NoA_snooze), snoozePendingIntent) - .extend(wearableExtender); - } public void restoreNotifications() { triggerNotifications(notificationDao.getAll(), false); @@ -236,7 +127,7 @@ public class Notifier { triggerNotifications(transform(entries, JobQueueEntry::toNotification), true); } - public void triggerNotifications(List entries, boolean alert) { + private void triggerNotifications(List entries, boolean alert) { Map notifications = new LinkedHashMap<>(); boolean ringFiveTimes = false; boolean ringNonstop = false; @@ -250,7 +141,7 @@ public class Notifier { ringFiveTimes |= task.isNotifyModeFive(); ringNonstop |= task.isNotifyModeNonstop(); } - NotificationCompat.Builder notification = getTaskNotification(entry); + NotificationCompat.Builder notification = notificationManager.getTaskNotification(entry); if (notification != null) { notification.setGroupAlertBehavior(alert && (preferences.bundleNotifications() ? entries.size() == 1 : i == entries.size() - 1) ? NotificationCompat.GROUP_ALERT_CHILDREN diff --git a/app/src/main/java/org/tasks/notifications/NotificationDao.java b/app/src/main/java/org/tasks/notifications/NotificationDao.java index 2ec492f7a..e51cf137a 100644 --- a/app/src/main/java/org/tasks/notifications/NotificationDao.java +++ b/app/src/main/java/org/tasks/notifications/NotificationDao.java @@ -7,8 +7,6 @@ import android.arch.persistence.room.Query; import java.util.List; -import io.reactivex.Single; - @Dao public interface NotificationDao { @Query("SELECT * FROM notification") @@ -24,7 +22,7 @@ public interface NotificationDao { int count(); @Query("DELETE FROM notification WHERE task = :taskId") - void delete(long taskId); + int delete(long taskId); @Query("SELECT MAX(timestamp) FROM notification") long latestTimestamp(); diff --git a/app/src/main/java/org/tasks/notifications/NotificationManager.java b/app/src/main/java/org/tasks/notifications/NotificationManager.java index a4ee15051..3046c2dc7 100644 --- a/app/src/main/java/org/tasks/notifications/NotificationManager.java +++ b/app/src/main/java/org/tasks/notifications/NotificationManager.java @@ -8,18 +8,28 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.text.TextUtils; +import com.google.common.base.Strings; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.reminders.ReminderService; import org.tasks.R; import org.tasks.injection.ApplicationScope; import org.tasks.injection.ForApplication; import org.tasks.intents.TaskIntents; import org.tasks.preferences.Preferences; +import org.tasks.receivers.CompleteTaskReceiver; +import org.tasks.reminders.NotificationActivity; +import org.tasks.reminders.SnoozeActivity; +import org.tasks.reminders.SnoozeDialog; +import org.tasks.reminders.SnoozeOption; import org.tasks.ui.CheckBoxes; import java.util.ArrayList; @@ -31,10 +41,14 @@ import javax.inject.Inject; import io.reactivex.Completable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.tryFind; import static com.google.common.collect.Lists.newArrayList; +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybean; import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo; @ApplicationScope @@ -48,7 +62,7 @@ public class NotificationManager { private static final int SUMMARY_NOTIFICATION_ID = 0; static final String EXTRA_NOTIFICATION_ID = "extra_notification_id"; - private final android.app.NotificationManager notificationManager; + private final NotificationManagerCompat notificationManagerCompat; private final NotificationDao notificationDao; private final TaskDao taskDao; private final Context context; @@ -63,9 +77,10 @@ public class NotificationManager { this.notificationDao = notificationDao; this.taskDao = taskDao; this.checkBoxes = checkBoxes; - notificationManager = (android.app.NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManagerCompat = NotificationManagerCompat.from(context); if (atLeastOreo()) { + android.app.NotificationManager notificationManager = (android.app.NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_DEFAULT, R.string.notifications)); notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_CALLS, R.string.missed_calls)); notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_TASKER, R.string.tasker_locale)); @@ -86,10 +101,20 @@ public class NotificationManager { } public void cancel(long id) { - notificationManager.cancel((int) id); + notificationManagerCompat.cancel((int) id); Completable.fromAction(() -> { - notificationDao.delete(id); - updateSummary(false, false, false); + if (notificationDao.delete(id) > 0) { + if (notificationDao.count() == 1) { + List notifications = notificationDao.getAll(); + org.tasks.notifications.Notification notification = notifications.get(0); + NotificationCompat.Builder builder = getTaskNotification(notification) + .setGroup(null) + .setOnlyAlertOnce(true) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); + notify(notification.taskId, builder, false, false, false); + } + updateSummary(false, false, false); + } }) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) @@ -97,6 +122,15 @@ public class NotificationManager { } public void notifyTasks(Map notifications, boolean alert, boolean nonstop, boolean fiveTimes) { + List existing = notificationDao.getAll(); + if (existing.size() == 1) { + org.tasks.notifications.Notification notification = existing.get(0); + NotificationCompat.Builder builder = getTaskNotification(notification) + .setGroup(GROUP_KEY) + .setOnlyAlertOnce(true) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); + notify(notification.taskId, builder, false, false, false); + } notificationDao.insertAll(newArrayList(notifications.keySet())); updateSummary(alert && notifications.size() > 1, nonstop, fiveTimes); ArrayList> entries = newArrayList(notifications.entrySet()); @@ -143,7 +177,7 @@ public class NotificationManager { deleteIntent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); notification.deleteIntent = PendingIntent.getBroadcast(context, (int) notificationId, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT); for (int i = 0 ; i < ringTimes ; i++) { - notificationManager.notify((int) notificationId, notification); + notificationManagerCompat.notify((int) notificationId, notification); } } } @@ -151,8 +185,8 @@ public class NotificationManager { private void updateSummary(boolean notify, boolean nonStop, boolean fiveTimes) { if (preferences.bundleNotifications()) { int taskCount = notificationDao.count(); - if (taskCount == 0) { - notificationManager.cancel(SUMMARY_NOTIFICATION_ID); + if (taskCount <= 1) { + notificationManagerCompat.cancel(SUMMARY_NOTIFICATION_ID); } else { List notifications = notificationDao.getAllOrdered(); Iterable notificationIds = transform(notifications, n -> n.taskId); @@ -197,8 +231,107 @@ public class NotificationManager { notify(NotificationManager.SUMMARY_NOTIFICATION_ID, builder, notify, nonStop, fiveTimes); } } else { - notificationManager.cancel(NotificationManager.SUMMARY_NOTIFICATION_ID); + notificationManagerCompat.cancel(NotificationManager.SUMMARY_NOTIFICATION_ID); } } + public NotificationCompat.Builder getTaskNotification(org.tasks.notifications.Notification notification) { + long id = notification.taskId; + int type = notification.type; + long when = notification.timestamp; + Task task = taskDao.fetch(id); + if (task == null) { + Timber.e("Could not find %s", id); + return null; + } + + // you're done, or not yours - don't sound, do delete + if (task.isCompleted() || task.isDeleted()) { + return null; + } + + // new task edit in progress + if (TextUtils.isEmpty(task.getTitle())) { + return null; + } + + // it's hidden - don't sound, don't delete + if (task.isHidden() && type == ReminderService.TYPE_RANDOM) { + return null; + } + + // task due date was changed, but alarm wasn't rescheduled + boolean dueInFuture = task.hasDueTime() && task.getDueDate() > DateUtilities.now() || + !task.hasDueTime() && task.getDueDate() - DateUtilities.now() > DateUtilities.ONE_DAY; + if ((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) && + (!task.hasDueDate() || dueInFuture)) { + return null; + } + + // read properties + final String taskTitle = task.getTitle(); + final String taskDescription = task.getNotes(); + + // update last reminder time + task.setReminderLast(when); + taskDao.saveExisting(task); + + final String appName = context.getString(R.string.app_name); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_REMINDER) + .setTicker(taskTitle) + .setContentTitle(taskTitle) + .setContentText(appName) + .setGroup(GROUP_KEY) + .setSmallIcon(R.drawable.ic_check_white_24dp) + .setWhen(when) + .setShowWhen(true) + .setPriority(NotificationCompat.PRIORITY_HIGH); + + if (atLeastJellybean()) { + builder.setContentIntent(PendingIntent.getActivity(context, (int) id, TaskIntents.getEditTaskIntent(context, null, id), PendingIntent.FLAG_UPDATE_CURRENT)); + } else { + final Intent intent = new Intent(context, NotificationActivity.class); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); + intent.setAction("NOTIFY" + id); //$NON-NLS-1$ + intent.putExtra(NotificationActivity.EXTRA_TASK_ID, id); + intent.putExtra(NotificationActivity.EXTRA_TITLE, taskTitle); + builder.setContentIntent(PendingIntent.getActivity(context, (int) id, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + } + + if (!Strings.isNullOrEmpty(taskDescription)) { + builder.setStyle(new NotificationCompat.BigTextStyle().bigText(taskDescription)); + } + Intent completeIntent = new Intent(context, CompleteTaskReceiver.class); + completeIntent.putExtra(CompleteTaskReceiver.TASK_ID, id); + PendingIntent completePendingIntent = PendingIntent.getBroadcast(context, (int) id, completeIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Action completeAction = new NotificationCompat.Action.Builder( + R.drawable.ic_check_white_24dp, context.getResources().getString(R.string.rmd_NoA_done), completePendingIntent).build(); + + Intent snoozeIntent = new Intent(context, SnoozeActivity.class); + snoozeIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); + snoozeIntent.putExtra(SnoozeActivity.EXTRA_TASK_ID, id); + PendingIntent snoozePendingIntent = PendingIntent.getActivity(context, (int) id, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); + wearableExtender.addAction(completeAction); + for (final SnoozeOption snoozeOption : SnoozeDialog.getSnoozeOptions(preferences)) { + final long timestamp = snoozeOption.getDateTime().getMillis(); + Intent wearableIntent = new Intent(context, SnoozeActivity.class); + wearableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + wearableIntent.setAction(String.format("snooze-%s-%s", id, timestamp)); + wearableIntent.putExtra(SnoozeActivity.EXTRA_TASK_ID, id); + wearableIntent.putExtra(SnoozeActivity.EXTRA_SNOOZE_TIME, timestamp); + PendingIntent wearablePendingIntent = PendingIntent.getActivity(context, (int) id, wearableIntent, PendingIntent.FLAG_UPDATE_CURRENT); + wearableExtender.addAction(new NotificationCompat.Action.Builder( + R.drawable.ic_snooze_white_24dp, context.getString(snoozeOption.getResId()), wearablePendingIntent) + .build()); + } + + return builder.addAction(completeAction) + .addAction(R.drawable.ic_snooze_white_24dp, context.getResources().getString(R.string.rmd_NoA_snooze), snoozePendingIntent) + .extend(wearableExtender); + } }