Adding snoozing, notifications for upcoming deadlines, and made the system wake you when an alarm hits.

pull/14/head
Tim Su 16 years ago
parent 41c02d743e
commit bfb0313f36

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionCode="6"
android:versionName="1.7">
android:versionCode="7"
android:versionName="1.7.1">
<uses-permission android:name="android.permission.VIBRATE"/>

@ -157,10 +157,14 @@
<string name="delete_this_tag_title">Remove this tag from all tasks?</string>
<string name="stop_timer_title">Stop the timer?</string>
<!-- Notification -->
<skip />
<string name="alarm_toast_title">Next notification time: </string>
<string name="notif_definiteDueDate">Absolute Deadline Coming Up!</string>
<string name="notif_preferredDueDate">Goal Deadline Coming Up!</string>
<!-- Dialog Boxes -->
<!-- Error MEssages -->
<skip />
<string name="error_opening">Couldn't find this item:</string>

@ -199,7 +199,7 @@ public class TaskEdit extends TaskModificationActivity<TaskModelForEdit> {
}
// set up notification
Notifications.updateAlarm(this, model);
Notifications.updateAlarm(this, model, true);
}
/** Save task tags. Must be called after task already has an ID */

@ -53,6 +53,7 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
// bundle tokens
public static final String FROM_NOTIFICATION_TOKEN = "notify";
public static final String NOTIF_FLAGS_TOKEN = "notif_flags";
// activities
private static final int ACTIVITY_EDIT = 0;
@ -108,7 +109,7 @@ public class TaskView extends TaskModificationActivity<TaskModelForView> {
// clear residual, schedule the next one
Notifications.clearAllNotifications(this, model.getTaskIdentifier());
Notifications.updateAlarm(this, model);
Notifications.updateAlarm(this, model, true);
String[] responses = r.getStringArray(R.array.reminder_responses);
String response = responses[new Random().nextInt(responses.length)];

@ -61,6 +61,28 @@ public class TaskController extends AbstractController {
return list;
}
/** Return a list of all active tasks with deadlines */
public List<TaskModelForNotify> getTasksWithDeadlines() {
List<TaskModelForNotify> list = new ArrayList<TaskModelForNotify>();
Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForNotify.FIELD_LIST,
String.format("%s < %d AND (%s != 0 OR %s != 0)",
AbstractTaskModel.PROGRESS_PERCENTAGE,
AbstractTaskModel.COMPLETE_PERCENTAGE,
AbstractTaskModel.DEFINITE_DUE_DATE,
AbstractTaskModel.PREFERRED_DUE_DATE), null, null, null, null, null);
if(cursor.getCount() == 0)
return list;
do {
cursor.moveToNext();
list.add(new TaskModelForNotify(cursor));
} while(!cursor.isLast());
cursor.close();
return list;
}
/** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */
public Cursor getActiveTaskListCursor() {
return database.query(TASK_TABLE_NAME, TaskModelForList.FIELD_LIST,

@ -36,6 +36,8 @@ public class TaskModelForNotify extends AbstractTaskModel implements Notifiable
NOTIFICATIONS,
HIDDEN_UNTIL,
PROGRESS_PERCENTAGE,
DEFINITE_DUE_DATE,
PREFERRED_DUE_DATE,
};
// --- constructors
@ -63,4 +65,14 @@ public class TaskModelForNotify extends AbstractTaskModel implements Notifiable
public Date getHiddenUntil() {
return super.getHiddenUntil();
}
@Override
public Date getDefiniteDueDate() {
return super.getDefiniteDueDate();
}
@Override
public Date getPreferredDueDate() {
return super.getPreferredDueDate();
}
}

