13a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting/* 23a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Copyright (C) 2012 The Android Open Source Project 33a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * 43a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Licensed under the Apache License, Version 2.0 (the "License"); 53a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * you may not use this file except in compliance with the License. 63a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * You may obtain a copy of the License at 73a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * 83a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * http://www.apache.org/licenses/LICENSE-2.0 93a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * 103a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Unless required by applicable law or agreed to in writing, software 113a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * distributed under the License is distributed on an "AS IS" BASIS, 123a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * See the License for the specific language governing permissions and 143a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * limitations under the License. 153a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 163a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 173a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingpackage com.android.calendar.alerts; 183a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 193a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.app.AlarmManager; 203a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.app.PendingIntent; 213a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.content.ContentResolver; 223a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.content.ContentUris; 233a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.content.Context; 243a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.content.Intent; 253a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.database.Cursor; 263a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.net.Uri; 273a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.provider.CalendarContract; 283a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.provider.CalendarContract.Events; 293a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.provider.CalendarContract.Instances; 303a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.provider.CalendarContract.Reminders; 313a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.text.format.DateUtils; 323a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.text.format.Time; 333a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport android.util.Log; 343a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 353a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport com.android.calendar.Utils; 363a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 373a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport java.util.ArrayList; 383a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport java.util.HashMap; 393a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport java.util.List; 403a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingimport java.util.Map; 413a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 423a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting/** 433a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events 443a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * and reminders tables for the next upcoming alert. 453a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 463a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Tingpublic class AlarmScheduler { 473a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final String TAG = "AlarmScheduler"; 483a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 493a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final String INSTANCES_WHERE = Events.VISIBLE + "=? AND " 503a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting + Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND " 513a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting + Events.ALL_DAY + "=?"; 523a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting static final String[] INSTANCES_PROJECTION = new String[] { 533a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Instances.EVENT_ID, 543a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Instances.BEGIN, 553a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Instances.ALL_DAY, 563a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting }; 573a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int INSTANCES_INDEX_EVENTID = 0; 583a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int INSTANCES_INDEX_BEGIN = 1; 593a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int INSTANCES_INDEX_ALL_DAY = 2; 603a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 613a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final String REMINDERS_WHERE = Reminders.METHOD + "=1 AND " 623a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting + Reminders.EVENT_ID + " IN "; 633a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting static final String[] REMINDERS_PROJECTION = new String[] { 643a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Reminders.EVENT_ID, 653a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Reminders.MINUTES, 663a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Reminders.METHOD, 673a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting }; 683a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int REMINDERS_INDEX_EVENT_ID = 0; 693a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int REMINDERS_INDEX_MINUTES = 1; 703a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int REMINDERS_INDEX_METHOD = 2; 713a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 723a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons: 733a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // (1) so that the concurrent reminder broadcast from the provider doesn't result 743a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // in a double ring, and (2) some OEMs modified the provider to not add an alert to 753a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // the CalendarAlerts table until the alert time, so for the unbundled app's 763a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // notifications to work on these devices, a delay ensures that AlertService won't 773a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // read from the CalendarAlerts table until the alert is present. 783a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting static final int ALARM_DELAY_MS = 1000; 793a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 803a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...". This 813a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // sets the max # of events in the query before batching into multiple queries, to 823a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // limit the SQL query length. 833a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final int REMINDER_QUERY_BATCH_SIZE = 50; 843a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 853a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // We really need to query for reminder times that fall in some interval, but 863a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // the Reminders table only stores the reminder interval (10min, 15min, etc), and 873a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // we cannot do the join with the Events table to calculate the actual alert time 883a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // from outside of the provider. So the best we can do for now consider events 893a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // whose start times begin within some interval (ie. 1 week out). This means 903a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // reminders which are configured for more than 1 week out won't fire on time. We 913a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // can minimize this to being only 1 day late by putting a 1 day max on the alarm time. 923a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final long EVENT_LOOKAHEAD_WINDOW_MS = DateUtils.WEEK_IN_MILLIS; 933a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static final long MAX_ALARM_ELAPSED_MS = DateUtils.DAY_IN_MILLIS; 943a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 953a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting /** 963a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Schedules the nearest upcoming alarm, to refresh notifications. 973a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * 983a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * This is historically done in the provider but we dupe this here so the unbundled 993a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * app will work on devices that have modified this portion of the provider. This 1003a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * has the limitation of querying events within some interval from now (ie. looks at 1013a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * reminders for all events occurring in the next week). This means for example, 1023a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * a 2 week notification will not fire on time. 1033a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 1043a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting public static void scheduleNextAlarm(Context context) { 1053a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting scheduleNextAlarm(context, AlertUtils.createAlarmManager(context), 1063a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis()); 1073a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1083a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1093a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // VisibleForTesting 1103a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting static void scheduleNextAlarm(Context context, AlarmManagerInterface alarmManager, 1113a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int batchSize, long currentMillis) { 1123a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Cursor instancesCursor = null; 1133a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting try { 1143a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting instancesCursor = queryUpcomingEvents(context, context.getContentResolver(), 1153a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting currentMillis); 1163a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (instancesCursor != null) { 1173a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryNextReminderAndSchedule(instancesCursor, context, 1183a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting context.getContentResolver(), alarmManager, batchSize, currentMillis); 1193a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1203a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } finally { 1213a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (instancesCursor != null) { 1223a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting instancesCursor.close(); 1233a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1243a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1253a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1263a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1273a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting /** 1283a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Queries events starting within a fixed interval from now. 1293a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 1303a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static Cursor queryUpcomingEvents(Context context, ContentResolver contentResolver, 1313a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long currentMillis) { 1323a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Time time = new Time(); 1333a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting time.normalize(false); 1343a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long localOffset = time.gmtoff * 1000; 1353a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting final long localStartMin = currentMillis; 1363a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting final long localStartMax = localStartMin + EVENT_LOOKAHEAD_WINDOW_MS; 1373a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting final long utcStartMin = localStartMin - localOffset; 1383a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting final long utcStartMax = utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS; 1393a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1403a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Expand Instances table range by a day on either end to account for 1413a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // all-day events. 1423a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon(); 1433a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting ContentUris.appendId(uriBuilder, localStartMin - DateUtils.DAY_IN_MILLIS); 1443a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS); 1453a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1463a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Build query for all events starting within the fixed interval. 1473a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting StringBuilder queryBuilder = new StringBuilder(); 1483a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.append("("); 1493a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.append(INSTANCES_WHERE); 1503a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.append(") OR ("); 1513a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.append(INSTANCES_WHERE); 1523a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.append(")"); 1533a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String[] queryArgs = new String[] { 1543a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // allday selection 1553a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting "1", /* visible = ? */ 1563a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String.valueOf(utcStartMin), /* begin >= ? */ 1573a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String.valueOf(utcStartMax), /* begin <= ? */ 1583a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting "1", /* allDay = ? */ 1593a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1603a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // non-allday selection 1613a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting "1", /* visible = ? */ 1623a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String.valueOf(localStartMin), /* begin >= ? */ 1633a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String.valueOf(localStartMax), /* begin <= ? */ 1643a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting "0" /* allDay = ? */ 1653a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting }; 1663a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1673a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Cursor cursor = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION, 1683a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting queryBuilder.toString(), queryArgs, null); 1693a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting return cursor; 1703a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1713a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1723a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting /** 1733a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Queries for all the reminders of the events in the instancesCursor, and schedules 1743a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * the alarm for the next upcoming reminder. 1753a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 1763a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static void queryNextReminderAndSchedule(Cursor instancesCursor, Context context, 1773a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting ContentResolver contentResolver, AlarmManagerInterface alarmManager, 1783a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int batchSize, long currentMillis) { 1793a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (AlertService.DEBUG) { 1803a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int eventCount = instancesCursor.getCount(); 1813a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (eventCount == 0) { 1823a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Log.d(TAG, "No events found starting within 1 week."); 1833a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } else { 1843a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Log.d(TAG, "Query result count for events starting within 1 week: " + eventCount); 1853a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1863a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 1873a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 1883a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Put query results of all events starting within some interval into map of event ID to 1893a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // local start time. 1903a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Map<Integer, List<Long>> eventMap = new HashMap<Integer, List<Long>>(); 1913a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Time timeObj = new Time(); 1923a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long nextAlarmTime = Long.MAX_VALUE; 1933a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int nextAlarmEventId = 0; 1943a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting instancesCursor.moveToPosition(-1); 1953a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting while (!instancesCursor.isAfterLast()) { 1963a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int index = 0; 1973a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventMap.clear(); 1983a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting StringBuilder eventIdsForQuery = new StringBuilder(); 1993a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventIdsForQuery.append('('); 2003a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting while (index++ < batchSize && instancesCursor.moveToNext()) { 2013a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int eventId = instancesCursor.getInt(INSTANCES_INDEX_EVENTID); 2023a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long begin = instancesCursor.getLong(INSTANCES_INDEX_BEGIN); 2033a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting boolean allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0; 2043a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long localStartTime; 2053a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (allday) { 2063a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Adjust allday to local time. 2073a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting localStartTime = Utils.convertAlldayUtcToLocal(timeObj, begin, 2083a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Time.getCurrentTimezone()); 2093a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } else { 2103a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting localStartTime = begin; 2113a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2123a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting List<Long> startTimes = eventMap.get(eventId); 2133a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (startTimes == null) { 2143a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting startTimes = new ArrayList<Long>(); 2153a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventMap.put(eventId, startTimes); 2163a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventIdsForQuery.append(eventId); 2173a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventIdsForQuery.append(","); 2183a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2193a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting startTimes.add(localStartTime); 2203a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2213a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Log for debugging. 2223a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (Log.isLoggable(TAG, Log.DEBUG)) { 2233a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting timeObj.set(localStartTime); 2243a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting StringBuilder msg = new StringBuilder(); 2253a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append("Events cursor result -- eventId:").append(eventId); 2263a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(", allDay:").append(allday); 2273a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(", start:").append(localStartTime); 2283a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")"); 2293a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Log.d(TAG, msg.toString()); 2303a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2313a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2323a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (eventIdsForQuery.charAt(eventIdsForQuery.length() - 1) == ',') { 2333a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventIdsForQuery.deleteCharAt(eventIdsForQuery.length() - 1); 2343a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2353a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting eventIdsForQuery.append(')'); 2363a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2373a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Query the reminders table for the events found. 2383a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Cursor cursor = null; 2393a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting try { 2403a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting cursor = contentResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION, 2413a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting REMINDERS_WHERE + eventIdsForQuery, null, null); 2423a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2433a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Process the reminders query results to find the next reminder time. 2443a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting cursor.moveToPosition(-1); 2453a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting while (cursor.moveToNext()) { 2463a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int eventId = cursor.getInt(REMINDERS_INDEX_EVENT_ID); 2473a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting int reminderMinutes = cursor.getInt(REMINDERS_INDEX_MINUTES); 2483a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting List<Long> startTimes = eventMap.get(eventId); 2493a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (startTimes != null) { 2503a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting for (Long startTime : startTimes) { 2513a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long alarmTime = startTime - 2523a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting reminderMinutes * DateUtils.MINUTE_IN_MILLIS; 2533a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (alarmTime > currentMillis && alarmTime < nextAlarmTime) { 2543a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting nextAlarmTime = alarmTime; 2553a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting nextAlarmEventId = eventId; 2563a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2573a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2583a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (Log.isLoggable(TAG, Log.DEBUG)) { 2593a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting timeObj.set(alarmTime); 2603a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting StringBuilder msg = new StringBuilder(); 2613a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append("Reminders cursor result -- eventId:").append(eventId); 2623a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(", startTime:").append(startTime); 2633a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(", minutes:").append(reminderMinutes); 2643a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(", alarmTime:").append(alarmTime); 2653a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")) 2663a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting .append(")"); 2673a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Log.d(TAG, msg.toString()); 2683a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2693a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2703a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2713a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2723a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } finally { 2733a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (cursor != null) { 2743a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting cursor.close(); 2753a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2763a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2773a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2783a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2793a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Schedule the alarm for the next reminder time. 2803a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (nextAlarmTime < Long.MAX_VALUE) { 2813a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting scheduleAlarm(context, nextAlarmEventId, nextAlarmTime, currentMillis, alarmManager); 2823a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2833a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2843a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 2853a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting /** 2863a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified 2873a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * alarm time with a slight delay (to account for the possible duplicate broadcast 2883a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting * from the provider). 2893a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting */ 2903a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting private static void scheduleAlarm(Context context, long eventId, long alarmTime, 2913a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long currentMillis, AlarmManagerInterface alarmManager) { 2923a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Max out the alarm time to 1 day out, so an alert for an event far in the future 2933a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // (not present in our event query results for a limited range) can only be at 2943a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // most 1 day late. 2953a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting long maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS; 2963a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (alarmTime > maxAlarmTime) { 2973a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting alarmTime = maxAlarmTime; 2983a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 2993a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 3003a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Add a slight delay (see comments on the member var). 3013a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting alarmTime += ALARM_DELAY_MS; 3023a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 3033a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting if (AlertService.DEBUG) { 3043a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Time time = new Time(); 3053a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting time.set(alarmTime); 3063a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting String schedTime = time.format("%a, %b %d, %Y %I:%M%P"); 3073a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Log.d(TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId 3083a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting + " at " + alarmTime + " (" + schedTime + ")"); 3093a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 3103a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting 3113a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager. The extra is 3123a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // only used by AlertService for logging. It is ignored by Intent.filterEquals, 3133a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // so this scheduling will still overwrite the alarm that was previously pending. 3143a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // Note that the 'setClass' is required, because otherwise it seems the broadcast 3153a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting // can be eaten by other apps and we somehow may never receive it. 3163a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting Intent intent = new Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION); 3173a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting intent.setClass(context, AlertReceiver.class); 3183a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime); 3193a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0); 3203a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi); 3213a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting } 3223a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting} 323