From d6dacfd24b3b95cbf83ad9f65c4a6c783a5b7a84 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 20 Jul 2016 17:01:24 -0400 Subject: [PATCH] Refactor reminder scheduling, add tests --- .../org/isoron/uhabits/BaseAndroidTest.java | 8 + .../org/isoron/uhabits/HabitLoggerTest.java | 66 +++++++++ .../isoron/uhabits/HabitsApplicationTest.java | 2 +- .../org/isoron/uhabits/AndroidModule.java | 20 +++ .../org/isoron/uhabits/BaseComponent.java | 3 + .../java/org/isoron/uhabits/HabitLogger.java | 45 ++++++ .../org/isoron/uhabits/HabitsApplication.java | 27 ++-- .../IntentFactory.java} | 53 +++++-- .../{receivers => intents}/IntentParser.java | 4 +- .../uhabits/intents/IntentScheduler.java | 55 +++++++ .../java/org/isoron/uhabits/models/Habit.java | 3 +- .../org/isoron/uhabits/models/Reminder.java | 4 +- .../uhabits/receivers/ReminderReceiver.java | 98 ++++++------ .../uhabits/receivers/WidgetReceiver.java | 1 + .../org/isoron/uhabits/ui/BaseSystem.java | 93 ++++++------ .../ui/habits/list/ListHabitsController.java | 2 +- .../ui/habits/show/views/ScoreCard.java | 2 +- .../uhabits/ui/settings/SettingsFragment.java | 125 ++++++++-------- .../uhabits/ui/widgets/CheckmarkWidget.java | 4 +- .../uhabits/ui/widgets/FrequencyWidget.java | 4 +- .../uhabits/ui/widgets/HabitPickerDialog.java | 2 +- .../uhabits/ui/widgets/HistoryWidget.java | 4 +- .../uhabits/ui/widgets/ScoreWidget.java | 4 +- .../uhabits/ui/widgets/StreakWidget.java | 4 +- .../uhabits/utils/ReminderScheduler.java | 86 +++++++++++ ...{ReminderUtils.java => RingtoneUtils.java} | 75 +--------- .../java/org/isoron/uhabits/BaseUnitTest.java | 7 +- .../java/org/isoron/uhabits/TestModule.java | 14 ++ .../uhabits/utils/ReminderSchedulerTest.java | 139 ++++++++++++++++++ 29 files changed, 675 insertions(+), 279 deletions(-) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/HabitLoggerTest.java create mode 100644 app/src/main/java/org/isoron/uhabits/HabitLogger.java rename app/src/main/java/org/isoron/uhabits/{receivers/PendingIntentFactory.java => intents/IntentFactory.java} (62%) rename app/src/main/java/org/isoron/uhabits/{receivers => intents}/IntentParser.java (96%) create mode 100644 app/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java create mode 100644 app/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java rename app/src/main/java/org/isoron/uhabits/utils/{ReminderUtils.java => RingtoneUtils.java} (58%) create mode 100644 app/src/test/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 8a8ba0a23..0bb843323 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -24,12 +24,15 @@ import android.content.*; import android.os.*; import android.support.annotation.*; import android.support.test.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; import org.isoron.uhabits.commands.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import org.junit.*; +import org.junit.runner.*; import java.util.*; import java.util.concurrent.*; @@ -40,6 +43,8 @@ import static junit.framework.Assert.*; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; +@RunWith(AndroidJUnit4.class) +@MediumTest public class BaseAndroidTest { // 8:00am, January 25th, 2015 (UTC) @@ -60,6 +65,9 @@ public class BaseAndroidTest @Inject protected CommandRunner commandRunner; + @Inject + protected HabitLogger logger; + protected AndroidTestComponent androidTestComponent; protected HabitFixtures fixtures; diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitLoggerTest.java b/app/src/androidTest/java/org/isoron/uhabits/HabitLoggerTest.java new file mode 100644 index 000000000..39234dc70 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitLoggerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits; + +import android.os.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.*; +import org.junit.*; +import org.junit.runner.*; + +import java.io.*; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@RunWith(AndroidJUnit4.class) +@MediumTest +public class HabitLoggerTest extends BaseAndroidTest +{ + @Test + public void testLogReminderScheduled() throws IOException + { + if (!isLogcatAvailable()) return; + + long time = 1422277200000L; // 13:00 jan 26, 2015 (UTC) + Habit habit = fixtures.createEmptyHabit(); + habit.setName("Write journal"); + + logger.logReminderScheduled(habit, time); + + String expectedMsg = "Setting alarm (2015-01-26 130000): Wri\n"; + assertLogcatContains(expectedMsg); + } + + protected void assertLogcatContains(String expectedMsg) throws IOException + { + BaseSystem system = new BaseSystem(targetContext); + String logcat = system.getLogcat(); + assertThat(logcat, containsString(expectedMsg)); + } + + protected boolean isLogcatAvailable() + { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java index 3807cbcd4..b5a60751f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java @@ -33,7 +33,7 @@ import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; @RunWith(AndroidJUnit4.class) -@SmallTest +@MediumTest public class HabitsApplicationTest extends BaseAndroidTest { @Test diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java index 3a371d2e8..06deb46d4 100644 --- a/app/src/main/java/org/isoron/uhabits/AndroidModule.java +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -19,7 +19,10 @@ package org.isoron.uhabits; +import android.content.*; + import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.*; import org.isoron.uhabits.utils.*; @@ -70,4 +73,21 @@ public class AndroidModule { return new WidgetPreferences(); } + + @Provides + @Singleton + ReminderScheduler provideReminderScheduler() + { + Context context = HabitsApplication.getContext(); + IntentScheduler intentScheduler = new IntentScheduler(context); + IntentFactory intentFactory = new IntentFactory(context); + return new ReminderScheduler(intentFactory, intentScheduler); + } + + @Provides + @Singleton + HabitLogger provideLogger() + { + return new HabitLogger(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/BaseComponent.java b/app/src/main/java/org/isoron/uhabits/BaseComponent.java index 48b394fed..d5cb21b29 100644 --- a/app/src/main/java/org/isoron/uhabits/BaseComponent.java +++ b/app/src/main/java/org/isoron/uhabits/BaseComponent.java @@ -32,6 +32,7 @@ import org.isoron.uhabits.ui.habits.list.model.*; import org.isoron.uhabits.ui.habits.list.views.*; import org.isoron.uhabits.ui.habits.show.*; import org.isoron.uhabits.ui.widgets.*; +import org.isoron.uhabits.utils.*; import org.isoron.uhabits.widgets.*; /** @@ -106,4 +107,6 @@ public interface BaseComponent void inject(ReceiverActions receiverActions); void inject(ReminderReceiver reminderReceiver); + + void inject(ReminderScheduler reminderScheduler); } diff --git a/app/src/main/java/org/isoron/uhabits/HabitLogger.java b/app/src/main/java/org/isoron/uhabits/HabitLogger.java new file mode 100644 index 000000000..752a33401 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/HabitLogger.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits; + +import android.support.annotation.*; +import android.util.*; + +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; + +import java.text.*; +import java.util.*; + +public class HabitLogger +{ + public void logReminderScheduled(@NonNull Habit habit, + @NonNull Long reminderTime) + { + int min = Math.min(3, habit.getName().length()); + String name = habit.getName().substring(0, min); + + DateFormat df = DateUtils.getBackupDateFormat(); + String time = df.format(new Date(reminderTime)); + + Log.i("ReminderHelper", + String.format("Setting alarm (%s): %s", time, name)); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 53fb23d24..c16907b0a 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -51,7 +51,7 @@ public class HabitsApplication extends Application @Nullable private static Context context; - private static WidgetUpdater widgetManager; + private static WidgetUpdater widgetUpdater; public static BaseComponent getComponent() { @@ -63,9 +63,10 @@ public class HabitsApplication extends Application HabitsApplication.component = component; } - @Nullable + @NonNull public static Context getContext() { + if (context == null) throw new RuntimeException("context is null"); return context; } @@ -76,21 +77,23 @@ public class HabitsApplication extends Application } @NonNull - public static WidgetUpdater getWidgetManager() + public static WidgetUpdater getWidgetUpdater() { - if (widgetManager == null) - throw new RuntimeException("widgetManager is null"); + if (widgetUpdater == null) throw new RuntimeException( + "widgetUpdater is null"); - return widgetManager; + return widgetUpdater; } public static boolean isTestMode() { try { - if (context != null) context - .getClassLoader() - .loadClass("org.isoron.uhabits.BaseAndroidTest"); + if (context != null) + { + String testClass = "org.isoron.uhabits.BaseAndroidTest"; + context.getClassLoader().loadClass(testClass); + } return true; } catch (final Exception e) @@ -114,8 +117,8 @@ public class HabitsApplication extends Application if (db.exists()) db.delete(); } - widgetManager = new WidgetUpdater(this); - widgetManager.startListening(); + widgetUpdater = new WidgetUpdater(this); + widgetUpdater.startListening(); DatabaseUtils.initializeActiveAndroid(); } @@ -125,7 +128,7 @@ public class HabitsApplication extends Application { HabitsApplication.context = null; ActiveAndroid.dispose(); - widgetManager.stopListening(); + widgetUpdater.stopListening(); super.onTerminate(); } } diff --git a/app/src/main/java/org/isoron/uhabits/receivers/PendingIntentFactory.java b/app/src/main/java/org/isoron/uhabits/intents/IntentFactory.java similarity index 62% rename from app/src/main/java/org/isoron/uhabits/receivers/PendingIntentFactory.java rename to app/src/main/java/org/isoron/uhabits/intents/IntentFactory.java index 1cfc64e30..090e4e414 100644 --- a/app/src/main/java/org/isoron/uhabits/receivers/PendingIntentFactory.java +++ b/app/src/main/java/org/isoron/uhabits/intents/IntentFactory.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.receivers; +package org.isoron.uhabits.intents; import android.app.*; import android.content.*; @@ -25,21 +25,23 @@ import android.net.*; import android.support.annotation.*; import org.isoron.uhabits.models.*; +import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.habits.show.*; -public class PendingIntentFactory -{ - public static final String BASE_URL = "content://org.isoron.uhabits/habit/"; +import static android.app.PendingIntent.*; +public class IntentFactory +{ @NonNull private final Context context; - public PendingIntentFactory(Context context) + public IntentFactory(@NonNull Context context) { this.context = context; } - public PendingIntent buildAddCheckmark(Habit habit, Long timestamp) + public PendingIntent buildAddCheckmark(@NonNull Habit habit, + @Nullable Long timestamp) { Uri data = habit.getUri(); Intent checkIntent = new Intent(context, WidgetReceiver.class); @@ -47,7 +49,7 @@ public class PendingIntentFactory checkIntent.setAction(WidgetReceiver.ACTION_ADD_REPETITION); if (timestamp != null) checkIntent.putExtra("timestamp", timestamp); return PendingIntent.getBroadcast(context, 1, checkIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + FLAG_UPDATE_CURRENT); } public PendingIntent buildDismissNotification() @@ -55,20 +57,37 @@ public class PendingIntentFactory Intent deleteIntent = new Intent(context, ReminderReceiver.class); deleteIntent.setAction(WidgetReceiver.ACTION_DISMISS_REMINDER); return PendingIntent.getBroadcast(context, 0, deleteIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + FLAG_UPDATE_CURRENT); } - public PendingIntent buildSnoozeNotification(Habit habit) + public PendingIntent buildShowReminder(@NonNull Habit habit, + @Nullable Long reminderTime, + long timestamp) + { + Uri uri = habit.getUri(); + + Intent intent = new Intent(context, ReminderReceiver.class); + intent.setAction(ReminderReceiver.ACTION_SHOW_REMINDER); + intent.setData(uri); + intent.putExtra("timestamp", timestamp); + intent.putExtra("reminderTime", reminderTime); + int reqCode = ((int) (habit.getId() % Integer.MAX_VALUE)) + 1; + return PendingIntent.getBroadcast(context, reqCode, intent, + FLAG_UPDATE_CURRENT); + } + + public PendingIntent buildSnoozeNotification(@NonNull Habit habit) { Uri data = habit.getUri(); Intent snoozeIntent = new Intent(context, ReminderReceiver.class); snoozeIntent.setData(data); snoozeIntent.setAction(ReminderReceiver.ACTION_SNOOZE_REMINDER); return PendingIntent.getBroadcast(context, 0, snoozeIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + FLAG_UPDATE_CURRENT); } - public PendingIntent buildToggleCheckmark(Habit habit, Long timestamp) + public PendingIntent buildToggleCheckmark(@NonNull Habit habit, + @Nullable Long timestamp) { Uri data = habit.getUri(); Intent checkIntent = new Intent(context, WidgetReceiver.class); @@ -76,16 +95,18 @@ public class PendingIntentFactory checkIntent.setAction(WidgetReceiver.ACTION_TOGGLE_REPETITION); if (timestamp != null) checkIntent.putExtra("timestamp", timestamp); return PendingIntent.getBroadcast(context, 2, checkIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + FLAG_UPDATE_CURRENT); } public PendingIntent buildViewHabit(Habit habit) { + Uri uri = habit.getUri(); + Intent intent = new Intent(context, ShowHabitActivity.class); - intent.setData(Uri.parse(BASE_URL + habit.getId())); + intent.setData(uri); return android.support.v4.app.TaskStackBuilder - .create(context.getApplicationContext()) - .addNextIntentWithParentStack(intent) - .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + .create(context) + .addNextIntentWithParentStack(intent) + .getPendingIntent(0, FLAG_UPDATE_CURRENT); } } diff --git a/app/src/main/java/org/isoron/uhabits/receivers/IntentParser.java b/app/src/main/java/org/isoron/uhabits/intents/IntentParser.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/receivers/IntentParser.java rename to app/src/main/java/org/isoron/uhabits/intents/IntentParser.java index ac8013fcc..1abd2155d 100644 --- a/app/src/main/java/org/isoron/uhabits/receivers/IntentParser.java +++ b/app/src/main/java/org/isoron/uhabits/intents/IntentParser.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.receivers; +package org.isoron.uhabits.intents; import android.content.*; import android.net.*; @@ -70,7 +70,7 @@ public class IntentParser return timestamp; } - class CheckmarkIntentData + public class CheckmarkIntentData { public Habit habit; diff --git a/app/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java b/app/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java new file mode 100644 index 000000000..5bcca76e7 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/intents/IntentScheduler.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.intents; + +import android.app.*; +import android.content.*; +import android.os.*; +import android.support.annotation.*; + +import static android.app.AlarmManager.*; +import static android.content.Context.*; + +public class IntentScheduler +{ + private final AlarmManager manager; + + public IntentScheduler(@NonNull Context context) + { + manager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + } + + public void schedule(@NonNull Long timestamp, PendingIntent intent) + { + if (Build.VERSION.SDK_INT >= 23) + { + manager.setExactAndAllowWhileIdle(RTC_WAKEUP, timestamp, intent); + return; + } + + if (Build.VERSION.SDK_INT >= 19) + { + manager.setExact(RTC_WAKEUP, timestamp, intent); + return; + } + + manager.set(RTC_WAKEUP, timestamp, intent); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 4e5696e43..9c8713f3f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -115,8 +115,7 @@ public class Habit } /** - * Clears the reminder for a habit. This sets all the related fields to - * null. + * Clears the reminder for a habit. */ public void clearReminder() { diff --git a/app/src/main/java/org/isoron/uhabits/models/Reminder.java b/app/src/main/java/org/isoron/uhabits/models/Reminder.java index 2377113c3..a0af9661b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Reminder.java +++ b/app/src/main/java/org/isoron/uhabits/models/Reminder.java @@ -38,8 +38,8 @@ public final class Reminder * Returns the days of the week the reminder should be shown. *

* This field can be converted to a list of booleans using the method - * DateHelper.unpackWeekdayList and converted back to an integer by using - * the method DateHelper.packWeekdayList. + * DateUtils.unpackWeekdayList and converted back to an integer by using the + * method DateUtils.packWeekdayList. */ public int getDays() { diff --git a/app/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java b/app/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java index 25bc3d515..1808fe376 100644 --- a/app/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java @@ -29,6 +29,7 @@ import android.support.v4.app.*; import android.util.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; @@ -45,19 +46,22 @@ import javax.inject.*; public class ReminderReceiver extends BroadcastReceiver { public static final String ACTION_DISMISS_REMINDER = - "org.isoron.uhabits.ACTION_DISMISS_REMINDER"; + "org.isoron.uhabits.ACTION_DISMISS_REMINDER"; public static final String ACTION_SHOW_REMINDER = - "org.isoron.uhabits.ACTION_SHOW_REMINDER"; + "org.isoron.uhabits.ACTION_SHOW_REMINDER"; public static final String ACTION_SNOOZE_REMINDER = - "org.isoron.uhabits.ACTION_SNOOZE_REMINDER"; + "org.isoron.uhabits.ACTION_SNOOZE_REMINDER"; private static final String TAG = "ReminderReceiver"; @Inject HabitList habits; + @Inject + ReminderScheduler reminderScheduler; + public ReminderReceiver() { super(); @@ -86,7 +90,7 @@ public class ReminderReceiver extends BroadcastReceiver break; case Intent.ACTION_BOOT_COMPLETED: - onActionBootCompleted(context); + onActionBootCompleted(); break; } } @@ -96,25 +100,25 @@ public class ReminderReceiver extends BroadcastReceiver } } - protected void onActionBootCompleted(Context context) + protected void onActionBootCompleted() { - ReminderUtils.createReminderAlarms(context, habits); + reminderScheduler.schedule(habits); } protected void onActionShowReminder(Context context, Intent intent) { createNotification(context, intent); - createReminderAlarmsDelayed(context); + createReminderAlarmsDelayed(); } private void createNotification(final Context context, final Intent intent) { final Uri data = intent.getData(); final Habit habit = habits.getById(ContentUris.parseId(data)); - final Long timestamp = - intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); - final Long reminderTime = - intent.getLongExtra("reminderTime", DateUtils.getStartOfToday()); + final Long timestamp = intent.getLongExtra("timestamp", + DateUtils.getStartOfToday()); + final Long reminderTime = intent.getLongExtra("reminderTime", + DateUtils.getStartOfToday()); if (habit == null) return; @@ -137,40 +141,40 @@ public class ReminderReceiver extends BroadcastReceiver Intent contentIntent = new Intent(context, MainActivity.class); contentIntent.setData(data); - PendingIntent contentPendingIntent = - PendingIntent.getActivity(context, 0, contentIntent, + PendingIntent contentPendingIntent = PendingIntent.getActivity( + context, 0, contentIntent, PendingIntent.FLAG_CANCEL_CURRENT); - PendingIntentFactory intentFactory = - new PendingIntentFactory(context); + IntentFactory intentFactory = new IntentFactory(context); PendingIntent dismissPendingIntent; dismissPendingIntent = intentFactory.buildDismissNotification(); PendingIntent checkIntentPending = - intentFactory.buildAddCheckmark(habit, timestamp); + intentFactory.buildAddCheckmark(habit, timestamp); PendingIntent snoozeIntentPending = - intentFactory.buildSnoozeNotification(habit); + intentFactory.buildSnoozeNotification(habit); - Uri ringtoneUri = ReminderUtils.getRingtoneUri(context); + Uri ringtoneUri = RingtoneUtils.getRingtoneUri(context); NotificationCompat.WearableExtender wearableExtender = - new NotificationCompat.WearableExtender().setBackground( - BitmapFactory.decodeResource(context.getResources(), - R.drawable.stripe)); + new NotificationCompat.WearableExtender().setBackground( + BitmapFactory.decodeResource( + context.getResources(), + R.drawable.stripe)); - Notification notification = - new NotificationCompat.Builder(context) + Notification notification = new NotificationCompat.Builder( + context) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(habit.getName()) .setContentText(habit.getDescription()) .setContentIntent(contentPendingIntent) .setDeleteIntent(dismissPendingIntent) .addAction(R.drawable.ic_action_check, - context.getString(R.string.check), - checkIntentPending) + context.getString(R.string.check), + checkIntentPending) .addAction(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), - snoozeIntentPending) + context.getString(R.string.snooze), + snoozeIntentPending) .setSound(ringtoneUri) .extend(wearableExtender) .setWhen(reminderTime) @@ -180,8 +184,8 @@ public class ReminderReceiver extends BroadcastReceiver notification.flags |= Notification.FLAG_AUTO_CANCEL; NotificationManager notificationManager = - (NotificationManager) context.getSystemService( - Activity.NOTIFICATION_SERVICE); + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); int notificationId = (int) (habit.getId() % Integer.MAX_VALUE); notificationManager.notify(notificationId, notification); @@ -191,17 +195,18 @@ public class ReminderReceiver extends BroadcastReceiver }.execute(); } - private void createReminderAlarmsDelayed(final Context context) + private void createReminderAlarmsDelayed() { - new Handler().postDelayed( - () -> ReminderUtils.createReminderAlarms(context, habits), 5000); + new Handler().postDelayed(() -> { + reminderScheduler.schedule(habits); + }, 5000); } private void dismissNotification(Context context, Long habitId) { NotificationManager notificationManager = - (NotificationManager) context.getSystemService( - Activity.NOTIFICATION_SERVICE); + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); int notificationId = (int) (habitId % Integer.MAX_VALUE); notificationManager.cancel(notificationId); @@ -210,15 +215,20 @@ public class ReminderReceiver extends BroadcastReceiver private void onActionSnoozeReminder(Context context, Intent intent) { Uri data = intent.getData(); - SharedPreferences prefs = - PreferenceManager.getDefaultSharedPreferences(context); - long delayMinutes = - Long.parseLong(prefs.getString("pref_snooze_interval", "15")); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( + context); + long delayMinutes = Long.parseLong( + prefs.getString("pref_snooze_interval", "15")); long habitId = ContentUris.parseId(data); Habit habit = habits.getById(habitId); - if (habit != null) ReminderUtils.createReminderAlarm(context, habit, - new Date().getTime() + delayMinutes * 60 * 1000); + + if (habit != null) + { + long reminderTime = new Date().getTime() + delayMinutes * 60 * 1000; + reminderScheduler.schedule(habit, reminderTime); + } + dismissNotification(context, habitId); } @@ -227,11 +237,11 @@ public class ReminderReceiver extends BroadcastReceiver if (!habit.hasReminder()) return false; Reminder reminder = habit.getReminder(); - Long timestamp = - intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); + Long timestamp = intent.getLongExtra("timestamp", + DateUtils.getStartOfToday()); - boolean reminderDays[] = - DateUtils.unpackWeekdayList(reminder.getDays()); + boolean reminderDays[] = DateUtils.unpackWeekdayList( + reminder.getDays()); int weekday = DateUtils.getWeekday(timestamp); return reminderDays[weekday]; diff --git a/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java b/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java index 32adbe88f..2f5f6c1b0 100644 --- a/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java @@ -24,6 +24,7 @@ import android.support.annotation.*; import android.util.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; import javax.inject.*; diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java index ec39c0270..f946915c5 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -50,6 +50,9 @@ public class BaseSystem @Inject HabitList habitList; + @Inject + ReminderScheduler reminderScheduler; + public BaseSystem(Context context) { this.context = context; @@ -69,16 +72,16 @@ public class BaseSystem @NonNull public File dumpBugReportToFile() throws IOException { - String date = - DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); + String date = DateUtils.getBackupDateFormat().format( + DateUtils.getLocalTime()); if (context == null) throw new RuntimeException( - "application context should not be null"); + "application context should not be null"); File dir = FileUtils.getFilesDir("Logs"); if (dir == null) throw new IOException("log dir should not be null"); - File logFile = - new File(String.format("%s/Log %s.txt", dir.getPath(), date)); + File logFile = new File( + String.format("%s/Log %s.txt", dir.getPath(), date)); FileWriter output = new FileWriter(logFile); output.write(getBugReport()); output.close(); @@ -102,54 +105,12 @@ public class BaseSystem return deviceInfo + "\n" + logcat; } - /** - * Recreates all application reminders. - */ - public void scheduleReminders() - { - new BaseTask() - { - - @Override - protected void doInBackground() - { - ReminderUtils.createReminderAlarms(context, habitList); - } - }.execute(); - } - - private String getDeviceInfo() - { - if (context == null) return "null context\n"; - - WindowManager wm = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - return - String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME) + - String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE) + - String.format("OS Version: %s (%s)\n", - System.getProperty("os.version"), Build.VERSION.INCREMENTAL) + - String.format("OS API Level: %s\n", Build.VERSION.SDK) + - String.format("Device: %s\n", Build.DEVICE) + - String.format("Model (Product): %s (%s)\n", Build.MODEL, - Build.PRODUCT) + - String.format("Manufacturer: %s\n", Build.MANUFACTURER) + - String.format("Other tags: %s\n", Build.TAGS) + - String.format("Screen Width: %s\n", - wm.getDefaultDisplay().getWidth()) + - String.format("Screen Height: %s\n", - wm.getDefaultDisplay().getHeight()) + - String.format("External storage state: %s\n\n", - Environment.getExternalStorageState()); - } - public String getLogcat() throws IOException { int maxLineCount = 250; StringBuilder builder = new StringBuilder(); - String[] command = new String[]{"logcat", "-d"}; + String[] command = new String[]{ "logcat", "-d" }; Process process = Runtime.getRuntime().exec(command); InputStreamReader in = new InputStreamReader(process.getInputStream()); @@ -172,4 +133,40 @@ public class BaseSystem return builder.toString(); } + + /** + * Recreates all application reminders. + */ + public void scheduleReminders() + { + new SimpleTask(() -> reminderScheduler.schedule(habitList)).execute(); + } + + private String getDeviceInfo() + { + if (context == null) return "null context\n"; + + WindowManager wm = (WindowManager) context.getSystemService( + Context.WINDOW_SERVICE); + + return String.format("App Version Name: %s\n", + BuildConfig.VERSION_NAME) + + String.format("App Version Code: %s\n", + BuildConfig.VERSION_CODE) + + String.format("OS Version: %s (%s)\n", + System.getProperty("os.version"), + Build.VERSION.INCREMENTAL) + + String.format("OS API Level: %s\n", Build.VERSION.SDK) + + String.format("Device: %s\n", Build.DEVICE) + + String.format("Model (Product): %s (%s)\n", Build.MODEL, + Build.PRODUCT) + + String.format("Manufacturer: %s\n", Build.MANUFACTURER) + + String.format("Other tags: %s\n", Build.TAGS) + + String.format("Screen Width: %s\n", + wm.getDefaultDisplay().getWidth()) + + String.format("Screen Height: %s\n", + wm.getDefaultDisplay().getHeight()) + + String.format("External storage state: %s\n\n", + Environment.getExternalStorageState()); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index 1bb34973d..ded3f4b8d 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -176,7 +176,7 @@ public class ListHabitsController new Handler().postDelayed(() -> { system.scheduleReminders(); - HabitsApplication.getWidgetManager().updateWidgets(); + HabitsApplication.getWidgetUpdater().updateWidgets(); }, 1000); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java index b11a58571..7d85b3d05 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java @@ -79,7 +79,7 @@ public class ScoreCard extends HabitCard public void onItemSelected(int position) { setBucketSizeFromPosition(position); - HabitsApplication.getWidgetManager().updateWidgets(); + HabitsApplication.getWidgetUpdater().updateWidgets(); refreshData(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index 40bcd8c96..3dd3d6b64 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -19,38 +19,46 @@ package org.isoron.uhabits.ui.settings; -import android.app.backup.BackupManager; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceCategory; -import android.support.v7.preference.PreferenceFragmentCompat; - -import org.isoron.uhabits.HabitsApplication; +import android.app.backup.*; +import android.content.*; +import android.os.*; +import android.support.v7.preference.*; + import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.utils.*; + +import static org.isoron.uhabits.HabitsApplication.*; public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { private static int RINGTONE_REQUEST_CODE = 1; + private SharedPreferences prefs; + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + if (requestCode == RINGTONE_REQUEST_CODE) + { + RingtoneUtils.parseRingtoneData(getContext(), data); + updateRingtoneDescription(); + return; + } + + super.onActivityResult(requestCode, resultCode, data); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - setResultOnPreferenceClick("importData", - HabitsApplication.RESULT_IMPORT_DATA); - setResultOnPreferenceClick("exportCSV", - HabitsApplication.RESULT_EXPORT_CSV); - setResultOnPreferenceClick("exportDB", - HabitsApplication.RESULT_EXPORT_DB); - setResultOnPreferenceClick("bugReport", - HabitsApplication.RESULT_BUG_REPORT); + setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA); + setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV); + setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB); + setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT); updateRingtoneDescription(); @@ -61,41 +69,38 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public void onCreatePreferences(Bundle bundle, String s) { - + // NOP } - private void removePreference(String preferenceKey, String categoryKey) + @Override + public void onPause() { - PreferenceCategory cat = - (PreferenceCategory) findPreference(categoryKey); - Preference pref = findPreference(preferenceKey); - cat.removePreference(pref); + prefs.unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); } - private void setResultOnPreferenceClick(String key, final int result) + @Override + public boolean onPreferenceTreeClick(Preference preference) { - Preference pref = findPreference(key); - pref.setOnPreferenceClickListener(preference -> { - getActivity().setResult(result); - getActivity().finish(); + String key = preference.getKey(); + if (key == null) return false; + + if (key.equals("reminderSound")) + { + RingtoneUtils.startRingtonePickerActivity(this, + RINGTONE_REQUEST_CODE); return true; - }); + } + + return super.onPreferenceTreeClick(preference); } @Override public void onResume() { super.onResume(); - getPreferenceManager().getSharedPreferences(). - registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() - { - getPreferenceManager().getSharedPreferences(). - unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); + prefs = getPreferenceManager().getSharedPreferences(); + prefs.registerOnSharedPreferenceChangeListener(this); } @Override @@ -105,38 +110,28 @@ public class SettingsFragment extends PreferenceFragmentCompat BackupManager.dataChanged("org.isoron.uhabits"); } - @Override - public boolean onPreferenceTreeClick(Preference preference) + private void removePreference(String preferenceKey, String categoryKey) { - if (preference.getKey() == null) return false; - - if (preference.getKey().equals("reminderSound")) - { - ReminderUtils.startRingtonePickerActivity(this, - RINGTONE_REQUEST_CODE); - return true; - } - - return super.onPreferenceTreeClick(preference); + PreferenceCategory cat = + (PreferenceCategory) findPreference(categoryKey); + Preference pref = findPreference(preferenceKey); + cat.removePreference(pref); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) + private void setResultOnPreferenceClick(String key, final int result) { - if (requestCode == RINGTONE_REQUEST_CODE) - { - ReminderUtils.parseRingtoneData(getContext(), data); - updateRingtoneDescription(); - return; - } - - super.onActivityResult(requestCode, resultCode, data); + Preference pref = findPreference(key); + pref.setOnPreferenceClickListener(preference -> { + getActivity().setResult(result); + getActivity().finish(); + return true; + }); } private void updateRingtoneDescription() { - String ringtoneName = ReminderUtils.getRingtoneName(getContext()); - if(ringtoneName == null) return; + String ringtoneName = RingtoneUtils.getRingtoneName(getContext()); + if (ringtoneName == null) return; Preference ringtonePreference = findPreference("reminderSound"); ringtonePreference.setSummary(ringtoneName); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/CheckmarkWidget.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/CheckmarkWidget.java index 9f0372f1e..6fcfb1133 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/CheckmarkWidget.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/CheckmarkWidget.java @@ -24,8 +24,8 @@ import android.content.*; import android.support.annotation.*; import android.view.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.utils.*; @@ -45,7 +45,7 @@ public class CheckmarkWidget extends BaseWidget @Override public PendingIntent getOnClickPendingIntent(Context context) { - PendingIntentFactory factory = new PendingIntentFactory(context); + IntentFactory factory = new IntentFactory(context); return factory.buildToggleCheckmark(habit, null); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/FrequencyWidget.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/FrequencyWidget.java index 9b83806b6..113b01d95 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/FrequencyWidget.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/FrequencyWidget.java @@ -24,8 +24,8 @@ import android.content.*; import android.support.annotation.*; import android.view.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.utils.*; @@ -46,7 +46,7 @@ public class FrequencyWidget extends BaseWidget @Override public PendingIntent getOnClickPendingIntent(Context context) { - PendingIntentFactory factory = new PendingIntentFactory(context); + IntentFactory factory = new IntentFactory(context); return factory.buildViewHabit(habit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/HabitPickerDialog.java index fe8bab08e..b596ef993 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/HabitPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/HabitPickerDialog.java @@ -56,7 +56,7 @@ public class HabitPickerDialog extends Activity { Long habitId = habitIds.get(position); preferences.addWidget(widgetId, habitId); - HabitsApplication.getWidgetManager().updateWidgets(); + HabitsApplication.getWidgetUpdater().updateWidgets(); Intent resultValue = new Intent(); resultValue.putExtra(EXTRA_APPWIDGET_ID, widgetId); diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/HistoryWidget.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/HistoryWidget.java index 9b5f63306..e34586cca 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/HistoryWidget.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/HistoryWidget.java @@ -24,8 +24,8 @@ import android.content.*; import android.support.annotation.*; import android.view.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.utils.*; @@ -44,7 +44,7 @@ public class HistoryWidget extends BaseWidget @Override public PendingIntent getOnClickPendingIntent(Context context) { - PendingIntentFactory factory = new PendingIntentFactory(context); + IntentFactory factory = new IntentFactory(context); return factory.buildViewHabit(habit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/ScoreWidget.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/ScoreWidget.java index 13b0ea565..fe11f3658 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/ScoreWidget.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/ScoreWidget.java @@ -24,8 +24,8 @@ import android.content.*; import android.support.annotation.*; import android.view.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.habits.show.views.*; import org.isoron.uhabits.ui.widgets.views.*; @@ -47,7 +47,7 @@ public class ScoreWidget extends BaseWidget @Override public PendingIntent getOnClickPendingIntent(Context context) { - PendingIntentFactory factory = new PendingIntentFactory(context); + IntentFactory factory = new IntentFactory(context); return factory.buildViewHabit(habit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/widgets/StreakWidget.java b/app/src/main/java/org/isoron/uhabits/ui/widgets/StreakWidget.java index 9f567eea3..4957867e6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/widgets/StreakWidget.java +++ b/app/src/main/java/org/isoron/uhabits/ui/widgets/StreakWidget.java @@ -25,8 +25,8 @@ import android.support.annotation.*; import android.view.*; import android.view.ViewGroup.*; +import org.isoron.uhabits.intents.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.utils.*; @@ -49,7 +49,7 @@ public class StreakWidget extends BaseWidget @Override public PendingIntent getOnClickPendingIntent(Context context) { - PendingIntentFactory factory = new PendingIntentFactory(context); + IntentFactory factory = new IntentFactory(context); return factory.buildViewHabit(habit); } diff --git a/app/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java b/app/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java new file mode 100644 index 000000000..19a6bbc82 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/utils/ReminderScheduler.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.utils; + +import android.app.*; +import android.support.annotation.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.intents.*; +import org.isoron.uhabits.models.*; + +import java.util.*; + +import javax.inject.*; + +import static org.isoron.uhabits.utils.DateUtils.*; + +public class ReminderScheduler +{ + private final IntentFactory intentFactory; + + private final IntentScheduler intentScheduler; + + @Inject + HabitLogger logger; + + public ReminderScheduler(IntentFactory intentFactory, + IntentScheduler intentScheduler) + { + this.intentFactory = intentFactory; + this.intentScheduler = intentScheduler; + HabitsApplication.getComponent().inject(this); + } + + public void schedule(@NonNull Habit habit, @Nullable Long reminderTime) + { + if (!habit.hasReminder()) return; + Reminder reminder = habit.getReminder(); + if (reminderTime == null) reminderTime = getReminderTime(reminder); + long timestamp = getStartOfDay(toLocalTime(reminderTime)); + + PendingIntent intent = intentFactory.buildShowReminder(habit, + reminderTime, timestamp); + intentScheduler.schedule(reminderTime, intent); + logger.logReminderScheduled(habit, reminderTime); + } + + public void schedule(@NonNull HabitList habits) + { + HabitList reminderHabits = habits.getFiltered(HabitMatcher.WITH_ALARM); + for (Habit habit : reminderHabits) + schedule(habit, null); + } + + @NonNull + private Long getReminderTime(@NonNull Reminder reminder) + { + Calendar calendar = DateUtils.getStartOfTodayCalendar(); + calendar.set(Calendar.HOUR_OF_DAY, reminder.getHour()); + calendar.set(Calendar.MINUTE, reminder.getMinute()); + calendar.set(Calendar.SECOND, 0); + Long time = calendar.getTimeInMillis(); + + if (DateUtils.getLocalTime() > time) + time += AlarmManager.INTERVAL_DAY; + + return time; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java b/app/src/main/java/org/isoron/uhabits/utils/RingtoneUtils.java similarity index 58% rename from app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java rename to app/src/main/java/org/isoron/uhabits/utils/RingtoneUtils.java index 633b1c553..96f8bce38 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/RingtoneUtils.java @@ -19,87 +19,18 @@ package org.isoron.uhabits.utils; -import android.app.*; import android.content.*; import android.media.*; import android.net.*; -import android.os.*; import android.preference.*; import android.provider.*; import android.support.annotation.*; -import android.support.v4.app.Fragment; -import android.util.*; +import android.support.v4.app.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.models.*; -import org.isoron.uhabits.receivers.*; -import java.text.*; -import java.util.*; - -public abstract class ReminderUtils +public abstract class RingtoneUtils { - public static void createReminderAlarm(Context context, - Habit habit, - @Nullable Long reminderTime) - { - if (!habit.hasReminder()) return; - Reminder reminder = habit.getReminder(); - - if (reminderTime == null) - { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.HOUR_OF_DAY, reminder.getHour()); - calendar.set(Calendar.MINUTE, reminder.getMinute()); - calendar.set(Calendar.SECOND, 0); - - reminderTime = calendar.getTimeInMillis(); - - if (System.currentTimeMillis() > reminderTime) - reminderTime += AlarmManager.INTERVAL_DAY; - } - - long timestamp = - DateUtils.getStartOfDay(DateUtils.toLocalTime(reminderTime)); - - Uri uri = habit.getUri(); - - Intent alarmIntent = new Intent(context, ReminderReceiver.class); - alarmIntent.setAction(ReminderReceiver.ACTION_SHOW_REMINDER); - alarmIntent.setData(uri); - alarmIntent.putExtra("timestamp", timestamp); - alarmIntent.putExtra("reminderTime", reminderTime); - - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, - ((int) (habit.getId() % Integer.MAX_VALUE)) + 1, alarmIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - AlarmManager manager = - (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - - if (Build.VERSION.SDK_INT >= 23) - manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, - reminderTime, pendingIntent); - else if (Build.VERSION.SDK_INT >= 19) - manager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, - pendingIntent); - else manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent); - - String name = - habit.getName().substring(0, Math.min(3, habit.getName().length())); - Log.d("ReminderHelper", String.format("Setting alarm (%s): %s", - DateFormat.getDateTimeInstance().format(new Date(reminderTime)), - name)); - } - - public static void createReminderAlarms(Context context, HabitList habits) - { - HabitList reminderHabits = habits.getFiltered(HabitMatcher.WITH_ALARM); - for (Habit habit : reminderHabits) - createReminderAlarm(context, habit, null); - } - @Nullable public static String getRingtoneName(Context context) { @@ -174,7 +105,7 @@ public abstract class ReminderUtils int requestCode) { Uri existingRingtoneUri = - ReminderUtils.getRingtoneUri(fragment.getContext()); + getRingtoneUri(fragment.getContext()); Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); diff --git a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java index e286ca01a..ef2e6af0f 100644 --- a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java +++ b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -36,11 +36,14 @@ public class BaseUnitTest @Inject protected ModelFactory modelFactory; - protected TestComponent testComponent; - @Inject protected HabitList habitList; + @Inject + protected HabitLogger logger; + + protected TestComponent testComponent; + protected HabitFixtures fixtures; @Before diff --git a/app/src/test/java/org/isoron/uhabits/TestModule.java b/app/src/test/java/org/isoron/uhabits/TestModule.java index f93845f89..d470ce474 100644 --- a/app/src/test/java/org/isoron/uhabits/TestModule.java +++ b/app/src/test/java/org/isoron/uhabits/TestModule.java @@ -73,4 +73,18 @@ public class TestModule { return mock(WidgetPreferences.class); } + + @Provides + @Singleton + ReminderScheduler provideReminderScheduler() + { + return mock(ReminderScheduler.class); + } + + @Provides + @Singleton + HabitLogger provideLogger() + { + return mock(HabitLogger.class); + } } diff --git a/app/src/test/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java b/app/src/test/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java new file mode 100644 index 000000000..07cc76311 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/utils/ReminderSchedulerTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.utils; + +import android.app.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.intents.*; +import org.isoron.uhabits.models.*; +import org.junit.*; + +import static org.mockito.Mockito.*; + +@SuppressWarnings("JavaDoc") +public class ReminderSchedulerTest extends BaseUnitTest +{ + private IntentFactory intentFactory; + + private IntentScheduler intentScheduler; + + private ReminderScheduler scheduler; + + private Habit habit; + + private PendingIntent intent; + + @Before + @Override + public void setUp() + { + super.setUp(); + intentFactory = mock(IntentFactory.class); + intentScheduler = mock(IntentScheduler.class); + intent = mock(PendingIntent.class); + + scheduler = new ReminderScheduler(intentFactory, intentScheduler); + habit = fixtures.createEmptyHabit(); + } + + @Test + public void testSchedule_atSpecificTime() + { + long atTime = 1422617400000L; // 11:30 jan 30, 2015 (UTC) + long expectedCheckmarkTime = 1422576000000L; // 00:00 jan 27, 2015 (UTC) + + habit.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); + scheduleAndVerify(atTime, expectedCheckmarkTime, atTime); + } + + @Test + public void testSchedule_laterToday() + { + long now = 1422253800000L; // 06:30 jan 26, 2015 (UTC) + DateUtils.setFixedLocalTime(now); + + long expectedCheckmarkTime = 1422230400000L; // 00:00 jan 26, 2015 (UTC) + long expectedReminderTime = 1422261000000L; // 08:30 jan 26, 2015 (UTC) + + habit.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); + + scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime); + } + + @Test + public void testSchedule_list() + { + long now = 1422277200000L; // 13:00 jan 26, 2015 (UTC) + DateUtils.setFixedLocalTime(now); + + fixtures.purgeHabits(); + + Habit h1 = fixtures.createEmptyHabit(); + h1.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); + + Habit h2 = fixtures.createEmptyHabit(); + h2.setReminder(new Reminder(18, 30, DateUtils.ALL_WEEK_DAYS)); + + fixtures.createEmptyHabit(); + + scheduler.schedule(habitList); + + verify(intentScheduler).schedule(1422347400000L, null); + verify(intentScheduler).schedule(1422297000000L, null); + verifyNoMoreInteractions(intentScheduler); + } + + @Test + public void testSchedule_tomorrow() + { + long now = 1453813200000L; // 13:00 jan 26, 2016 (UTC) + DateUtils.setFixedLocalTime(now); + + long expectedCheckmarkTime = 1453852800000L; // 00:00 jan 27, 2016 (UTC) + long expectedReminderTime = 1453883400000L; // 08:30 jan 27, 2016 (UTC) + + habit.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); + scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime); + } + + @Test + public void testSchedule_withoutReminder() + { + scheduler.schedule(habit, null); + verifyZeroInteractions(intentScheduler); + } + + private void scheduleAndVerify(Long atTime, + long expectedCheckmarkTime, + long expectedReminderTime) + { + when(intentFactory.buildShowReminder(habit, expectedReminderTime, + expectedCheckmarkTime)).thenReturn(intent); + + scheduler.schedule(habit, atTime); + + verify(logger).logReminderScheduled(habit, expectedReminderTime); + + verify(intentFactory).buildShowReminder(habit, expectedReminderTime, + expectedCheckmarkTime); + verify(intentScheduler).schedule(expectedReminderTime, intent); + } +}