Refactor reminder scheduling, add tests

pull/151/head
Alinson S. Xavier 9 years ago
parent 3938ae6fa8
commit d6dacfd24b

@ -24,12 +24,15 @@ import android.content.*;
import android.os.*; import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.test.*; import android.support.test.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.uhabits.commands.*; import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
import org.junit.*; import org.junit.*;
import org.junit.runner.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -40,6 +43,8 @@ import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class BaseAndroidTest public class BaseAndroidTest
{ {
// 8:00am, January 25th, 2015 (UTC) // 8:00am, January 25th, 2015 (UTC)
@ -60,6 +65,9 @@ public class BaseAndroidTest
@Inject @Inject
protected CommandRunner commandRunner; protected CommandRunner commandRunner;
@Inject
protected HabitLogger logger;
protected AndroidTestComponent androidTestComponent; protected AndroidTestComponent androidTestComponent;
protected HabitFixtures fixtures; protected HabitFixtures fixtures;

@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

@ -33,7 +33,7 @@ import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @MediumTest
public class HabitsApplicationTest extends BaseAndroidTest public class HabitsApplicationTest extends BaseAndroidTest
{ {
@Test @Test

@ -19,7 +19,10 @@
package org.isoron.uhabits; package org.isoron.uhabits;
import android.content.*;
import org.isoron.uhabits.commands.*; import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.*; import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -70,4 +73,21 @@ public class AndroidModule
{ {
return new WidgetPreferences(); 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();
}
} }

@ -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.list.views.*;
import org.isoron.uhabits.ui.habits.show.*; import org.isoron.uhabits.ui.habits.show.*;
import org.isoron.uhabits.ui.widgets.*; import org.isoron.uhabits.ui.widgets.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*; import org.isoron.uhabits.widgets.*;
/** /**
@ -106,4 +107,6 @@ public interface BaseComponent
void inject(ReceiverActions receiverActions); void inject(ReceiverActions receiverActions);
void inject(ReminderReceiver reminderReceiver); void inject(ReminderReceiver reminderReceiver);
void inject(ReminderScheduler reminderScheduler);
} }

@ -0,0 +1,45 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

@ -51,7 +51,7 @@ public class HabitsApplication extends Application
@Nullable @Nullable
private static Context context; private static Context context;
private static WidgetUpdater widgetManager; private static WidgetUpdater widgetUpdater;
public static BaseComponent getComponent() public static BaseComponent getComponent()
{ {
@ -63,9 +63,10 @@ public class HabitsApplication extends Application
HabitsApplication.component = component; HabitsApplication.component = component;
} }
@Nullable @NonNull
public static Context getContext() public static Context getContext()
{ {
if (context == null) throw new RuntimeException("context is null");
return context; return context;
} }
@ -76,21 +77,23 @@ public class HabitsApplication extends Application
} }
@NonNull @NonNull
public static WidgetUpdater getWidgetManager() public static WidgetUpdater getWidgetUpdater()
{ {
if (widgetManager == null) if (widgetUpdater == null) throw new RuntimeException(
throw new RuntimeException("widgetManager is null"); "widgetUpdater is null");
return widgetManager; return widgetUpdater;
} }
public static boolean isTestMode() public static boolean isTestMode()
{ {
try try
{ {
if (context != null) context if (context != null)
.getClassLoader() {
.loadClass("org.isoron.uhabits.BaseAndroidTest"); String testClass = "org.isoron.uhabits.BaseAndroidTest";
context.getClassLoader().loadClass(testClass);
}
return true; return true;
} }
catch (final Exception e) catch (final Exception e)
@ -114,8 +117,8 @@ public class HabitsApplication extends Application
if (db.exists()) db.delete(); if (db.exists()) db.delete();
} }
widgetManager = new WidgetUpdater(this); widgetUpdater = new WidgetUpdater(this);
widgetManager.startListening(); widgetUpdater.startListening();
DatabaseUtils.initializeActiveAndroid(); DatabaseUtils.initializeActiveAndroid();
} }
@ -125,7 +128,7 @@ public class HabitsApplication extends Application
{ {
HabitsApplication.context = null; HabitsApplication.context = null;
ActiveAndroid.dispose(); ActiveAndroid.dispose();
widgetManager.stopListening(); widgetUpdater.stopListening();
super.onTerminate(); super.onTerminate();
} }
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.receivers; package org.isoron.uhabits.intents;
import android.app.*; import android.app.*;
import android.content.*; import android.content.*;
@ -25,21 +25,23 @@ import android.net.*;
import android.support.annotation.*; import android.support.annotation.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.habits.show.*; import org.isoron.uhabits.ui.habits.show.*;
public class PendingIntentFactory import static android.app.PendingIntent.*;
{
public static final String BASE_URL = "content://org.isoron.uhabits/habit/";
public class IntentFactory
{
@NonNull @NonNull
private final Context context; private final Context context;
public PendingIntentFactory(Context context) public IntentFactory(@NonNull Context context)
{ {
this.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(); Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class); Intent checkIntent = new Intent(context, WidgetReceiver.class);
@ -47,7 +49,7 @@ public class PendingIntentFactory
checkIntent.setAction(WidgetReceiver.ACTION_ADD_REPETITION); checkIntent.setAction(WidgetReceiver.ACTION_ADD_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp); if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 1, checkIntent, return PendingIntent.getBroadcast(context, 1, checkIntent,
PendingIntent.FLAG_UPDATE_CURRENT); FLAG_UPDATE_CURRENT);
} }
public PendingIntent buildDismissNotification() public PendingIntent buildDismissNotification()
@ -55,20 +57,37 @@ public class PendingIntentFactory
Intent deleteIntent = new Intent(context, ReminderReceiver.class); Intent deleteIntent = new Intent(context, ReminderReceiver.class);
deleteIntent.setAction(WidgetReceiver.ACTION_DISMISS_REMINDER); deleteIntent.setAction(WidgetReceiver.ACTION_DISMISS_REMINDER);
return PendingIntent.getBroadcast(context, 0, deleteIntent, 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(); Uri data = habit.getUri();
Intent snoozeIntent = new Intent(context, ReminderReceiver.class); Intent snoozeIntent = new Intent(context, ReminderReceiver.class);
snoozeIntent.setData(data); snoozeIntent.setData(data);
snoozeIntent.setAction(ReminderReceiver.ACTION_SNOOZE_REMINDER); snoozeIntent.setAction(ReminderReceiver.ACTION_SNOOZE_REMINDER);
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 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(); Uri data = habit.getUri();
Intent checkIntent = new Intent(context, WidgetReceiver.class); Intent checkIntent = new Intent(context, WidgetReceiver.class);
@ -76,16 +95,18 @@ public class PendingIntentFactory
checkIntent.setAction(WidgetReceiver.ACTION_TOGGLE_REPETITION); checkIntent.setAction(WidgetReceiver.ACTION_TOGGLE_REPETITION);
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp); if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 2, checkIntent, return PendingIntent.getBroadcast(context, 2, checkIntent,
PendingIntent.FLAG_UPDATE_CURRENT); FLAG_UPDATE_CURRENT);
} }
public PendingIntent buildViewHabit(Habit habit) public PendingIntent buildViewHabit(Habit habit)
{ {
Uri uri = habit.getUri();
Intent intent = new Intent(context, ShowHabitActivity.class); Intent intent = new Intent(context, ShowHabitActivity.class);
intent.setData(Uri.parse(BASE_URL + habit.getId())); intent.setData(uri);
return android.support.v4.app.TaskStackBuilder return android.support.v4.app.TaskStackBuilder
.create(context.getApplicationContext()) .create(context)
.addNextIntentWithParentStack(intent) .addNextIntentWithParentStack(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); .getPendingIntent(0, FLAG_UPDATE_CURRENT);
} }
} }

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.receivers; package org.isoron.uhabits.intents;
import android.content.*; import android.content.*;
import android.net.*; import android.net.*;
@ -70,7 +70,7 @@ public class IntentParser
return timestamp; return timestamp;
} }
class CheckmarkIntentData public class CheckmarkIntentData
{ {
public Habit habit; public Habit habit;

@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

@ -115,8 +115,7 @@ public class Habit
} }
/** /**
* Clears the reminder for a habit. This sets all the related fields to * Clears the reminder for a habit.
* null.
*/ */
public void clearReminder() public void clearReminder()
{ {

@ -38,8 +38,8 @@ public final class Reminder
* Returns the days of the week the reminder should be shown. * Returns the days of the week the reminder should be shown.
* <p> * <p>
* This field can be converted to a list of booleans using the method * This field can be converted to a list of booleans using the method
* DateHelper.unpackWeekdayList and converted back to an integer by using * DateUtils.unpackWeekdayList and converted back to an integer by using the
* the method DateHelper.packWeekdayList. * method DateUtils.packWeekdayList.
*/ */
public int getDays() public int getDays()
{ {

@ -29,6 +29,7 @@ import android.support.v4.app.*;
import android.util.*; import android.util.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -58,6 +59,9 @@ public class ReminderReceiver extends BroadcastReceiver
@Inject @Inject
HabitList habits; HabitList habits;
@Inject
ReminderScheduler reminderScheduler;
public ReminderReceiver() public ReminderReceiver()
{ {
super(); super();
@ -86,7 +90,7 @@ public class ReminderReceiver extends BroadcastReceiver
break; break;
case Intent.ACTION_BOOT_COMPLETED: case Intent.ACTION_BOOT_COMPLETED:
onActionBootCompleted(context); onActionBootCompleted();
break; 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) protected void onActionShowReminder(Context context, Intent intent)
{ {
createNotification(context, intent); createNotification(context, intent);
createReminderAlarmsDelayed(context); createReminderAlarmsDelayed();
} }
private void createNotification(final Context context, final Intent intent) private void createNotification(final Context context, final Intent intent)
{ {
final Uri data = intent.getData(); final Uri data = intent.getData();
final Habit habit = habits.getById(ContentUris.parseId(data)); final Habit habit = habits.getById(ContentUris.parseId(data));
final Long timestamp = final Long timestamp = intent.getLongExtra("timestamp",
intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); DateUtils.getStartOfToday());
final Long reminderTime = final Long reminderTime = intent.getLongExtra("reminderTime",
intent.getLongExtra("reminderTime", DateUtils.getStartOfToday()); DateUtils.getStartOfToday());
if (habit == null) return; if (habit == null) return;
@ -137,12 +141,11 @@ public class ReminderReceiver extends BroadcastReceiver
Intent contentIntent = new Intent(context, MainActivity.class); Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data); contentIntent.setData(data);
PendingIntent contentPendingIntent = PendingIntent contentPendingIntent = PendingIntent.getActivity(
PendingIntent.getActivity(context, 0, contentIntent, context, 0, contentIntent,
PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntentFactory intentFactory = IntentFactory intentFactory = new IntentFactory(context);
new PendingIntentFactory(context);
PendingIntent dismissPendingIntent; PendingIntent dismissPendingIntent;
dismissPendingIntent = intentFactory.buildDismissNotification(); dismissPendingIntent = intentFactory.buildDismissNotification();
@ -151,15 +154,16 @@ public class ReminderReceiver extends BroadcastReceiver
PendingIntent snoozeIntentPending = PendingIntent snoozeIntentPending =
intentFactory.buildSnoozeNotification(habit); intentFactory.buildSnoozeNotification(habit);
Uri ringtoneUri = ReminderUtils.getRingtoneUri(context); Uri ringtoneUri = RingtoneUtils.getRingtoneUri(context);
NotificationCompat.WearableExtender wearableExtender = NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground( new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(), BitmapFactory.decodeResource(
context.getResources(),
R.drawable.stripe)); R.drawable.stripe));
Notification notification = Notification notification = new NotificationCompat.Builder(
new NotificationCompat.Builder(context) context)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.getName()) .setContentTitle(habit.getName())
.setContentText(habit.getDescription()) .setContentText(habit.getDescription())
@ -191,10 +195,11 @@ public class ReminderReceiver extends BroadcastReceiver
}.execute(); }.execute();
} }
private void createReminderAlarmsDelayed(final Context context) private void createReminderAlarmsDelayed()
{ {
new Handler().postDelayed( new Handler().postDelayed(() -> {
() -> ReminderUtils.createReminderAlarms(context, habits), 5000); reminderScheduler.schedule(habits);
}, 5000);
} }
private void dismissNotification(Context context, Long habitId) private void dismissNotification(Context context, Long habitId)
@ -210,15 +215,20 @@ public class ReminderReceiver extends BroadcastReceiver
private void onActionSnoozeReminder(Context context, Intent intent) private void onActionSnoozeReminder(Context context, Intent intent)
{ {
Uri data = intent.getData(); Uri data = intent.getData();
SharedPreferences prefs = SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
PreferenceManager.getDefaultSharedPreferences(context); context);
long delayMinutes = long delayMinutes = Long.parseLong(
Long.parseLong(prefs.getString("pref_snooze_interval", "15")); prefs.getString("pref_snooze_interval", "15"));
long habitId = ContentUris.parseId(data); long habitId = ContentUris.parseId(data);
Habit habit = habits.getById(habitId); 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); dismissNotification(context, habitId);
} }
@ -227,11 +237,11 @@ public class ReminderReceiver extends BroadcastReceiver
if (!habit.hasReminder()) return false; if (!habit.hasReminder()) return false;
Reminder reminder = habit.getReminder(); Reminder reminder = habit.getReminder();
Long timestamp = Long timestamp = intent.getLongExtra("timestamp",
intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); DateUtils.getStartOfToday());
boolean reminderDays[] = boolean reminderDays[] = DateUtils.unpackWeekdayList(
DateUtils.unpackWeekdayList(reminder.getDays()); reminder.getDays());
int weekday = DateUtils.getWeekday(timestamp); int weekday = DateUtils.getWeekday(timestamp);
return reminderDays[weekday]; return reminderDays[weekday];

