From 8a30fde2f2d9886e2f32e7b50c9c6684327f3648 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 27 Jul 2020 16:27:25 -0500 Subject: [PATCH] Convert NotificationManager to Kotlin --- app/src/main/java/org/tasks/Notifier.kt | 13 +- .../notifications/NotificationManager.java | 441 ------------------ .../notifications/NotificationManager.kt | 401 ++++++++++++++++ .../org/tasks/reminders/SnoozeActivity.java | 4 +- 4 files changed, 411 insertions(+), 448 deletions(-) delete mode 100644 app/src/main/java/org/tasks/notifications/NotificationManager.java create mode 100644 app/src/main/java/org/tasks/notifications/NotificationManager.kt diff --git a/app/src/main/java/org/tasks/Notifier.kt b/app/src/main/java/org/tasks/Notifier.kt index bd2176f51..df1ef593b 100644 --- a/app/src/main/java/org/tasks/Notifier.kt +++ b/app/src/main/java/org/tasks/Notifier.kt @@ -93,11 +93,14 @@ class Notifier @Inject constructor( && !ringNonstop && !audioManager.notificationsMuted() && telephonyManager.callStateIdle()) { - for (notification in notifications) { - AndroidUtilities.sleepDeep(2000) - voiceOutputAssistant.speak( - notificationManager.getTaskNotification(notification).build().tickerText.toString()) - } + notifications + .mapNotNull { + notificationManager.getTaskNotification(it)?.build()?.tickerText?.toString() + } + .forEach { + AndroidUtilities.sleepDeep(2000) + voiceOutputAssistant.speak(it) + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/notifications/NotificationManager.java b/app/src/main/java/org/tasks/notifications/NotificationManager.java deleted file mode 100644 index 3352df89b..000000000 --- a/app/src/main/java/org/tasks/notifications/NotificationManager.java +++ /dev/null @@ -1,441 +0,0 @@ -package org.tasks.notifications; - -import static androidx.core.app.NotificationCompat.FLAG_INSISTENT; -import static androidx.core.app.NotificationCompat.FLAG_NO_CLEAR; -import static com.google.common.collect.Iterables.concat; -import static com.google.common.collect.Iterables.tryFind; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Lists.transform; -import static com.todoroo.andlib.utility.AndroidUtilities.atLeastNougat; -import static com.todoroo.andlib.utility.AndroidUtilities.preOreo; -import static com.todoroo.astrid.reminders.ReminderService.TYPE_GEOFENCE_ENTER; -import static com.todoroo.astrid.reminders.ReminderService.TYPE_GEOFENCE_EXIT; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.tasks.Strings.isNullOrEmpty; - -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import com.google.common.base.Joiner; -import com.todoroo.andlib.sql.Join; -import com.todoroo.andlib.sql.QueryTemplate; -import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.dao.TaskDaoBlocking; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.reminders.ReminderService; -import dagger.hilt.android.qualifiers.ApplicationContext; -import io.reactivex.Completable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.tasks.LocalBroadcastManager; -import org.tasks.R; -import org.tasks.data.LocationDaoBlocking; -import org.tasks.data.Place; -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.themes.ColorProvider; -import org.tasks.time.DateTime; -import timber.log.Timber; - -@Singleton -public class NotificationManager { - - public static final String NOTIFICATION_CHANNEL_DEFAULT = "notifications"; - public static final String NOTIFICATION_CHANNEL_TASKER = "notifications_tasker"; - public static final String NOTIFICATION_CHANNEL_TIMERS = "notifications_timers"; - public static final String NOTIFICATION_CHANNEL_MISCELLANEOUS = "notifications_miscellaneous"; - public static final int MAX_NOTIFICATIONS = 21; - static final String EXTRA_NOTIFICATION_ID = "extra_notification_id"; - static final int SUMMARY_NOTIFICATION_ID = 0; - private static final String GROUP_KEY = "tasks"; - private static final int NOTIFICATIONS_PER_SECOND = 4; - private final NotificationManagerCompat notificationManagerCompat; - private final ColorProvider colorProvider; - private final LocalBroadcastManager localBroadcastManager; - private final LocationDaoBlocking locationDao; - private final NotificationDaoBlocking notificationDao; - private final TaskDaoBlocking taskDao; - private final Context context; - private final Preferences preferences; - private final Throttle throttle = new Throttle(NOTIFICATIONS_PER_SECOND); - private final NotificationLimiter queue = new NotificationLimiter(MAX_NOTIFICATIONS); - - @Inject - public NotificationManager( - @ApplicationContext Context context, - Preferences preferences, - NotificationDaoBlocking notificationDao, - TaskDaoBlocking taskDao, - LocationDaoBlocking locationDao, - LocalBroadcastManager localBroadcastManager) { - this.context = context; - this.preferences = preferences; - this.notificationDao = notificationDao; - this.taskDao = taskDao; - this.locationDao = locationDao; - this.localBroadcastManager = localBroadcastManager; - this.colorProvider = new ColorProvider(context, preferences); - notificationManagerCompat = NotificationManagerCompat.from(context); - } - - @SuppressLint("CheckResult") - public void cancel(long id) { - if (id == SUMMARY_NOTIFICATION_ID) { - //noinspection ResultOfMethodCallIgnored - Single.fromCallable(() -> concat(notificationDao.getAll(), singletonList(id))) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::cancel); - } else { - cancel(singletonList(id)); - } - } - - @SuppressLint("CheckResult") - public void cancel(Iterable ids) { - for (Long id : ids) { - notificationManagerCompat.cancel(id.intValue()); - queue.remove(id); - } - - //noinspection ResultOfMethodCallIgnored - Completable.fromAction(() -> notificationDao.deleteAll(newArrayList(ids))) - .subscribeOn(Schedulers.io()) - .subscribe(() -> notifyTasks(emptyList(), false, false, false)); - } - - public void restoreNotifications(boolean cancelExisting) { - List notifications = notificationDao.getAllOrdered(); - if (cancelExisting) { - for (Notification notification : notifications) { - notificationManagerCompat.cancel((int) notification.getTaskId()); - } - } - - if (preferences.bundleNotifications() && notifications.size() > 1) { - updateSummary(false, false, false, Collections.emptyList()); - createNotifications(notifications, false, false, false, true); - } else { - createNotifications(notifications, false, false, false, false); - cancelSummaryNotification(); - } - } - - public void notifyTasks( - List newNotifications, boolean alert, boolean nonstop, boolean fiveTimes) { - List existingNotifications = notificationDao.getAllOrdered(); - notificationDao.insertAll(newNotifications); - int totalCount = existingNotifications.size() + newNotifications.size(); - if (totalCount == 0) { - cancelSummaryNotification(); - } else if (totalCount == 1) { - List notifications = - newArrayList(concat(existingNotifications, newNotifications)); - createNotifications(notifications, alert, nonstop, fiveTimes, false); - cancelSummaryNotification(); - } else if (preferences.bundleNotifications()) { - updateSummary(false, false, false, Collections.emptyList()); - - if (existingNotifications.size() == 1) { - createNotifications(existingNotifications, false, false, false, true); - } - - if (atLeastNougat() && newNotifications.size() == 1) { - createNotifications(newNotifications, alert, nonstop, fiveTimes, true); - } else { - createNotifications(newNotifications, false, false, false, true); - updateSummary(alert, nonstop, fiveTimes, newNotifications); - } - } else { - createNotifications(newNotifications, alert, nonstop, fiveTimes, false); - } - - localBroadcastManager.broadcastRefresh(); - } - - private void createNotifications( - List notifications, - boolean alert, - boolean nonstop, - boolean fiveTimes, - boolean useGroupKey) { - for (Notification notification : notifications) { - NotificationCompat.Builder builder = getTaskNotification(notification); - if (builder == null) { - notificationManagerCompat.cancel((int) notification.getTaskId()); - notificationDao.delete(notification.getTaskId()); - } else { - builder - .setGroup( - useGroupKey - ? GROUP_KEY - : (atLeastNougat() ? Long.toString(notification.getTaskId()) : null)) - .setGroupAlertBehavior( - alert - ? NotificationCompat.GROUP_ALERT_CHILDREN - : NotificationCompat.GROUP_ALERT_SUMMARY); - notify(notification.getTaskId(), builder, alert, nonstop, fiveTimes); - alert = false; - } - } - } - - public void notify( - long notificationId, - NotificationCompat.Builder builder, - boolean alert, - boolean nonstop, - boolean fiveTimes) { - if (!preferences.getBoolean(R.string.p_rmd_enabled, true)) { - return; - } - builder.setLocalOnly(!preferences.getBoolean(R.string.p_wearable_notifications, true)); - if (preOreo()) { - if (alert) { - builder - .setSound(preferences.getRingtone()) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setDefaults(preferences.getNotificationDefaults()); - } else { - builder.setDefaults(0).setTicker(null); - } - } - android.app.Notification notification = builder.build(); - int ringTimes = fiveTimes ? 5 : 1; - if (alert && nonstop) { - notification.flags |= FLAG_INSISTENT; - ringTimes = 1; - } - if (preferences.usePersistentReminders()) { - notification.flags |= FLAG_NO_CLEAR; - } - Intent deleteIntent = new Intent(context, NotificationClearedReceiver.class); - deleteIntent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); - notification.deleteIntent = - PendingIntent.getBroadcast( - context, (int) notificationId, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - List evicted = queue.add(notificationId); - if (evicted.size() > 0) { - cancel(evicted); - } - - for (int i = 0; i < ringTimes; i++) { - throttle.run(() -> notificationManagerCompat.notify((int) notificationId, notification)); - } - } - - private void updateSummary( - boolean notify, boolean nonStop, boolean fiveTimes, List newNotifications) { - List tasks = taskDao.activeNotifications(); - int taskCount = tasks.size(); - if (taskCount == 0) { - cancelSummaryNotification(); - return; - } - ArrayList taskIds = new ArrayList<>(transform(tasks, Task::getId)); - Filter filter = - new Filter( - context.getString(R.string.notifications), - new QueryTemplate() - .join(Join.inner(Notification.TABLE, Task.ID.eq(Notification.TASK)))); - long when = notificationDao.latestTimestamp(); - int maxPriority = 3; - String summaryTitle = - context.getResources().getQuantityString(R.plurals.task_count, taskCount, taskCount); - NotificationCompat.InboxStyle style = - new NotificationCompat.InboxStyle().setBigContentTitle(summaryTitle); - List titles = new ArrayList<>(); - List ticker = new ArrayList<>(); - for (Task task : tasks) { - String title = task.getTitle(); - style.addLine(title); - titles.add(title); - maxPriority = Math.min(maxPriority, task.getPriority()); - } - for (Notification notification : newNotifications) { - Task task = tryFind(tasks, t -> t.getId() == notification.getTaskId()).orNull(); - if (task == null) { - continue; - } - ticker.add(task.getTitle()); - } - NotificationCompat.Builder builder = - new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT) - .setContentTitle(summaryTitle) - .setContentText( - Joiner.on(context.getString(R.string.list_separator_with_space)).join(titles)) - .setShowWhen(true) - .setWhen(when) - .setSmallIcon(R.drawable.ic_done_all_white_24dp) - .setStyle(style) - .setColor(colorProvider.getPriorityColor(maxPriority, true)) - .setOnlyAlertOnce(false) - .setContentIntent( - PendingIntent.getActivity( - context, - 0, - TaskIntents.getTaskListIntent(context, filter), - PendingIntent.FLAG_UPDATE_CURRENT)) - .setGroupSummary(true) - .setGroup(GROUP_KEY) - .setTicker( - Joiner.on(context.getString(R.string.list_separator_with_space)).join(ticker)) - .setGroupAlertBehavior( - notify - ? NotificationCompat.GROUP_ALERT_SUMMARY - : NotificationCompat.GROUP_ALERT_CHILDREN); - - Intent snoozeIntent = SnoozeActivity.newIntent(context, taskIds); - builder.addAction( - R.drawable.ic_snooze_white_24dp, - context.getString(R.string.snooze_all), - PendingIntent.getActivity(context, 0, snoozeIntent, PendingIntent.FLAG_CANCEL_CURRENT)); - - notify(NotificationManager.SUMMARY_NOTIFICATION_ID, builder, notify, nonStop, fiveTimes); - } - - public NotificationCompat.Builder getTaskNotification(Notification notification) { - long id = notification.getTaskId(); - int type = notification.getType(); - long when = notification.getTimestamp(); - Task task = taskDao.fetchBlocking(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 (isNullOrEmpty(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() - && new DateTime(task.getDueDate()).startOfMinute().getMillis() > 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 - long reminderTime = new DateTime(when).endOfMinute().getMillis(); - if (reminderTime != task.getReminderLast()) { - task.setReminderLast(reminderTime); - taskDao.save(task); - } - - NotificationCompat.Builder builder = - new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT) - .setCategory(NotificationCompat.CATEGORY_REMINDER) - .setContentTitle(taskTitle) - .setColor(colorProvider.getPriorityColor(task.getPriority(), true)) - .setSmallIcon(R.drawable.ic_check_white_24dp) - .setWhen(when) - .setOnlyAlertOnce(false) - .setShowWhen(true) - .setTicker(taskTitle); - - Intent intent = NotificationActivity.newIntent(context, taskTitle, id); - builder.setContentIntent( - PendingIntent.getActivity(context, (int) id, intent, PendingIntent.FLAG_UPDATE_CURRENT)); - - if (type == TYPE_GEOFENCE_ENTER || type == TYPE_GEOFENCE_EXIT) { - Place place = locationDao.getPlace(notification.getLocation()); - if (place != null) { - builder.setContentText( - context.getString( - type == TYPE_GEOFENCE_ENTER - ? R.string.location_arrived - : R.string.location_departed, - place.getDisplayName())); - } - } else if (!isNullOrEmpty(taskDescription)) { - builder - .setContentText(taskDescription) - .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.getString(R.string.rmd_NoA_done), - completePendingIntent) - .build(); - - Intent snoozeIntent = SnoozeActivity.newIntent(context, 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 = SnoozeActivity.newIntent(context, id); - wearableIntent.setAction(String.format("snooze-%s-%s", id, timestamp)); - 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.getString(R.string.rmd_NoA_snooze), - snoozePendingIntent) - .extend(wearableExtender); - } - - private void cancelSummaryNotification() { - notificationManagerCompat.cancel(SUMMARY_NOTIFICATION_ID); - } -} diff --git a/app/src/main/java/org/tasks/notifications/NotificationManager.kt b/app/src/main/java/org/tasks/notifications/NotificationManager.kt new file mode 100644 index 000000000..3dc338955 --- /dev/null +++ b/app/src/main/java/org/tasks/notifications/NotificationManager.kt @@ -0,0 +1,401 @@ +package org.tasks.notifications + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.todoroo.andlib.sql.Join.Companion.inner +import com.todoroo.andlib.sql.QueryTemplate +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.dao.TaskDaoBlocking +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.reminders.ReminderService +import dagger.hilt.android.qualifiers.ApplicationContext +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import org.tasks.LocalBroadcastManager +import org.tasks.R +import org.tasks.Strings.isNullOrEmpty +import org.tasks.data.LocationDaoBlocking +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.themes.ColorProvider +import org.tasks.time.DateTime +import timber.log.Timber +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.math.min + +@Singleton +class NotificationManager @Inject constructor( + @param:ApplicationContext private val context: Context, + private val preferences: Preferences, + private val notificationDao: NotificationDaoBlocking, + private val taskDao: TaskDaoBlocking, + private val locationDao: LocationDaoBlocking, + private val localBroadcastManager: LocalBroadcastManager) { + private val notificationManagerCompat = NotificationManagerCompat.from(context) + private val colorProvider = ColorProvider(context, preferences) + private val throttle = Throttle(NOTIFICATIONS_PER_SECOND) + private val queue = NotificationLimiter(MAX_NOTIFICATIONS) + + @SuppressLint("CheckResult") + fun cancel(id: Long) { + if (id == SUMMARY_NOTIFICATION_ID.toLong()) { + Single.fromCallable { notificationDao.getAll() + id } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { ids: Iterable -> this.cancel(ids) } + } else { + cancel(listOf(id)) + } + } + + @SuppressLint("CheckResult") + fun cancel(ids: Iterable) { + for (id in ids) { + notificationManagerCompat.cancel(id.toInt()) + queue.remove(id) + } + Completable.fromAction { notificationDao.deleteAll(ids.toList()) } + .subscribeOn(Schedulers.io()) + .subscribe { + notifyTasks( + emptyList(), + alert = false, + nonstop = false, + fiveTimes = false) + } + } + + fun restoreNotifications(cancelExisting: Boolean) { + val notifications = notificationDao.getAllOrdered() + if (cancelExisting) { + for (notification in notifications) { + notificationManagerCompat.cancel(notification.taskId.toInt()) + } + } + if (preferences.bundleNotifications() && notifications.size > 1) { + updateSummary( + notify = false, + nonStop = false, + fiveTimes = false, + newNotifications = emptyList()) + createNotifications( + notifications, + alert = false, + nonstop = false, + fiveTimes = false, + useGroupKey = true) + } else { + createNotifications( + notifications, + alert = false, + nonstop = false, + fiveTimes = false, + useGroupKey = false) + cancelSummaryNotification() + } + } + + fun notifyTasks( + newNotifications: List, alert: Boolean, nonstop: Boolean, fiveTimes: Boolean) { + val existingNotifications = notificationDao.getAllOrdered() + notificationDao.insertAll(newNotifications) + val totalCount = existingNotifications.size + newNotifications.size + if (totalCount == 0) { + cancelSummaryNotification() + } else if (totalCount == 1) { + val notifications = existingNotifications + newNotifications + createNotifications(notifications, alert, nonstop, fiveTimes, false) + cancelSummaryNotification() + } else if (preferences.bundleNotifications()) { + updateSummary( + notify = false, + nonStop = false, + fiveTimes = false, + newNotifications = emptyList()) + if (existingNotifications.size == 1) { + createNotifications( + existingNotifications, + alert = false, + nonstop = false, + fiveTimes = false, + useGroupKey = true) + } + if (AndroidUtilities.atLeastNougat() && newNotifications.size == 1) { + createNotifications(newNotifications, alert, nonstop, fiveTimes, true) + } else { + createNotifications( + newNotifications, + alert = false, + nonstop = false, + fiveTimes = false, + useGroupKey = true) + updateSummary(alert, nonstop, fiveTimes, newNotifications) + } + } else { + createNotifications(newNotifications, alert, nonstop, fiveTimes, false) + } + localBroadcastManager.broadcastRefresh() + } + + private fun createNotifications( + notifications: List, + alert: Boolean, + nonstop: Boolean, + fiveTimes: Boolean, + useGroupKey: Boolean) { + var alert = alert + for (notification in notifications) { + val builder = getTaskNotification(notification) + if (builder == null) { + notificationManagerCompat.cancel(notification.taskId.toInt()) + notificationDao.delete(notification.taskId) + } else { + builder + .setGroup( + if (useGroupKey) GROUP_KEY else if (AndroidUtilities.atLeastNougat()) java.lang.Long.toString(notification.taskId) else null) + .setGroupAlertBehavior( + if (alert) NotificationCompat.GROUP_ALERT_CHILDREN else NotificationCompat.GROUP_ALERT_SUMMARY) + notify(notification.taskId, builder, alert, nonstop, fiveTimes) + alert = false + } + } + } + + fun notify( + notificationId: Long, + builder: NotificationCompat.Builder, + alert: Boolean, + nonstop: Boolean, + fiveTimes: Boolean) { + if (!preferences.getBoolean(R.string.p_rmd_enabled, true)) { + return + } + builder.setLocalOnly(!preferences.getBoolean(R.string.p_wearable_notifications, true)) + if (AndroidUtilities.preOreo()) { + if (alert) { + builder + .setSound(preferences.ringtone) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setDefaults(preferences.notificationDefaults) + } else { + builder.setDefaults(0).setTicker(null) + } + } + val notification = builder.build() + var ringTimes = if (fiveTimes) 5 else 1 + if (alert && nonstop) { + notification.flags = notification.flags or NotificationCompat.FLAG_INSISTENT + ringTimes = 1 + } + if (preferences.usePersistentReminders()) { + notification.flags = notification.flags or NotificationCompat.FLAG_NO_CLEAR + } + val deleteIntent = Intent(context, NotificationClearedReceiver::class.java) + deleteIntent.putExtra(EXTRA_NOTIFICATION_ID, notificationId) + notification.deleteIntent = PendingIntent.getBroadcast( + context, notificationId.toInt(), deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val evicted = queue.add(notificationId) + if (evicted.size > 0) { + cancel(evicted) + } + for (i in 0 until ringTimes) { + throttle.run { notificationManagerCompat.notify(notificationId.toInt(), notification) } + } + } + + private fun updateSummary( + notify: Boolean, nonStop: Boolean, fiveTimes: Boolean, newNotifications: List) { + val tasks = taskDao.activeNotifications() + val taskCount = tasks.size + if (taskCount == 0) { + cancelSummaryNotification() + return + } + val taskIds = tasks.map { it.id } + val filter = Filter( + context.getString(R.string.notifications), + QueryTemplate() + .join(inner(Notification.TABLE, Task.ID.eq(Notification.TASK)))) + val `when` = notificationDao.latestTimestamp() + var maxPriority = 3 + val summaryTitle = context.resources.getQuantityString(R.plurals.task_count, taskCount, taskCount) + val style = NotificationCompat.InboxStyle().setBigContentTitle(summaryTitle) + val titles: MutableList = ArrayList() + val ticker: MutableList = ArrayList() + for (task in tasks) { + val title = task.title + style.addLine(title) + titles.add(title) + maxPriority = min(maxPriority, task.priority) + } + for (notification in newNotifications) { + tasks.find { it.id == notification.taskId }?.let { ticker.add(it.title) } + } + val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_DEFAULT) + .setContentTitle(summaryTitle) + .setContentText( + titles.joinToString(context.getString(R.string.list_separator_with_space))) + .setShowWhen(true) + .setWhen(`when`) + .setSmallIcon(R.drawable.ic_done_all_white_24dp) + .setStyle(style) + .setColor(colorProvider.getPriorityColor(maxPriority, true)) + .setOnlyAlertOnce(false) + .setContentIntent( + PendingIntent.getActivity( + context, + 0, + TaskIntents.getTaskListIntent(context, filter), + PendingIntent.FLAG_UPDATE_CURRENT)) + .setGroupSummary(true) + .setGroup(GROUP_KEY) + .setTicker( + ticker.joinToString(context.getString(R.string.list_separator_with_space))) + .setGroupAlertBehavior( + if (notify) NotificationCompat.GROUP_ALERT_SUMMARY else NotificationCompat.GROUP_ALERT_CHILDREN) + val snoozeIntent = SnoozeActivity.newIntent(context, taskIds) + builder.addAction( + R.drawable.ic_snooze_white_24dp, + context.getString(R.string.snooze_all), + PendingIntent.getActivity(context, 0, snoozeIntent, PendingIntent.FLAG_CANCEL_CURRENT)) + notify(SUMMARY_NOTIFICATION_ID.toLong(), builder, notify, nonStop, fiveTimes) + } + + fun getTaskNotification(notification: Notification): NotificationCompat.Builder? { + val id = notification.taskId + val type = notification.type + val `when` = notification.timestamp + val task = taskDao.fetchBlocking(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 (isNullOrEmpty(task.title)) { + 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 + val dueInFuture = (task.hasDueTime() + && DateTime(task.dueDate).startOfMinute().millis > DateUtilities.now() + || !task.hasDueTime() + && task.dueDate - DateUtilities.now() > DateUtilities.ONE_DAY) + if ((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) + && (!task.hasDueDate() || dueInFuture)) { + return null + } + + // read properties + val taskTitle = task.title + val taskDescription = task.notes + + // update last reminder time + val reminderTime = DateTime(`when`).endOfMinute().millis + if (reminderTime != task.reminderLast) { + task.reminderLast = reminderTime + taskDao.save(task) + } + val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_REMINDER) + .setContentTitle(taskTitle) + .setColor(colorProvider.getPriorityColor(task.priority, true)) + .setSmallIcon(R.drawable.ic_check_white_24dp) + .setWhen(`when`) + .setOnlyAlertOnce(false) + .setShowWhen(true) + .setTicker(taskTitle) + val intent = NotificationActivity.newIntent(context, taskTitle, id) + builder.setContentIntent( + PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)) + if (type == ReminderService.TYPE_GEOFENCE_ENTER || type == ReminderService.TYPE_GEOFENCE_EXIT) { + val place = locationDao.getPlace(notification.location!!) + if (place != null) { + builder.setContentText( + context.getString( + if (type == ReminderService.TYPE_GEOFENCE_ENTER) R.string.location_arrived else R.string.location_departed, + place.displayName)) + } + } else if (!isNullOrEmpty(taskDescription)) { + builder + .setContentText(taskDescription) + .setStyle(NotificationCompat.BigTextStyle().bigText(taskDescription)) + } + val completeIntent = Intent(context, CompleteTaskReceiver::class.java) + completeIntent.putExtra(CompleteTaskReceiver.TASK_ID, id) + val completePendingIntent = PendingIntent.getBroadcast( + context, id.toInt(), completeIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val completeAction = NotificationCompat.Action.Builder( + R.drawable.ic_check_white_24dp, + context.getString(R.string.rmd_NoA_done), + completePendingIntent) + .build() + val snoozeIntent = SnoozeActivity.newIntent(context, id) + val snoozePendingIntent = PendingIntent.getActivity( + context, id.toInt(), snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val wearableExtender = NotificationCompat.WearableExtender() + wearableExtender.addAction(completeAction) + for (snoozeOption in SnoozeDialog.getSnoozeOptions(preferences)) { + val timestamp = snoozeOption.dateTime.millis + val wearableIntent = SnoozeActivity.newIntent(context, id) + wearableIntent.action = String.format("snooze-%s-%s", id, timestamp) + wearableIntent.putExtra(SnoozeActivity.EXTRA_SNOOZE_TIME, timestamp) + val wearablePendingIntent = PendingIntent.getActivity( + context, id.toInt(), wearableIntent, PendingIntent.FLAG_UPDATE_CURRENT) + wearableExtender.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_snooze_white_24dp, + context.getString(snoozeOption.resId), + wearablePendingIntent) + .build()) + } + return builder + .addAction(completeAction) + .addAction( + R.drawable.ic_snooze_white_24dp, + context.getString(R.string.rmd_NoA_snooze), + snoozePendingIntent) + .extend(wearableExtender) + } + + private fun cancelSummaryNotification() { + notificationManagerCompat.cancel(SUMMARY_NOTIFICATION_ID) + } + + companion object { + const val NOTIFICATION_CHANNEL_DEFAULT = "notifications" + const val NOTIFICATION_CHANNEL_TASKER = "notifications_tasker" + const val NOTIFICATION_CHANNEL_TIMERS = "notifications_timers" + const val NOTIFICATION_CHANNEL_MISCELLANEOUS = "notifications_miscellaneous" + const val MAX_NOTIFICATIONS = 21 + const val EXTRA_NOTIFICATION_ID = "extra_notification_id" + const val SUMMARY_NOTIFICATION_ID = 0 + private const val GROUP_KEY = "tasks" + private const val NOTIFICATIONS_PER_SECOND = 4 + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/reminders/SnoozeActivity.java b/app/src/main/java/org/tasks/reminders/SnoozeActivity.java index 8a5389168..703ee9a5d 100644 --- a/app/src/main/java/org/tasks/reminders/SnoozeActivity.java +++ b/app/src/main/java/org/tasks/reminders/SnoozeActivity.java @@ -43,10 +43,10 @@ public class SnoozeActivity extends InjectingAppCompatActivity return intent; } - public static Intent newIntent(Context context, ArrayList ids) { + public static Intent newIntent(Context context, List ids) { Intent intent = new Intent(context, SnoozeActivity.class); intent.setFlags(FLAGS); - intent.putExtra(SnoozeActivity.EXTRA_TASK_IDS, ids); + intent.putExtra(SnoozeActivity.EXTRA_TASK_IDS, new ArrayList(ids)); return intent; }