summaryrefslogtreecommitdiff
path: root/src/com/android/calendar/alerts/AlarmScheduler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/alerts/AlarmScheduler.java')
-rw-r--r--src/com/android/calendar/alerts/AlarmScheduler.java322
1 files changed, 0 insertions, 322 deletions
diff --git a/src/com/android/calendar/alerts/AlarmScheduler.java b/src/com/android/calendar/alerts/AlarmScheduler.java
deleted file mode 100644
index 97828229..00000000
--- a/src/com/android/calendar/alerts/AlarmScheduler.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calendar.alerts;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.Instances;
-import android.provider.CalendarContract.Reminders;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.calendar.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Schedules the next EVENT_REMINDER_APP broadcast with AlarmManager, by querying the events
- * and reminders tables for the next upcoming alert.
- */
-public class AlarmScheduler {
- private static final String TAG = "AlarmScheduler";
-
- private static final String INSTANCES_WHERE = Events.VISIBLE + "=? AND "
- + Instances.BEGIN + ">=? AND " + Instances.BEGIN + "<=? AND "
- + Events.ALL_DAY + "=?";
- static final String[] INSTANCES_PROJECTION = new String[] {
- Instances.EVENT_ID,
- Instances.BEGIN,
- Instances.ALL_DAY,
- };
- private static final int INSTANCES_INDEX_EVENTID = 0;
- private static final int INSTANCES_INDEX_BEGIN = 1;
- private static final int INSTANCES_INDEX_ALL_DAY = 2;
-
- private static final String REMINDERS_WHERE = Reminders.METHOD + "=1 AND "
- + Reminders.EVENT_ID + " IN ";
- static final String[] REMINDERS_PROJECTION = new String[] {
- Reminders.EVENT_ID,
- Reminders.MINUTES,
- Reminders.METHOD,
- };
- private static final int REMINDERS_INDEX_EVENT_ID = 0;
- private static final int REMINDERS_INDEX_MINUTES = 1;
- private static final int REMINDERS_INDEX_METHOD = 2;
-
- // Add a slight delay for the EVENT_REMINDER_APP broadcast for a couple reasons:
- // (1) so that the concurrent reminder broadcast from the provider doesn't result
- // in a double ring, and (2) some OEMs modified the provider to not add an alert to
- // the CalendarAlerts table until the alert time, so for the unbundled app's
- // notifications to work on these devices, a delay ensures that AlertService won't
- // read from the CalendarAlerts table until the alert is present.
- static final int ALARM_DELAY_MS = 1000;
-
- // The reminders query looks like "SELECT ... AND eventId IN 101,102,202,...". This
- // sets the max # of events in the query before batching into multiple queries, to
- // limit the SQL query length.
- private static final int REMINDER_QUERY_BATCH_SIZE = 50;
-
- // We really need to query for reminder times that fall in some interval, but
- // the Reminders table only stores the reminder interval (10min, 15min, etc), and
- // we cannot do the join with the Events table to calculate the actual alert time
- // from outside of the provider. So the best we can do for now consider events
- // whose start times begin within some interval (ie. 1 week out). This means
- // reminders which are configured for more than 1 week out won't fire on time. We
- // can minimize this to being only 1 day late by putting a 1 day max on the alarm time.
- private static final long EVENT_LOOKAHEAD_WINDOW_MS = DateUtils.WEEK_IN_MILLIS;
- private static final long MAX_ALARM_ELAPSED_MS = DateUtils.DAY_IN_MILLIS;
-
- /**
- * Schedules the nearest upcoming alarm, to refresh notifications.
- *
- * This is historically done in the provider but we dupe this here so the unbundled
- * app will work on devices that have modified this portion of the provider. This
- * has the limitation of querying events within some interval from now (ie. looks at
- * reminders for all events occurring in the next week). This means for example,
- * a 2 week notification will not fire on time.
- */
- public static void scheduleNextAlarm(Context context) {
- scheduleNextAlarm(context, AlertUtils.createAlarmManager(context),
- REMINDER_QUERY_BATCH_SIZE, System.currentTimeMillis());
- }
-
- // VisibleForTesting
- static void scheduleNextAlarm(Context context, AlarmManagerInterface alarmManager,
- int batchSize, long currentMillis) {
- Cursor instancesCursor = null;
- try {
- instancesCursor = queryUpcomingEvents(context, context.getContentResolver(),
- currentMillis);
- if (instancesCursor != null) {
- queryNextReminderAndSchedule(instancesCursor, context,
- context.getContentResolver(), alarmManager, batchSize, currentMillis);
- }
- } finally {
- if (instancesCursor != null) {
- instancesCursor.close();
- }
- }
- }
-
- /**
- * Queries events starting within a fixed interval from now.
- */
- private static Cursor queryUpcomingEvents(Context context, ContentResolver contentResolver,
- long currentMillis) {
- Time time = new Time();
- time.normalize(false);
- long localOffset = time.gmtoff * 1000;
- final long localStartMin = currentMillis;
- final long localStartMax = localStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
- final long utcStartMin = localStartMin - localOffset;
- final long utcStartMax = utcStartMin + EVENT_LOOKAHEAD_WINDOW_MS;
-
- // Expand Instances table range by a day on either end to account for
- // all-day events.
- Uri.Builder uriBuilder = Instances.CONTENT_URI.buildUpon();
- ContentUris.appendId(uriBuilder, localStartMin - DateUtils.DAY_IN_MILLIS);
- ContentUris.appendId(uriBuilder, localStartMax + DateUtils.DAY_IN_MILLIS);
-
- // Build query for all events starting within the fixed interval.
- StringBuilder queryBuilder = new StringBuilder();
- queryBuilder.append("(");
- queryBuilder.append(INSTANCES_WHERE);
- queryBuilder.append(") OR (");
- queryBuilder.append(INSTANCES_WHERE);
- queryBuilder.append(")");
- String[] queryArgs = new String[] {
- // allday selection
- "1", /* visible = ? */
- String.valueOf(utcStartMin), /* begin >= ? */
- String.valueOf(utcStartMax), /* begin <= ? */
- "1", /* allDay = ? */
-
- // non-allday selection
- "1", /* visible = ? */
- String.valueOf(localStartMin), /* begin >= ? */
- String.valueOf(localStartMax), /* begin <= ? */
- "0" /* allDay = ? */
- };
-
- Cursor cursor = contentResolver.query(uriBuilder.build(), INSTANCES_PROJECTION,
- queryBuilder.toString(), queryArgs, null);
- return cursor;
- }
-
- /**
- * Queries for all the reminders of the events in the instancesCursor, and schedules
- * the alarm for the next upcoming reminder.
- */
- private static void queryNextReminderAndSchedule(Cursor instancesCursor, Context context,
- ContentResolver contentResolver, AlarmManagerInterface alarmManager,
- int batchSize, long currentMillis) {
- if (AlertService.DEBUG) {
- int eventCount = instancesCursor.getCount();
- if (eventCount == 0) {
- Log.d(TAG, "No events found starting within 1 week.");
- } else {
- Log.d(TAG, "Query result count for events starting within 1 week: " + eventCount);
- }
- }
-
- // Put query results of all events starting within some interval into map of event ID to
- // local start time.
- Map<Integer, List<Long>> eventMap = new HashMap<Integer, List<Long>>();
- Time timeObj = new Time();
- long nextAlarmTime = Long.MAX_VALUE;
- int nextAlarmEventId = 0;
- instancesCursor.moveToPosition(-1);
- while (!instancesCursor.isAfterLast()) {
- int index = 0;
- eventMap.clear();
- StringBuilder eventIdsForQuery = new StringBuilder();
- eventIdsForQuery.append('(');
- while (index++ < batchSize && instancesCursor.moveToNext()) {
- int eventId = instancesCursor.getInt(INSTANCES_INDEX_EVENTID);
- long begin = instancesCursor.getLong(INSTANCES_INDEX_BEGIN);
- boolean allday = instancesCursor.getInt(INSTANCES_INDEX_ALL_DAY) != 0;
- long localStartTime;
- if (allday) {
- // Adjust allday to local time.
- localStartTime = Utils.convertAlldayUtcToLocal(timeObj, begin,
- Time.getCurrentTimezone());
- } else {
- localStartTime = begin;
- }
- List<Long> startTimes = eventMap.get(eventId);
- if (startTimes == null) {
- startTimes = new ArrayList<Long>();
- eventMap.put(eventId, startTimes);
- eventIdsForQuery.append(eventId);
- eventIdsForQuery.append(",");
- }
- startTimes.add(localStartTime);
-
- // Log for debugging.
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- timeObj.set(localStartTime);
- StringBuilder msg = new StringBuilder();
- msg.append("Events cursor result -- eventId:").append(eventId);
- msg.append(", allDay:").append(allday);
- msg.append(", start:").append(localStartTime);
- msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P")).append(")");
- Log.d(TAG, msg.toString());
- }
- }
- if (eventIdsForQuery.charAt(eventIdsForQuery.length() - 1) == ',') {
- eventIdsForQuery.deleteCharAt(eventIdsForQuery.length() - 1);
- }
- eventIdsForQuery.append(')');
-
- // Query the reminders table for the events found.
- Cursor cursor = null;
- try {
- cursor = contentResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
- REMINDERS_WHERE + eventIdsForQuery, null, null);
-
- // Process the reminders query results to find the next reminder time.
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- int eventId = cursor.getInt(REMINDERS_INDEX_EVENT_ID);
- int reminderMinutes = cursor.getInt(REMINDERS_INDEX_MINUTES);
- List<Long> startTimes = eventMap.get(eventId);
- if (startTimes != null) {
- for (Long startTime : startTimes) {
- long alarmTime = startTime -
- reminderMinutes * DateUtils.MINUTE_IN_MILLIS;
- if (alarmTime > currentMillis && alarmTime < nextAlarmTime) {
- nextAlarmTime = alarmTime;
- nextAlarmEventId = eventId;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- timeObj.set(alarmTime);
- StringBuilder msg = new StringBuilder();
- msg.append("Reminders cursor result -- eventId:").append(eventId);
- msg.append(", startTime:").append(startTime);
- msg.append(", minutes:").append(reminderMinutes);
- msg.append(", alarmTime:").append(alarmTime);
- msg.append(" (").append(timeObj.format("%a, %b %d, %Y %I:%M%P"))
- .append(")");
- Log.d(TAG, msg.toString());
- }
- }
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- // Schedule the alarm for the next reminder time.
- if (nextAlarmTime < Long.MAX_VALUE) {
- scheduleAlarm(context, nextAlarmEventId, nextAlarmTime, currentMillis, alarmManager);
- }
- }
-
- /**
- * Schedules an alarm for the EVENT_REMINDER_APP broadcast, for the specified
- * alarm time with a slight delay (to account for the possible duplicate broadcast
- * from the provider).
- */
- private static void scheduleAlarm(Context context, long eventId, long alarmTime,
- long currentMillis, AlarmManagerInterface alarmManager) {
- // Max out the alarm time to 1 day out, so an alert for an event far in the future
- // (not present in our event query results for a limited range) can only be at
- // most 1 day late.
- long maxAlarmTime = currentMillis + MAX_ALARM_ELAPSED_MS;
- if (alarmTime > maxAlarmTime) {
- alarmTime = maxAlarmTime;
- }
-
- // Add a slight delay (see comments on the member var).
- alarmTime += ALARM_DELAY_MS;
-
- if (AlertService.DEBUG) {
- Time time = new Time();
- time.set(alarmTime);
- String schedTime = time.format("%a, %b %d, %Y %I:%M%P");
- Log.d(TAG, "Scheduling alarm for EVENT_REMINDER_APP broadcast for event " + eventId
- + " at " + alarmTime + " (" + schedTime + ")");
- }
-
- // Schedule an EVENT_REMINDER_APP broadcast with AlarmManager. The extra is
- // only used by AlertService for logging. It is ignored by Intent.filterEquals,
- // so this scheduling will still overwrite the alarm that was previously pending.
- // Note that the 'setClass' is required, because otherwise it seems the broadcast
- // can be eaten by other apps and we somehow may never receive it.
- Intent intent = new Intent(AlertReceiver.EVENT_REMINDER_APP_ACTION);
- intent.setClass(context, AlertReceiver.class);
- intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
- alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
- }
-}