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