@ -24,6 +24,7 @@ import android.support.annotation.*;
import android.util.*; import android.util.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import javax.inject.*; import javax.inject.*;

@ -50,6 +50,9 @@ public class BaseSystem
@Inject @Inject
HabitList habitList; HabitList habitList;
@Inject
ReminderScheduler reminderScheduler;
public BaseSystem(Context context) public BaseSystem(Context context)
{ {
this.context = context; this.context = context;
@ -69,16 +72,16 @@ public class BaseSystem
@NonNull @NonNull
public File dumpBugReportToFile() throws IOException public File dumpBugReportToFile() throws IOException
{ {
String date = String date = DateUtils.getBackupDateFormat().format(
DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); DateUtils.getLocalTime());
if (context == null) throw new RuntimeException( if (context == null) throw new RuntimeException(
"application context should not be null"); "application context should not be null");
File dir = FileUtils.getFilesDir("Logs"); File dir = FileUtils.getFilesDir("Logs");
if (dir == null) throw new IOException("log dir should not be null"); if (dir == null) throw new IOException("log dir should not be null");
File logFile = File logFile = new File(
new File(String.format("%s/Log %s.txt", dir.getPath(), date)); String.format("%s/Log %s.txt", dir.getPath(), date));
FileWriter output = new FileWriter(logFile); FileWriter output = new FileWriter(logFile);
output.write(getBugReport()); output.write(getBugReport());
output.close(); output.close();
@ -102,48 +105,6 @@ public class BaseSystem
return deviceInfo + "\n" + logcat; 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 public String getLogcat() throws IOException
{ {
int maxLineCount = 250; int maxLineCount = 250;
@ -172,4 +133,40 @@ public class BaseSystem
return builder.toString(); 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());
}
} }

@ -176,7 +176,7 @@ public class ListHabitsController
new Handler().postDelayed(() -> { new Handler().postDelayed(() -> {
system.scheduleReminders(); system.scheduleReminders();
HabitsApplication.getWidgetManager().updateWidgets(); HabitsApplication.getWidgetUpdater().updateWidgets();
}, 1000); }, 1000);
} }

