AlertService.java revision f58faf32bbce943eb4791cef2ad6c327e3724cc7
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 {
55f58faf32bbce943eb4791cef2ad6c327e3724cc7Michael Chan    static final boolean DEBUG = true;
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[] {
9143ffa461b738692925a94c005c90ff60757455a7RoboErik            Integer.toString(CalendarAlerts.STATE_FIRED),
9243ffa461b738692925a94c005c90ff60757455a7RoboErik            Integer.toString(CalendarAlerts.STATE_SCHEDULED)
93146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
940e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
95e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
960e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
9743ffa461b738692925a94c005c90ff60757455a7RoboErik    private static final String DISMISS_OLD_SELECTION = CalendarAlerts.END + "<? AND "
9843ffa461b738692925a94c005c90ff60757455a7RoboErik            + CalendarAlerts.STATE + "=?";
9943ffa461b738692925a94c005c90ff60757455a7RoboErik
1008af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project    void processMessage(Message msg) {
101146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Bundle bundle = (Bundle) msg.obj;
1020e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
103146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // On reboot, update the notification bar with the contents of the
104146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // CalendarAlerts table.
105146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        String action = bundle.getString("action");
106e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
107fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, bundle.getLong(android.provider.CalendarContract.CalendarAlerts.ALARM_TIME)
108e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    + " Action = " + action);
109e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
110e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
111146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        if (action.equals(Intent.ACTION_BOOT_COMPLETED)
112146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                || action.equals(Intent.ACTION_TIME_CHANGED)) {
113146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            doTimeChanged();
114146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            return;
115146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
116146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
117a27a886892fe3ec5edbc63c0b58e0a988623011aRoboErik        if (!action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER)
11843ffa461b738692925a94c005c90ff60757455a7RoboErik                && !action.equals(Intent.ACTION_LOCALE_CHANGED)
11943ffa461b738692925a94c005c90ff60757455a7RoboErik                && !action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) {
120e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.w(TAG, "Invalid action: " + action);
121e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
122146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
12343ffa461b738692925a94c005c90ff60757455a7RoboErik        if (action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) {
12443ffa461b738692925a94c005c90ff60757455a7RoboErik            dismissOldAlerts(this);
12543ffa461b738692925a94c005c90ff60757455a7RoboErik        }
126e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
127e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
128605a0901134c0840b2fcf0514b4c1f8bc10dc7e0Michael Chan
12943ffa461b738692925a94c005c90ff60757455a7RoboErik    static void dismissOldAlerts(Context context) {
13043ffa461b738692925a94c005c90ff60757455a7RoboErik        ContentResolver cr = context.getContentResolver();
13143ffa461b738692925a94c005c90ff60757455a7RoboErik        final long currentTime = System.currentTimeMillis();
13243ffa461b738692925a94c005c90ff60757455a7RoboErik        ContentValues vals = new ContentValues();
13343ffa461b738692925a94c005c90ff60757455a7RoboErik        vals.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED);
13443ffa461b738692925a94c005c90ff60757455a7RoboErik        cr.update(CalendarAlerts.CONTENT_URI, vals, DISMISS_OLD_SELECTION, new String[] {
13543ffa461b738692925a94c005c90ff60757455a7RoboErik                Long.toString(currentTime), Integer.toString(CalendarAlerts.STATE_SCHEDULED)
13643ffa461b738692925a94c005c90ff60757455a7RoboErik        });
13743ffa461b738692925a94c005c90ff60757455a7RoboErik    }
13843ffa461b738692925a94c005c90ff60757455a7RoboErik
139e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    static boolean updateAlertNotification(Context context) {
140e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        ContentResolver cr = context.getContentResolver();
141e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        final long currentTime = System.currentTimeMillis();
1420e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
14343ffa461b738692925a94c005c90ff60757455a7RoboErik        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
14443ffa461b738692925a94c005c90ff60757455a7RoboErik
14543ffa461b738692925a94c005c90ff60757455a7RoboErik        boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
14643ffa461b738692925a94c005c90ff60757455a7RoboErik        boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
14743ffa461b738692925a94c005c90ff60757455a7RoboErik
14843ffa461b738692925a94c005c90ff60757455a7RoboErik        if (!doAlert) {
14943ffa461b738692925a94c005c90ff60757455a7RoboErik            if (DEBUG) {
15043ffa461b738692925a94c005c90ff60757455a7RoboErik                Log.d(TAG, "alert preference is OFF");
15143ffa461b738692925a94c005c90ff60757455a7RoboErik            }
15243ffa461b738692925a94c005c90ff60757455a7RoboErik
15343ffa461b738692925a94c005c90ff60757455a7RoboErik            // If we shouldn't be showing notifications cancel any existing ones
15443ffa461b738692925a94c005c90ff60757455a7RoboErik            // and return.
15543ffa461b738692925a94c005c90ff60757455a7RoboErik            NotificationManager nm = (NotificationManager) context
15643ffa461b738692925a94c005c90ff60757455a7RoboErik                    .getSystemService(Context.NOTIFICATION_SERVICE);
15743ffa461b738692925a94c005c90ff60757455a7RoboErik            nm.cancelAll();
15843ffa461b738692925a94c005c90ff60757455a7RoboErik            return true;
15943ffa461b738692925a94c005c90ff60757455a7RoboErik        }
16043ffa461b738692925a94c005c90ff60757455a7RoboErik
161fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
162fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
163fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                ACTIVE_ALERTS_SORT);
1640e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
165e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (alertCursor == null || alertCursor.getCount() == 0) {
166146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            if (alertCursor != null) {
167146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                alertCursor.close();
168146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
169e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
170e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
171e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            NotificationManager nm =
172e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
173e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
174e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return false;
175146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1760e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
177e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
178e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "alert count:" + alertCursor.getCount());
179146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1800e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventName = null;
182e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventLocation = null;
183e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        long notificationEventBegin = 0;
184e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int notificationEventStatus = 0;
1857321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan        boolean notificationEventAllDay = true;
186e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
187e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numReminders = 0;
188e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numFired = 0;
189146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        try {
190e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            while (alertCursor.moveToNext()) {
191e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
192e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
193e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
194e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
195e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
196e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
197e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
198e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
199e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long endTime = alertCursor.getLong(ALERT_INDEX_END);
200e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final Uri alertUri = ContentUris
201e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
202e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
203e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int state = alertCursor.getInt(ALERT_INDEX_STATE);
2047321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
205e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
206e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (DEBUG) {
207e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
208e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
209e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " declined:" + declined + " beginTime:" + beginTime
210e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " endTime:" + endTime);
211146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2120e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
213e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                ContentValues values = new ContentValues();
214e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newState = -1;
215e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
216e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Uncomment for the behavior of clearing out alerts after the
217e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // events ended. b/1880369
218e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //
219e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // if (endTime < currentTime) {
220e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //     newState = CalendarAlerts.DISMISSED;
221e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // } else
222e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
2233dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                // Remove declined events
2243dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                if (!declined) {
2253dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    // Don't count duplicate alerts for the same event
2263dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    if (eventIds.put(eventId, beginTime) == null) {
2273dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                        numReminders++;
2283dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    }
2293dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan
230fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (state == CalendarAlerts.STATE_SCHEDULED) {
231fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        newState = CalendarAlerts.STATE_FIRED;
232e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        numFired++;
233e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
234e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // Record the received time in the CalendarAlerts table.
235e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // This is useful for finding bugs that cause alarms to be
236e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // missed or delayed.
237e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
238e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    }
239e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                } else {
240fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    newState = CalendarAlerts.STATE_DISMISSED;
241146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
242146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
243e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Update row if state changed
244e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (newState != -1) {
245e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.STATE, newState);
246e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    state = newState;
247146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2480e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
249fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state == CalendarAlerts.STATE_FIRED) {
250e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // Record the time posting to notification manager.
251e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // This is used for debugging missed alarms.
252e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
253146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2540e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
255e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Write row to if anything changed
256e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (values.size() > 0) cr.update(alertUri, values, null, null);
2570e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
258fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state != CalendarAlerts.STATE_FIRED) {
259e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    continue;
260e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
261146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
262e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Pick an Event title for the notification panel by the latest
263e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // alertTime and give prefer accepted events in case of ties.
264e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newStatus;
265e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                switch (status) {
266e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_ACCEPTED:
267e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 2;
268e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
269e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_TENTATIVE:
270e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 1;
271e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
272e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    default:
273e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 0;
274e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
275e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
276e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // TODO Prioritize by "primary" calendar
277e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Assumes alerts are sorted by begin time in reverse
278e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (notificationEventName == null
279e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        || (notificationEventBegin <= beginTime &&
280e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                                notificationEventStatus < newStatus)) {
281e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventName = eventName;
282e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventLocation = location;
283e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventBegin = beginTime;
284e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventStatus = newStatus;
2857321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                    notificationEventAllDay = allDay;
286146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
287146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
288146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        } finally {
289e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (alertCursor != null) {
290e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                alertCursor.close();
291e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
292146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2930e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
294ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang        boolean quietUpdate = numFired == 0;
295cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang        boolean highPriority = numFired > 0 && doPopup;
296e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        postNotification(context, prefs, notificationEventName, notificationEventLocation,
2977321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                numReminders, quietUpdate, highPriority, notificationEventBegin,
2987321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                notificationEventAllDay);
299e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
300e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        return true;
301e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
302e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
303e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static void postNotification(Context context, SharedPreferences prefs,
304ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang            String eventName, String location, int numReminders,
3057321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan            boolean quietUpdate, boolean highPriority, long startMillis, boolean allDay) {
306e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
307e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders
308c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (quietUpdate ? " QUIET" : " loud")
309c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (highPriority ? " high-priority" : ""));
310146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3110e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3120e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        NotificationManager nm =
313e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
314146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
315e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (numReminders == 0) {
316e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
317e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
318146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3190e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
320e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName,
3217321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                location, numReminders, highPriority, startMillis, allDay);
322e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        notification.defaults |= Notification.DEFAULT_LIGHTS;
323e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
324e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // Quietly update notification bar. Nothing new. Maybe something just got deleted.
325e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!quietUpdate) {
326e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Flash ticker in status bar
327e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            notification.tickerText = eventName;
328e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (!TextUtils.isEmpty(location)) {
329e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.tickerText = eventName + " - " + location;
330e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
331e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
332e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Generate either a pop-up dialog, status bar notification, or
333e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // neither. Pop-up dialog and status bar notification may include a
334e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // sound, an alert, or both. A status bar notification also includes
335e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // a toast.
3361fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma
3371fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Find out the circumstances under which to vibrate.
3381fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Migrate from pre-Froyo boolean setting if necessary.
3391fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            String vibrateWhen; // "always" or "silent" or "never"
3404b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
3411fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            {
3421fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // Look up Froyo setting
3431fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen =
3444b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
3454b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
3461fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
3471fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                boolean vibrate =
3484b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
3491fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = vibrate ?
3501fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_true) :
3511fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_false);
3521fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            } else {
3531fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No setting. Use Froyo-defined default.
3541fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen);
3551fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            }
3561fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateAlways = vibrateWhen.equals("always");
3571fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateSilent = vibrateWhen.equals("silent");
3581fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            AudioManager audioManager =
3591fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
3601fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean nowSilent =
3611fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
362e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
363e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Possibly generate a vibration
3641fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            if (vibrateAlways || (vibrateSilent && nowSilent)) {
365e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.defaults |= Notification.DEFAULT_VIBRATE;
366e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
3670e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3680e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // Possibly generate a sound. If 'Silent' is chosen, the ringtone
3690e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // string will be empty.
3700e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            String reminderRingtone = prefs.getString(
3714b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    GeneralPreferences.KEY_ALERTS_RINGTONE, null);
3720e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
3730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan                    .parse(reminderRingtone);
374146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
375146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
376146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        nm.notify(0, notification);
377146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3780e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
379146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private void doTimeChanged() {
380146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        ContentResolver cr = getContentResolver();
381146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Object service = getSystemService(Context.ALARM_SERVICE);
382146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        AlarmManager manager = (AlarmManager) service;
383fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO Move this into Provider
384fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        rescheduleMissedAlarms(cr, this, manager);
385e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
386146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3870e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
388fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String SORT_ORDER_ALARMTIME_ASC =
389fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME + " ASC";
390fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
391fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String WHERE_RESCHEDULE_MISSED_ALARMS =
392fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.STATE
393fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "="
394fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.STATE_SCHEDULED
395fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
396fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
397fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "<?"
398fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
399fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
400fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + ">?"
401fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
402fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.END + ">=?";
403fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
404fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    /**
405fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * Searches the CalendarAlerts table for alarms that should have fired but
406fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * have not and then reschedules them. This method can be called at boot
407fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * time to restore alarms that may have been lost due to a phone reboot.
408fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     *
409fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param cr the ContentResolver
410fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param context the Context
411fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param manager the AlarmManager
412fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     */
413fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    public static final void rescheduleMissedAlarms(ContentResolver cr, Context context,
414fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            AlarmManager manager) {
415fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // Get all the alerts that have been scheduled but have not fired
416fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // and should have fired by now and are not too old.
417fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long now = System.currentTimeMillis();
418fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long ancient = now - DateUtils.DAY_IN_MILLIS;
419fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        String[] projection = new String[] {
420fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME,
421fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        };
422fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
423fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO: construct an explicit SQL query so that we can add
424fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // "GROUPBY" instead of doing a sort and de-dup
425fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
426fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
427fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Long.toString(now), Long.toString(ancient), Long.toString(now)
428fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }), SORT_ORDER_ALARMTIME_ASC);
429fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (cursor == null) {
430fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            return;
431fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
432fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
433fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (DEBUG) {
434fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, "missed alarms found: " + cursor.getCount());
435fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
436fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
437fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        try {
438fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            long alarmTime = -1;
439fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
440fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            while (cursor.moveToNext()) {
441fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                long newAlarmTime = cursor.getLong(0);
442fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (alarmTime != newAlarmTime) {
443fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (DEBUG) {
444fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
445fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    }
446fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    AlertActivity.scheduleAlarm(context, manager, newAlarmTime);
447fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    alarmTime = newAlarmTime;
448fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }
449fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            }
450fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        } finally {
451fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            cursor.close();
452fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
453fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    }
454fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
455146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private final class ServiceHandler extends Handler {
456146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public ServiceHandler(Looper looper) {
457146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            super(looper);
458146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
4590e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
460146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        @Override
461146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public void handleMessage(Message msg) {
462146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            processMessage(msg);
463146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // NOTE: We MUST not call stopSelf() directly, since we need to
464146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // make sure the wake lock acquired by AlertReceiver is released.
465146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
4660e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        }
467e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
468146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
469146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
470146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onCreate() {
471146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        HandlerThread thread = new HandlerThread("AlertService",
472146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Process.THREAD_PRIORITY_BACKGROUND);
473146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        thread.start();
4740e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
475146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper = thread.getLooper();
476146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceHandler = new ServiceHandler(mServiceLooper);
477146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
478146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
479146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
480c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff    public int onStartCommand(Intent intent, int flags, int startId) {
481c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        if (intent != null) {
482c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            Message msg = mServiceHandler.obtainMessage();
483c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.arg1 = startId;
484c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.obj = intent.getExtras();
485c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            mServiceHandler.sendMessage(msg);
486c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        }
487c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        return START_REDELIVER_INTENT;
488146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
489146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
490146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
491146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onDestroy() {
492146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper.quit();
493146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
494146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
495146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
496146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public IBinder onBind(Intent intent) {
497146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        return null;
498146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
499146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project}
500