Refactor receivers

pull/157/head
Alinson S. Xavier 9 years ago
parent c262adbe85
commit 7eb454788f

@ -26,7 +26,6 @@ import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.receivers.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
@ -57,14 +56,14 @@ public interface AppComponent
IntentFactory getIntentFactory();
IntentParser getIntentParser();
ModelFactory getModelFactory();
PendingIntentFactory getPendingIntentFactory();
Preferences getPreferences();
ReceiverActions getReceiverActions();
ReminderScheduler getReminderScheduler();
TaskRunner getTaskRunner();

@ -25,6 +25,4 @@ import javax.inject.*;
* Scope used by objects that live as long as the activity is alive.
*/
@Scope
public @interface ActivityScope
{
}
public @interface ActivityScope { }

@ -26,10 +26,16 @@ import android.support.annotation.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
import static android.content.ContentUris.*;
@Singleton
public class IntentParser
{
private HabitList habits;
@Inject
public IntentParser(@NonNull HabitList habits)
{
this.habits = habits;
@ -38,6 +44,8 @@ public class IntentParser
public CheckmarkIntentData parseCheckmarkIntent(@NonNull Intent intent)
{
Uri uri = intent.getData();
if (uri == null) throw new IllegalArgumentException("uri is null");
CheckmarkIntentData data = new CheckmarkIntentData();
data.habit = parseHabit(uri);
data.timestamp = parseTimestamp(intent);
@ -47,12 +55,9 @@ public class IntentParser
@NonNull
protected Habit parseHabit(@NonNull Uri uri)
{
long habitId = ContentUris.parseId(uri);
Habit habit = habits.getById(habitId);
Habit habit = habits.getById(parseId(uri));
if (habit == null)
throw new IllegalArgumentException("habit not found");
return habit;
}
@ -60,7 +65,6 @@ public class IntentParser
protected Long parseTimestamp(@NonNull Intent intent)
{
long today = DateUtils.getStartOfToday();
Long timestamp = intent.getLongExtra("timestamp", today);
timestamp = DateUtils.getStartOfDay(timestamp);

@ -0,0 +1,155 @@
/*
* 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.notifications;
import android.app.*;
import android.content.*;
import android.support.annotation.*;
import android.support.v4.app.*;
import android.support.v4.app.NotificationCompat.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
import static android.graphics.BitmapFactory.*;
import static org.isoron.uhabits.utils.RingtoneUtils.*;
public class NotificationTray
{
@NonNull
private final Context context;
@NonNull
private final TaskRunner taskRunner;
@NonNull
private final PendingIntentFactory pendingIntents;
@Inject
public NotificationTray(@AppContext @NonNull Context context,
@NonNull TaskRunner taskRunner,
@NonNull PendingIntentFactory pendingIntents)
{
this.context = context;
this.taskRunner = taskRunner;
this.pendingIntents = pendingIntents;
}
public void cancel(@NonNull Habit habit)
{
int notificationId = getNotificationId(habit);
NotificationManagerCompat.from(context).cancel(notificationId);
}
public void show(@NonNull Habit habit, long timestamp, long reminderTime)
{
taskRunner.execute(
new ShowNotificationTask(habit, timestamp, reminderTime));
}
private int getNotificationId(Habit habit)
{
Long id = habit.getId();
if (id == null) return 0;
return (int) (id % Integer.MAX_VALUE);
}
private class ShowNotificationTask implements Task
{
int todayValue;
private final Habit habit;
private final long timestamp;
private final long reminderTime;
public ShowNotificationTask(Habit habit,
long timestamp,
long reminderTime)
{
this.habit = habit;
this.timestamp = timestamp;
this.reminderTime = reminderTime;
}
@Override
public void doInBackground()
{
todayValue = habit.getCheckmarks().getTodayValue();
}
@Override
public void onPostExecute()
{
if (todayValue != Checkmark.UNCHECKED) return;
if (!shouldShowReminderToday()) return;
if (!habit.hasReminder()) return;
WearableExtender wearableExtender =
new WearableExtender().setBackground(
decodeResource(context.getResources(), R.drawable.stripe));
Notification notification = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.getName())
.setContentText(habit.getDescription())
.setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification())
.addAction(R.drawable.ic_action_check,
context.getString(R.string.check),
pendingIntents.addCheckmark(habit, timestamp))
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit))
.setSound(getRingtoneUri(context))
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
int notificationId = getNotificationId(habit);
notificationManager.notify(notificationId, notification);
}
private boolean shouldShowReminderToday()
{
if (!habit.hasReminder()) return false;
Reminder reminder = habit.getReminder();
boolean reminderDays[] = reminder.getDays().toArray();
int weekday = DateUtils.getWeekday(timestamp);
return reminderDays[weekday];
}
}
}

@ -59,6 +59,11 @@ public class Preferences
return defaultScoreInterval;
}
public long getSnoozeInterval()
{
return Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
}
public void setDefaultScoreSpinnerPosition(int position)
{
prefs.edit().putInt("pref_score_view_interval", position).apply();

@ -0,0 +1,25 @@
/*
* 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.receivers;
import javax.inject.*;
@Scope
public @interface ReceiverScope { }

@ -0,0 +1,80 @@
/*
* 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.receivers;
import android.support.annotation.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.notifications.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import javax.inject.*;
@ReceiverScope
public class ReminderController
{
@NonNull
private final ReminderScheduler reminderScheduler;
@NonNull
private final NotificationTray notificationTray;
private Preferences preferences;
@Inject
public ReminderController(@NonNull ReminderScheduler reminderScheduler,
@NonNull NotificationTray notificationTray,
@NonNull Preferences preferences)
{
this.reminderScheduler = reminderScheduler;
this.notificationTray = notificationTray;
this.preferences = preferences;
}
public void onBootCompleted()
{
reminderScheduler.scheduleAll();
}
public void onShowReminder(@NonNull Habit habit,
long timestamp,
long reminderTime)
{
notificationTray.show(habit, timestamp, reminderTime);
reminderScheduler.scheduleAll();
}
public void onSnooze(@NonNull Habit habit)
{
long snoozeInterval = preferences.getSnoozeInterval();
long now = DateUtils.getLocalTime();
long reminderTime = now + snoozeInterval * 60 * 1000;
reminderScheduler.schedule(habit, reminderTime);
notificationTray.cancel(habit);
}
public void onDismiss(@NonNull Habit habit)
{
// nop
}
}

@ -19,21 +19,17 @@
package org.isoron.uhabits.receivers;
import android.app.*;
import android.content.*;
import android.graphics.*;
import android.net.*;
import android.os.*;
import android.preference.*;
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.*;
import dagger.*;
import static android.content.ContentUris.*;
/**
* The Android BroadcastReceiver for Loop Habit Tracker.
* <p>
@ -52,45 +48,50 @@ public class ReminderReceiver extends BroadcastReceiver
private static final String TAG = "ReminderReceiver";
private HabitList habits;
private TaskRunner taskRunner;
private ReminderScheduler reminderScheduler;
private PendingIntentFactory pendingIntentFactory;
@Override
public void onReceive(final Context context, Intent intent)
{
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
habits = app.getComponent().getHabitList();
taskRunner = app.getComponent().getTaskRunner();
reminderScheduler = app.getComponent().getReminderScheduler();
pendingIntentFactory = app.getComponent().getPendingIntentFactory();
ReminderComponent component = DaggerReminderReceiver_ReminderComponent
.builder()
.appComponent(app.getComponent())
.build();
HabitList habits = app.getComponent().getHabitList();
ReminderController reminderController =
component.getReminderController();
Log.i(TAG, String.format("Received intent: %s", intent.toString()));
long today = DateUtils.getStartOfToday();
final Habit habit = habits.getById(parseId(intent.getData()));
final Long timestamp = intent.getLongExtra("timestamp", today);
final Long reminderTime = intent.getLongExtra("reminderTime", today);
try
{
switch (intent.getAction())
{
case ACTION_SHOW_REMINDER:
onActionShowReminder(context, intent);
if (habit == null) return;
reminderController.onShowReminder(habit, timestamp,
reminderTime);
break;
case ACTION_DISMISS_REMINDER:
// NOP
if (habit == null) return;
reminderController.onDismiss(habit);
break;
case ACTION_SNOOZE_REMINDER:
onActionSnoozeReminder(context, intent);
if (habit == null) return;
reminderController.onSnooze(habit);
break;
case Intent.ACTION_BOOT_COMPLETED:
onActionBootCompleted();
reminderController.onBootCompleted();
break;
}
}
@ -100,145 +101,10 @@ public class ReminderReceiver extends BroadcastReceiver
}
}
protected void onActionBootCompleted()
{
reminderScheduler.scheduleAll();
}
protected void onActionShowReminder(Context context, Intent intent)
{
createNotification(context, intent);
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());
if (habit == null) return;
taskRunner.execute(new Task()
{
int todayValue;
@Override
public void doInBackground()
{
todayValue = habit.getCheckmarks().getTodayValue();
}
@Override
public void onPostExecute()
{
if (todayValue != Checkmark.UNCHECKED) return;
if (!shouldShowReminderToday(intent, habit)) return;
if (!habit.hasReminder()) return;
Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data);
PendingIntent contentPendingIntent =
PendingIntent.getActivity(context, 0, contentIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent dismissPendingIntent;
dismissPendingIntent =
pendingIntentFactory.dismissNotification();
PendingIntent checkIntentPending =
pendingIntentFactory.addCheckmark(habit, timestamp);
PendingIntent snoozeIntentPending =
pendingIntentFactory.snoozeNotification(habit);
Uri ringtoneUri = RingtoneUtils.getRingtoneUri(context);
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(),
R.drawable.stripe));
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)
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze),
snoozeIntentPending)
.setSound(ringtoneUri)
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
notificationManager.notify(notificationId, notification);
}
});
}
private void createReminderAlarmsDelayed()
{
new Handler().postDelayed(() -> {
reminderScheduler.scheduleAll();
}, 5000);
}
private void dismissNotification(Context context, Long habitId)
{
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(
Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habitId % Integer.MAX_VALUE);
notificationManager.cancel(notificationId);
}
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"));
long habitId = ContentUris.parseId(data);
Habit habit = habits.getById(habitId);
if (habit != null)
{
long reminderTime = DateUtils.getLocalTime() + delayMinutes * 60 * 1000;
reminderScheduler.schedule(habit, reminderTime);
}
dismissNotification(context, habitId);
}
private boolean shouldShowReminderToday(Intent intent, Habit habit)
@ReceiverScope
@Component(dependencies = AppComponent.class, modules = AppModule.class)
interface ReminderComponent
{
if (!habit.hasReminder()) return false;
Reminder reminder = habit.getReminder();
Long timestamp =
intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
boolean reminderDays[] = reminder.getDays().toArray();
int weekday = DateUtils.getWeekday(timestamp);
return reminderDays[weekday];
ReminderController getReminderController();
}
}

@ -26,32 +26,38 @@ import org.isoron.uhabits.models.*;
import javax.inject.*;
@Singleton
public class ReceiverActions
@ReceiverScope
public class WidgetController
{
@NonNull
private final CommandRunner commandRunner;
@Inject
public ReceiverActions(CommandRunner commandRunner)
public WidgetController(@NonNull CommandRunner commandRunner)
{
this.commandRunner = commandRunner;
}
public void addRepetition(@NonNull Habit habit, long timestamp)
public void onAddRepetition(@NonNull Habit habit, long timestamp)
{
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep != null) return;
toggleRepetition(habit, timestamp);
performToggle(habit, timestamp);
}
public void removeRepetition(@NonNull Habit habit, long timestamp)
public void onRemoveRepetition(@NonNull Habit habit, long timestamp)
{
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep == null) return;
toggleRepetition(habit, timestamp);
performToggle(habit, timestamp);
}
public void toggleRepetition(@NonNull Habit habit, long timestamp)
public void onToggleRepetition(@NonNull Habit habit, long timestamp)
{
performToggle(habit, timestamp);
}
private void performToggle(@NonNull Habit habit, long timestamp)
{
commandRunner.execute(new ToggleRepetitionCommand(habit, timestamp),
habit.getId());

@ -20,12 +20,12 @@
package org.isoron.uhabits.receivers;
import android.content.*;
import android.support.annotation.*;
import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.models.*;
import dagger.*;
/**
* The Android BroadcastReceiver for Loop Habit Tracker.
@ -46,41 +46,37 @@ public class WidgetReceiver extends BroadcastReceiver
public static final String ACTION_TOGGLE_REPETITION =
"org.isoron.uhabits.ACTION_TOGGLE_REPETITION";
@NonNull
private HabitList habits;
@NonNull
private IntentParser parser;
@NonNull
private ReceiverActions actions;
@Override
public void onReceive(final Context context, Intent intent)
{
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
habits = app.getComponent().getHabitList();
actions = app.getComponent().getReceiverActions();
parser = new IntentParser(habits);
WidgetComponent component = DaggerWidgetReceiver_WidgetComponent
.builder()
.appComponent(app.getComponent())
.build();
IntentParser parser = app.getComponent().getIntentParser();
WidgetController controller = component.getWidgetController();
Log.d("WidgetReceiver",
String.format("Received intent: %s", intent.toString()));
try
{
IntentParser.CheckmarkIntentData data;
data = parser.parseCheckmarkIntent(intent);
switch (intent.getAction())
{
case ACTION_ADD_REPETITION:
onActionAddRepetition(intent);
controller.onAddRepetition(data.habit, data.timestamp);
break;
case ACTION_TOGGLE_REPETITION:
onActionToggleRepetition(intent);
controller.onToggleRepetition(data.habit, data.timestamp);
break;
case ACTION_REMOVE_REPETITION:
onActionRemoveRepetition(intent);
controller.onRemoveRepetition(data.habit, data.timestamp);
break;
}
}
@ -90,24 +86,10 @@ public class WidgetReceiver extends BroadcastReceiver
}
}
private void onActionAddRepetition(Intent intent)
{
IntentParser.CheckmarkIntentData data;
data = parser.parseCheckmarkIntent(intent);
actions.addRepetition(data.habit, data.timestamp);
}
private void onActionRemoveRepetition(Intent intent)
{
IntentParser.CheckmarkIntentData data;
data = parser.parseCheckmarkIntent(intent);
actions.removeRepetition(data.habit, data.timestamp);
}
private void onActionToggleRepetition(Intent intent)
@ReceiverScope
@Component(dependencies = AppComponent.class, modules = AppModule.class)
interface WidgetComponent
{
IntentParser.CheckmarkIntentData data;
data = parser.parseCheckmarkIntent(intent);
actions.toggleRepetition(data.habit, data.timestamp);
WidgetController getWidgetController();
}
}

@ -74,6 +74,8 @@ public class ListHabitsScreenTest extends BaseUnitTest
private EditHabitDialogFactory editHabitDialogFactory;
private ThemeSwitcher themeSwitcher;
@Before
@Override
public void setUp()
@ -85,6 +87,7 @@ public class ListHabitsScreenTest extends BaseUnitTest
dirFinder = mock(DirFinder.class);
rootView = mock(ListHabitsRootView.class);
intentFactory = mock(IntentFactory.class);
themeSwitcher = mock(ThemeSwitcher.class);
confirmDeleteDialogFactory = mock(ConfirmDeleteDialogFactory.class);
createHabitDialogFactory = mock(CreateHabitDialogFactory.class);
filePickerDialogFactory = mock(FilePickerDialogFactory.class);
@ -93,7 +96,7 @@ public class ListHabitsScreenTest extends BaseUnitTest
screen =
new ListHabitsScreen(activity, commandRunner, dirFinder, rootView,
intentFactory, confirmDeleteDialogFactory,
intentFactory, themeSwitcher, confirmDeleteDialogFactory,
createHabitDialogFactory, filePickerDialogFactory,
colorPickerDialogFactory, editHabitDialogFactory);

Loading…
Cancel
Save