From 6875fc04288f305c3c938d3ea7ab8d2fb03754c6 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 2 Jun 2017 19:30:39 -0400 Subject: [PATCH] Move notifications and reminders to uhabits-core --- .../org/isoron/uhabits/HabitsApplication.java | 7 +- .../org/isoron/uhabits/HabitsComponent.java | 4 +- .../java/org/isoron/uhabits/HabitsModule.java | 22 +- .../uhabits/intents/IntentScheduler.java | 29 ++- .../AndroidNotificationTray.java | 233 ++++-------------- .../uhabits/receivers/ReminderController.java | 8 +- .../receivers/ReminderControllerTest.java | 8 +- .../receivers/WidgetControllerTest.java | 6 +- .../core/reminders}/ReminderScheduler.java | 40 ++- .../uhabits/core/ui/NotificationTray.java | 187 +++++++++++++- .../java/org/isoron/uhabits/BaseUnitTest.java | 1 + .../reminders}/ReminderSchedulerTest.java | 94 +++---- 12 files changed, 345 insertions(+), 294 deletions(-) rename {uhabits-android/src/main/java/org/isoron/uhabits/utils => uhabits-core/src/main/java/org/isoron/uhabits/core/reminders}/ReminderScheduler.java (72%) rename {uhabits-android/src/androidTest/java/org/isoron/uhabits/utils => uhabits-core/src/test/java/org/isoron/uhabits/core/reminders}/ReminderSchedulerTest.java (69%) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.java b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.java index cee212115..066fae376 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -26,9 +26,10 @@ import com.activeandroid.*; import org.isoron.androidbase.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.reminders.*; import org.isoron.uhabits.core.tasks.*; +import org.isoron.uhabits.core.ui.*; import org.isoron.uhabits.models.sqlite.*; -import org.isoron.uhabits.notifications.*; import org.isoron.uhabits.utils.*; import org.isoron.uhabits.widgets.*; @@ -47,7 +48,7 @@ public class HabitsApplication extends Application private ReminderScheduler reminderScheduler; - private AndroidNotificationTray notificationTray; + private NotificationTray notificationTray; public HabitsComponent getComponent() { @@ -106,7 +107,7 @@ public class HabitsApplication extends Application reminderScheduler = component.getReminderScheduler(); reminderScheduler.startListening(); - notificationTray = component.getAndroidNotificationTray(); + notificationTray = component.getNotificationTray(); notificationTray.startListening(); Preferences prefs = component.getPreferences(); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsComponent.java b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsComponent.java index 96b8cbe15..ae7b9eb4c 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsComponent.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsComponent.java @@ -26,6 +26,7 @@ import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.reminders.*; import org.isoron.uhabits.core.tasks.*; import org.isoron.uhabits.core.ui.*; import org.isoron.uhabits.core.ui.screens.habits.list.*; @@ -36,7 +37,6 @@ import org.isoron.uhabits.models.sqlite.*; import org.isoron.uhabits.notifications.*; import org.isoron.uhabits.sync.*; import org.isoron.uhabits.tasks.*; -import org.isoron.uhabits.utils.*; import org.isoron.uhabits.widgets.*; import dagger.*; @@ -50,8 +50,6 @@ import dagger.*; }) public interface HabitsComponent { - AndroidNotificationTray getAndroidNotificationTray(); - BaseSystem getBaseSystem(); CommandRunner getCommandRunner(); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.java b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.java index 7e66cbe68..6c3841474 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.java @@ -20,8 +20,13 @@ package org.isoron.uhabits; import org.isoron.uhabits.core.*; +import org.isoron.uhabits.core.commands.*; +import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.reminders.*; +import org.isoron.uhabits.core.tasks.*; import org.isoron.uhabits.core.ui.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.notifications.*; import org.isoron.uhabits.preferences.*; @@ -39,9 +44,22 @@ public class HabitsModule @Provides @AppScope - public static NotificationTray getTray(AndroidNotificationTray tray) + public static ReminderScheduler getReminderScheduler(IntentScheduler sys, + CommandRunner commandRunner, + HabitList habitList) { - return tray; + return new ReminderScheduler(commandRunner, habitList, sys); + } + + @Provides + @AppScope + public static NotificationTray getTray(TaskRunner taskRunner, + CommandRunner commandRunner, + Preferences preferences, + AndroidNotificationTray screen) + { + return new NotificationTray(taskRunner, commandRunner, preferences, + screen); } @Provides diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java index bd8cc5d77..df604ee0c 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java @@ -25,30 +25,51 @@ import android.os.*; import android.support.annotation.*; import org.isoron.androidbase.*; +import org.isoron.uhabits.*; import org.isoron.uhabits.core.*; +import org.isoron.uhabits.core.models.*; import javax.inject.*; import static android.app.AlarmManager.*; import static android.content.Context.*; -import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.*; @AppScope public class IntentScheduler + implements org.isoron.uhabits.core.reminders.ReminderScheduler.SystemScheduler { private final AlarmManager manager; + @NonNull + private final PendingIntentFactory pendingIntents; + + private HabitLogger logger; + @Inject - public IntentScheduler(@AppContext Context context) + public IntentScheduler(@AppContext Context context, + @NonNull PendingIntentFactory pendingIntents, + @NonNull HabitLogger logger) { manager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + this.pendingIntents = pendingIntents; + this.logger = logger; } public void schedule(@NonNull Long timestamp, PendingIntent intent) { if (Build.VERSION.SDK_INT >= M) manager.setExactAndAllowWhileIdle(RTC_WAKEUP, timestamp, intent); - else - manager.setExact(RTC_WAKEUP, timestamp, intent); + else manager.setExact(RTC_WAKEUP, timestamp, intent); + } + + @Override + public void scheduleShowReminder(long reminderTime, + @NonNull Habit habit, + long timestamp) + { + schedule(reminderTime, + pendingIntents.showReminder(habit, reminderTime, timestamp)); + logger.logReminderScheduled(habit, reminderTime); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.java b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.java index e5fbdf50b..868137cc0 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.java @@ -29,228 +29,87 @@ import android.support.v4.app.NotificationCompat.*; import org.isoron.androidbase.*; import org.isoron.uhabits.*; import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.tasks.*; import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.intents.*; -import java.util.*; - import javax.inject.*; import static android.graphics.BitmapFactory.*; import static org.isoron.uhabits.notifications.RingtoneManager.*; @AppScope -public class AndroidNotificationTray - implements CommandRunner.Listener, Preferences.Listener, - NotificationTray +public class AndroidNotificationTray implements NotificationTray.SystemTray { @NonNull private final Context context; - @NonNull - private final TaskRunner taskRunner; - @NonNull private final PendingIntentFactory pendingIntents; - @NonNull - private final CommandRunner commandRunner; - @NonNull private final Preferences preferences; - @NonNull - private final HashMap active; - @Inject public AndroidNotificationTray(@AppContext @NonNull Context context, - @NonNull TaskRunner taskRunner, @NonNull PendingIntentFactory pendingIntents, - @NonNull CommandRunner commandRunner, @NonNull Preferences preferences) { this.context = context; - this.taskRunner = taskRunner; this.pendingIntents = pendingIntents; - this.commandRunner = commandRunner; this.preferences = preferences; - this.active = new HashMap<>(); } @Override - public void cancel(@NonNull Habit habit) + public void removeNotification(int id) { - int notificationId = getNotificationId(habit); - NotificationManagerCompat.from(context).cancel(notificationId); - active.remove(habit); + NotificationManagerCompat.from(context).cancel(id); } - @Override - public void onCommandExecuted(@NonNull Command command, - @Nullable Long refreshKey) - { - if (command instanceof ToggleRepetitionCommand) - { - ToggleRepetitionCommand toggleCmd = - (ToggleRepetitionCommand) command; - - Habit habit = toggleCmd.getHabit(); - taskRunner.execute(() -> - { - if (habit.getCheckmarks().getTodayValue() != - Checkmark.UNCHECKED) cancel(habit); - }); - } - - if (command instanceof DeleteHabitsCommand) - { - DeleteHabitsCommand deleteCommand = (DeleteHabitsCommand) command; - List deleted = deleteCommand.getSelected(); - for (Habit habit : deleted) - cancel(habit); - } - } - - @Override - public void onNotificationsChanged() + public void showNotification(@NonNull Habit habit, + int notificationId, + long timestamp, + long reminderTime) { - reshowAll(); - } - - public void show(@NonNull Habit habit, long timestamp, long reminderTime) - { - NotificationData data = new NotificationData(timestamp, reminderTime); - active.put(habit, data); - taskRunner.execute(new ShowNotificationTask(habit, data)); - } - - public void startListening() - { - commandRunner.addListener(this); - preferences.addListener(this); - } - - public void stopListening() - { - commandRunner.removeListener(this); - preferences.removeListener(this); - } - - private int getNotificationId(Habit habit) - { - Long id = habit.getId(); - if (id == null) return 0; - return (int) (id % Integer.MAX_VALUE); - } - - private void reshowAll() - { - for (Habit habit : active.keySet()) - { - NotificationData data = active.get(habit); - taskRunner.execute(new ShowNotificationTask(habit, data)); - } - } - - class NotificationData - { - public final long timestamp; - - public final long reminderTime; - - public NotificationData(long timestamp, long reminderTime) - { - this.timestamp = timestamp; - this.reminderTime = reminderTime; - } - } - - private class ShowNotificationTask implements Task - { - int todayValue; - - private final Habit habit; - - private final long timestamp; - - private final long reminderTime; - - public ShowNotificationTask(Habit habit, NotificationData data) - { - this.habit = habit; - this.timestamp = data.timestamp; - this.reminderTime = data.reminderTime; - } - - @Override - public void doInBackground() - { - todayValue = habit.getCheckmarks().getTodayValue(); - } - - @Override - public void onPostExecute() - { - if (todayValue != Checkmark.UNCHECKED) return; - if (!shouldShowReminderToday()) return; - if (!habit.hasReminder()) return; - - Action checkAction = new Action(R.drawable.ic_action_check, - context.getString(R.string.check), - pendingIntents.addCheckmark(habit, timestamp)); - - Action snoozeAction = new Action(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), - pendingIntents.snoozeNotification(habit)); - - Bitmap wearableBg = - decodeResource(context.getResources(), R.drawable.stripe); - - // Even though the set of actions is the same on the phone and - // on the watch, Pebble requires us to add them to the - // WearableExtender. - WearableExtender wearableExtender = new WearableExtender() - .setBackground(wearableBg) - .addAction(checkAction) - .addAction(snoozeAction); - - Notification notification = new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(habit.getName()) - .setContentText(habit.getDescription()) - .setContentIntent(pendingIntents.showHabit(habit)) - .setDeleteIntent(pendingIntents.dismissNotification(habit)) - .addAction(checkAction) - .addAction(snoozeAction) - .setSound(getRingtoneUri(context)) - .extend(wearableExtender) - .setWhen(reminderTime) - .setShowWhen(true) - .setOngoing(preferences.shouldMakeNotificationsSticky()) - .build(); - - NotificationManager notificationManager = - (NotificationManager) context.getSystemService( - Activity.NOTIFICATION_SERVICE); - - int notificationId = getNotificationId(habit); - notificationManager.notify(notificationId, notification); - } - - private boolean shouldShowReminderToday() - { - if (!habit.hasReminder()) return false; - Reminder reminder = habit.getReminder(); - - boolean reminderDays[] = reminder.getDays().toArray(); - int weekday = DateUtils.getWeekday(timestamp); - - return reminderDays[weekday]; - } + Action checkAction = new Action(R.drawable.ic_action_check, + context.getString(R.string.check), + pendingIntents.addCheckmark(habit, timestamp)); + + Action snoozeAction = new Action(R.drawable.ic_action_snooze, + context.getString(R.string.snooze), + pendingIntents.snoozeNotification(habit)); + + Bitmap wearableBg = + decodeResource(context.getResources(), R.drawable.stripe); + + // Even though the set of actions is the same on the phone and + // on the watch, Pebble requires us to add them to the + // WearableExtender. + WearableExtender wearableExtender = new WearableExtender() + .setBackground(wearableBg) + .addAction(checkAction) + .addAction(snoozeAction); + + Notification notification = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(habit.getName()) + .setContentText(habit.getDescription()) + .setContentIntent(pendingIntents.showHabit(habit)) + .setDeleteIntent(pendingIntents.dismissNotification(habit)) + .addAction(checkAction) + .addAction(snoozeAction) + .setSound(getRingtoneUri(context)) + .extend(wearableExtender) + .setWhen(reminderTime) + .setShowWhen(true) + .setOngoing(preferences.shouldMakeNotificationsSticky()) + .build(); + + NotificationManager notificationManager = + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); + + notificationManager.notify(notificationId, notification); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java index 5d771c0fa..d29655aa0 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java @@ -23,9 +23,9 @@ import android.support.annotation.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.reminders.*; +import org.isoron.uhabits.core.ui.*; import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.notifications.*; -import org.isoron.uhabits.utils.*; import javax.inject.*; @@ -36,13 +36,13 @@ public class ReminderController private final ReminderScheduler reminderScheduler; @NonNull - private final AndroidNotificationTray notificationTray; + private final NotificationTray notificationTray; private Preferences preferences; @Inject public ReminderController(@NonNull ReminderScheduler reminderScheduler, - @NonNull AndroidNotificationTray notificationTray, + @NonNull NotificationTray notificationTray, @NonNull Preferences preferences) { this.reminderScheduler = reminderScheduler; diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java index 4a7a0351b..cdce6b81a 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java +++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java @@ -22,9 +22,9 @@ package org.isoron.uhabits.receivers; import org.isoron.uhabits.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.reminders.*; +import org.isoron.uhabits.core.ui.*; import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.notifications.*; -import org.isoron.uhabits.utils.*; import org.junit.*; import static org.mockito.Mockito.*; @@ -36,7 +36,7 @@ public class ReminderControllerTest extends BaseAndroidUnitTest private ReminderScheduler reminderScheduler; - private AndroidNotificationTray notificationTray; + private NotificationTray notificationTray; private Preferences preferences; @@ -46,7 +46,7 @@ public class ReminderControllerTest extends BaseAndroidUnitTest super.setUp(); reminderScheduler = mock(ReminderScheduler.class); - notificationTray = mock(AndroidNotificationTray.class); + notificationTray = mock(NotificationTray.class); preferences = mock(Preferences.class); controller = new ReminderController(reminderScheduler, diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java index 5cbb3a1a4..adba7d2b7 100644 --- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java +++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.receivers; import org.isoron.uhabits.*; import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.notifications.*; +import org.isoron.uhabits.core.ui.*; import org.isoron.uhabits.core.ui.widgets.*; import org.isoron.uhabits.core.utils.*; import org.junit.*; @@ -42,7 +42,7 @@ public class WidgetControllerTest extends BaseAndroidUnitTest private long today; - private AndroidNotificationTray notificationTray; + private NotificationTray notificationTray; @Override public void setUp() @@ -52,7 +52,7 @@ public class WidgetControllerTest extends BaseAndroidUnitTest today = DateUtils.getStartOfToday(); habit = fixtures.createEmptyHabit(); commandRunner = mock(CommandRunner.class); - notificationTray = mock(AndroidNotificationTray.class); + notificationTray = mock(NotificationTray.class); controller = new WidgetBehavior(commandRunner, notificationTray); } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java similarity index 72% rename from uhabits-android/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java rename to uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java index 914c8a8f6..f1a6224fb 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java @@ -17,17 +17,14 @@ * with this program. If not, see . */ -package org.isoron.uhabits.utils; +package org.isoron.uhabits.core.reminders; -import android.app.*; import android.support.annotation.*; -import org.isoron.uhabits.*; import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.intents.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.utils.*; import java.util.*; @@ -38,28 +35,20 @@ import static org.isoron.uhabits.core.utils.DateUtils.*; @AppScope public class ReminderScheduler implements CommandRunner.Listener { - private final PendingIntentFactory pendingIntentFactory; - - private final IntentScheduler intentScheduler; - - private final HabitLogger logger; - private CommandRunner commandRunner; private HabitList habitList; + private SystemScheduler sys; + @Inject - public ReminderScheduler(@NonNull PendingIntentFactory pendingIntentFactory, - @NonNull IntentScheduler intentScheduler, - @NonNull HabitLogger logger, - @NonNull CommandRunner commandRunner, - @NonNull HabitList habitList) + public ReminderScheduler(@NonNull CommandRunner commandRunner, + @NonNull HabitList habitList, + @NonNull SystemScheduler sys) { - this.pendingIntentFactory = pendingIntentFactory; - this.intentScheduler = intentScheduler; - this.logger = logger; this.commandRunner = commandRunner; this.habitList = habitList; + this.sys = sys; } @Override @@ -79,10 +68,7 @@ public class ReminderScheduler implements CommandRunner.Listener if (reminderTime == null) reminderTime = getReminderTime(reminder); long timestamp = getStartOfDay(removeTimezone(reminderTime)); - PendingIntent intent = - pendingIntentFactory.showReminder(habit, reminderTime, timestamp); - intentScheduler.schedule(reminderTime, intent); - logger.logReminderScheduled(habit, reminderTime); + sys.scheduleShowReminder(reminderTime, habit, timestamp); } public void scheduleAll() @@ -112,8 +98,14 @@ public class ReminderScheduler implements CommandRunner.Listener calendar.set(Calendar.SECOND, 0); Long time = calendar.getTimeInMillis(); - if (DateUtils.getLocalTime() > time) time += AlarmManager.INTERVAL_DAY; + if (DateUtils.getLocalTime() > time) + time += DateUtils.millisecondsInOneDay; return applyTimezone(time); } + + public interface SystemScheduler + { + void scheduleShowReminder(long reminderTime, Habit habit, long timestamp); + } } diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java index 3a882f465..82eb4cd39 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/NotificationTray.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Álinson Santos Xavier + * Copyright (C) 2016 Álinson Santos Xavier * * This file is part of Loop Habit Tracker. * @@ -19,9 +19,190 @@ package org.isoron.uhabits.core.ui; +import android.support.annotation.*; + +import org.isoron.uhabits.core.*; +import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.tasks.*; +import org.isoron.uhabits.core.utils.*; + +import java.util.*; + +import javax.inject.*; -public interface NotificationTray +@AppScope +public class NotificationTray + implements CommandRunner.Listener, Preferences.Listener { - void cancel(Habit habit); + @NonNull + private final TaskRunner taskRunner; + + @NonNull + private final CommandRunner commandRunner; + + @NonNull + private final Preferences preferences; + + private SystemTray systemTray; + + @NonNull + private final HashMap active; + + @Inject + public NotificationTray(@NonNull TaskRunner taskRunner, + @NonNull CommandRunner commandRunner, + @NonNull Preferences preferences, + @NonNull SystemTray systemTray) + { + this.taskRunner = taskRunner; + this.commandRunner = commandRunner; + this.preferences = preferences; + this.systemTray = systemTray; + this.active = new HashMap<>(); + } + + public void cancel(@NonNull Habit habit) + { + int notificationId = getNotificationId(habit); + systemTray.removeNotification(notificationId); + active.remove(habit); + } + + @Override + public void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey) + { + if (command instanceof ToggleRepetitionCommand) + { + ToggleRepetitionCommand toggleCmd = + (ToggleRepetitionCommand) command; + + Habit habit = toggleCmd.getHabit(); + taskRunner.execute(() -> + { + if (habit.getCheckmarks().getTodayValue() != + Checkmark.UNCHECKED) cancel(habit); + }); + } + + if (command instanceof DeleteHabitsCommand) + { + DeleteHabitsCommand deleteCommand = (DeleteHabitsCommand) command; + List deleted = deleteCommand.getSelected(); + for (Habit habit : deleted) + cancel(habit); + } + } + + @Override + public void onNotificationsChanged() + { + reshowAll(); + } + + public void show(@NonNull Habit habit, long timestamp, long reminderTime) + { + NotificationData data = new NotificationData(timestamp, reminderTime); + active.put(habit, data); + taskRunner.execute(new ShowNotificationTask(habit, data)); + } + + public void startListening() + { + commandRunner.addListener(this); + preferences.addListener(this); + } + + public void stopListening() + { + commandRunner.removeListener(this); + preferences.removeListener(this); + } + + private int getNotificationId(Habit habit) + { + Long id = habit.getId(); + if (id == null) return 0; + return (int) (id % Integer.MAX_VALUE); + } + + private void reshowAll() + { + for (Habit habit : active.keySet()) + { + NotificationData data = active.get(habit); + taskRunner.execute(new ShowNotificationTask(habit, data)); + } + } + + public interface SystemTray + { + void removeNotification(int notificationId); + + void showNotification(Habit habit, + int notificationId, + long timestamp, + long reminderTime); + } + + class NotificationData + { + public final long timestamp; + + public final long reminderTime; + + public NotificationData(long timestamp, long reminderTime) + { + this.timestamp = timestamp; + this.reminderTime = reminderTime; + } + } + + private class ShowNotificationTask implements Task + { + int todayValue; + + private final Habit habit; + + private final long timestamp; + + private final long reminderTime; + + public ShowNotificationTask(Habit habit, NotificationData data) + { + this.habit = habit; + this.timestamp = data.timestamp; + this.reminderTime = data.reminderTime; + } + + @Override + public void doInBackground() + { + todayValue = habit.getCheckmarks().getTodayValue(); + } + + @Override + public void onPostExecute() + { + if (todayValue != Checkmark.UNCHECKED) return; + if (!shouldShowReminderToday()) return; + if (!habit.hasReminder()) return; + + systemTray.showNotification(habit, getNotificationId(habit), timestamp, + reminderTime); + } + + private boolean shouldShowReminderToday() + { + if (!habit.hasReminder()) return false; + Reminder reminder = habit.getReminder(); + + boolean reminderDays[] = reminder.getDays().toArray(); + int weekday = DateUtils.getWeekday(timestamp); + + return reminderDays[weekday]; + } + } } diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/BaseUnitTest.java index 0cc34b800..5473ba296 100644 --- a/uhabits-core/src/test/java/org/isoron/uhabits/BaseUnitTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -63,6 +63,7 @@ public class BaseUnitTest @After public void tearDown() { + validateMockitoUsage(); DateUtils.setFixedLocalTime(null); } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java similarity index 69% rename from uhabits-android/src/androidTest/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java rename to uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java index 8b43504a9..352e8e5bb 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java +++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java @@ -17,60 +17,68 @@ * with this program. If not, see . */ -package org.isoron.uhabits.utils; - -import android.app.*; -import android.support.test.runner.*; -import android.test.suitebuilder.annotation.*; +package org.isoron.uhabits.core.reminders; import org.isoron.uhabits.*; -import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.intents.*; import org.junit.*; import org.junit.runner.*; +import org.mockito.*; +import org.mockito.junit.*; import java.util.*; -import static java.util.Arrays.*; import static org.mockito.Mockito.*; -@RunWith(AndroidJUnit4.class) -@MediumTest -public class ReminderSchedulerTest extends BaseAndroidTest +@RunWith(MockitoJUnitRunner.class) +public class ReminderSchedulerTest extends BaseUnitTest { private Habit habit; private ReminderScheduler reminderScheduler; - private HabitLogger logger; - - private PendingIntentFactory pendingIntentFactory; - - private IntentScheduler intentScheduler; - - private CommandRunner commandRunner; + @Mock + private ReminderScheduler.SystemScheduler sys; @Before @Override public void setUp() { super.setUp(); - logger = mock(HabitLogger.class); - intentScheduler = mock(IntentScheduler.class); - commandRunner = mock(CommandRunner.class); - pendingIntentFactory = - new PendingIntentFactory(targetContext, new IntentFactory()); + habit = fixtures.createEmptyHabit(); reminderScheduler = - new ReminderScheduler(pendingIntentFactory, intentScheduler, logger, - commandRunner, habitList); - habit = fixtures.createEmptyHabit(); + new ReminderScheduler(commandRunner, habitList, sys); DateUtils.setFixedTimeZone(TimeZone.getTimeZone("GMT-4")); } + @Test + public void testScheduleAll() + { + long now = timestamp(2015, 1, 26, 13, 0); + DateUtils.setFixedLocalTime(now); + + Habit h1 = fixtures.createEmptyHabit(); + Habit h2 = fixtures.createEmptyHabit(); + Habit h3 = fixtures.createEmptyHabit(); + h1.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); + h2.setReminder(new Reminder(18, 30, WeekdayList.EVERY_DAY)); + h3.setReminder(null); + habitList.add(h1); + habitList.add(h2); + habitList.add(h3); + + reminderScheduler.scheduleAll(); + + verify(sys).scheduleShowReminder(eq(timestamp(2015, 1, 27, 12, 30)), + eq(h1), anyLong()); + verify(sys).scheduleShowReminder(eq(timestamp(2015, 1, 26, 22, 30)), + eq(h2), anyLong()); + Mockito.verifyNoMoreInteractions(sys); + } + @Test public void testSchedule_atSpecificTime() { @@ -91,32 +99,9 @@ public class ReminderSchedulerTest extends BaseAndroidTest long expectedReminderTime = timestamp(2015, 1, 26, 12, 30); habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime); } - @Test - public void testScheduleAll() - { - long now = timestamp(2015, 1, 26, 13, 0); - DateUtils.setFixedLocalTime(now); - - fixtures.purgeHabits(habitList); - Habit h1 = fixtures.createEmptyHabit(); - Habit h2 = fixtures.createEmptyHabit(); - Habit h3 = fixtures.createEmptyHabit(); - h1.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - h2.setReminder(new Reminder(18, 30, WeekdayList.EVERY_DAY)); - h3.setReminder(null); - habitList.update(asList(h1, h2, h3)); - - reminderScheduler.scheduleAll(); - - verify(intentScheduler).schedule(eq(timestamp(2015, 1, 27, 12, 30)), any()); - verify(intentScheduler).schedule(eq(timestamp(2015, 1, 26, 22, 30)), any()); - verifyNoMoreInteractions(intentScheduler); - } - @Test public void testSchedule_tomorrow() { @@ -134,7 +119,7 @@ public class ReminderSchedulerTest extends BaseAndroidTest public void testSchedule_withoutReminder() { reminderScheduler.schedule(habit, null); - verifyZeroInteractions(intentScheduler); + Mockito.verifyZeroInteractions(sys); } public long timestamp(int year, int month, int day, int hour, int minute) @@ -148,13 +133,8 @@ public class ReminderSchedulerTest extends BaseAndroidTest long expectedCheckmarkTime, long expectedReminderTime) { - PendingIntent intent = - pendingIntentFactory.showReminder(habit, expectedReminderTime, - expectedCheckmarkTime); - reminderScheduler.schedule(habit, atTime); - - verify(logger).logReminderScheduled(habit, expectedReminderTime); - verify(intentScheduler).schedule(expectedReminderTime, intent); + verify(sys).scheduleShowReminder(expectedReminderTime, habit, + expectedCheckmarkTime); } }