diff --git a/android/gradle.properties b/android/gradle.properties index 9fb12060f..6745c8011 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ -VERSION_CODE = 47 -VERSION_NAME = 1.8.4 +VERSION_CODE = 48 +VERSION_NAME = 1.8.5 MIN_SDK_VERSION = 21 TARGET_SDK_VERSION = 29 diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt index 8b0416cc1..bfe411cb6 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt @@ -61,7 +61,6 @@ class CheckmarkPanelViewTest : BaseViewTest() { @After public override fun tearDown() { -// view.onDetachedFromWindow() super.tearDown() } @@ -70,11 +69,12 @@ class CheckmarkPanelViewTest : BaseViewTest() { assertRenders(view, "$PATH/render.png") } - @Test - fun testRender_withDifferentColor() { - view.color = PaletteUtils.getAndroidTestColor(1) - assertRenders(view, "$PATH/render_different_color.png") - } +// // Flaky test +// @Test +// fun testRender_withDifferentColor() { +// view.color = PaletteUtils.getAndroidTestColor(1) +// assertRenders(view, "$PATH/render_different_color.png") +// } // // Flaky test // @Test diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt index 53a9aa5b1..616c00c4e 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -65,17 +65,19 @@ class NumberPanelViewTest : BaseViewTest() { assertRenders(view, "$PATH/render.png") } - @Test - fun testRender_withDifferentColor() { - view.color = PaletteUtils.getAndroidTestColor(1) - assertRenders(view, "$PATH/render_different_color.png") - } +// // Flaky test +// @Test +// fun testRender_withDifferentColor() { +// view.color = PaletteUtils.getAndroidTestColor(1) +// assertRenders(view, "$PATH/render_different_color.png") +// } - @Test - fun testRender_Reversed() { - prefs.isCheckmarkSequenceReversed = true - assertRenders(view, "$PATH/render_reversed.png") - } +// // Flaky test +// @Test +// fun testRender_Reversed() { +// prefs.isCheckmarkSequenceReversed = true +// assertRenders(view, "$PATH/render_reversed.png") +// } // // Flaky test // @Test diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.kt index adbb32a11..a1a813d51 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/HabitsModule.kt @@ -48,9 +48,10 @@ class HabitsModule { fun getReminderScheduler( sys: IntentScheduler, commandRunner: CommandRunner, - habitList: HabitList + habitList: HabitList, + widgetPreferences: WidgetPreferences ): ReminderScheduler { - return ReminderScheduler(commandRunner, habitList, sys) + return ReminderScheduler(commandRunner, habitList, sys, widgetPreferences) } @Provides diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java index 966450a6c..41bb7beb2 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java @@ -148,13 +148,9 @@ public class SettingsFragment extends PreferenceFragmentCompat updateWeekdayPreference(); - if (SDK_INT < Build.VERSION_CODES.O) - findPreference("reminderCustomize").setVisible(false); - else - { - findPreference("reminderSound").setVisible(false); - findPreference("pref_snooze_interval").setVisible(false); - } + // Temporarily disable this; we now always ask + findPreference("reminderSound").setVisible(false); + findPreference("pref_snooze_interval").setVisible(false); updateSync(); } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt index d3cd51e0a..0e91220ab 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt @@ -66,8 +66,8 @@ class AndroidNotificationTray timestamp: Timestamp, reminderTime: Long) { val notificationManager = NotificationManagerCompat.from(context) - val summary = buildSummary(habit, reminderTime) - notificationManager.notify(Int.MAX_VALUE, summary) + //val summary = buildSummary(habit, reminderTime) + //notificationManager.notify(Int.MAX_VALUE, summary) val notification = buildNotification(habit, reminderTime, timestamp) createAndroidNotificationChannel(context) try { @@ -132,13 +132,11 @@ class AndroidNotificationTray if (preferences.shouldMakeNotificationsLed()) builder.setLights(Color.RED, 1000, 1000) - if (SDK_INT < Build.VERSION_CODES.O) { - val snoozeAction = Action(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), - pendingIntents.snoozeNotification(habit)) - wearableExtender.addAction(snoozeAction) - builder.addAction(snoozeAction) - } + val snoozeAction = Action(R.drawable.ic_action_snooze, + context.getString(R.string.snooze), + pendingIntents.snoozeNotification(habit)) + wearableExtender.addAction(snoozeAction) + builder.addAction(snoozeAction) builder.extend(wearableExtender) return builder.build() diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java index 03d26a707..f4866960d 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java @@ -26,6 +26,9 @@ public class SnoozeDelayPickerActivity extends FragmentActivity private ReminderController reminderController; + @Nullable + private AlertDialog dialog; + @Override protected void onCreate(@Nullable Bundle bundle) { @@ -40,7 +43,7 @@ public class SnoozeDelayPickerActivity extends FragmentActivity if (habit == null) finish(); int theme = R.style.Theme_AppCompat_Light_Dialog_Alert; - AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(this, theme)) + dialog = new AlertDialog.Builder(new ContextThemeWrapper(this, theme)) .setTitle(R.string.select_snooze_delay) .setItems(R.array.snooze_picker_names, null) .create(); @@ -82,4 +85,11 @@ public class SnoozeDelayPickerActivity extends FragmentActivity super.finish(); overridePendingTransition(0, 0); } + + @Override + protected void onPause() + { + if (dialog != null) dialog.dismiss(); + super.onPause(); + } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java index 585857d4b..23ad67f4b 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java @@ -70,17 +70,13 @@ public class ReminderController public void onSnoozePressed(@NonNull Habit habit, final Context context) { - long delay = preferences.getSnoozeInterval(); - - if (delay < 0) - showSnoozeDelayPicker(habit, context); - else - scheduleReminderMinutesFromNow(habit, delay); + showSnoozeDelayPicker(habit, context); } - public void onSnoozeDelayPicked(Habit habit, int delay) + public void onSnoozeDelayPicked(Habit habit, int delayInMinutes) { - scheduleReminderMinutesFromNow(habit, delay); + reminderScheduler.snoozeReminder(habit, delayInMinutes); + notificationTray.cancel(habit); } public void onSnoozeTimePicked(Habit habit, int hour, int minute) @@ -95,12 +91,6 @@ public class ReminderController notificationTray.cancel(habit); } - private void scheduleReminderMinutesFromNow(Habit habit, long minutes) - { - reminderScheduler.scheduleMinutesFromNow(habit, minutes); - notificationTray.cancel(habit); - } - private void showSnoozeDelayPicker(@NonNull Habit habit, Context context) { context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); diff --git a/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt b/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt index 001be71d9..6669fe446 100644 --- a/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt +++ b/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt @@ -1,4 +1,4 @@ -1.8.4 +1.8.5 * Bugfixes 1.8: * New bar chart showing number of repetitions performed each week, month or year diff --git a/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java b/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java index b17bbace5..c95dbd8af 100644 --- a/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java +++ b/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java @@ -61,21 +61,6 @@ public class ReminderControllerTest extends BaseAndroidJVMTest verifyNoMoreInteractions(preferences); } - @Test - public void testOnSnooze() throws Exception - { - Habit habit = mock(Habit.class); - long now = timestamp(2015, 1, 1); - long nowTz = DateUtils.applyTimezone(now); - DateUtils.setFixedLocalTime(now); - when(preferences.getSnoozeInterval()).thenReturn(15L); - - controller.onSnoozePressed(habit,null); - - verify(reminderScheduler).scheduleMinutesFromNow(habit, 15L); - verify(notificationTray).cancel(habit); - } - @Test public void testOnShowReminder() throws Exception { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/WidgetPreferences.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/WidgetPreferences.java index 255170d41..86d96b60d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/WidgetPreferences.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/WidgetPreferences.java @@ -57,7 +57,27 @@ public class WidgetPreferences { storage.remove(habitIdKey); } + public long getSnoozeTime(long id) + { + return storage.getLong(getSnoozeKey(id), 0); + } + private String getHabitIdKey(int id) { return String.format("widget-%06d-habit", id); } + + private String getSnoozeKey(long id) + { + return String.format("snooze-%06d", id); + } + + public void removeSnoozeTime(long id) + { + storage.putLong(getSnoozeKey(id), 0); + } + + public void setSnoozeTime(Long id, long time) + { + storage.putLong(getSnoozeKey(id), time); + } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java index 67e3869c8..185aaec71 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java @@ -24,6 +24,7 @@ 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 java.util.*; @@ -34,6 +35,8 @@ import static org.isoron.uhabits.core.utils.DateUtils.*; @AppScope public class ReminderScheduler implements CommandRunner.Listener { + private final WidgetPreferences widgetPreferences; + private CommandRunner commandRunner; private HabitList habitList; @@ -43,43 +46,77 @@ public class ReminderScheduler implements CommandRunner.Listener @Inject public ReminderScheduler(@NonNull CommandRunner commandRunner, @NonNull HabitList habitList, - @NonNull SystemScheduler sys) + @NonNull SystemScheduler sys, + @NonNull WidgetPreferences widgetPreferences) { this.commandRunner = commandRunner; this.habitList = habitList; this.sys = sys; + this.widgetPreferences = widgetPreferences; } @Override - public void onCommandExecuted(@NonNull Command command, - @Nullable Long refreshKey) + public synchronized void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey) { if (command instanceof ToggleRepetitionCommand) return; if (command instanceof ChangeHabitColorCommand) return; scheduleAll(); } - public void schedule(@NonNull Habit habit) + public synchronized void schedule(@NonNull Habit habit) { - if (!habit.hasReminder()) { + if (habit.getId() == null) + { + sys.log("ReminderScheduler", "Habit has null id. Returning."); + return; + } + + if (!habit.hasReminder()) + { sys.log("ReminderScheduler", "habit=" + habit.id + " has no reminder. Skipping."); return; } long reminderTime = habit.getReminder().getTimeInMillis(); + long snoozeReminderTime = widgetPreferences.getSnoozeTime(habit.getId()); + + if (snoozeReminderTime != 0) + { + long now = applyTimezone(getLocalTime()); + sys.log("ReminderScheduler", String.format( + Locale.US, + "Habit %d has been snoozed until %d", + habit.getId(), + snoozeReminderTime)); + + if (snoozeReminderTime > now) + { + sys.log("ReminderScheduler", "Snooze time is in the future. Accepting."); + reminderTime = snoozeReminderTime; + } + else + { + sys.log("ReminderScheduler", "Snooze time is in the past. Discarding."); + widgetPreferences.removeSnoozeTime(habit.getId()); + } + } scheduleAtTime(habit, reminderTime); + } - public void scheduleAtTime(@NonNull Habit habit, long reminderTime) + public synchronized void scheduleAtTime(@NonNull Habit habit, long reminderTime) { sys.log("ReminderScheduler", "Scheduling alarm for habit=" + habit.id); - if (!habit.hasReminder()) { + if (!habit.hasReminder()) + { sys.log("ReminderScheduler", "habit=" + habit.id + " has no reminder. Skipping."); return; } - if (habit.isArchived()) { + if (habit.isArchived()) + { sys.log("ReminderScheduler", "habit=" + habit.id + " is archived. Skipping."); return; } @@ -100,26 +137,27 @@ public class ReminderScheduler implements CommandRunner.Listener { sys.log("ReminderScheduler", "Scheduling all alarms"); HabitList reminderHabits = - habitList.getFiltered(HabitMatcher.WITH_ALARM); + habitList.getFiltered(HabitMatcher.WITH_ALARM); for (Habit habit : reminderHabits) schedule(habit); } - public void startListening() + public synchronized void startListening() { commandRunner.addListener(this); } - public void stopListening() + public synchronized void stopListening() { commandRunner.removeListener(this); } - public void scheduleMinutesFromNow(Habit habit, long minutes) + public synchronized void snoozeReminder(Habit habit, long minutes) { long now = applyTimezone(getLocalTime()); - long reminderTime = now + minutes * 60 * 1000; - scheduleAtTime(habit, reminderTime); + long snoozedUntil = now + minutes * 60 * 1000; + widgetPreferences.setSnoozeTime(habit.getId(), snoozedUntil); + schedule(habit); } public interface SystemScheduler diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java index 5c098c8d9..4f8790c73 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.reminders; import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.utils.*; import org.junit.*; import org.junit.runner.*; @@ -29,11 +30,16 @@ import org.mockito.junit.*; import java.util.*; +import static org.isoron.uhabits.core.utils.DateUtils.applyTimezone; +import static org.isoron.uhabits.core.utils.DateUtils.removeTimezone; +import static org.isoron.uhabits.core.utils.DateUtils.setFixedLocalTime; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class ReminderSchedulerTest extends BaseUnitTest { + private final long habitId = 10L; + private Habit habit; private ReminderScheduler reminderScheduler; @@ -41,15 +47,19 @@ public class ReminderSchedulerTest extends BaseUnitTest @Mock private ReminderScheduler.SystemScheduler sys; + @Mock + private WidgetPreferences widgetPreferences; + @Before @Override public void setUp() throws Exception { super.setUp(); habit = fixtures.createEmptyHabit(); + habit.id = habitId; reminderScheduler = - new ReminderScheduler(commandRunner, habitList, sys); + new ReminderScheduler(commandRunner, habitList, sys, widgetPreferences); DateUtils.setFixedTimeZone(TimeZone.getTimeZone("GMT-4")); } @@ -58,7 +68,7 @@ public class ReminderSchedulerTest extends BaseUnitTest public void testScheduleAll() { long now = unixTime(2015, 1, 26, 13, 0); - DateUtils.setFixedLocalTime(now); + setFixedLocalTime(now); Habit h1 = fixtures.createEmptyHabit(); Habit h2 = fixtures.createEmptyHabit(); @@ -88,11 +98,33 @@ public class ReminderSchedulerTest extends BaseUnitTest scheduleAndVerify(atTime, expectedCheckmarkTime, atTime); } + @Test + public void testSchedule_withSnooze() + { + long now = removeTimezone(unixTime(2015, 1, 1, 15, 0)); + setFixedLocalTime(now); + + long snoozeTimeInFuture = unixTime(2015, 1, 1, 21, 0); + long snoozeTimeInPast = unixTime(2015, 1, 1, 7, 0); + long regularReminderTime = applyTimezone(unixTime(2015, 1, 2, 8, 30)); + long todayCheckmarkTime = unixTime(2015, 1, 1, 0, 0); + long tomorrowCheckmarkTime = unixTime(2015, 1, 2, 0, 0); + habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); + + when(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInFuture); + reminderScheduler.schedule(habit); + verify(sys).scheduleShowReminder(snoozeTimeInFuture, habit, todayCheckmarkTime); + + when(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInPast); + reminderScheduler.schedule(habit); + verify(sys).scheduleShowReminder(regularReminderTime, habit, tomorrowCheckmarkTime); + } + @Test public void testSchedule_laterToday() { long now = unixTime(2015, 1, 26, 6, 30); - DateUtils.setFixedLocalTime(now); + setFixedLocalTime(now); long expectedCheckmarkTime = unixTime(2015, 1, 26, 0, 0); long expectedReminderTime = unixTime(2015, 1, 26, 12, 30); @@ -105,7 +137,7 @@ public class ReminderSchedulerTest extends BaseUnitTest public void testSchedule_tomorrow() { long now = unixTime(2015, 1, 26, 13, 0); - DateUtils.setFixedLocalTime(now); + setFixedLocalTime(now); long expectedCheckmarkTime = unixTime(2015, 1, 27, 0, 0); long expectedReminderTime = unixTime(2015, 1, 27, 12, 30);