1e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio/*
2e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * Copyright (C) 2010 The Android Open Source Project
3e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio *
4e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
5e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * you may not use this file except in compliance with the License.
6e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * You may obtain a copy of the License at
7e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio *
8e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
9e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio *
10e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
11e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
12e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * See the License for the specific language governing permissions and
14e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * limitations under the License.
15e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio */
16e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
17e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Megliopackage com.android.providers.calendar;
18e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
19420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
20420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
21420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport com.google.common.annotations.VisibleForTesting;
22420b7fb569773ae573fbe90c3a9c522d4c368863Erik
23e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglioimport android.app.AlarmManager;
24e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglioimport android.app.PendingIntent;
25e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglioimport android.content.ContentResolver;
26e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglioimport android.content.Context;
27420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.content.Intent;
28420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.database.Cursor;
29420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.database.sqlite.SQLiteDatabase;
30420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.net.Uri;
31420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.os.PowerManager;
32420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.os.PowerManager.WakeLock;
33420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.os.SystemClock;
34b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
35b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
36b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
37b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
38b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
39b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
40420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.text.format.DateUtils;
41420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.text.format.Time;
42420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport android.util.Log;
43420b7fb569773ae573fbe90c3a9c522d4c368863Erik
44420b7fb569773ae573fbe90c3a9c522d4c368863Erikimport java.util.concurrent.atomic.AtomicBoolean;
45e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
46e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio/**
47e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * We are using the CalendarAlertManager to be able to mock the AlarmManager as the AlarmManager
48e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio * cannot be extended.
49e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio *
50656f9cb431c798c972260f31a4ebcd56047dff21Fabrice Di Meglio * CalendarAlertManager is delegating its calls to the real AlarmService.
51e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio */
52e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Megliopublic class CalendarAlarmManager {
53e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    protected static final String TAG = "CalendarAlarmManager";
54e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
55420b7fb569773ae573fbe90c3a9c522d4c368863Erik    // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false)
56420b7fb569773ae573fbe90c3a9c522d4c368863Erik    // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true)
57420b7fb569773ae573fbe90c3a9c522d4c368863Erik    // TODO: use a service to schedule alarms rather than private URI
58420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /* package */static final String SCHEDULE_ALARM_PATH = "schedule_alarms";
59420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /* package */static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
60420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private static final String REMOVE_ALARM_VALUE = "removeAlarms";
61420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /* package */static final Uri SCHEDULE_ALARM_REMOVE_URI = Uri.withAppendedPath(
62b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
63420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /* package */static final Uri SCHEDULE_ALARM_URI = Uri.withAppendedPath(
64b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.CONTENT_URI, SCHEDULE_ALARM_PATH);
65420b7fb569773ae573fbe90c3a9c522d4c368863Erik
66420b7fb569773ae573fbe90c3a9c522d4c368863Erik    static final String INVALID_CALENDARALERTS_SELECTOR =
67420b7fb569773ae573fbe90c3a9c522d4c368863Erik    "_id IN (SELECT ca." + CalendarAlerts._ID + " FROM "
68420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + Tables.CALENDAR_ALERTS + " AS ca"
69420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " LEFT OUTER JOIN " + Tables.INSTANCES
70420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " USING (" + Instances.EVENT_ID + ","
71420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + Instances.BEGIN + "," + Instances.END + ")"
72420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " LEFT OUTER JOIN " + Tables.REMINDERS + " AS r ON"
73420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " (ca." + CalendarAlerts.EVENT_ID + "=r." + Reminders.EVENT_ID
74420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " AND ca." + CalendarAlerts.MINUTES + "=r." + Reminders.MINUTES + ")"
75420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " LEFT OUTER JOIN " + Views.EVENTS + " AS e ON"
76420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " (ca." + CalendarAlerts.EVENT_ID + "=e." + Events._ID + ")"
77420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + " WHERE " + Tables.INSTANCES + "." + Instances.BEGIN + " ISNULL"
78420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + "   OR ca." + CalendarAlerts.ALARM_TIME + "<?"
79420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + "   OR (r." + Reminders.MINUTES + " ISNULL"
80420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + "       AND ca." + CalendarAlerts.MINUTES + "<>0)"
814067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden            + "   OR e." + Calendars.VISIBLE + "=0)";
82420b7fb569773ae573fbe90c3a9c522d4c368863Erik
83420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
84420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * We search backward in time for event reminders that we may have missed
85420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * and schedule them if the event has not yet expired. The amount in the
86420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * past to search backwards is controlled by this constant. It should be at
87420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * least a few minutes to allow for an event that was recently created on
88420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * the web to make its way to the phone. Two hours might seem like overkill,
89420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * but it is useful in the case where the user just crossed into a new
90420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * timezone and might have just missed an alarm.
91420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
92420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS;
93420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
94420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * Alarms older than this threshold will be deleted from the CalendarAlerts
95420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * table. This should be at least a day because if the timezone is wrong and
96420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * the user corrects it we might delete good alarms that appear to be old
97420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * because the device time was incorrectly in the future. This threshold
98420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * must also be larger than SCHEDULE_ALARM_SLACK. We add the
99420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * SCHEDULE_ALARM_SLACK to ensure this. To make it easier to find and debug
100420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * problems with missed reminders, set this to something greater than a day.
101420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
102420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private static final long CLEAR_OLD_ALARM_THRESHOLD = 7 * DateUtils.DAY_IN_MILLIS
103420b7fb569773ae573fbe90c3a9c522d4c368863Erik            + SCHEDULE_ALARM_SLACK;
104420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private static final String SCHEDULE_NEXT_ALARM_WAKE_LOCK = "ScheduleNextAlarmWakeLock";
105420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected static final String ACTION_CHECK_NEXT_ALARM =
106420b7fb569773ae573fbe90c3a9c522d4c368863Erik            "com.android.providers.calendar.intent.CalendarProvider2";
107420b7fb569773ae573fbe90c3a9c522d4c368863Erik    static final int ALARM_CHECK_DELAY_MILLIS = 5000;
108420b7fb569773ae573fbe90c3a9c522d4c368863Erik
109420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
110420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * Used for tracking if the next alarm is already scheduled
111420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
112420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
113420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected AtomicBoolean mNextAlarmCheckScheduled;
114420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
115420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * Used for synchronization
116420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
117420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
118420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected Object mAlarmLock;
119420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
120420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * Used to keep the process from getting killed while scheduling alarms
121420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
122420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private WakeLock mScheduleNextAlarmWakeLock;
123420b7fb569773ae573fbe90c3a9c522d4c368863Erik
124420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
125420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected Context mContext;
126e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private AlarmManager mAlarmManager;
127e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
128e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public CalendarAlarmManager(Context context) {
129e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        initializeWithContext(context);
130e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
131e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
132e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    protected void initializeWithContext(Context context) {
133e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext = context;
134e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
135420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mNextAlarmCheckScheduled = new AtomicBoolean(false);
136420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mAlarmLock = new Object();
137420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
138420b7fb569773ae573fbe90c3a9c522d4c368863Erik
139420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void scheduleNextAlarm(boolean removeAlarms) {
14049b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // We must always run the following when 'removeAlarms' is true.  Previously it
14149b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // was possible to have a race condition on startup between TIME_CHANGED and
14249b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // BOOT_COMPLETED broadcast actions.  This resulted in alarms being
14349b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // missed (Bug 7221716) when the TIME_CHANGED broadcast ('removeAlarms' = false)
14449b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // happened right before the BOOT_COMPLETED ('removeAlarms' = true), and the
14549b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        // BOOT_COMPLETED action was skipped since there was concurrent scheduling in progress.
14649b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting        if (!mNextAlarmCheckScheduled.getAndSet(true) || removeAlarms) {
147420b7fb569773ae573fbe90c3a9c522d4c368863Erik            if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
148420b7fb569773ae573fbe90c3a9c522d4c368863Erik                Log.d(CalendarProvider2.TAG, "Scheduling check of next Alarm");
149420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
150420b7fb569773ae573fbe90c3a9c522d4c368863Erik            Intent intent = new Intent(ACTION_CHECK_NEXT_ALARM);
151420b7fb569773ae573fbe90c3a9c522d4c368863Erik            intent.putExtra(REMOVE_ALARM_VALUE, removeAlarms);
152420b7fb569773ae573fbe90c3a9c522d4c368863Erik            PendingIntent pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent,
153420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    PendingIntent.FLAG_NO_CREATE);
154420b7fb569773ae573fbe90c3a9c522d4c368863Erik            if (pending != null) {
155420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // Cancel any previous Alarm check requests
156420b7fb569773ae573fbe90c3a9c522d4c368863Erik                cancel(pending);
157420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
158420b7fb569773ae573fbe90c3a9c522d4c368863Erik            pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent,
159420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    PendingIntent.FLAG_CANCEL_CURRENT);
160420b7fb569773ae573fbe90c3a9c522d4c368863Erik
161420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // Trigger the check in 5s from now
162420b7fb569773ae573fbe90c3a9c522d4c368863Erik            long triggerAtTime = SystemClock.elapsedRealtime() + ALARM_CHECK_DELAY_MILLIS;
163420b7fb569773ae573fbe90c3a9c522d4c368863Erik            set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pending);
164420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
165420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
166420b7fb569773ae573fbe90c3a9c522d4c368863Erik
167420b7fb569773ae573fbe90c3a9c522d4c368863Erik    PowerManager.WakeLock getScheduleNextAlarmWakeLock() {
168420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mScheduleNextAlarmWakeLock == null) {
169420b7fb569773ae573fbe90c3a9c522d4c368863Erik            PowerManager powerManager = (PowerManager) mContext.getSystemService(
170420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    Context.POWER_SERVICE);
171420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // Create a wake lock that will be used when we are actually
172420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // scheduling the next alarm
173420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mScheduleNextAlarmWakeLock = powerManager.newWakeLock(
174420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    PowerManager.PARTIAL_WAKE_LOCK, SCHEDULE_NEXT_ALARM_WAKE_LOCK);
175420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // We want the Wake Lock to be reference counted (so that we dont
176420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // need to take care
177420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // about its reference counting)
178420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mScheduleNextAlarmWakeLock.setReferenceCounted(true);
179420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
180420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mScheduleNextAlarmWakeLock;
181420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
182420b7fb569773ae573fbe90c3a9c522d4c368863Erik
183420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireScheduleNextAlarmWakeLock() {
184420b7fb569773ae573fbe90c3a9c522d4c368863Erik        getScheduleNextAlarmWakeLock().acquire();
185420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
186420b7fb569773ae573fbe90c3a9c522d4c368863Erik
187420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void releaseScheduleNextAlarmWakeLock() {
188420b7fb569773ae573fbe90c3a9c522d4c368863Erik        getScheduleNextAlarmWakeLock().release();
189420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
190420b7fb569773ae573fbe90c3a9c522d4c368863Erik
191420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void rescheduleMissedAlarms() {
192420b7fb569773ae573fbe90c3a9c522d4c368863Erik        rescheduleMissedAlarms(mContext.getContentResolver());
193420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
194420b7fb569773ae573fbe90c3a9c522d4c368863Erik
195420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
196420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * This method runs in a background thread and schedules an alarm for the
197420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * next calendar event, if necessary.
198420b7fb569773ae573fbe90c3a9c522d4c368863Erik     *
199420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * @param db TODO
200420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
201420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void runScheduleNextAlarm(boolean removeAlarms, CalendarProvider2 cp2) {
202a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting        SQLiteDatabase db = cp2.mDb;
203a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting        if (db == null) {
204a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting            return;
205a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting        }
206a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting
207420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Reset so that we can accept other schedules of next alarm
208420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mNextAlarmCheckScheduled.set(false);
209420b7fb569773ae573fbe90c3a9c522d4c368863Erik        db.beginTransaction();
210420b7fb569773ae573fbe90c3a9c522d4c368863Erik        try {
211420b7fb569773ae573fbe90c3a9c522d4c368863Erik            if (removeAlarms) {
212420b7fb569773ae573fbe90c3a9c522d4c368863Erik                removeScheduledAlarmsLocked(db);
213420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
214420b7fb569773ae573fbe90c3a9c522d4c368863Erik            scheduleNextAlarmLocked(db, cp2);
215420b7fb569773ae573fbe90c3a9c522d4c368863Erik            db.setTransactionSuccessful();
216420b7fb569773ae573fbe90c3a9c522d4c368863Erik        } finally {
217420b7fb569773ae573fbe90c3a9c522d4c368863Erik            db.endTransaction();
218420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
219420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
220420b7fb569773ae573fbe90c3a9c522d4c368863Erik
221420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void scheduleNextAlarmCheck(long triggerTime) {
222420b7fb569773ae573fbe90c3a9c522d4c368863Erik        Intent intent = new Intent(CalendarReceiver.SCHEDULE);
223420b7fb569773ae573fbe90c3a9c522d4c368863Erik        intent.setClass(mContext, CalendarReceiver.class);
224420b7fb569773ae573fbe90c3a9c522d4c368863Erik        PendingIntent pending = PendingIntent.getBroadcast(
225420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mContext, 0, intent, PendingIntent.FLAG_NO_CREATE);
226420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (pending != null) {
227420b7fb569773ae573fbe90c3a9c522d4c368863Erik            // Cancel any previous alarms that do the same thing.
228420b7fb569773ae573fbe90c3a9c522d4c368863Erik            cancel(pending);
229420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
230420b7fb569773ae573fbe90c3a9c522d4c368863Erik        pending = PendingIntent.getBroadcast(
231420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
232420b7fb569773ae573fbe90c3a9c522d4c368863Erik
233420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
234420b7fb569773ae573fbe90c3a9c522d4c368863Erik            Time time = new Time();
235420b7fb569773ae573fbe90c3a9c522d4c368863Erik            time.set(triggerTime);
236420b7fb569773ae573fbe90c3a9c522d4c368863Erik            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
237420b7fb569773ae573fbe90c3a9c522d4c368863Erik            Log.d(CalendarProvider2.TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
238420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
239420b7fb569773ae573fbe90c3a9c522d4c368863Erik
240420b7fb569773ae573fbe90c3a9c522d4c368863Erik        set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
241420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
242420b7fb569773ae573fbe90c3a9c522d4c368863Erik
243420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
244420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * This method looks at the 24-hour window from now for any events that it
245420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * needs to schedule. This method runs within a database transaction. It
246420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * also runs in a background thread. The CalendarProvider2 keeps track of
247420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * which alarms it has already scheduled to avoid scheduling them more than
248420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * once and for debugging problems with alarms. It stores this knowledge in
249420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * a database table called CalendarAlerts which persists across reboots. But
250420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * the actual alarm list is in memory and disappears if the phone loses
251420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * power. To avoid missing an alarm, we clear the entries in the
252420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * CalendarAlerts table when we start up the CalendarProvider2. Scheduling
253420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * an alarm multiple times is not tragic -- we filter out the extra ones
254420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * when we receive them. But we still need to keep track of the scheduled
255420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * alarms. The main reason is that we need to prevent multiple notifications
256420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * for the same alarm (on the receive side) in case we accidentally schedule
257420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * the same alarm multiple times. We don't have visibility into the system's
258420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * alarm list so we can never know for sure if we have already scheduled an
259420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * alarm and it's better to err on scheduling an alarm twice rather than
260420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * missing an alarm. Another reason we keep track of scheduled alarms in a
261420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * database table is that it makes it easy to run an SQL query to find the
262420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * next reminder that we haven't scheduled.
263420b7fb569773ae573fbe90c3a9c522d4c368863Erik     *
264420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * @param db the database
265420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * @param cp2 TODO
266420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
267420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private void scheduleNextAlarmLocked(SQLiteDatabase db, CalendarProvider2 cp2) {
268420b7fb569773ae573fbe90c3a9c522d4c368863Erik        Time time = new Time();
269420b7fb569773ae573fbe90c3a9c522d4c368863Erik
270420b7fb569773ae573fbe90c3a9c522d4c368863Erik        final long currentMillis = System.currentTimeMillis();
271420b7fb569773ae573fbe90c3a9c522d4c368863Erik        final long start = currentMillis - SCHEDULE_ALARM_SLACK;
272420b7fb569773ae573fbe90c3a9c522d4c368863Erik        final long end = start + (24 * 60 * 60 * 1000);
273420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
274420b7fb569773ae573fbe90c3a9c522d4c368863Erik            time.set(start);
275420b7fb569773ae573fbe90c3a9c522d4c368863Erik            String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
276420b7fb569773ae573fbe90c3a9c522d4c368863Erik            Log.d(CalendarProvider2.TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
277420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
278420b7fb569773ae573fbe90c3a9c522d4c368863Erik
279420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Delete rows in CalendarAlert where the corresponding Instance or
280420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Reminder no longer exist.
281420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Also clear old alarms but keep alarms around for a while to prevent
282420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // multiple alerts for the same reminder. The "clearUpToTime'
283420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // should be further in the past than the point in time where
284420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // we start searching for events (the "start" variable defined above).
285420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String selectArg[] = new String[] { Long.toString(
286420b7fb569773ae573fbe90c3a9c522d4c368863Erik                currentMillis - CLEAR_OLD_ALARM_THRESHOLD) };
287420b7fb569773ae573fbe90c3a9c522d4c368863Erik
288420b7fb569773ae573fbe90c3a9c522d4c368863Erik        int rowsDeleted = db.delete(
289420b7fb569773ae573fbe90c3a9c522d4c368863Erik                CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg);
290420b7fb569773ae573fbe90c3a9c522d4c368863Erik
291420b7fb569773ae573fbe90c3a9c522d4c368863Erik        long nextAlarmTime = end;
292420b7fb569773ae573fbe90c3a9c522d4c368863Erik        final ContentResolver resolver = mContext.getContentResolver();
293420b7fb569773ae573fbe90c3a9c522d4c368863Erik        final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(resolver, currentMillis);
294420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) {
295420b7fb569773ae573fbe90c3a9c522d4c368863Erik            nextAlarmTime = tmpAlarmTime;
296420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
297420b7fb569773ae573fbe90c3a9c522d4c368863Erik
298420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Extract events from the database sorted by alarm time. The
299420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // alarm times are computed from Instances.begin (whose units
300420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // are milliseconds) and Reminders.minutes (whose units are
301420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // minutes).
302420b7fb569773ae573fbe90c3a9c522d4c368863Erik        //
303420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Also, ignore events whose end time is already in the past.
304420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Also, ignore events alarms that we have already scheduled.
305420b7fb569773ae573fbe90c3a9c522d4c368863Erik        //
306420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Note 1: we can add support for the case where Reminders.minutes
307420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // equals -1 to mean use Calendars.minutes by adding a UNION for
308420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // that case where the two halves restrict the WHERE clause on
309420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Reminders.minutes != -1 and Reminders.minutes = 1, respectively.
310420b7fb569773ae573fbe90c3a9c522d4c368863Erik        //
311420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Note 2: we have to name "myAlarmTime" different from the
312420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // "alarmTime" column in CalendarAlerts because otherwise the
313420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // query won't find multiple alarms for the same event.
314420b7fb569773ae573fbe90c3a9c522d4c368863Erik        //
315420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // The CAST is needed in the query because otherwise the expression
316420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // will be untyped and sqlite3's manifest typing will not convert the
317420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // string query parameter to an int in myAlarmtime>=?, so the comparison
318420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // will fail. This could be simplified if bug 2464440 is resolved.
319420b7fb569773ae573fbe90c3a9c522d4c368863Erik
320420b7fb569773ae573fbe90c3a9c522d4c368863Erik        time.setToNow();
321420b7fb569773ae573fbe90c3a9c522d4c368863Erik        time.normalize(false);
322420b7fb569773ae573fbe90c3a9c522d4c368863Erik        long localOffset = time.gmtoff * 1000;
323420b7fb569773ae573fbe90c3a9c522d4c368863Erik
324420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String allDayOffset = " -(" + localOffset + ") ";
325420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String subQueryPrefix = "SELECT " + Instances.BEGIN;
326420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String subQuerySuffix = " -(" + Reminders.MINUTES + "*" + +DateUtils.MINUTE_IN_MILLIS + ")"
327420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " AS myAlarmTime" + "," + Tables.INSTANCES + "." + Instances.EVENT_ID
328420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " AS eventId" + "," + Instances.BEGIN + "," + Instances.END + ","
329420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Instances.TITLE + "," + Instances.ALL_DAY + "," + Reminders.METHOD + ","
330420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Reminders.MINUTES + " FROM " + Tables.INSTANCES + " INNER JOIN " + Views.EVENTS
331420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " ON (" + Views.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "."
332420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Instances.EVENT_ID + ")" + " INNER JOIN " + Tables.REMINDERS + " ON ("
333420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Tables.INSTANCES + "." + Instances.EVENT_ID + "=" + Tables.REMINDERS + "."
3344067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                + Reminders.EVENT_ID + ")" + " WHERE " + Calendars.VISIBLE + "=1"
335420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " AND myAlarmTime>=CAST(? AS INT)" + " AND myAlarmTime<=CAST(? AS INT)" + " AND "
336420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Instances.END + ">=?" + " AND " + Reminders.METHOD + "=" + Reminders.METHOD_ALERT;
337420b7fb569773ae573fbe90c3a9c522d4c368863Erik
338420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // we query separately for all day events to convert to local time from
339420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // UTC
340420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // we need to /subtract/ the offset to get the correct resulting local
341420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // time
342420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String allDayQuery = subQueryPrefix + allDayOffset + subQuerySuffix + " AND "
343420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Instances.ALL_DAY + "=1";
344420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String nonAllDayQuery = subQueryPrefix + subQuerySuffix + " AND " + Instances.ALL_DAY
345420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + "=0";
346420b7fb569773ae573fbe90c3a9c522d4c368863Erik
347420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // we use UNION ALL because we are guaranteed to have no dupes between
348420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // the two queries, and it is less expensive
349420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String query = "SELECT *" + " FROM (" + allDayQuery + " UNION ALL " + nonAllDayQuery + ")"
350420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // avoid rescheduling existing alarms
351420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " WHERE 0=(SELECT count(*) FROM " + Tables.CALENDAR_ALERTS + " CA" + " WHERE CA."
352420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + CalendarAlerts.EVENT_ID + "=eventId" + " AND CA." + CalendarAlerts.BEGIN + "="
353420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + Instances.BEGIN + " AND CA." + CalendarAlerts.ALARM_TIME + "=myAlarmTime)"
354420b7fb569773ae573fbe90c3a9c522d4c368863Erik                + " ORDER BY myAlarmTime," + Instances.BEGIN + "," + Instances.TITLE;
355420b7fb569773ae573fbe90c3a9c522d4c368863Erik
356420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String queryParams[] = new String[] { String.valueOf(start), String.valueOf(nextAlarmTime),
357420b7fb569773ae573fbe90c3a9c522d4c368863Erik                String.valueOf(currentMillis), String.valueOf(start), String.valueOf(nextAlarmTime),
358420b7fb569773ae573fbe90c3a9c522d4c368863Erik                String.valueOf(currentMillis) };
359420b7fb569773ae573fbe90c3a9c522d4c368863Erik
360420b7fb569773ae573fbe90c3a9c522d4c368863Erik        String instancesTimezone = cp2.mCalendarCache.readTimezoneInstances();
361420b7fb569773ae573fbe90c3a9c522d4c368863Erik        boolean isHomeTimezone = cp2.mCalendarCache.readTimezoneType().equals(
362420b7fb569773ae573fbe90c3a9c522d4c368863Erik                CalendarCache.TIMEZONE_TYPE_HOME);
363420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // expand this range by a day on either end to account for all day
364420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // events
365420b7fb569773ae573fbe90c3a9c522d4c368863Erik        cp2.acquireInstanceRangeLocked(
366420b7fb569773ae573fbe90c3a9c522d4c368863Erik                start - DateUtils.DAY_IN_MILLIS, end + DateUtils.DAY_IN_MILLIS, false /*
367420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       * don't
368420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       * use
369420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       * minimum
370420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       * expansion
371420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       * windows
372420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                                                                       */,
373420b7fb569773ae573fbe90c3a9c522d4c368863Erik                false /* do not force Instances deletion and expansion */, instancesTimezone,
374420b7fb569773ae573fbe90c3a9c522d4c368863Erik                isHomeTimezone);
375420b7fb569773ae573fbe90c3a9c522d4c368863Erik        Cursor cursor = null;
376420b7fb569773ae573fbe90c3a9c522d4c368863Erik        try {
377420b7fb569773ae573fbe90c3a9c522d4c368863Erik            cursor = db.rawQuery(query, queryParams);
378420b7fb569773ae573fbe90c3a9c522d4c368863Erik
379420b7fb569773ae573fbe90c3a9c522d4c368863Erik            final int beginIndex = cursor.getColumnIndex(Instances.BEGIN);
380420b7fb569773ae573fbe90c3a9c522d4c368863Erik            final int endIndex = cursor.getColumnIndex(Instances.END);
381420b7fb569773ae573fbe90c3a9c522d4c368863Erik            final int eventIdIndex = cursor.getColumnIndex("eventId");
382420b7fb569773ae573fbe90c3a9c522d4c368863Erik            final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime");
383420b7fb569773ae573fbe90c3a9c522d4c368863Erik            final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES);
384420b7fb569773ae573fbe90c3a9c522d4c368863Erik
385420b7fb569773ae573fbe90c3a9c522d4c368863Erik            if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
386420b7fb569773ae573fbe90c3a9c522d4c368863Erik                time.set(nextAlarmTime);
387420b7fb569773ae573fbe90c3a9c522d4c368863Erik                String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
388420b7fb569773ae573fbe90c3a9c522d4c368863Erik                Log.d(CalendarProvider2.TAG,
389420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        "cursor results: " + cursor.getCount() + " nextAlarmTime: " + alarmTimeStr);
390420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
391420b7fb569773ae573fbe90c3a9c522d4c368863Erik
392420b7fb569773ae573fbe90c3a9c522d4c368863Erik            while (cursor.moveToNext()) {
393420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // Schedule all alarms whose alarm time is as early as any
394420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // scheduled alarm. For example, if the earliest alarm is at
395420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // 1pm, then we will schedule all alarms that occur at 1pm
396420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // but no alarms that occur later than 1pm.
397420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // Actually, we allow alarms up to a minute later to also
398420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // be scheduled so that we don't have to check immediately
399420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // again after an event alarm goes off.
400420b7fb569773ae573fbe90c3a9c522d4c368863Erik                final long alarmTime = cursor.getLong(alarmTimeIndex);
401420b7fb569773ae573fbe90c3a9c522d4c368863Erik                final long eventId = cursor.getLong(eventIdIndex);
402420b7fb569773ae573fbe90c3a9c522d4c368863Erik                final int minutes = cursor.getInt(minutesIndex);
403420b7fb569773ae573fbe90c3a9c522d4c368863Erik                final long startTime = cursor.getLong(beginIndex);
404420b7fb569773ae573fbe90c3a9c522d4c368863Erik                final long endTime = cursor.getLong(endIndex);
405420b7fb569773ae573fbe90c3a9c522d4c368863Erik
406420b7fb569773ae573fbe90c3a9c522d4c368863Erik                if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
407420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    time.set(alarmTime);
408420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
409420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    time.set(startTime);
410420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
411420b7fb569773ae573fbe90c3a9c522d4c368863Erik
412420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    Log.d(CalendarProvider2.TAG,
413420b7fb569773ae573fbe90c3a9c522d4c368863Erik                            "  looking at id: " + eventId + " " + startTime + startTimeStr
414420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                    + " alarm: " + alarmTime + schedTime);
415420b7fb569773ae573fbe90c3a9c522d4c368863Erik                }
416420b7fb569773ae573fbe90c3a9c522d4c368863Erik
417420b7fb569773ae573fbe90c3a9c522d4c368863Erik                if (alarmTime < nextAlarmTime) {
418420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    nextAlarmTime = alarmTime;
419420b7fb569773ae573fbe90c3a9c522d4c368863Erik                } else if (alarmTime > nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) {
420420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    // This event alarm (and all later ones) will be scheduled
421420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    // later.
422420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
423420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        Log.d(CalendarProvider2.TAG,
424420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                "This event alarm (and all later ones) will be scheduled later");
425420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    }
426420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    break;
427420b7fb569773ae573fbe90c3a9c522d4c368863Erik                }
428420b7fb569773ae573fbe90c3a9c522d4c368863Erik
429420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // Avoid an SQLiteContraintException by checking if this alarm
430420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // already exists in the table.
431420b7fb569773ae573fbe90c3a9c522d4c368863Erik                if (CalendarAlerts.alarmExists(resolver, eventId, startTime, alarmTime)) {
432420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
433420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        int titleIndex = cursor.getColumnIndex(Events.TITLE);
434420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        String title = cursor.getString(titleIndex);
435420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        Log.d(CalendarProvider2.TAG,
436420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                "  alarm exists for id: " + eventId + " " + title);
437420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    }
438420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    continue;
439420b7fb569773ae573fbe90c3a9c522d4c368863Erik                }
440420b7fb569773ae573fbe90c3a9c522d4c368863Erik
441420b7fb569773ae573fbe90c3a9c522d4c368863Erik                // Insert this alarm into the CalendarAlerts table
442420b7fb569773ae573fbe90c3a9c522d4c368863Erik                Uri uri = CalendarAlerts.insert(
443420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        resolver, eventId, startTime, endTime, alarmTime, minutes);
444420b7fb569773ae573fbe90c3a9c522d4c368863Erik                if (uri == null) {
445420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) {
446420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        Log.e(CalendarProvider2.TAG, "runScheduleNextAlarm() insert into "
447420b7fb569773ae573fbe90c3a9c522d4c368863Erik                                + "CalendarAlerts table failed");
448420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    }
449420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    continue;
450420b7fb569773ae573fbe90c3a9c522d4c368863Erik                }
451420b7fb569773ae573fbe90c3a9c522d4c368863Erik
452420b7fb569773ae573fbe90c3a9c522d4c368863Erik                scheduleAlarm(alarmTime);
453420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
454420b7fb569773ae573fbe90c3a9c522d4c368863Erik        } finally {
455420b7fb569773ae573fbe90c3a9c522d4c368863Erik            if (cursor != null) {
456420b7fb569773ae573fbe90c3a9c522d4c368863Erik                cursor.close();
457420b7fb569773ae573fbe90c3a9c522d4c368863Erik            }
458420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
459420b7fb569773ae573fbe90c3a9c522d4c368863Erik
460420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // Refresh notification bar
461420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (rowsDeleted > 0) {
462420b7fb569773ae573fbe90c3a9c522d4c368863Erik            scheduleAlarm(currentMillis);
463420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
464420b7fb569773ae573fbe90c3a9c522d4c368863Erik
465420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // If we scheduled an event alarm, then schedule the next alarm check
466420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // for one minute past that alarm. Otherwise, if there were no
467420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // event alarms scheduled, then check again in 24 hours. If a new
468420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // event is inserted before the next alarm check, then this method
469420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // will be run again when the new event is inserted.
470420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (nextAlarmTime != Long.MAX_VALUE) {
471420b7fb569773ae573fbe90c3a9c522d4c368863Erik            scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS);
472420b7fb569773ae573fbe90c3a9c522d4c368863Erik        } else {
473420b7fb569773ae573fbe90c3a9c522d4c368863Erik            scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS);
474420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
475420b7fb569773ae573fbe90c3a9c522d4c368863Erik    }
476420b7fb569773ae573fbe90c3a9c522d4c368863Erik
477420b7fb569773ae573fbe90c3a9c522d4c368863Erik    /**
478420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * Removes the entries in the CalendarAlerts table for alarms that we have
479420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * scheduled but that have not fired yet. We do this to ensure that we don't
480420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * miss an alarm. The CalendarAlerts table keeps track of the alarms that we
481420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * have scheduled but the actual alarm list is in memory and will be cleared
482420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * if the phone reboots. We don't need to remove entries that have already
483420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * fired, and in fact we should not remove them because we need to display
484420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * the notifications until the user dismisses them. We could remove entries
485420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * that have fired and been dismissed, but we leave them around for a while
486420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * because it makes it easier to debug problems. Entries that are old enough
487420b7fb569773ae573fbe90c3a9c522d4c368863Erik     * will be cleaned up later when we schedule new alarms.
488420b7fb569773ae573fbe90c3a9c522d4c368863Erik     */
489420b7fb569773ae573fbe90c3a9c522d4c368863Erik    private static void removeScheduledAlarmsLocked(SQLiteDatabase db) {
490420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) {
491420b7fb569773ae573fbe90c3a9c522d4c368863Erik            Log.d(CalendarProvider2.TAG, "removing scheduled alarms");
492420b7fb569773ae573fbe90c3a9c522d4c368863Erik        }
493744fa975b40b24ea7377c0e273f60a7a4d47e2e0RoboErik        db.delete(CalendarAlerts.TABLE_NAME, CalendarAlerts.STATE + "="
494744fa975b40b24ea7377c0e273f60a7a4d47e2e0RoboErik                + CalendarAlerts.STATE_SCHEDULED, null /* whereArgs */);
495e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
496e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
497e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void set(int type, long triggerAtTime, PendingIntent operation) {
498e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mAlarmManager.set(type, triggerAtTime, operation);
499e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
500e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
501e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void cancel(PendingIntent operation) {
502e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mAlarmManager.cancel(operation);
503e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
504e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
505e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void scheduleAlarm(long alarmTime) {
506b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.CalendarAlerts.scheduleAlarm(mContext, mAlarmManager, alarmTime);
507e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
508e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
509e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void rescheduleMissedAlarms(ContentResolver cr) {
510b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.CalendarAlerts.rescheduleMissedAlarms(cr, mContext, mAlarmManager);
511e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
512e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio}
513