/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.alarms;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.MetadataService.SynchronizeMetadataCallback;
import com.todoroo.astrid.utility.Constants;
/**
* Provides operations for working with alerts
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class AlarmService {
// --- singleton
private static AlarmService instance = null;
public static synchronized AlarmService getInstance() {
if(instance == null)
instance = new AlarmService();
return instance;
}
// --- data retrieval
public static final String IDENTIFIER = "alarms"; //$NON-NLS-1$
/**
* Return alarms for the given task. PLEASE CLOSE THE CURSOR!
*
* @param taskId
*/
public TodorooCursor<Metadata> getAlarms(long taskId) {
return PluginServices.getMetadataService().query(Query.select(
Metadata.PROPERTIES).where(MetadataCriteria.byTaskAndwithKey(
taskId, AlarmFields.METADATA_KEY)).orderBy(Order.asc(AlarmFields.TIME)));
}
/**
* Save the given array of alarms into the database
* @param taskId
* @param tags
* @return true if data was changed
*/
public boolean synchronizeAlarms(final long taskId, LinkedHashSet<Long> alarms) {
MetadataService service = PluginServices.getMetadataService();
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
for(Long alarm : alarms) {
Metadata item = new Metadata();
item.setValue(Metadata.KEY, AlarmFields.METADATA_KEY);
item.setValue(AlarmFields.TIME, alarm);
item.setValue(AlarmFields.TYPE, AlarmFields.TYPE_SINGLE);
metadata.add(item);
}
final Context context = ContextManager.getContext();
final AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
boolean changed = service.synchronizeMetadata(taskId, metadata, Metadata.KEY.eq(AlarmFields.METADATA_KEY), new SynchronizeMetadataCallback() {
@Override
public void beforeDeleteMetadata(Metadata m) {
// Cancel the alarm before the metadata is deleted
PendingIntent pendingIntent = pendingIntentForAlarm(m, taskId);
am.cancel(pendingIntent);
}
}, true);
if(changed)
scheduleAlarms(taskId);
return changed;
}
// --- alarm scheduling
/**
* Gets a listing of all alarms that are active
* @param properties
* @return todoroo cursor. PLEASE CLOSE THIS CURSOR!
*/
private TodorooCursor<Metadata> getActiveAlarms() {
return PluginServices.getMetadataService().query(Query.select(Metadata.ID, Metadata.TASK, AlarmFields.TIME).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(AlarmFields.METADATA_KEY))));
}
/**
* Gets a listing of alarms by task
* @param properties
* @return todoroo cursor. PLEASE CLOSE THIS CURSOR!
*/
private TodorooCursor<Metadata> getActiveAlarmsForTask(long taskId) {
return PluginServices.getMetadataService().query(Query.select(Metadata.ID, Metadata.TASK, AlarmFields.TIME).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isActive(),
MetadataCriteria.byTaskAndwithKey(taskId, AlarmFields.METADATA_KEY))));
}
/**
* Schedules all alarms
*/
public void scheduleAllAlarms() {
TodorooCursor<Metadata> cursor = getActiveAlarms();
try {
Metadata alarm = new Metadata();
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
alarm.readFromCursor(cursor);
scheduleAlarm(alarm);
}
} catch (Exception e) {
// suppress
} finally {
cursor.close();
}
}
private static final long NO_ALARM = Long.MAX_VALUE;
/**
* Schedules alarms for a single task
* @param task
*/
public void scheduleAlarms(long taskId) {
TodorooCursor<Metadata> cursor = getActiveAlarmsForTask(taskId);
try {
Metadata alarm = new Metadata();
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
alarm.readFromCursor(cursor);
scheduleAlarm(alarm);
}
} catch (Exception e) {
Log.i("astrid-alarms", "Error scheduling alarm", e); //$NON-NLS-1$ //$NON-NLS-2$
} finally {
cursor.close();
}
}
private PendingIntent pendingIntentForAlarm(Metadata alarm, long taskId) {
Context context = ContextManager.getContext();
Intent intent = new Intent(context, Notifications.class);
intent.setAction("ALARM" + alarm.getId()); //$NON-NLS-1$
intent.putExtra(Notifications.ID_KEY, taskId);
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_ALARM);
return PendingIntent.getBroadcast(context, (int)alarm.getId(),
intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Schedules alarms for a single task
*
* @param shouldPerformPropertyCheck
* whether to check if task has requisite properties
*/
@SuppressWarnings("nls")
private void scheduleAlarm(Metadata alarm) {
if(alarm == null)
return;
long taskId = alarm.getValue(Metadata.TASK);
Context context = ContextManager.getContext();
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = pendingIntentForAlarm(alarm, taskId);
long time = alarm.getValue(AlarmFields.TIME);
if(time == 0 || time == NO_ALARM)
am.cancel(pendingIntent);
else if(time > DateUtilities.now()) {
if(Constants.DEBUG)
Log.e("Astrid", "Alarm (" + taskId + ", " + ReminderService.TYPE_ALARM +
", " + alarm.getId() + ") set for " + new Date(time));
am.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
}
}
}