@ -24,13 +24,25 @@ import com.timsu.astrid.data.task.TaskModelForNotify;
public class Notifications extends BroadcastReceiver {
private static final String ID_KEY = "id";
private static final int MIN_INTERVAL_SECONDS = 300;
private static final float FUDGE_MIN = 0.2f;
private static final float FUDGE_MAX = 0.8f;
private static Random random = new Random();
private static final String ID_KEY = "id";
private static final String FLAGS_KEY = "flags";
// stuff for scheduling
private static final int MIN_INTERVAL_SECONDS = 300;
private static final float FUDGE_MIN = 0.2f;
private static final float FUDGE_MAX = 0.8f;
/** # of seconds before a deadline to notify */
private static final int DEADLINE_NOTIFY_SECS = 3600;
/** # of seconds after now, if a deadline is in the past */
private static final int TIME_IN_PAST_OFFSET = 60;
/** # of seconds after first deadline reminder to repeat */
private static final int DEADLINE_REPEAT = 600;
// flags
public static final int FLAG_DEFINITE_DEADLINE = 1;
public static final int FLAG_PREFERRED_DEADLINE = 2;
private static Random random = new Random();
/** Something we can create a notification for */
public interface Notifiable {
@ -38,14 +50,27 @@ public class Notifications extends BroadcastReceiver {
public Integer getNotificationIntervalSeconds();
public boolean isTaskCompleted();
public Date getHiddenUntil();
public Date getDefiniteDueDate();
public Date getPreferredDueDate();
}
@Override
/** Alarm intent */
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(ID_KEY, 0);
int flags = intent.getIntExtra(FLAGS_KEY, 0);
Log.e("ALARM", "Alarm triggered id " + id);
if(!showNotification(context, id)) {
Resources r = context.getResources();
String reminder;
if((flags & FLAG_DEFINITE_DEADLINE) > 0)
reminder = r.getString(R.string.notif_definiteDueDate);
else if((flags & FLAG_PREFERRED_DEADLINE) > 0)
reminder = r.getString(R.string.notif_preferredDueDate);
else
reminder = getRandomReminder(r);
if(!showNotification(context, id, flags, reminder)) {
deleteAlarm(context, id);
NotificationManager nm = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
@ -55,11 +80,7 @@ public class Notifications extends BroadcastReceiver {
// --- alarm manager stuff
private static boolean isAlarmEnabled(Notifiable task) {
if(task.getNotificationIntervalSeconds() == null ||
task.getNotificationIntervalSeconds() == 0)
return false;
private static boolean shouldDeleteAlarm(Notifiable task) {
if(task.isTaskCompleted())
return false;
@ -72,37 +93,84 @@ public class Notifications extends BroadcastReceiver {
List<TaskModelForNotify> tasks = controller.getTasksWithNotifications();
for(TaskModelForNotify task : tasks)
updateAlarm(context, task);
updateAlarm(context, task, false);
controller.close();
}
/** Schedules the next notification for this task */
public static void updateAlarm(Context context, Notifiable task) {
public static void updateAlarm(Context context, Notifiable task,
boolean shouldSnooze) {
if(task.getTaskIdentifier() == null)
return;
if(!isAlarmEnabled(task)) {
if(!shouldDeleteAlarm(task)) {
deleteAlarm(context, task.getTaskIdentifier().getId());
return;
}
Long when = null; // when to schedule alarm (ms)
Integer interval = null; // how often to repeat (s)
if(task.getNotificationIntervalSeconds() > 0) {
// compute, and add a fudge factor to mix things up a bit
interval = task.getNotificationIntervalSeconds();
int currentSeconds = (int)(System.currentTimeMillis() / 1000);
int untilNextInterval = interval - currentSeconds % interval;
untilNextInterval = 60;
untilNextInterval *= FUDGE_MIN + random.nextFloat() * (FUDGE_MAX - FUDGE_MIN);
if(untilNextInterval < MIN_INTERVAL_SECONDS)
untilNextInterval = MIN_INTERVAL_SECONDS;
when = System.currentTimeMillis() + untilNextInterval * 1000;
}
// if deadlines come before, do that instead
int flags = 0;
if(task.getDefiniteDueDate() != null) {
long deadlineWhen = task.getDefiniteDueDate().getTime() -
DEADLINE_NOTIFY_SECS * 1000;
if(when == null || deadlineWhen < when) {
when = deadlineWhen;
interval = DEADLINE_REPEAT;
flags = FLAG_DEFINITE_DEADLINE;
}
}
if(task.getPreferredDueDate() != null) {
long deadlineWhen = task.getPreferredDueDate().getTime() -
DEADLINE_NOTIFY_SECS * 1000;
if(when == null || deadlineWhen < when) {
when = deadlineWhen;
interval = DEADLINE_REPEAT;
flags = FLAG_PREFERRED_DEADLINE;
}
}
if(when == null) {
deleteAlarm(context, task.getTaskIdentifier().getId());
return;
}
// compute, and add a fudge factor to mix things up a bit
int interval = task.getNotificationIntervalSeconds();
int currentSeconds = (int)(System.currentTimeMillis() / 1000);
int untilNextInterval = interval - currentSeconds % interval;
untilNextInterval *= FUDGE_MIN + random.nextFloat() * (FUDGE_MAX - FUDGE_MIN);
if(untilNextInterval < MIN_INTERVAL_SECONDS)
untilNextInterval = MIN_INTERVAL_SECONDS;
long when = System.currentTimeMillis() + untilNextInterval * 1000;
scheduleAlarm(context, task.getTaskIdentifier().getId(), when,
interval*1000);
// snooze if the user just interacted with this item
if(shouldSnooze) {
long snoozeWhen = System.currentTimeMillis() + DEADLINE_REPEAT;
if(when < snoozeWhen)
when = snoozeWhen;
} else if(when < System.currentTimeMillis())
when = System.currentTimeMillis() + TIME_IN_PAST_OFFSET*1000;
if(interval == null)
scheduleAlarm(context, task.getTaskIdentifier().getId(), when, flags);
else
scheduleRepeatingAlarm(context, task.getTaskIdentifier().getId(),
when, flags, interval*1000);
}
private static PendingIntent createPendingIntent(Context context, long id) {
private static PendingIntent createPendingIntent(Context context,
long id, int flags) {
Intent intent = new Intent(context, Notifications.class);
intent.setType(Long.toString(id));
intent.putExtra(ID_KEY, id);
intent.putExtra(FLAGS_KEY, flags);
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
return sender;
@ -112,15 +180,26 @@ public class Notifications extends BroadcastReceiver {
public static void deleteAlarm(Context context, long id) {
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.cancel(createPendingIntent(context, id));
am.cancel(createPendingIntent(context, id, 0));
}
/** Schedules a recurring alarm for a single task */
public static void scheduleAlarm(Context context, long id, long when, long interval) {
/** Schedules a single alarm for a single task */
public static void scheduleAlarm(Context context, long id, long when,
int flags) {
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Log.e("ALARM", "Alarm set for " + new Date(when));
am.setRepeating(AlarmManager.RTC, when, interval, createPendingIntent(context, id));
am.set(AlarmManager.RTC_WAKEUP, when, createPendingIntent(context, id, flags));
}
/** Schedules a recurring alarm for a single task */
public static void scheduleRepeatingAlarm(Context context, long id, long when,
int flags, long interval) {
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Log.e("ALARM", "Alarm set for " + new Date(when) + " every " + interval);
am.setRepeating(AlarmManager.RTC_WAKEUP, when, interval,
createPendingIntent(context, id, flags));
}
// --- notification manager stuff
@ -132,30 +211,42 @@ public class Notifications extends BroadcastReceiver {
nm.cancel((int)taskId.getId());
}
private static String getRandomReminder(Resources r) {
String[] reminders = r.getStringArray(R.array.reminders);
int next = random.nextInt(reminders.length);
String reminder = reminders[next];
return reminder;
}
/** Schedule a new notification about the given task. Returns false if there was
* some sort of error or the alarm should be disabled. */
public static boolean showNotification(Context context, long id) {
public static boolean showNotification(Context context, long id, int flags,
String reminder) {
String taskName;
TaskController controller = new TaskController(context);
try {
controller.open();
TaskModelForList task = controller.fetchTaskForList(new TaskIdentifier(id));
// you're working on it - don't delete
// you're working on it - don't sound, don't delete
if(task.getTimerStart() != null)
return true;
// you're done - delete
// you're done - don't sound, do delete
if(task.isTaskCompleted())
return false;
// it's hidden - don't delete
// it's hidden - don't sound, do don't delete
if(task.getHiddenUntil() != null && task.getHiddenUntil().after(new Date()))
return true;
taskName = task.getName();
} catch (Exception e) {
// task could be deleted, for example
Log.e(Notifications.class.getSimpleName(), "Error loading task", e);
Log.e(Notifications.class.getSimpleName(),
"Error loading task for notification", e);
return false;
} finally {
controller.close();
@ -168,21 +259,19 @@ public class Notifications extends BroadcastReceiver {
Intent notifyIntent = new Intent(context, TaskView.class);
notifyIntent.putExtra(TaskView.LOAD_INSTANCE_TOKEN, id);
notifyIntent.putExtra(TaskView.FROM_NOTIFICATION_TOKEN, true);
notifyIntent.putExtra(TaskView.NOTIF_FLAGS_TOKEN, flags);
PendingIntent pendingIntent = PendingIntent.getActivity(context,
0, notifyIntent, PendingIntent.FLAG_ONE_SHOT);
// notification text
String appName = r.getString(R.string.app_name);
String[] reminders = r.getStringArray(R.array.reminders);
int next = random.nextInt(reminders.length);
String reminder = reminders[next];
Notification notification = new Notification(
android.R.drawable.stat_notify_chat, reminder,
System.currentTimeMillis());
notification.setLatestEventInfo(context,
appName,
appName + ": " + taskName,
reminder,
pendingIntent);

Loading…
Cancel
Save