AlertService.java revision fa292a0db2a6f04255c75a57908b17ba48a96183
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
49e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport java.util.HashMap;
50146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
51146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/**
52146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * This service is used to handle calendar event reminders.
53146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */
54146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpublic class AlertService extends Service {
55fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    static final boolean DEBUG = false;
56146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final String TAG = "AlertService";
570e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
58146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile Looper mServiceLooper;
59146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile ServiceHandler mServiceHandler;
600e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
610e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan    private static final String[] ALERT_PROJECTION = new String[] {
62146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts._ID,                     // 0
63146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_ID,                // 1
64146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.STATE,                   // 2
65146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.TITLE,                   // 3
66146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_LOCATION,          // 4
67146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
68146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALL_DAY,                 // 6
69146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALARM_TIME,              // 7
70146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.MINUTES,                 // 8
71146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.BEGIN,                   // 9
72e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        CalendarAlerts.END,                     // 10
73146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
740e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
75146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ID = 0;
76146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_ID = 1;
77146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_STATE = 2;
78146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_TITLE = 3;
79146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_LOCATION = 4;
80146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
81146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALL_DAY = 6;
82146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALARM_TIME = 7;
83146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_MINUTES = 8;
84146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_BEGIN = 9;
85e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final int ALERT_INDEX_END = 10;
86146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
87e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
88e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
89146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
90e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
91fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Integer.toString(CalendarAlerts.STATE_FIRED), Integer.toString(CalendarAlerts.STATE_SCHEDULED)
92146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
930e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
94e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
950e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
968af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project    void processMessage(Message msg) {
97146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Bundle bundle = (Bundle) msg.obj;
980e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
99146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // On reboot, update the notification bar with the contents of the
100146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // CalendarAlerts table.
101146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        String action = bundle.getString("action");
102e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
103fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, bundle.getLong(android.provider.CalendarContract.CalendarAlerts.ALARM_TIME)
104e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    + " Action = " + action);
105e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
106e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
107146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        if (action.equals(Intent.ACTION_BOOT_COMPLETED)
108146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                || action.equals(Intent.ACTION_TIME_CHANGED)) {
109146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            doTimeChanged();
110146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            return;
111146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
112146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
113a27a886892fe3ec5edbc63c0b58e0a988623011aRoboErik        if (!action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER)
114e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                && !action.equals(Intent.ACTION_LOCALE_CHANGED)) {
115e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.w(TAG, "Invalid action: " + action);
116e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
117146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
118146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
119e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
120e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
121605a0901134c0840b2fcf0514b4c1f8bc10dc7e0Michael Chan
122e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    static boolean updateAlertNotification(Context context) {
123e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        ContentResolver cr = context.getContentResolver();
124e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        final long currentTime = System.currentTimeMillis();
1250e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
126fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
127fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
128fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                ACTIVE_ALERTS_SORT);
1290e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
130e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (alertCursor == null || alertCursor.getCount() == 0) {
131146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            if (alertCursor != null) {
132146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                alertCursor.close();
133146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
134e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
135e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
136e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            NotificationManager nm =
137e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
138e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
139e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return false;
140146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1410e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
142e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
143e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "alert count:" + alertCursor.getCount());
144146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1450e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
146e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventName = null;
147e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventLocation = null;
148e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        long notificationEventBegin = 0;
149e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int notificationEventStatus = 0;
1507321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan        boolean notificationEventAllDay = true;
151e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
152e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numReminders = 0;
153e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numFired = 0;
154146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        try {
155e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            while (alertCursor.moveToNext()) {
156e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
157e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
158e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
159e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
160e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
161e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
162e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
163e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
164e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long endTime = alertCursor.getLong(ALERT_INDEX_END);
165e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final Uri alertUri = ContentUris
166e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
167e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
168e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int state = alertCursor.getInt(ALERT_INDEX_STATE);
1697321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
170e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
171e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (DEBUG) {
172e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
173e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
174e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " declined:" + declined + " beginTime:" + beginTime
175e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " endTime:" + endTime);
176146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
1770e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
178e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                ContentValues values = new ContentValues();
179e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newState = -1;
180e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Uncomment for the behavior of clearing out alerts after the
182e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // events ended. b/1880369
183e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //
184e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // if (endTime < currentTime) {
185e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //     newState = CalendarAlerts.DISMISSED;
186e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // } else
187e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
1883dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                // Remove declined events
1893dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                if (!declined) {
1903dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    // Don't count duplicate alerts for the same event
1913dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    if (eventIds.put(eventId, beginTime) == null) {
1923dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                        numReminders++;
1933dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    }
1943dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan
195fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (state == CalendarAlerts.STATE_SCHEDULED) {
196fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        newState = CalendarAlerts.STATE_FIRED;
197e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        numFired++;
198e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
199e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // Record the received time in the CalendarAlerts table.
200e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // This is useful for finding bugs that cause alarms to be
201e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // missed or delayed.
202e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
203e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    }
204e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                } else {
205fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    newState = CalendarAlerts.STATE_DISMISSED;
206146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
207146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
208e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Update row if state changed
209e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (newState != -1) {
210e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.STATE, newState);
211e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    state = newState;
212146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2130e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
214fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state == CalendarAlerts.STATE_FIRED) {
215e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // Record the time posting to notification manager.
216e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // This is used for debugging missed alarms.
217e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
218146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2190e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
220e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Write row to if anything changed
221e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (values.size() > 0) cr.update(alertUri, values, null, null);
2220e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
223fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state != CalendarAlerts.STATE_FIRED) {
224e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    continue;
225e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
226146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
227e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Pick an Event title for the notification panel by the latest
228e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // alertTime and give prefer accepted events in case of ties.
229e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newStatus;
230e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                switch (status) {
231e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_ACCEPTED:
232e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 2;
233e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
234e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_TENTATIVE:
235e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 1;
236e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
237e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    default:
238e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 0;
239e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
240e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
241e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // TODO Prioritize by "primary" calendar
242e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Assumes alerts are sorted by begin time in reverse
243e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (notificationEventName == null
244e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        || (notificationEventBegin <= beginTime &&
245e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                                notificationEventStatus < newStatus)) {
246e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventName = eventName;
247e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventLocation = location;
248e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventBegin = beginTime;
249e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventStatus = newStatus;
2507321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                    notificationEventAllDay = allDay;
251146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
252146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
253146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        } finally {
254e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (alertCursor != null) {
255e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                alertCursor.close();
256e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
257146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2580e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
2594b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
260cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang
2614b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa        boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
2624b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa        boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
2630e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
264e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // TODO check for this before adding stuff to the alerts table.
265cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang        if (!doAlert) {
266e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) {
267146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Log.d(TAG, "alert preference is OFF");
268146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
269e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return true;
270e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
271e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
272ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang        boolean quietUpdate = numFired == 0;
273cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang        boolean highPriority = numFired > 0 && doPopup;
274e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        postNotification(context, prefs, notificationEventName, notificationEventLocation,
2757321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                numReminders, quietUpdate, highPriority, notificationEventBegin,
2767321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                notificationEventAllDay);
277e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
278e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        return true;
279e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
280e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
281e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static void postNotification(Context context, SharedPreferences prefs,
282ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang            String eventName, String location, int numReminders,
2837321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan            boolean quietUpdate, boolean highPriority, long startMillis, boolean allDay) {
284e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
285e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders
286c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (quietUpdate ? " QUIET" : " loud")
287c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (highPriority ? " high-priority" : ""));
288146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2890e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
2900e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        NotificationManager nm =
291e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
292146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
293e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (numReminders == 0) {
294e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
295e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
296146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2970e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
298e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName,
2997321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                location, numReminders, highPriority, startMillis, allDay);
300e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        notification.defaults |= Notification.DEFAULT_LIGHTS;
301e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
302e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // Quietly update notification bar. Nothing new. Maybe something just got deleted.
303e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!quietUpdate) {
304e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Flash ticker in status bar
305e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            notification.tickerText = eventName;
306e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (!TextUtils.isEmpty(location)) {
307e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.tickerText = eventName + " - " + location;
308e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
309e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
310e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Generate either a pop-up dialog, status bar notification, or
311e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // neither. Pop-up dialog and status bar notification may include a
312e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // sound, an alert, or both. A status bar notification also includes
313e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // a toast.
3141fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma
3151fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Find out the circumstances under which to vibrate.
3161fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Migrate from pre-Froyo boolean setting if necessary.
3171fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            String vibrateWhen; // "always" or "silent" or "never"
3184b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
3191fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            {
3201fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // Look up Froyo setting
3211fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen =
3224b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
3234b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
3241fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
3251fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                boolean vibrate =
3264b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
3271fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = vibrate ?
3281fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_true) :
3291fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_false);
3301fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            } else {
3311fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No setting. Use Froyo-defined default.
3321fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen);
3331fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            }
3341fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateAlways = vibrateWhen.equals("always");
3351fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateSilent = vibrateWhen.equals("silent");
3361fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            AudioManager audioManager =
3371fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
3381fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean nowSilent =
3391fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
340e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
341e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Possibly generate a vibration
3421fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            if (vibrateAlways || (vibrateSilent && nowSilent)) {
343e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.defaults |= Notification.DEFAULT_VIBRATE;
344e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
3450e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3460e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // Possibly generate a sound. If 'Silent' is chosen, the ringtone
3470e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // string will be empty.
3480e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            String reminderRingtone = prefs.getString(
3494b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    GeneralPreferences.KEY_ALERTS_RINGTONE, null);
3500e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
3510e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan                    .parse(reminderRingtone);
352146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
353146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
354146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        nm.notify(0, notification);
355146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3560e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
357146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private void doTimeChanged() {
358146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        ContentResolver cr = getContentResolver();
359146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Object service = getSystemService(Context.ALARM_SERVICE);
360146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        AlarmManager manager = (AlarmManager) service;
361fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO Move this into Provider
362fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        rescheduleMissedAlarms(cr, this, manager);
363e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
364146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3650e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
366fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String SORT_ORDER_ALARMTIME_ASC =
367fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME + " ASC";
368fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
369fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String WHERE_RESCHEDULE_MISSED_ALARMS =
370fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.STATE
371fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "="
372fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.STATE_SCHEDULED
373fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
374fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
375fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "<?"
376fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
377fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
378fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + ">?"
379fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
380fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.END + ">=?";
381fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
382fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    /**
383fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * Searches the CalendarAlerts table for alarms that should have fired but
384fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * have not and then reschedules them. This method can be called at boot
385fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * time to restore alarms that may have been lost due to a phone reboot.
386fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     *
387fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param cr the ContentResolver
388fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param context the Context
389fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param manager the AlarmManager
390fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     */
391fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    public static final void rescheduleMissedAlarms(ContentResolver cr, Context context,
392fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            AlarmManager manager) {
393fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // Get all the alerts that have been scheduled but have not fired
394fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // and should have fired by now and are not too old.
395fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long now = System.currentTimeMillis();
396fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long ancient = now - DateUtils.DAY_IN_MILLIS;
397fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        String[] projection = new String[] {
398fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME,
399fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        };
400fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
401fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO: construct an explicit SQL query so that we can add
402fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // "GROUPBY" instead of doing a sort and de-dup
403fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
404fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
405fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Long.toString(now), Long.toString(ancient), Long.toString(now)
406fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }), SORT_ORDER_ALARMTIME_ASC);
407fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (cursor == null) {
408fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            return;
409fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
410fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
411fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (DEBUG) {
412fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, "missed alarms found: " + cursor.getCount());
413fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
414fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
415fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        try {
416fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            long alarmTime = -1;
417fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
418fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            while (cursor.moveToNext()) {
419fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                long newAlarmTime = cursor.getLong(0);
420fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (alarmTime != newAlarmTime) {
421fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (DEBUG) {
422fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
423fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    }
424fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    AlertActivity.scheduleAlarm(context, manager, newAlarmTime);
425fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    alarmTime = newAlarmTime;
426fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }
427fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            }
428fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        } finally {
429fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            cursor.close();
430fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
431fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    }
432fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
433146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private final class ServiceHandler extends Handler {
434146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public ServiceHandler(Looper looper) {
435146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            super(looper);
436146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
4370e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
438146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        @Override
439146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public void handleMessage(Message msg) {
440146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            processMessage(msg);
441146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // NOTE: We MUST not call stopSelf() directly, since we need to
442146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // make sure the wake lock acquired by AlertReceiver is released.
443146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
4440e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        }
445e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
446146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
447146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
448146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onCreate() {
449146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        HandlerThread thread = new HandlerThread("AlertService",
450146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Process.THREAD_PRIORITY_BACKGROUND);
451146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        thread.start();
4520e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
453146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper = thread.getLooper();
454146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceHandler = new ServiceHandler(mServiceLooper);
455146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
456146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
457146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
458c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff    public int onStartCommand(Intent intent, int flags, int startId) {
459c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        if (intent != null) {
460c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            Message msg = mServiceHandler.obtainMessage();
461c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.arg1 = startId;
462c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.obj = intent.getExtras();
463c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            mServiceHandler.sendMessage(msg);
464c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        }
465c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        return START_REDELIVER_INTENT;
466146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
467146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
468146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
469146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onDestroy() {
470146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper.quit();
471146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
472146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
473146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
474146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public IBinder onBind(Intent intent) {
475146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        return null;
476146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
477146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project}
478