mirror of https://github.com/tasks/tasks
Updating reminders to use new stuff...
parent
acccf78cd0
commit
3cb2ed563e
@ -0,0 +1,249 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.timsu.astrid.utilities.Constants;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.utility.Preferences;
|
||||
|
||||
|
||||
/**
|
||||
* Data service for reminders
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class ReminderService {
|
||||
|
||||
// --- constants
|
||||
|
||||
private static final Property<?>[] PROPERTIES = new Property<?>[] {
|
||||
Task.DUE_DATE,
|
||||
Task.REMINDER_FLAGS,
|
||||
Task.REMINDER_PERIOD,
|
||||
Task.REMINDER_LAST
|
||||
};
|
||||
|
||||
/** flag for due date reminder */
|
||||
static final byte TYPE_DUE = 0;
|
||||
/** flag for overdue reminder */
|
||||
static final byte TYPE_OVERDUE = 1;
|
||||
/** flag for random reminder */
|
||||
static final byte TYPE_RANDOM = 2;
|
||||
/** flag for a snoozed reminder */
|
||||
static final byte TYPE_SNOOZE = 3;
|
||||
|
||||
// --- instance variables
|
||||
|
||||
@Autowired
|
||||
private TaskDao taskDao;
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
public ReminderService() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
// --- reminder scheduling logic
|
||||
|
||||
/**
|
||||
* Schedules all alarms
|
||||
*/
|
||||
public void scheduleAllAlarms() {
|
||||
TodorooCursor<Task> cursor = getTasksWithReminders(PROPERTIES);
|
||||
try {
|
||||
Task task = new Task();
|
||||
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
task.readFromCursor(cursor);
|
||||
scheduleAlarm(task, false);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static final long NO_ALARM = Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Schedules alarms for a single task
|
||||
* @param task
|
||||
*/
|
||||
public void scheduleAlarm(Task task) {
|
||||
scheduleAlarm(task, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules alarms for a single task
|
||||
*
|
||||
* @param shouldPerformPropertyCheck
|
||||
* whether to check if task has requisite properties
|
||||
*/
|
||||
private void scheduleAlarm(Task task, boolean shouldPerformPropertyCheck) {
|
||||
if(!task.isSaved())
|
||||
return;
|
||||
|
||||
// read data if necessary
|
||||
if(shouldPerformPropertyCheck) {
|
||||
for(Property<?> property : PROPERTIES) {
|
||||
if(!task.containsValue(property)) {
|
||||
task = taskDao.fetch(task.getId(), PROPERTIES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// random reminders
|
||||
long whenRandom = calculateNextRandomReminder(task);
|
||||
|
||||
// notifications at due date
|
||||
long whenDueDate = calculateNextDueDateReminder(task);
|
||||
|
||||
// notifications after due date
|
||||
long whenOverdue = calculateNextOverdueReminder(task);
|
||||
|
||||
if(whenRandom < whenDueDate && whenRandom < whenOverdue)
|
||||
createAlarm(task, whenRandom, TYPE_RANDOM);
|
||||
else if(whenDueDate < whenOverdue)
|
||||
createAlarm(task, whenDueDate, TYPE_DUE);
|
||||
else if(whenOverdue != NO_ALARM)
|
||||
createAlarm(task, whenOverdue, TYPE_OVERDUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for overdue reminders. If the due date
|
||||
* has passed, we schedule a reminder some time in the next 4 - 24 hours.
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextOverdueReminder(Task task) {
|
||||
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE)) {
|
||||
long dueDate = task.getValue(Task.DUE_DATE);
|
||||
if(dueDate > DateUtilities.now())
|
||||
return NO_ALARM;
|
||||
return DateUtilities.now() + (long)((4 + 20 * random.nextFloat()) * DateUtilities.ONE_HOUR);
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for due date reminders. If the due date
|
||||
* has not already passed, we return the due date, altering the time
|
||||
* if the date was indicated to not have a due time
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextDueDateReminder(Task task) {
|
||||
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE)) {
|
||||
long dueDate = task.getValue(Task.DUE_DATE);
|
||||
if(dueDate < DateUtilities.now())
|
||||
return NO_ALARM;
|
||||
else if(task.hasDueTime())
|
||||
// return due date straight up
|
||||
return dueDate;
|
||||
else {
|
||||
// return notification time on this day
|
||||
Date date = new Date(dueDate);
|
||||
date.setHours(Preferences.getIntegerFromString(R.string.p_reminder_time));
|
||||
date.setMinutes(0);
|
||||
return date.getTime();
|
||||
}
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for random reminders. We take the last
|
||||
* random reminder time and add approximately the reminder period, until
|
||||
* we get a time that's in the future.
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextRandomReminder(Task task) {
|
||||
long reminderPeriod = task.getValue(Task.REMINDER_PERIOD);
|
||||
if((reminderPeriod) > 0) {
|
||||
long when = task.getValue(Task.REMINDER_LAST);
|
||||
|
||||
// get or make up a last notification time
|
||||
if(when == 0) {
|
||||
when = DateUtilities.now();
|
||||
task.setValue(Task.REMINDER_LAST, when);
|
||||
taskDao.save(task, false);
|
||||
}
|
||||
|
||||
when += (long)(reminderPeriod * (0.85f + 0.3f * random.nextFloat()));
|
||||
return when;
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
// --- alarm manager alarm creation
|
||||
|
||||
/**
|
||||
* Create an alarm for the given task at the given type
|
||||
*
|
||||
* @param task
|
||||
* @param time
|
||||
* @param type
|
||||
* @param flags
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
static void createAlarm(Task task, long time, byte type) {
|
||||
if(time == 0 || time == NO_ALARM)
|
||||
return;
|
||||
|
||||
if(time < DateUtilities.now()) {
|
||||
time = DateUtilities.now() + (long)((0.5f +
|
||||
4 * random.nextFloat()) * DateUtilities.ONE_HOUR);
|
||||
}
|
||||
|
||||
Context context = ContextManager.getContext();
|
||||
Intent intent = new Intent(context, Notifications.class);
|
||||
intent.setType(Long.toString(task.getId()));
|
||||
intent.setAction(Integer.toString(type));
|
||||
intent.putExtra(Notifications.ID_KEY, task.getId());
|
||||
intent.putExtra(Notifications.TYPE_KEY, type);
|
||||
|
||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
|
||||
intent, 0);
|
||||
|
||||
if(Constants.DEBUG)
|
||||
Log.e("Astrid", "Alarm (" + task.getId() + ", " + type + ") set for " + new Date(time));
|
||||
am.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
|
||||
}
|
||||
|
||||
// --- data fetching classes
|
||||
|
||||
/**
|
||||
* Gets a listing of all tasks that are active &
|
||||
* @param properties
|
||||
* @return todoroo cursor. PLEASE CLOSE THIS CURSOR!
|
||||
*/
|
||||
public TodorooCursor<Task> getTasksWithReminders(Property<?>... properties) {
|
||||
return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(),
|
||||
Task.REMINDER_FLAGS.gt(0))));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
|
||||
/**
|
||||
* Service which handles jobs that need to be run when phone boots
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class ReminderStartupService extends BroadcastReceiver {
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
// --- system startup
|
||||
|
||||
@Override
|
||||
/** Called when the system is started up */
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
new ReminderService().scheduleAllAlarms();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/prefs_category_alerts">
|
||||
|
||||
<ListPreference
|
||||
android:key="@string/p_notif_quietStart"
|
||||
android:entries="@array/EPr_quiet_hours_start"
|
||||
android:entryValues="@array/EPr_quiet_hours_start_values"
|
||||
android:title="@string/prefs_quietStart_title"
|
||||
android:summary="@string/prefs_quietStart_desc" />
|
||||
<ListPreference
|
||||
android:key="@string/p_notif_quietEnd"
|
||||
android:entries="@array/EPr_quiet_hours_end"
|
||||
android:entryValues="@array/EPr_quiet_hours_end_values"
|
||||
android:title="@string/prefs_quietEnd_title"
|
||||
android:summary="@string/prefs_quietEnd_desc" />
|
||||
<CheckBoxPreference
|
||||
android:key="@string/p_notif_annoy"
|
||||
android:title="@string/prefs_annoy_title"
|
||||
android:summary="@string/prefs_annoy_desc" />
|
||||
<CheckBoxPreference
|
||||
android:key="@string/p_notif_vibrate"
|
||||
android:title="@string/prefs_vibrate_title"
|
||||
android:summary="@string/prefs_vibrate_desc" />
|
||||
<EditTextPreference
|
||||
android:key="@string/p_default_reminder_random"
|
||||
android:title="@string/prefs_defaultRemind_title"
|
||||
android:summary="@string/prefs_defaultRemind_desc" />
|
||||
<RingtonePreference
|
||||
android:key="@string/p_notification_ringtone"
|
||||
android:title="@string/prefs_notification_title"
|
||||
android:summary="@string/prefs_notification_desc"
|
||||
android:ringtoneType="notification"
|
||||
android:showDefault="true"
|
||||
android:showSilent="true" />
|
||||
<ListPreference
|
||||
android:key="@string/p_notif_icon"
|
||||
android:entries="@array/notif_icon_entries"
|
||||
android:entryValues="@array/notif_icon_values"
|
||||
android:title="@string/prefs_notificon_title"
|
||||
android:summary="@string/prefs_notificon_desc" />
|
||||
|
||||
</PreferenceScreen>
|
@ -1,154 +0,0 @@
|
||||
package com.timsu.astrid.utilities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.timsu.astrid.activities.SyncPreferences;
|
||||
import com.timsu.astrid.appwidget.AstridAppWidgetProvider.UpdateService;
|
||||
import com.timsu.astrid.sync.SynchronizationService;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
import com.todoroo.astrid.service.UpgradeService;
|
||||
|
||||
public class StartupReceiver extends BroadcastReceiver {
|
||||
|
||||
private static boolean hasStartedUp = false;
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
/** Called when the system is started up */
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
Notifications.scheduleAllAlarms(context);
|
||||
}
|
||||
|
||||
/** Called when this application is started up */
|
||||
public static void onStartupApplication(final Context context) {
|
||||
if(hasStartedUp)
|
||||
return;
|
||||
|
||||
ContextManager.setContext(context);
|
||||
|
||||
int latestSetVersion = Preferences.getCurrentVersion(context);
|
||||
int version = 0;
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
PackageInfo pi = pm.getPackageInfo("com.timsu.astrid", 0);
|
||||
version = pi.versionCode;
|
||||
} catch (Exception e) {
|
||||
Log.e("StartupAstrid", "Error getting version!", e);
|
||||
}
|
||||
|
||||
// if we just got upgraded, set the alarms
|
||||
boolean justUpgraded = latestSetVersion != version;
|
||||
final int finalVersion = version;
|
||||
if(justUpgraded) {
|
||||
// perform version-specific processing
|
||||
if(latestSetVersion <= 99) {
|
||||
if(Preferences.getSyncOldAutoSyncFrequency(context) != null) {
|
||||
float value = Preferences.getSyncOldAutoSyncFrequency(context);
|
||||
Preferences.setSyncAutoSyncFrequency(context,
|
||||
Math.round(value * 3600));
|
||||
DialogUtilities.okDialog(context, context.getResources().getString(
|
||||
R.string.sync_upgrade_v99), new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
context.startActivity(new Intent(context, SyncPreferences.class));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Preferences.setCurrentVersion(context, finalVersion);
|
||||
new UpgradeService().performUpgrade(latestSetVersion);
|
||||
}
|
||||
|
||||
|
||||
// perform startup activities in a background thread
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
// schedule alarms
|
||||
Notifications.scheduleAllAlarms(context);
|
||||
|
||||
// start widget updating alarm
|
||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context,
|
||||
0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
am.setInexactRepeating(AlarmManager.RTC, 0,
|
||||
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
|
||||
|
||||
// start synchronization service
|
||||
if(Constants.SYNCHRONIZE)
|
||||
SynchronizationService.scheduleService(context);
|
||||
|
||||
// start backup service
|
||||
BackupService.scheduleService(context);
|
||||
}
|
||||
}).start();
|
||||
|
||||
Preferences.setPreferenceDefaults(context);
|
||||
|
||||
// check for task killers
|
||||
if(!Constants.OEM)
|
||||
showTaskKillerHelp(context);
|
||||
|
||||
hasStartedUp = true;
|
||||
}
|
||||
|
||||
private static void showTaskKillerHelp(final Context context) {
|
||||
if(!Preferences.shouldShowTaskKillerHelp(context))
|
||||
return;
|
||||
|
||||
// search for task killers. if they exist, show the help!
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<PackageInfo> apps = pm
|
||||
.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
outer: for (PackageInfo app : apps) {
|
||||
if(app == null || app.requestedPermissions == null)
|
||||
continue;
|
||||
if(app.packageName.startsWith("com.android"))
|
||||
continue;
|
||||
for (String permission : app.requestedPermissions) {
|
||||
if (Manifest.permission.RESTART_PACKAGES.equals(permission)) {
|
||||
CharSequence appName = app.applicationInfo.loadLabel(pm);
|
||||
Log.e("astrid", "found task killer: " + app.packageName);
|
||||
|
||||
OnClickListener listener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface arg0,
|
||||
int arg1) {
|
||||
Preferences.disableTaskKillerHelp(context);
|
||||
}
|
||||
};
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.information_title)
|
||||
.setMessage(context.getString(R.string.task_killer_help,
|
||||
appName))
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(R.string.task_killer_help_ok, listener)
|
||||
.show();
|
||||
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue