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 */ 12298e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert private final 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); 13098e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert 13198e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert PowerManager powerManager = (PowerManager) mContext.getSystemService( 13298e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert Context.POWER_SERVICE); 13398e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert // Create a wake lock that will be used when we are actually 13498e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert // scheduling the next alarm 13598e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert mScheduleNextAlarmWakeLock = powerManager.newWakeLock( 13698e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert PowerManager.PARTIAL_WAKE_LOCK, SCHEDULE_NEXT_ALARM_WAKE_LOCK); 13798e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert // We want the Wake Lock to be reference counted (so that we dont 13898e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert // need to take care 13998e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert // about its reference counting) 14098e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert mScheduleNextAlarmWakeLock.setReferenceCounted(true); 141e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 142e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio 143e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio protected void initializeWithContext(Context context) { 144e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio mContext = context; 145e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 146420b7fb569773ae573fbe90c3a9c522d4c368863Erik mNextAlarmCheckScheduled = new AtomicBoolean(false); 147420b7fb569773ae573fbe90c3a9c522d4c368863Erik mAlarmLock = new Object(); 148420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 149420b7fb569773ae573fbe90c3a9c522d4c368863Erik 150420b7fb569773ae573fbe90c3a9c522d4c368863Erik void scheduleNextAlarm(boolean removeAlarms) { 15149b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // We must always run the following when 'removeAlarms' is true. Previously it 15249b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // was possible to have a race condition on startup between TIME_CHANGED and 15349b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // BOOT_COMPLETED broadcast actions. This resulted in alarms being 15449b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // missed (Bug 7221716) when the TIME_CHANGED broadcast ('removeAlarms' = false) 15549b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // happened right before the BOOT_COMPLETED ('removeAlarms' = true), and the 15649b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting // BOOT_COMPLETED action was skipped since there was concurrent scheduling in progress. 15749b7d3c4222a6b9e8e4639ba6d5128df5eac7e73Sara Ting if (!mNextAlarmCheckScheduled.getAndSet(true) || removeAlarms) { 158420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 159420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, "Scheduling check of next Alarm"); 160420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 161420b7fb569773ae573fbe90c3a9c522d4c368863Erik Intent intent = new Intent(ACTION_CHECK_NEXT_ALARM); 162420b7fb569773ae573fbe90c3a9c522d4c368863Erik intent.putExtra(REMOVE_ALARM_VALUE, removeAlarms); 163420b7fb569773ae573fbe90c3a9c522d4c368863Erik PendingIntent pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent, 164420b7fb569773ae573fbe90c3a9c522d4c368863Erik PendingIntent.FLAG_NO_CREATE); 165420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (pending != null) { 166420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Cancel any previous Alarm check requests 167420b7fb569773ae573fbe90c3a9c522d4c368863Erik cancel(pending); 168420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 169420b7fb569773ae573fbe90c3a9c522d4c368863Erik pending = PendingIntent.getBroadcast(mContext, 0 /* ignored */, intent, 170420b7fb569773ae573fbe90c3a9c522d4c368863Erik PendingIntent.FLAG_CANCEL_CURRENT); 171420b7fb569773ae573fbe90c3a9c522d4c368863Erik 172420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Trigger the check in 5s from now 173420b7fb569773ae573fbe90c3a9c522d4c368863Erik long triggerAtTime = SystemClock.elapsedRealtime() + ALARM_CHECK_DELAY_MILLIS; 174420b7fb569773ae573fbe90c3a9c522d4c368863Erik set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pending); 175420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 176420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 177420b7fb569773ae573fbe90c3a9c522d4c368863Erik 178420b7fb569773ae573fbe90c3a9c522d4c368863Erik PowerManager.WakeLock getScheduleNextAlarmWakeLock() { 179420b7fb569773ae573fbe90c3a9c522d4c368863Erik return mScheduleNextAlarmWakeLock; 180420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 181420b7fb569773ae573fbe90c3a9c522d4c368863Erik 182420b7fb569773ae573fbe90c3a9c522d4c368863Erik void acquireScheduleNextAlarmWakeLock() { 183420b7fb569773ae573fbe90c3a9c522d4c368863Erik getScheduleNextAlarmWakeLock().acquire(); 184420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 185420b7fb569773ae573fbe90c3a9c522d4c368863Erik 186420b7fb569773ae573fbe90c3a9c522d4c368863Erik void releaseScheduleNextAlarmWakeLock() { 187d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert try { 188d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert getScheduleNextAlarmWakeLock().release(); 189d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert } catch (RuntimeException e) { 190d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert if (!e.getMessage().startsWith("WakeLock under-locked ")) { 191d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert throw e; 192d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert } 193d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert Log.w(TAG, "WakeLock under-locked ignored."); 194d46932bf3e0cc194ca8e6194a9f4e619b6bd009fAlon Albert } 195420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 196420b7fb569773ae573fbe90c3a9c522d4c368863Erik 197420b7fb569773ae573fbe90c3a9c522d4c368863Erik void rescheduleMissedAlarms() { 198420b7fb569773ae573fbe90c3a9c522d4c368863Erik rescheduleMissedAlarms(mContext.getContentResolver()); 199420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 200420b7fb569773ae573fbe90c3a9c522d4c368863Erik 201420b7fb569773ae573fbe90c3a9c522d4c368863Erik /** 202420b7fb569773ae573fbe90c3a9c522d4c368863Erik * This method runs in a background thread and schedules an alarm for the 203420b7fb569773ae573fbe90c3a9c522d4c368863Erik * next calendar event, if necessary. 204420b7fb569773ae573fbe90c3a9c522d4c368863Erik * 205420b7fb569773ae573fbe90c3a9c522d4c368863Erik * @param db TODO 206420b7fb569773ae573fbe90c3a9c522d4c368863Erik */ 207420b7fb569773ae573fbe90c3a9c522d4c368863Erik void runScheduleNextAlarm(boolean removeAlarms, CalendarProvider2 cp2) { 208a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting SQLiteDatabase db = cp2.mDb; 209a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting if (db == null) { 210a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting return; 211a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting } 212a7c3f329245dc370151e611fdad85e177f4f6000Sara Ting 213420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Reset so that we can accept other schedules of next alarm 214420b7fb569773ae573fbe90c3a9c522d4c368863Erik mNextAlarmCheckScheduled.set(false); 215420b7fb569773ae573fbe90c3a9c522d4c368863Erik db.beginTransaction(); 216420b7fb569773ae573fbe90c3a9c522d4c368863Erik try { 217420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (removeAlarms) { 218420b7fb569773ae573fbe90c3a9c522d4c368863Erik removeScheduledAlarmsLocked(db); 219420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 220420b7fb569773ae573fbe90c3a9c522d4c368863Erik scheduleNextAlarmLocked(db, cp2); 221420b7fb569773ae573fbe90c3a9c522d4c368863Erik db.setTransactionSuccessful(); 222420b7fb569773ae573fbe90c3a9c522d4c368863Erik } finally { 223420b7fb569773ae573fbe90c3a9c522d4c368863Erik db.endTransaction(); 224420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 225420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 226420b7fb569773ae573fbe90c3a9c522d4c368863Erik 227420b7fb569773ae573fbe90c3a9c522d4c368863Erik void scheduleNextAlarmCheck(long triggerTime) { 228420b7fb569773ae573fbe90c3a9c522d4c368863Erik Intent intent = new Intent(CalendarReceiver.SCHEDULE); 229420b7fb569773ae573fbe90c3a9c522d4c368863Erik intent.setClass(mContext, CalendarReceiver.class); 230420b7fb569773ae573fbe90c3a9c522d4c368863Erik PendingIntent pending = PendingIntent.getBroadcast( 231420b7fb569773ae573fbe90c3a9c522d4c368863Erik mContext, 0, intent, PendingIntent.FLAG_NO_CREATE); 232420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (pending != null) { 233420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Cancel any previous alarms that do the same thing. 234420b7fb569773ae573fbe90c3a9c522d4c368863Erik cancel(pending); 235420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 236420b7fb569773ae573fbe90c3a9c522d4c368863Erik pending = PendingIntent.getBroadcast( 237420b7fb569773ae573fbe90c3a9c522d4c368863Erik mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 238420b7fb569773ae573fbe90c3a9c522d4c368863Erik 239420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 240420b7fb569773ae573fbe90c3a9c522d4c368863Erik Time time = new Time(); 241420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.set(triggerTime); 242420b7fb569773ae573fbe90c3a9c522d4c368863Erik String timeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 243420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr); 244420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 245420b7fb569773ae573fbe90c3a9c522d4c368863Erik 246420b7fb569773ae573fbe90c3a9c522d4c368863Erik set(AlarmManager.RTC_WAKEUP, triggerTime, pending); 247420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 248420b7fb569773ae573fbe90c3a9c522d4c368863Erik 249420b7fb569773ae573fbe90c3a9c522d4c368863Erik /** 250420b7fb569773ae573fbe90c3a9c522d4c368863Erik * This method looks at the 24-hour window from now for any events that it 251420b7fb569773ae573fbe90c3a9c522d4c368863Erik * needs to schedule. This method runs within a database transaction. It 252420b7fb569773ae573fbe90c3a9c522d4c368863Erik * also runs in a background thread. The CalendarProvider2 keeps track of 253420b7fb569773ae573fbe90c3a9c522d4c368863Erik * which alarms it has already scheduled to avoid scheduling them more than 254420b7fb569773ae573fbe90c3a9c522d4c368863Erik * once and for debugging problems with alarms. It stores this knowledge in 255420b7fb569773ae573fbe90c3a9c522d4c368863Erik * a database table called CalendarAlerts which persists across reboots. But 256420b7fb569773ae573fbe90c3a9c522d4c368863Erik * the actual alarm list is in memory and disappears if the phone loses 257420b7fb569773ae573fbe90c3a9c522d4c368863Erik * power. To avoid missing an alarm, we clear the entries in the 258420b7fb569773ae573fbe90c3a9c522d4c368863Erik * CalendarAlerts table when we start up the CalendarProvider2. Scheduling 259420b7fb569773ae573fbe90c3a9c522d4c368863Erik * an alarm multiple times is not tragic -- we filter out the extra ones 260420b7fb569773ae573fbe90c3a9c522d4c368863Erik * when we receive them. But we still need to keep track of the scheduled 261420b7fb569773ae573fbe90c3a9c522d4c368863Erik * alarms. The main reason is that we need to prevent multiple notifications 262420b7fb569773ae573fbe90c3a9c522d4c368863Erik * for the same alarm (on the receive side) in case we accidentally schedule 263420b7fb569773ae573fbe90c3a9c522d4c368863Erik * the same alarm multiple times. We don't have visibility into the system's 264420b7fb569773ae573fbe90c3a9c522d4c368863Erik * alarm list so we can never know for sure if we have already scheduled an 265420b7fb569773ae573fbe90c3a9c522d4c368863Erik * alarm and it's better to err on scheduling an alarm twice rather than 266420b7fb569773ae573fbe90c3a9c522d4c368863Erik * missing an alarm. Another reason we keep track of scheduled alarms in a 267420b7fb569773ae573fbe90c3a9c522d4c368863Erik * database table is that it makes it easy to run an SQL query to find the 268420b7fb569773ae573fbe90c3a9c522d4c368863Erik * next reminder that we haven't scheduled. 269420b7fb569773ae573fbe90c3a9c522d4c368863Erik * 270420b7fb569773ae573fbe90c3a9c522d4c368863Erik * @param db the database 271420b7fb569773ae573fbe90c3a9c522d4c368863Erik * @param cp2 TODO 272420b7fb569773ae573fbe90c3a9c522d4c368863Erik */ 273420b7fb569773ae573fbe90c3a9c522d4c368863Erik private void scheduleNextAlarmLocked(SQLiteDatabase db, CalendarProvider2 cp2) { 274420b7fb569773ae573fbe90c3a9c522d4c368863Erik Time time = new Time(); 275420b7fb569773ae573fbe90c3a9c522d4c368863Erik 276420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long currentMillis = System.currentTimeMillis(); 277420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long start = currentMillis - SCHEDULE_ALARM_SLACK; 278420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long end = start + (24 * 60 * 60 * 1000); 279420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 280420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.set(start); 281420b7fb569773ae573fbe90c3a9c522d4c368863Erik String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 282420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, "runScheduleNextAlarm() start search: " + startTimeStr); 283420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 284420b7fb569773ae573fbe90c3a9c522d4c368863Erik 285420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Delete rows in CalendarAlert where the corresponding Instance or 286420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Reminder no longer exist. 287420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Also clear old alarms but keep alarms around for a while to prevent 288420b7fb569773ae573fbe90c3a9c522d4c368863Erik // multiple alerts for the same reminder. The "clearUpToTime' 289420b7fb569773ae573fbe90c3a9c522d4c368863Erik // should be further in the past than the point in time where 290420b7fb569773ae573fbe90c3a9c522d4c368863Erik // we start searching for events (the "start" variable defined above). 291420b7fb569773ae573fbe90c3a9c522d4c368863Erik String selectArg[] = new String[] { Long.toString( 292420b7fb569773ae573fbe90c3a9c522d4c368863Erik currentMillis - CLEAR_OLD_ALARM_THRESHOLD) }; 293420b7fb569773ae573fbe90c3a9c522d4c368863Erik 294420b7fb569773ae573fbe90c3a9c522d4c368863Erik int rowsDeleted = db.delete( 295420b7fb569773ae573fbe90c3a9c522d4c368863Erik CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg); 296420b7fb569773ae573fbe90c3a9c522d4c368863Erik 297420b7fb569773ae573fbe90c3a9c522d4c368863Erik long nextAlarmTime = end; 298420b7fb569773ae573fbe90c3a9c522d4c368863Erik final ContentResolver resolver = mContext.getContentResolver(); 299420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(resolver, currentMillis); 300420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) { 301420b7fb569773ae573fbe90c3a9c522d4c368863Erik nextAlarmTime = tmpAlarmTime; 302420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 303420b7fb569773ae573fbe90c3a9c522d4c368863Erik 304420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Extract events from the database sorted by alarm time. The 305420b7fb569773ae573fbe90c3a9c522d4c368863Erik // alarm times are computed from Instances.begin (whose units 306420b7fb569773ae573fbe90c3a9c522d4c368863Erik // are milliseconds) and Reminders.minutes (whose units are 307420b7fb569773ae573fbe90c3a9c522d4c368863Erik // minutes). 308420b7fb569773ae573fbe90c3a9c522d4c368863Erik // 309420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Also, ignore events whose end time is already in the past. 310420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Also, ignore events alarms that we have already scheduled. 311420b7fb569773ae573fbe90c3a9c522d4c368863Erik // 312420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Note 1: we can add support for the case where Reminders.minutes 313420b7fb569773ae573fbe90c3a9c522d4c368863Erik // equals -1 to mean use Calendars.minutes by adding a UNION for 314420b7fb569773ae573fbe90c3a9c522d4c368863Erik // that case where the two halves restrict the WHERE clause on 315420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Reminders.minutes != -1 and Reminders.minutes = 1, respectively. 316420b7fb569773ae573fbe90c3a9c522d4c368863Erik // 317420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Note 2: we have to name "myAlarmTime" different from the 318420b7fb569773ae573fbe90c3a9c522d4c368863Erik // "alarmTime" column in CalendarAlerts because otherwise the 319420b7fb569773ae573fbe90c3a9c522d4c368863Erik // query won't find multiple alarms for the same event. 320420b7fb569773ae573fbe90c3a9c522d4c368863Erik // 321420b7fb569773ae573fbe90c3a9c522d4c368863Erik // The CAST is needed in the query because otherwise the expression 322420b7fb569773ae573fbe90c3a9c522d4c368863Erik // will be untyped and sqlite3's manifest typing will not convert the 323420b7fb569773ae573fbe90c3a9c522d4c368863Erik // string query parameter to an int in myAlarmtime>=?, so the comparison 324420b7fb569773ae573fbe90c3a9c522d4c368863Erik // will fail. This could be simplified if bug 2464440 is resolved. 325420b7fb569773ae573fbe90c3a9c522d4c368863Erik 326420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.setToNow(); 327420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.normalize(false); 328420b7fb569773ae573fbe90c3a9c522d4c368863Erik long localOffset = time.gmtoff * 1000; 329420b7fb569773ae573fbe90c3a9c522d4c368863Erik 330420b7fb569773ae573fbe90c3a9c522d4c368863Erik String allDayOffset = " -(" + localOffset + ") "; 331420b7fb569773ae573fbe90c3a9c522d4c368863Erik String subQueryPrefix = "SELECT " + Instances.BEGIN; 332420b7fb569773ae573fbe90c3a9c522d4c368863Erik String subQuerySuffix = " -(" + Reminders.MINUTES + "*" + +DateUtils.MINUTE_IN_MILLIS + ")" 333420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " AS myAlarmTime" + "," + Tables.INSTANCES + "." + Instances.EVENT_ID 334420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " AS eventId" + "," + Instances.BEGIN + "," + Instances.END + "," 335420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Instances.TITLE + "," + Instances.ALL_DAY + "," + Reminders.METHOD + "," 336420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Reminders.MINUTES + " FROM " + Tables.INSTANCES + " INNER JOIN " + Views.EVENTS 337420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " ON (" + Views.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "." 338420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Instances.EVENT_ID + ")" + " INNER JOIN " + Tables.REMINDERS + " ON (" 339420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Tables.INSTANCES + "." + Instances.EVENT_ID + "=" + Tables.REMINDERS + "." 3404067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden + Reminders.EVENT_ID + ")" + " WHERE " + Calendars.VISIBLE + "=1" 341420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " AND myAlarmTime>=CAST(? AS INT)" + " AND myAlarmTime<=CAST(? AS INT)" + " AND " 342420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Instances.END + ">=?" + " AND " + Reminders.METHOD + "=" + Reminders.METHOD_ALERT; 343420b7fb569773ae573fbe90c3a9c522d4c368863Erik 344420b7fb569773ae573fbe90c3a9c522d4c368863Erik // we query separately for all day events to convert to local time from 345420b7fb569773ae573fbe90c3a9c522d4c368863Erik // UTC 346420b7fb569773ae573fbe90c3a9c522d4c368863Erik // we need to /subtract/ the offset to get the correct resulting local 347420b7fb569773ae573fbe90c3a9c522d4c368863Erik // time 348420b7fb569773ae573fbe90c3a9c522d4c368863Erik String allDayQuery = subQueryPrefix + allDayOffset + subQuerySuffix + " AND " 349420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Instances.ALL_DAY + "=1"; 350420b7fb569773ae573fbe90c3a9c522d4c368863Erik String nonAllDayQuery = subQueryPrefix + subQuerySuffix + " AND " + Instances.ALL_DAY 351420b7fb569773ae573fbe90c3a9c522d4c368863Erik + "=0"; 352420b7fb569773ae573fbe90c3a9c522d4c368863Erik 353420b7fb569773ae573fbe90c3a9c522d4c368863Erik // we use UNION ALL because we are guaranteed to have no dupes between 354420b7fb569773ae573fbe90c3a9c522d4c368863Erik // the two queries, and it is less expensive 355420b7fb569773ae573fbe90c3a9c522d4c368863Erik String query = "SELECT *" + " FROM (" + allDayQuery + " UNION ALL " + nonAllDayQuery + ")" 356420b7fb569773ae573fbe90c3a9c522d4c368863Erik // avoid rescheduling existing alarms 357420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " WHERE 0=(SELECT count(*) FROM " + Tables.CALENDAR_ALERTS + " CA" + " WHERE CA." 358420b7fb569773ae573fbe90c3a9c522d4c368863Erik + CalendarAlerts.EVENT_ID + "=eventId" + " AND CA." + CalendarAlerts.BEGIN + "=" 359420b7fb569773ae573fbe90c3a9c522d4c368863Erik + Instances.BEGIN + " AND CA." + CalendarAlerts.ALARM_TIME + "=myAlarmTime)" 360420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " ORDER BY myAlarmTime," + Instances.BEGIN + "," + Instances.TITLE; 361420b7fb569773ae573fbe90c3a9c522d4c368863Erik 362420b7fb569773ae573fbe90c3a9c522d4c368863Erik String queryParams[] = new String[] { String.valueOf(start), String.valueOf(nextAlarmTime), 363420b7fb569773ae573fbe90c3a9c522d4c368863Erik String.valueOf(currentMillis), String.valueOf(start), String.valueOf(nextAlarmTime), 364420b7fb569773ae573fbe90c3a9c522d4c368863Erik String.valueOf(currentMillis) }; 365420b7fb569773ae573fbe90c3a9c522d4c368863Erik 366420b7fb569773ae573fbe90c3a9c522d4c368863Erik String instancesTimezone = cp2.mCalendarCache.readTimezoneInstances(); 367420b7fb569773ae573fbe90c3a9c522d4c368863Erik boolean isHomeTimezone = cp2.mCalendarCache.readTimezoneType().equals( 368420b7fb569773ae573fbe90c3a9c522d4c368863Erik CalendarCache.TIMEZONE_TYPE_HOME); 369420b7fb569773ae573fbe90c3a9c522d4c368863Erik // expand this range by a day on either end to account for all day 370420b7fb569773ae573fbe90c3a9c522d4c368863Erik // events 371420b7fb569773ae573fbe90c3a9c522d4c368863Erik cp2.acquireInstanceRangeLocked( 372420b7fb569773ae573fbe90c3a9c522d4c368863Erik start - DateUtils.DAY_IN_MILLIS, end + DateUtils.DAY_IN_MILLIS, false /* 373420b7fb569773ae573fbe90c3a9c522d4c368863Erik * don't 374420b7fb569773ae573fbe90c3a9c522d4c368863Erik * use 375420b7fb569773ae573fbe90c3a9c522d4c368863Erik * minimum 376420b7fb569773ae573fbe90c3a9c522d4c368863Erik * expansion 377420b7fb569773ae573fbe90c3a9c522d4c368863Erik * windows 378420b7fb569773ae573fbe90c3a9c522d4c368863Erik */, 379420b7fb569773ae573fbe90c3a9c522d4c368863Erik false /* do not force Instances deletion and expansion */, instancesTimezone, 380420b7fb569773ae573fbe90c3a9c522d4c368863Erik isHomeTimezone); 381420b7fb569773ae573fbe90c3a9c522d4c368863Erik Cursor cursor = null; 382420b7fb569773ae573fbe90c3a9c522d4c368863Erik try { 383420b7fb569773ae573fbe90c3a9c522d4c368863Erik cursor = db.rawQuery(query, queryParams); 384420b7fb569773ae573fbe90c3a9c522d4c368863Erik 385420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int beginIndex = cursor.getColumnIndex(Instances.BEGIN); 386420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int endIndex = cursor.getColumnIndex(Instances.END); 387420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int eventIdIndex = cursor.getColumnIndex("eventId"); 388420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime"); 389420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES); 390420b7fb569773ae573fbe90c3a9c522d4c368863Erik 391420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 392420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.set(nextAlarmTime); 393420b7fb569773ae573fbe90c3a9c522d4c368863Erik String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 394420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, 395420b7fb569773ae573fbe90c3a9c522d4c368863Erik "cursor results: " + cursor.getCount() + " nextAlarmTime: " + alarmTimeStr); 396420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 397420b7fb569773ae573fbe90c3a9c522d4c368863Erik 398420b7fb569773ae573fbe90c3a9c522d4c368863Erik while (cursor.moveToNext()) { 399420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Schedule all alarms whose alarm time is as early as any 400420b7fb569773ae573fbe90c3a9c522d4c368863Erik // scheduled alarm. For example, if the earliest alarm is at 401420b7fb569773ae573fbe90c3a9c522d4c368863Erik // 1pm, then we will schedule all alarms that occur at 1pm 402420b7fb569773ae573fbe90c3a9c522d4c368863Erik // but no alarms that occur later than 1pm. 403420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Actually, we allow alarms up to a minute later to also 404420b7fb569773ae573fbe90c3a9c522d4c368863Erik // be scheduled so that we don't have to check immediately 405420b7fb569773ae573fbe90c3a9c522d4c368863Erik // again after an event alarm goes off. 406420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long alarmTime = cursor.getLong(alarmTimeIndex); 407420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long eventId = cursor.getLong(eventIdIndex); 408420b7fb569773ae573fbe90c3a9c522d4c368863Erik final int minutes = cursor.getInt(minutesIndex); 409420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long startTime = cursor.getLong(beginIndex); 410420b7fb569773ae573fbe90c3a9c522d4c368863Erik final long endTime = cursor.getLong(endIndex); 411420b7fb569773ae573fbe90c3a9c522d4c368863Erik 412420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 413420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.set(alarmTime); 414420b7fb569773ae573fbe90c3a9c522d4c368863Erik String schedTime = time.format(" %a, %b %d, %Y %I:%M%P"); 415420b7fb569773ae573fbe90c3a9c522d4c368863Erik time.set(startTime); 416420b7fb569773ae573fbe90c3a9c522d4c368863Erik String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 417420b7fb569773ae573fbe90c3a9c522d4c368863Erik 418420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, 419420b7fb569773ae573fbe90c3a9c522d4c368863Erik " looking at id: " + eventId + " " + startTime + startTimeStr 420420b7fb569773ae573fbe90c3a9c522d4c368863Erik + " alarm: " + alarmTime + schedTime); 421420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 422420b7fb569773ae573fbe90c3a9c522d4c368863Erik 423420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (alarmTime < nextAlarmTime) { 424420b7fb569773ae573fbe90c3a9c522d4c368863Erik nextAlarmTime = alarmTime; 425420b7fb569773ae573fbe90c3a9c522d4c368863Erik } else if (alarmTime > nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) { 426420b7fb569773ae573fbe90c3a9c522d4c368863Erik // This event alarm (and all later ones) will be scheduled 427420b7fb569773ae573fbe90c3a9c522d4c368863Erik // later. 428420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 429420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, 430420b7fb569773ae573fbe90c3a9c522d4c368863Erik "This event alarm (and all later ones) will be scheduled later"); 431420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 432420b7fb569773ae573fbe90c3a9c522d4c368863Erik break; 433420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 434420b7fb569773ae573fbe90c3a9c522d4c368863Erik 435420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Avoid an SQLiteContraintException by checking if this alarm 436420b7fb569773ae573fbe90c3a9c522d4c368863Erik // already exists in the table. 437420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (CalendarAlerts.alarmExists(resolver, eventId, startTime, alarmTime)) { 438420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 439420b7fb569773ae573fbe90c3a9c522d4c368863Erik int titleIndex = cursor.getColumnIndex(Events.TITLE); 440420b7fb569773ae573fbe90c3a9c522d4c368863Erik String title = cursor.getString(titleIndex); 441420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, 442420b7fb569773ae573fbe90c3a9c522d4c368863Erik " alarm exists for id: " + eventId + " " + title); 443420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 444420b7fb569773ae573fbe90c3a9c522d4c368863Erik continue; 445420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 446420b7fb569773ae573fbe90c3a9c522d4c368863Erik 447420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Insert this alarm into the CalendarAlerts table 448420b7fb569773ae573fbe90c3a9c522d4c368863Erik Uri uri = CalendarAlerts.insert( 449420b7fb569773ae573fbe90c3a9c522d4c368863Erik resolver, eventId, startTime, endTime, alarmTime, minutes); 450420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (uri == null) { 451420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 452420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.e(CalendarProvider2.TAG, "runScheduleNextAlarm() insert into " 453420b7fb569773ae573fbe90c3a9c522d4c368863Erik + "CalendarAlerts table failed"); 454420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 455420b7fb569773ae573fbe90c3a9c522d4c368863Erik continue; 456420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 457420b7fb569773ae573fbe90c3a9c522d4c368863Erik 458420b7fb569773ae573fbe90c3a9c522d4c368863Erik scheduleAlarm(alarmTime); 459420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 460420b7fb569773ae573fbe90c3a9c522d4c368863Erik } finally { 461420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (cursor != null) { 462420b7fb569773ae573fbe90c3a9c522d4c368863Erik cursor.close(); 463420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 464420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 465420b7fb569773ae573fbe90c3a9c522d4c368863Erik 466420b7fb569773ae573fbe90c3a9c522d4c368863Erik // Refresh notification bar 467420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (rowsDeleted > 0) { 468420b7fb569773ae573fbe90c3a9c522d4c368863Erik scheduleAlarm(currentMillis); 469420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 470420b7fb569773ae573fbe90c3a9c522d4c368863Erik 471420b7fb569773ae573fbe90c3a9c522d4c368863Erik // If we scheduled an event alarm, then schedule the next alarm check 472420b7fb569773ae573fbe90c3a9c522d4c368863Erik // for one minute past that alarm. Otherwise, if there were no 473420b7fb569773ae573fbe90c3a9c522d4c368863Erik // event alarms scheduled, then check again in 24 hours. If a new 474420b7fb569773ae573fbe90c3a9c522d4c368863Erik // event is inserted before the next alarm check, then this method 475420b7fb569773ae573fbe90c3a9c522d4c368863Erik // will be run again when the new event is inserted. 476420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (nextAlarmTime != Long.MAX_VALUE) { 477420b7fb569773ae573fbe90c3a9c522d4c368863Erik scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS); 478420b7fb569773ae573fbe90c3a9c522d4c368863Erik } else { 479420b7fb569773ae573fbe90c3a9c522d4c368863Erik scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS); 480420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 481420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 482420b7fb569773ae573fbe90c3a9c522d4c368863Erik 483420b7fb569773ae573fbe90c3a9c522d4c368863Erik /** 484420b7fb569773ae573fbe90c3a9c522d4c368863Erik * Removes the entries in the CalendarAlerts table for alarms that we have 485420b7fb569773ae573fbe90c3a9c522d4c368863Erik * scheduled but that have not fired yet. We do this to ensure that we don't 486420b7fb569773ae573fbe90c3a9c522d4c368863Erik * miss an alarm. The CalendarAlerts table keeps track of the alarms that we 487420b7fb569773ae573fbe90c3a9c522d4c368863Erik * have scheduled but the actual alarm list is in memory and will be cleared 488420b7fb569773ae573fbe90c3a9c522d4c368863Erik * if the phone reboots. We don't need to remove entries that have already 489420b7fb569773ae573fbe90c3a9c522d4c368863Erik * fired, and in fact we should not remove them because we need to display 490420b7fb569773ae573fbe90c3a9c522d4c368863Erik * the notifications until the user dismisses them. We could remove entries 491420b7fb569773ae573fbe90c3a9c522d4c368863Erik * that have fired and been dismissed, but we leave them around for a while 492420b7fb569773ae573fbe90c3a9c522d4c368863Erik * because it makes it easier to debug problems. Entries that are old enough 493420b7fb569773ae573fbe90c3a9c522d4c368863Erik * will be cleaned up later when we schedule new alarms. 494420b7fb569773ae573fbe90c3a9c522d4c368863Erik */ 495420b7fb569773ae573fbe90c3a9c522d4c368863Erik private static void removeScheduledAlarmsLocked(SQLiteDatabase db) { 496420b7fb569773ae573fbe90c3a9c522d4c368863Erik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 497420b7fb569773ae573fbe90c3a9c522d4c368863Erik Log.d(CalendarProvider2.TAG, "removing scheduled alarms"); 498420b7fb569773ae573fbe90c3a9c522d4c368863Erik } 499744fa975b40b24ea7377c0e273f60a7a4d47e2e0RoboErik db.delete(CalendarAlerts.TABLE_NAME, CalendarAlerts.STATE + "=" 500744fa975b40b24ea7377c0e273f60a7a4d47e2e0RoboErik + CalendarAlerts.STATE_SCHEDULED, null /* whereArgs */); 501e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 502e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio 503e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio public void set(int type, long triggerAtTime, PendingIntent operation) { 50474dfe7de007563af5cab1c1bb458f4ebd4e4f66dChristopher Tate mAlarmManager.setExact(type, triggerAtTime, operation); 505e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 506e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio 507e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio public void cancel(PendingIntent operation) { 508e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio mAlarmManager.cancel(operation); 509e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 510e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio 511e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio public void scheduleAlarm(long alarmTime) { 512b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik CalendarContract.CalendarAlerts.scheduleAlarm(mContext, mAlarmManager, alarmTime); 513e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 514e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio 515e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio public void rescheduleMissedAlarms(ContentResolver cr) { 516b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik CalendarContract.CalendarAlerts.rescheduleMissedAlarms(cr, mContext, mAlarmManager); 517e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio } 518e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio} 519