@ -79,7 +79,7 @@ public class ScoreCard extends HabitCard
public void onItemSelected(int position) public void onItemSelected(int position)
{ {
setBucketSizeFromPosition(position); setBucketSizeFromPosition(position);
HabitsApplication.getWidgetManager().updateWidgets(); HabitsApplication.getWidgetUpdater().updateWidgets();
refreshData(); refreshData();
} }

@ -19,38 +19,46 @@
package org.isoron.uhabits.ui.settings; package org.isoron.uhabits.ui.settings;
import android.app.backup.BackupManager; import android.app.backup.*;
import android.content.Intent; import android.content.*;
import android.content.SharedPreferences; import android.os.*;
import android.os.Bundle; import android.support.v7.preference.*;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceFragmentCompat;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.utils.ReminderUtils;
import static org.isoron.uhabits.HabitsApplication.*;
public class SettingsFragment extends PreferenceFragmentCompat public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener implements SharedPreferences.OnSharedPreferenceChangeListener
{ {
private static int RINGTONE_REQUEST_CODE = 1; 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 @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
setResultOnPreferenceClick("importData", setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA);
HabitsApplication.RESULT_IMPORT_DATA); setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV);
setResultOnPreferenceClick("exportCSV", setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB);
HabitsApplication.RESULT_EXPORT_CSV); setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT);
setResultOnPreferenceClick("exportDB",
HabitsApplication.RESULT_EXPORT_DB);
setResultOnPreferenceClick("bugReport",
HabitsApplication.RESULT_BUG_REPORT);
updateRingtoneDescription(); updateRingtoneDescription();
@ -61,41 +69,38 @@ public class SettingsFragment extends PreferenceFragmentCompat
@Override @Override
public void onCreatePreferences(Bundle bundle, String s) public void onCreatePreferences(Bundle bundle, String s)
{ {
// NOP
} }
private void removePreference(String preferenceKey, String categoryKey) @Override
public void onPause()
{ {
PreferenceCategory cat = prefs.unregisterOnSharedPreferenceChangeListener(this);
(PreferenceCategory) findPreference(categoryKey); super.onPause();
Preference pref = findPreference(preferenceKey);
cat.removePreference(pref);
} }
private void setResultOnPreferenceClick(String key, final int result) @Override
public boolean onPreferenceTreeClick(Preference preference)
{ {
Preference pref = findPreference(key); String key = preference.getKey();
pref.setOnPreferenceClickListener(preference -> { if (key == null) return false;
getActivity().setResult(result);
getActivity().finish(); if (key.equals("reminderSound"))
{
RingtoneUtils.startRingtonePickerActivity(this,
RINGTONE_REQUEST_CODE);
return true; return true;
});
} }
@Override return super.onPreferenceTreeClick(preference);
public void onResume()
{
super.onResume();
getPreferenceManager().getSharedPreferences().
registerOnSharedPreferenceChangeListener(this);
} }
@Override @Override
public void onPause() public void onResume()
{ {
getPreferenceManager().getSharedPreferences(). super.onResume();
unregisterOnSharedPreferenceChangeListener(this); prefs = getPreferenceManager().getSharedPreferences();
super.onPause(); prefs.registerOnSharedPreferenceChangeListener(this);
} }
@Override @Override
@ -105,37 +110,27 @@ public class SettingsFragment extends PreferenceFragmentCompat
BackupManager.dataChanged("org.isoron.uhabits"); BackupManager.dataChanged("org.isoron.uhabits");
} }
@Override private void removePreference(String preferenceKey, String categoryKey)
public boolean onPreferenceTreeClick(Preference preference)
{
if (preference.getKey() == null) return false;
if (preference.getKey().equals("reminderSound"))
{ {
ReminderUtils.startRingtonePickerActivity(this, PreferenceCategory cat =
RINGTONE_REQUEST_CODE); (PreferenceCategory) findPreference(categoryKey);
return true; Preference pref = findPreference(preferenceKey);
} cat.removePreference(pref);
return super.onPreferenceTreeClick(preference);
} }
@Override private void setResultOnPreferenceClick(String key, final int result)
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == RINGTONE_REQUEST_CODE)
{ {
ReminderUtils.parseRingtoneData(getContext(), data); Preference pref = findPreference(key);
updateRingtoneDescription(); pref.setOnPreferenceClickListener(preference -> {
return; getActivity().setResult(result);
} getActivity().finish();
return true;
super.onActivityResult(requestCode, resultCode, data); });
} }
private void updateRingtoneDescription() private void updateRingtoneDescription()
{ {
String ringtoneName = ReminderUtils.getRingtoneName(getContext()); String ringtoneName = RingtoneUtils.getRingtoneName(getContext());
if (ringtoneName == null) return; if (ringtoneName == null) return;
Preference ringtonePreference = findPreference("reminderSound"); Preference ringtonePreference = findPreference("reminderSound");
ringtonePreference.setSummary(ringtoneName); ringtonePreference.setSummary(ringtoneName);

@ -24,8 +24,8 @@ import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.ui.widgets.views.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -45,7 +45,7 @@ public class CheckmarkWidget extends BaseWidget
@Override @Override
public PendingIntent getOnClickPendingIntent(Context context) public PendingIntent getOnClickPendingIntent(Context context)
{ {
PendingIntentFactory factory = new PendingIntentFactory(context); IntentFactory factory = new IntentFactory(context);
return factory.buildToggleCheckmark(habit, null); return factory.buildToggleCheckmark(habit, null);
} }

@ -24,8 +24,8 @@ import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.ui.widgets.views.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -46,7 +46,7 @@ public class FrequencyWidget extends BaseWidget
@Override @Override
public PendingIntent getOnClickPendingIntent(Context context) public PendingIntent getOnClickPendingIntent(Context context)
{ {
PendingIntentFactory factory = new PendingIntentFactory(context); IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit); return factory.buildViewHabit(habit);
} }

@ -56,7 +56,7 @@ public class HabitPickerDialog extends Activity
{ {
Long habitId = habitIds.get(position); Long habitId = habitIds.get(position);
preferences.addWidget(widgetId, habitId); preferences.addWidget(widgetId, habitId);
HabitsApplication.getWidgetManager().updateWidgets(); HabitsApplication.getWidgetUpdater().updateWidgets();
Intent resultValue = new Intent(); Intent resultValue = new Intent();
resultValue.putExtra(EXTRA_APPWIDGET_ID, widgetId); resultValue.putExtra(EXTRA_APPWIDGET_ID, widgetId);

@ -24,8 +24,8 @@ import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.ui.widgets.views.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -44,7 +44,7 @@ public class HistoryWidget extends BaseWidget
@Override @Override
public PendingIntent getOnClickPendingIntent(Context context) public PendingIntent getOnClickPendingIntent(Context context)
{ {
PendingIntentFactory factory = new PendingIntentFactory(context); IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit); return factory.buildViewHabit(habit);
} }

@ -24,8 +24,8 @@ import android.content.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.view.*; import android.view.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.habits.show.views.*; import org.isoron.uhabits.ui.habits.show.views.*;
import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.ui.widgets.views.*;
@ -47,7 +47,7 @@ public class ScoreWidget extends BaseWidget
@Override @Override
public PendingIntent getOnClickPendingIntent(Context context) public PendingIntent getOnClickPendingIntent(Context context)
{ {
PendingIntentFactory factory = new PendingIntentFactory(context); IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit); return factory.buildViewHabit(habit);
} }

@ -25,8 +25,8 @@ import android.support.annotation.*;
import android.view.*; import android.view.*;
import android.view.ViewGroup.*; import android.view.ViewGroup.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.ui.common.views.*; import org.isoron.uhabits.ui.common.views.*;
import org.isoron.uhabits.ui.widgets.views.*; import org.isoron.uhabits.ui.widgets.views.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -49,7 +49,7 @@ public class StreakWidget extends BaseWidget
@Override @Override
public PendingIntent getOnClickPendingIntent(Context context) public PendingIntent getOnClickPendingIntent(Context context)
{ {
PendingIntentFactory factory = new PendingIntentFactory(context); IntentFactory factory = new IntentFactory(context);
return factory.buildViewHabit(habit); return factory.buildViewHabit(habit);
} }

@ -0,0 +1,86 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

@ -19,87 +19,18 @@
package org.isoron.uhabits.utils; package org.isoron.uhabits.utils;
import android.app.*;
import android.content.*; import android.content.*;
import android.media.*; import android.media.*;
import android.net.*; import android.net.*;
import android.os.*;
import android.preference.*; import android.preference.*;
import android.provider.*; import android.provider.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v4.app.Fragment; import android.support.v4.app.*;
import android.util.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.receivers.*;
import java.text.*; public abstract class RingtoneUtils
import java.util.*;
public abstract class ReminderUtils
{
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 @Nullable
public static String getRingtoneName(Context context) public static String getRingtoneName(Context context)
{ {
@ -174,7 +105,7 @@ public abstract class ReminderUtils
int requestCode) int requestCode)
{ {
Uri existingRingtoneUri = Uri existingRingtoneUri =
ReminderUtils.getRingtoneUri(fragment.getContext()); getRingtoneUri(fragment.getContext());
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);

@ -36,11 +36,14 @@ public class BaseUnitTest
@Inject @Inject
protected ModelFactory modelFactory; protected ModelFactory modelFactory;
protected TestComponent testComponent;
@Inject @Inject
protected HabitList habitList; protected HabitList habitList;
@Inject
protected HabitLogger logger;
protected TestComponent testComponent;
protected HabitFixtures fixtures; protected HabitFixtures fixtures;
@Before @Before

@ -73,4 +73,18 @@ public class TestModule
{ {
return mock(WidgetPreferences.class); return mock(WidgetPreferences.class);
} }
@Provides
@Singleton
ReminderScheduler provideReminderScheduler()
{
return mock(ReminderScheduler.class);
}
@Provides
@Singleton
HabitLogger provideLogger()
{
return mock(HabitLogger.class);
}
} }

@ -0,0 +1,139 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
Loading…
Cancel
Save