AlertService.java revision 7e4c339fb171f542ff8d5d4916c649f9597aa926
1146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/*
2146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
3146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project *
4146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
5146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * you may not use this file except in compliance with the License.
6146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * You may obtain a copy of the License at
7146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project *
8146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project *
10146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * See the License for the specific language governing permissions and
14146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * limitations under the License.
15146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */
16146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
1723e7da3eacee7bceb105cdfc7b5329c7a43846d5Mason Tangpackage com.android.calendar.alerts;
1823e7da3eacee7bceb105cdfc7b5329c7a43846d5Mason Tang
194b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawaimport com.android.calendar.GeneralPreferences;
2023e7da3eacee7bceb105cdfc7b5329c7a43846d5Mason Tangimport com.android.calendar.R;
21146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
22146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.AlarmManager;
23146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Notification;
24146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.NotificationManager;
25146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Service;
26146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentResolver;
27e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport android.content.ContentUris;
28146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentValues;
29146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Context;
30146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Intent;
31146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.SharedPreferences;
32146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.database.Cursor;
331fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shumaimport android.media.AudioManager;
34146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.net.Uri;
35146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Bundle;
36146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Handler;
37146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.HandlerThread;
38146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.IBinder;
39146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Looper;
40146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Message;
41146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Process;
42fa292a0db2a6f04255c75a57908b17ba48a96183RoboErikimport android.provider.CalendarContract;
43a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Attendees;
44a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.CalendarAlerts;
45146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.text.TextUtils;
46fa292a0db2a6f04255c75a57908b17ba48a96183RoboErikimport android.text.format.DateUtils;
47146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.util.Log;
48e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
494e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Tingimport java.util.ArrayList;
50e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport java.util.HashMap;
51146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
52146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/**
53146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * This service is used to handle calendar event reminders.
54146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */
55146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpublic class AlertService extends Service {
56f58faf32bbce943eb4791cef2ad6c327e3724cc7Michael Chan    static final boolean DEBUG = true;
57146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final String TAG = "AlertService";
580e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
59146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile Looper mServiceLooper;
60146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile ServiceHandler mServiceHandler;
610e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
620e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan    private static final String[] ALERT_PROJECTION = new String[] {
63146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts._ID,                     // 0
64146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_ID,                // 1
65146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.STATE,                   // 2
66146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.TITLE,                   // 3
67146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_LOCATION,          // 4
68146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
69146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALL_DAY,                 // 6
70146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALARM_TIME,              // 7
71146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.MINUTES,                 // 8
72146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.BEGIN,                   // 9
73e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        CalendarAlerts.END,                     // 10
7442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        CalendarAlerts.DESCRIPTION,             // 11
75146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
760e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
77146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ID = 0;
78146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_ID = 1;
79146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_STATE = 2;
80146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_TITLE = 3;
81146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_LOCATION = 4;
82146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
83146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALL_DAY = 6;
84146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALARM_TIME = 7;
85146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_MINUTES = 8;
86146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_BEGIN = 9;
87e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final int ALERT_INDEX_END = 10;
8842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    private static final int ALERT_INDEX_DESCRIPTION = 11;
89146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
90e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
91e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
92146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
93e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
9443ffa461b738692925a94c005c90ff60757455a7RoboErik            Integer.toString(CalendarAlerts.STATE_FIRED),
9543ffa461b738692925a94c005c90ff60757455a7RoboErik            Integer.toString(CalendarAlerts.STATE_SCHEDULED)
96146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
970e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
98e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
990e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
10043ffa461b738692925a94c005c90ff60757455a7RoboErik    private static final String DISMISS_OLD_SELECTION = CalendarAlerts.END + "<? AND "
10143ffa461b738692925a94c005c90ff60757455a7RoboErik            + CalendarAlerts.STATE + "=?";
10243ffa461b738692925a94c005c90ff60757455a7RoboErik
10360edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting    // The grace period before changing a notification's priority bucket.
10460edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting    private static final int DEPRIORITIZE_GRACE_PERIOD_MS = 15 * 60 * 1000;
10560edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting
1068af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project    void processMessage(Message msg) {
107146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Bundle bundle = (Bundle) msg.obj;
1080e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
109146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // On reboot, update the notification bar with the contents of the
110146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // CalendarAlerts table.
111146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        String action = bundle.getString("action");
112e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
113fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, bundle.getLong(android.provider.CalendarContract.CalendarAlerts.ALARM_TIME)
114e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    + " Action = " + action);
115e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
116e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
117146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        if (action.equals(Intent.ACTION_BOOT_COMPLETED)
118146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                || action.equals(Intent.ACTION_TIME_CHANGED)) {
119146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            doTimeChanged();
120146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            return;
121146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
122146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
123a27a886892fe3ec5edbc63c0b58e0a988623011aRoboErik        if (!action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER)
12443ffa461b738692925a94c005c90ff60757455a7RoboErik                && !action.equals(Intent.ACTION_LOCALE_CHANGED)
12543ffa461b738692925a94c005c90ff60757455a7RoboErik                && !action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) {
126e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.w(TAG, "Invalid action: " + action);
127e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
128146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
12943ffa461b738692925a94c005c90ff60757455a7RoboErik        if (action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) {
13043ffa461b738692925a94c005c90ff60757455a7RoboErik            dismissOldAlerts(this);
13143ffa461b738692925a94c005c90ff60757455a7RoboErik        }
13242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
13342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER) &&
13442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                bundle.getBoolean(AlertUtils.QUIET_UPDATE_KEY)) {
13542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            updateAlertNotification(this, true);
13642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        } else {
13742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            updateAlertNotification(this, false);
13842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
139e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
140605a0901134c0840b2fcf0514b4c1f8bc10dc7e0Michael Chan
14143ffa461b738692925a94c005c90ff60757455a7RoboErik    static void dismissOldAlerts(Context context) {
14243ffa461b738692925a94c005c90ff60757455a7RoboErik        ContentResolver cr = context.getContentResolver();
14343ffa461b738692925a94c005c90ff60757455a7RoboErik        final long currentTime = System.currentTimeMillis();
14443ffa461b738692925a94c005c90ff60757455a7RoboErik        ContentValues vals = new ContentValues();
14543ffa461b738692925a94c005c90ff60757455a7RoboErik        vals.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED);
14643ffa461b738692925a94c005c90ff60757455a7RoboErik        cr.update(CalendarAlerts.CONTENT_URI, vals, DISMISS_OLD_SELECTION, new String[] {
14743ffa461b738692925a94c005c90ff60757455a7RoboErik                Long.toString(currentTime), Integer.toString(CalendarAlerts.STATE_SCHEDULED)
14843ffa461b738692925a94c005c90ff60757455a7RoboErik        });
14943ffa461b738692925a94c005c90ff60757455a7RoboErik    }
15043ffa461b738692925a94c005c90ff60757455a7RoboErik
15142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    static boolean updateAlertNotification(Context context, boolean quietUpdate) {
152e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        ContentResolver cr = context.getContentResolver();
1534e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        NotificationManager nm =
1544e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
155e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        final long currentTime = System.currentTimeMillis();
15643ffa461b738692925a94c005c90ff60757455a7RoboErik        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
15743ffa461b738692925a94c005c90ff60757455a7RoboErik
15843ffa461b738692925a94c005c90ff60757455a7RoboErik        boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
15943ffa461b738692925a94c005c90ff60757455a7RoboErik        if (!doAlert) {
16043ffa461b738692925a94c005c90ff60757455a7RoboErik            if (DEBUG) {
16143ffa461b738692925a94c005c90ff60757455a7RoboErik                Log.d(TAG, "alert preference is OFF");
16243ffa461b738692925a94c005c90ff60757455a7RoboErik            }
16343ffa461b738692925a94c005c90ff60757455a7RoboErik
16443ffa461b738692925a94c005c90ff60757455a7RoboErik            // If we shouldn't be showing notifications cancel any existing ones
16543ffa461b738692925a94c005c90ff60757455a7RoboErik            // and return.
16643ffa461b738692925a94c005c90ff60757455a7RoboErik            nm.cancelAll();
16743ffa461b738692925a94c005c90ff60757455a7RoboErik            return true;
16843ffa461b738692925a94c005c90ff60757455a7RoboErik        }
16943ffa461b738692925a94c005c90ff60757455a7RoboErik
170fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
171fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
172fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                ACTIVE_ALERTS_SORT);
1730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
174e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (alertCursor == null || alertCursor.getCount() == 0) {
175146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            if (alertCursor != null) {
176146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                alertCursor.close();
177146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
178e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
179e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
18042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            nm.cancelAll();
181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return false;
182146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1830e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
184e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
185e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "alert count:" + alertCursor.getCount());
186146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1870e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
18876180232681f9a3091fda763e364ee2e40765c9bSara Ting        // Process the query results and bucketize events.
189d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        ArrayList<NotificationInfo> highPriorityEvents = new ArrayList<NotificationInfo>();
190d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        ArrayList<NotificationInfo> mediumPriorityEvents = new ArrayList<NotificationInfo>();
19142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        ArrayList<NotificationInfo> expiredEvents = new ArrayList<NotificationInfo>();
19276180232681f9a3091fda763e364ee2e40765c9bSara Ting        StringBuilder expiredDigestTitleBuilder = new StringBuilder();
193d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        int numFired = processQuery(alertCursor, cr, currentTime, highPriorityEvents,
194d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                mediumPriorityEvents, expiredEvents, expiredDigestTitleBuilder);
19576180232681f9a3091fda763e364ee2e40765c9bSara Ting        String expiredDigestTitle = expiredDigestTitleBuilder.toString();
19676180232681f9a3091fda763e364ee2e40765c9bSara Ting
197d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        if (highPriorityEvents.size() + mediumPriorityEvents.size() + expiredEvents.size() == 0) {
19876180232681f9a3091fda763e364ee2e40765c9bSara Ting            nm.cancelAll();
19976180232681f9a3091fda763e364ee2e40765c9bSara Ting            return true;
20076180232681f9a3091fda763e364ee2e40765c9bSara Ting        }
20176180232681f9a3091fda763e364ee2e40765c9bSara Ting
20276180232681f9a3091fda763e364ee2e40765c9bSara Ting        quietUpdate = quietUpdate || (numFired == 0);
20376180232681f9a3091fda763e364ee2e40765c9bSara Ting        boolean doPopup = numFired > 0 &&
20476180232681f9a3091fda763e364ee2e40765c9bSara Ting                prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
20576180232681f9a3091fda763e364ee2e40765c9bSara Ting        boolean defaultVibrate = shouldUseDefaultVibrate(context, prefs);
20676180232681f9a3091fda763e364ee2e40765c9bSara Ting        String ringtone = quietUpdate ? null : prefs.getString(
20776180232681f9a3091fda763e364ee2e40765c9bSara Ting                GeneralPreferences.KEY_ALERTS_RINGTONE, null);
2087e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting        boolean notificationPosted = false;
20976180232681f9a3091fda763e364ee2e40765c9bSara Ting        long nextRefreshTime = Long.MAX_VALUE;
21076180232681f9a3091fda763e364ee2e40765c9bSara Ting
211d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        // Post the individual higher priority events (future and recently started
212d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        // concurrent events).  Order these so that earlier start times appear higher in
213d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        // the notification list.
214d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        for (NotificationInfo info : highPriorityEvents) {
21576180232681f9a3091fda763e364ee2e40765c9bSara Ting            String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis,
21676180232681f9a3091fda763e364ee2e40765c9bSara Ting                    info.allDay, info.location);
21776180232681f9a3091fda763e364ee2e40765c9bSara Ting            postNotification(info, summaryText, context, quietUpdate, doPopup, defaultVibrate,
2187e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                    ringtone, true, notificationPosted, nm);
2197e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            notificationPosted = true;
22076180232681f9a3091fda763e364ee2e40765c9bSara Ting        }
22176180232681f9a3091fda763e364ee2e40765c9bSara Ting
222d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        // Post the medium priority events (concurrent events that started a while ago).
223d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        // Order these so more recent start times appear higher in the notification list.
224d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        for (int i = mediumPriorityEvents.size() - 1; i >= 0; i--) {
225d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            NotificationInfo info = mediumPriorityEvents.get(i);
22676180232681f9a3091fda763e364ee2e40765c9bSara Ting            // TODO: Change to a relative time description like: "Started 40 minutes ago".
22776180232681f9a3091fda763e364ee2e40765c9bSara Ting            // This requires constant refreshing to the message as time goes.
22876180232681f9a3091fda763e364ee2e40765c9bSara Ting            String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis,
22976180232681f9a3091fda763e364ee2e40765c9bSara Ting                    info.allDay, info.location);
23076180232681f9a3091fda763e364ee2e40765c9bSara Ting
231d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            // Refresh when concurrent event ends so it will drop into the expired digest.
23276180232681f9a3091fda763e364ee2e40765c9bSara Ting            nextRefreshTime = Math.min(nextRefreshTime, info.endMillis);
23376180232681f9a3091fda763e364ee2e40765c9bSara Ting
234d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            postNotification(info, summaryText, context, quietUpdate, false, defaultVibrate,
2357e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                    ringtone, false, notificationPosted, nm);
2367e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            notificationPosted = true;
23776180232681f9a3091fda763e364ee2e40765c9bSara Ting        }
23876180232681f9a3091fda763e364ee2e40765c9bSara Ting
23976180232681f9a3091fda763e364ee2e40765c9bSara Ting        // Post the expired events as 1 combined notification.
24076180232681f9a3091fda763e364ee2e40765c9bSara Ting        int numExpired = expiredEvents.size();
24176180232681f9a3091fda763e364ee2e40765c9bSara Ting        if (numExpired > 0) {
24276180232681f9a3091fda763e364ee2e40765c9bSara Ting            Notification notification;
24376180232681f9a3091fda763e364ee2e40765c9bSara Ting            if (numExpired == 1) {
24476180232681f9a3091fda763e364ee2e40765c9bSara Ting                // If only 1 expired event, display an "old-style" basic alert.
24576180232681f9a3091fda763e364ee2e40765c9bSara Ting                NotificationInfo info = expiredEvents.get(0);
24676180232681f9a3091fda763e364ee2e40765c9bSara Ting                String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis,
24776180232681f9a3091fda763e364ee2e40765c9bSara Ting                        info.allDay, info.location);
24876180232681f9a3091fda763e364ee2e40765c9bSara Ting                notification = AlertReceiver.makeBasicNotification(context, info.eventName,
24976180232681f9a3091fda763e364ee2e40765c9bSara Ting                        summaryText, info.startMillis, info.endMillis, info.eventId,
25076180232681f9a3091fda763e364ee2e40765c9bSara Ting                        info.notificationId, false);
25176180232681f9a3091fda763e364ee2e40765c9bSara Ting            } else {
25276180232681f9a3091fda763e364ee2e40765c9bSara Ting                // Multiple expired events are listed in a digest.
25376180232681f9a3091fda763e364ee2e40765c9bSara Ting                notification = AlertReceiver.makeDigestNotification(context,
2548748724e382ca014067a3ceb5ff4eacbd9c4021aSara Ting                    expiredEvents, expiredDigestTitle, false);
25576180232681f9a3091fda763e364ee2e40765c9bSara Ting            }
25676180232681f9a3091fda763e364ee2e40765c9bSara Ting
25776180232681f9a3091fda763e364ee2e40765c9bSara Ting            // Add options for a quiet update.
2587e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            addNotificationOptions(notification, true, expiredDigestTitle, defaultVibrate,
2597e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                    ringtone, notificationPosted);
26076180232681f9a3091fda763e364ee2e40765c9bSara Ting
26176180232681f9a3091fda763e364ee2e40765c9bSara Ting            // Remove any individual expired notifications before posting.
26276180232681f9a3091fda763e364ee2e40765c9bSara Ting            for (NotificationInfo expiredInfo : expiredEvents) {
26376180232681f9a3091fda763e364ee2e40765c9bSara Ting                nm.cancel(expiredInfo.notificationId);
26476180232681f9a3091fda763e364ee2e40765c9bSara Ting            }
26576180232681f9a3091fda763e364ee2e40765c9bSara Ting
26676180232681f9a3091fda763e364ee2e40765c9bSara Ting            // Post the new notification for the group.
26776180232681f9a3091fda763e364ee2e40765c9bSara Ting            nm.notify(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, notification);
2687e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            notificationPosted = true;
26976180232681f9a3091fda763e364ee2e40765c9bSara Ting
27076180232681f9a3091fda763e364ee2e40765c9bSara Ting            if (DEBUG) {
27176180232681f9a3091fda763e364ee2e40765c9bSara Ting                Log.d(TAG, "Posting digest alarm notification, numEvents:" + expiredEvents.size()
27276180232681f9a3091fda763e364ee2e40765c9bSara Ting                        + ", notificationId:" + AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID
27376180232681f9a3091fda763e364ee2e40765c9bSara Ting                        + (quietUpdate ? ", quiet" : ", loud"));
27476180232681f9a3091fda763e364ee2e40765c9bSara Ting            }
27576180232681f9a3091fda763e364ee2e40765c9bSara Ting        } else {
27676180232681f9a3091fda763e364ee2e40765c9bSara Ting            nm.cancel(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID);
27776180232681f9a3091fda763e364ee2e40765c9bSara Ting        }
27876180232681f9a3091fda763e364ee2e40765c9bSara Ting
27976180232681f9a3091fda763e364ee2e40765c9bSara Ting        // Schedule the next silent refresh time so notifications will change
28076180232681f9a3091fda763e364ee2e40765c9bSara Ting        // buckets (eg. drop into expired digest, etc).
281d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        if (nextRefreshTime > currentTime) {
282d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            AlertUtils.scheduleNextNotificationRefresh(context, null, nextRefreshTime);
283d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        } else if (nextRefreshTime < currentTime) {
284d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            Log.e(TAG, "Illegal state: next notification refresh time found to be in the past.");
285d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting        }
28676180232681f9a3091fda763e364ee2e40765c9bSara Ting
28776180232681f9a3091fda763e364ee2e40765c9bSara Ting        return true;
28876180232681f9a3091fda763e364ee2e40765c9bSara Ting    }
28976180232681f9a3091fda763e364ee2e40765c9bSara Ting
29076180232681f9a3091fda763e364ee2e40765c9bSara Ting    /**
29176180232681f9a3091fda763e364ee2e40765c9bSara Ting     * Processes the query results and bucketizes the alerts.
29276180232681f9a3091fda763e364ee2e40765c9bSara Ting     *
293d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting     * @param highPriorityEvents This will contain future events, and concurrent events
294d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting     *     that started recently (less than the interval DEPRIORITIZE_GRACE_PERIOD_MS).
295d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting     * @param mediumPriorityEvents This will contain concurrent events that started
296d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting     *     more than DEPRIORITIZE_GRACE_PERIOD_MS ago.
297d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting     * @param expiredEvents Will contain events that have ended.
29876180232681f9a3091fda763e364ee2e40765c9bSara Ting     * @param expiredDigestTitle Should pass in an empty StringBuilder; this will be
29976180232681f9a3091fda763e364ee2e40765c9bSara Ting     *     modified to contain a title consolidating all expired event titles.
30076180232681f9a3091fda763e364ee2e40765c9bSara Ting     * @return Returns the number of new alerts to fire.  If this is 0, it implies
30176180232681f9a3091fda763e364ee2e40765c9bSara Ting     *     a quiet update.
30276180232681f9a3091fda763e364ee2e40765c9bSara Ting     */
30376180232681f9a3091fda763e364ee2e40765c9bSara Ting    private static int processQuery(final Cursor alertCursor, final ContentResolver cr,
304d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            final long currentTime, ArrayList<NotificationInfo> highPriorityEvents,
305d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            ArrayList<NotificationInfo> mediumPriorityEvents,
306d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting            ArrayList<NotificationInfo> expiredEvents, StringBuilder expiredDigestTitle) {
30776180232681f9a3091fda763e364ee2e40765c9bSara Ting        HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
30876180232681f9a3091fda763e364ee2e40765c9bSara Ting        int numFired = 0;
309146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        try {
310e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            while (alertCursor.moveToNext()) {
311e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
312e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
313e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
314e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
31542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                final String description = alertCursor.getString(ALERT_INDEX_DESCRIPTION);
316e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
317e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
318e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
319e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
320e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long endTime = alertCursor.getLong(ALERT_INDEX_END);
321e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final Uri alertUri = ContentUris
322e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
323e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
324e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int state = alertCursor.getInt(ALERT_INDEX_STATE);
3257321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
326e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
327e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (DEBUG) {
328e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
329e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
330e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " declined:" + declined + " beginTime:" + beginTime
331e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " endTime:" + endTime);
332146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
3330e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
334e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                ContentValues values = new ContentValues();
335e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newState = -1;
336e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
337e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Uncomment for the behavior of clearing out alerts after the
338e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // events ended. b/1880369
339e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //
340e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // if (endTime < currentTime) {
341e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //     newState = CalendarAlerts.DISMISSED;
342e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // } else
343e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
3443dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                // Remove declined events
3453dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                if (!declined) {
346fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (state == CalendarAlerts.STATE_SCHEDULED) {
347fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        newState = CalendarAlerts.STATE_FIRED;
348e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        numFired++;
349e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
350e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // Record the received time in the CalendarAlerts table.
351e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // This is useful for finding bugs that cause alarms to be
352e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // missed or delayed.
353e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
354e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    }
355e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                } else {
356fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    newState = CalendarAlerts.STATE_DISMISSED;
357146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
358146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
359e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Update row if state changed
360e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (newState != -1) {
361e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.STATE, newState);
362e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    state = newState;
363146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
3640e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
365fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state == CalendarAlerts.STATE_FIRED) {
366e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // Record the time posting to notification manager.
367e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // This is used for debugging missed alarms.
368e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
369146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
3700e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
371e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Write row to if anything changed
372e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (values.size() > 0) cr.update(alertUri, values, null, null);
3730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
374fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state != CalendarAlerts.STATE_FIRED) {
375e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    continue;
376e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
377146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
378e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Pick an Event title for the notification panel by the latest
379e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // alertTime and give prefer accepted events in case of ties.
380e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newStatus;
381e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                switch (status) {
382e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_ACCEPTED:
383e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 2;
384e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
385e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_TENTATIVE:
386e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 1;
387e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
388e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    default:
389e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 0;
390e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
391e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
3924e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                // Don't count duplicate alerts for the same event
3934e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                if (eventIds.put(eventId, beginTime) == null) {
39442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    NotificationInfo notificationInfo = new NotificationInfo(eventName, location,
39557dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting                            description, beginTime, endTime, eventId, allDay);
39642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
397d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                    // TODO: Prioritize by "primary" calendar
398d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                    long highPriorityCutoff = currentTime - DEPRIORITIZE_GRACE_PERIOD_MS;
399d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                    if (beginTime > highPriorityCutoff) {
400d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                        highPriorityEvents.add(notificationInfo);
401d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                    } else if (endTime >= currentTime) {
402d8abe4ec4f0dd7f4edf9e9ab31766bab85cdf9bdSara Ting                        mediumPriorityEvents.add(notificationInfo);
40342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    } else {
40457dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting                        expiredEvents.add(notificationInfo);
40542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                        if (!TextUtils.isEmpty(eventName)) {
40676180232681f9a3091fda763e364ee2e40765c9bSara Ting                            if (expiredDigestTitle.length() > 0) {
40776180232681f9a3091fda763e364ee2e40765c9bSara Ting                                expiredDigestTitle.append(", ");
40842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                            }
40976180232681f9a3091fda763e364ee2e40765c9bSara Ting                            expiredDigestTitle.append(eventName);
41042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                        }
4114e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                    }
4129881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                }
413146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
414146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        } finally {
415e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (alertCursor != null) {
416e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                alertCursor.close();
417e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
418146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
41976180232681f9a3091fda763e364ee2e40765c9bSara Ting        return numFired;
420e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
421e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
42242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    private static void postNotification(NotificationInfo info, String summaryText,
42342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            Context context, boolean quietUpdate, boolean doPopup, boolean defaultVibrate,
4247e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            String ringtone, boolean highPriority, boolean invokedNotify,
4257e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            NotificationManager notificationMgr) {
42642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        String tickerText = getTickerText(info.eventName, info.location);
42742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        Notification notification = AlertReceiver.makeExpandingNotification(context,
42842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                info.eventName, summaryText, info.description, info.startMillis,
42942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                info.endMillis, info.eventId, info.notificationId, doPopup, highPriority);
43042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        addNotificationOptions(notification, quietUpdate, tickerText, defaultVibrate,
4317e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                ringtone, invokedNotify);
43242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        notificationMgr.notify(info.notificationId, notification);
43342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
43442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (DEBUG) {
43542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            Log.d(TAG, "Posting individual alarm notification, eventId:" + info.eventId
43642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    + ", notificationId:" + info.notificationId
43742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    + (quietUpdate ? ", quiet" : ", loud")
43842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    + (highPriority ? ", high-priority" : ""));
43942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
44042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    }
44142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
44242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    private static String getTickerText(String eventName, String location) {
44342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        String tickerText = eventName;
44442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (!TextUtils.isEmpty(location)) {
44542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            tickerText = eventName + " - " + location;
44642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
44742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        return tickerText;
44842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    }
44942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
4504e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting    static class NotificationInfo {
4514e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        String eventName;
4524e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        String location;
45342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        String description;
4544e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        long startMillis;
4554e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        long endMillis;
4564e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        long eventId;
45742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        int notificationId;
4584e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        boolean allDay;
4594e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting
46042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        NotificationInfo(String eventName, String location, String description, long startMillis,
46157dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting                long endMillis, long eventId, boolean allDay) {
4624e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.eventName = eventName;
4634e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.location = location;
46442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            this.description = description;
4654e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.startMillis = startMillis;
4664e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.endMillis = endMillis;
4674e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.eventId = eventId;
4684e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            this.allDay = allDay;
46957dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            this.notificationId = getNotificationId(eventId, startMillis);
47057dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting        }
47142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
47257dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting        /*
47357dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting         * Convert reminder into the ID for posting notifications.  Use hash so we don't
47457dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting         * have to worry about any limits (but handle the case of a collision with the ID
47557dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting         * reserved for representing the expired notification digest).
47657dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting         */
47757dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting        private static int getNotificationId(long eventId, long startMillis) {
47857dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            long result = 17;
47957dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            result = 37 * result + eventId;
48057dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            result = 37 * result + startMillis;
48157dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            int notificationId = Long.valueOf(result).hashCode();
48242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            if (notificationId == AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID) {
48357dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting                notificationId = Integer.MAX_VALUE;
48442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            }
48557dd943e5598d4778698d3c94a112f124aa7c0d1Sara Ting            return notificationId;
486146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
4874e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting    }
4880e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
4894e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting    private static boolean shouldUseDefaultVibrate(Context context, SharedPreferences prefs) {
4904e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        // Find out the circumstances under which to vibrate.
4914e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        // Migrate from pre-Froyo boolean setting if necessary.
4924e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        String vibrateWhen; // "always" or "silent" or "never"
4934e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
4944e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        {
4954e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            // Look up Froyo setting
4964e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            vibrateWhen =
4974e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
4984e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
4994e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
5004e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            boolean vibrate =
5014e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
5024e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            vibrateWhen = vibrate ?
5034e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                context.getString(R.string.prefDefault_alerts_vibrate_true) :
5044e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting                context.getString(R.string.prefDefault_alerts_vibrate_false);
5054e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        } else {
5064e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            // No setting. Use Froyo-defined default.
5074e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen);
5084e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        }
509146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
5104e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        if (vibrateWhen.equals("always")) {
5114e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            return true;
512146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
5134e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        if (!vibrateWhen.equals("silent")) {
5144e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            return false;
5154e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        }
5164e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting
5174e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        // Settings are to vibrate when silent.  Return true if it is now silent.
5184e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        AudioManager audioManager =
5194e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting            (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
5204e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting        return audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
5214e9544570d6df4cf4e8ae2f3ca9ff47b84bf899dSara Ting    }
5220e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
52342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    private static void addNotificationOptions(Notification notification, boolean quietUpdate,
5247e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            String tickerText, boolean defaultVibrate, String reminderRingtone,
5257e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            boolean invokedNotify) {
526e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        notification.defaults |= Notification.DEFAULT_LIGHTS;
527e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
528e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // Quietly update notification bar. Nothing new. Maybe something just got deleted.
529e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!quietUpdate) {
530e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Flash ticker in status bar
53142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            if (!TextUtils.isEmpty(tickerText)) {
53242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                notification.tickerText = tickerText;
53342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            }
534e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
5357e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            // If we've already posted a notification, don't play any more sounds so only
5367e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            // 1 sound per group of notifications.
5377e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            if (!invokedNotify) {
5380e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
5397e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // Generate either a pop-up dialog, status bar notification, or
5407e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // neither. Pop-up dialog and status bar notification may include a
5417e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // sound, an alert, or both. A status bar notification also includes
5427e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // a toast.
5437e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                if (defaultVibrate) {
5447e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                    notification.defaults |= Notification.DEFAULT_VIBRATE;
5457e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                }
5467e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting
5477e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // Possibly generate a sound. If 'Silent' is chosen, the ringtone
5487e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                // string will be empty.
5497e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
5507e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting                        .parse(reminderRingtone);
5517e4c339fb171f542ff8d5d4916c649f9597aa926Sara Ting            }
552146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
553146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
5540e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
555146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private void doTimeChanged() {
556146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        ContentResolver cr = getContentResolver();
557146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Object service = getSystemService(Context.ALARM_SERVICE);
558146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        AlarmManager manager = (AlarmManager) service;
559fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO Move this into Provider
560fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        rescheduleMissedAlarms(cr, this, manager);
56142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        updateAlertNotification(this, false);
562146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
5630e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
564fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String SORT_ORDER_ALARMTIME_ASC =
565fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME + " ASC";
566fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
567fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String WHERE_RESCHEDULE_MISSED_ALARMS =
568fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.STATE
569fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "="
570fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.STATE_SCHEDULED
571fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
572fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
573fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "<?"
574fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
575fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
576fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + ">?"
577fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
578fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.END + ">=?";
579fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
580fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    /**
581fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * Searches the CalendarAlerts table for alarms that should have fired but
582fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * have not and then reschedules them. This method can be called at boot
583fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * time to restore alarms that may have been lost due to a phone reboot.
584fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     *
585fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param cr the ContentResolver
586fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param context the Context
587fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param manager the AlarmManager
588fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     */
589fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    public static final void rescheduleMissedAlarms(ContentResolver cr, Context context,
590fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            AlarmManager manager) {
591fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // Get all the alerts that have been scheduled but have not fired
592fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // and should have fired by now and are not too old.
593fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long now = System.currentTimeMillis();
594fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long ancient = now - DateUtils.DAY_IN_MILLIS;
595fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        String[] projection = new String[] {
596fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME,
597fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        };
598fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
599fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO: construct an explicit SQL query so that we can add
600fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // "GROUPBY" instead of doing a sort and de-dup
601fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
602fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
603fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Long.toString(now), Long.toString(ancient), Long.toString(now)
604fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }), SORT_ORDER_ALARMTIME_ASC);
605fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (cursor == null) {
606fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            return;
607fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
608fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
609fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (DEBUG) {
610fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, "missed alarms found: " + cursor.getCount());
611fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
612fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
613fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        try {
614fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            long alarmTime = -1;
615fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
616fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            while (cursor.moveToNext()) {
617fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                long newAlarmTime = cursor.getLong(0);
618fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (alarmTime != newAlarmTime) {
619fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (DEBUG) {
620fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
621fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    }
6229881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    AlertUtils.scheduleAlarm(context, manager, newAlarmTime);
623fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    alarmTime = newAlarmTime;
624fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }
625fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            }
626fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        } finally {
627fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            cursor.close();
628fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
629fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    }
630fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
631146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private final class ServiceHandler extends Handler {
632146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public ServiceHandler(Looper looper) {
633146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            super(looper);
634146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
6350e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
636146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        @Override
637146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public void handleMessage(Message msg) {
638146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            processMessage(msg);
639146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // NOTE: We MUST not call stopSelf() directly, since we need to
640146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // make sure the wake lock acquired by AlertReceiver is released.
641146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
6420e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        }
643e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
644146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
645146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
646146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onCreate() {
647146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        HandlerThread thread = new HandlerThread("AlertService",
648146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Process.THREAD_PRIORITY_BACKGROUND);
649146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        thread.start();
6500e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
651146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper = thread.getLooper();
652146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceHandler = new ServiceHandler(mServiceLooper);
653146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
654146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
655146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
656c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff    public int onStartCommand(Intent intent, int flags, int startId) {
657c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        if (intent != null) {
658c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            Message msg = mServiceHandler.obtainMessage();
659c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.arg1 = startId;
660c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.obj = intent.getExtras();
661c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            mServiceHandler.sendMessage(msg);
662c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        }
663c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        return START_REDELIVER_INTENT;
664146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
665146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
666146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
667146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onDestroy() {
668146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper.quit();
669146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
670146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
671146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
672146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public IBinder onBind(Intent intent) {
673146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        return null;
674146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
675146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project}
676