AlertService.java revision 9881907c47b2658fa85954bfb339c4b1eab9fc8e
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();
14243ffa461b738692925a94c005c90ff60757455a7RoboErik        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
14343ffa461b738692925a94c005c90ff60757455a7RoboErik
14443ffa461b738692925a94c005c90ff60757455a7RoboErik        boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
14543ffa461b738692925a94c005c90ff60757455a7RoboErik        boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
14643ffa461b738692925a94c005c90ff60757455a7RoboErik
14743ffa461b738692925a94c005c90ff60757455a7RoboErik        if (!doAlert) {
14843ffa461b738692925a94c005c90ff60757455a7RoboErik            if (DEBUG) {
14943ffa461b738692925a94c005c90ff60757455a7RoboErik                Log.d(TAG, "alert preference is OFF");
15043ffa461b738692925a94c005c90ff60757455a7RoboErik            }
15143ffa461b738692925a94c005c90ff60757455a7RoboErik
15243ffa461b738692925a94c005c90ff60757455a7RoboErik            // If we shouldn't be showing notifications cancel any existing ones
15343ffa461b738692925a94c005c90ff60757455a7RoboErik            // and return.
15443ffa461b738692925a94c005c90ff60757455a7RoboErik            NotificationManager nm = (NotificationManager) context
15543ffa461b738692925a94c005c90ff60757455a7RoboErik                    .getSystemService(Context.NOTIFICATION_SERVICE);
15643ffa461b738692925a94c005c90ff60757455a7RoboErik            nm.cancelAll();
15743ffa461b738692925a94c005c90ff60757455a7RoboErik            return true;
15843ffa461b738692925a94c005c90ff60757455a7RoboErik        }
15943ffa461b738692925a94c005c90ff60757455a7RoboErik
160fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
161fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
162fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                ACTIVE_ALERTS_SORT);
1630e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
164e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (alertCursor == null || alertCursor.getCount() == 0) {
165146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            if (alertCursor != null) {
166146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                alertCursor.close();
167146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
168e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
169e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
170e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            NotificationManager nm =
171e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
1729881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson            nm.cancel(AlertUtils.NOTIFICATION_ID);
173e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return false;
174146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1750e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
176e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
177e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "alert count:" + alertCursor.getCount());
178146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1790e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
180e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventName = null;
181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventLocation = null;
182e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        long notificationEventBegin = 0;
1839881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        long notificationEventEnd = 0;
1849881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        long notificationEventId = -1;
185e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int notificationEventStatus = 0;
1867321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan        boolean notificationEventAllDay = true;
187e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
188e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numReminders = 0;
189e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numFired = 0;
190146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        try {
191e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            while (alertCursor.moveToNext()) {
192e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
193e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
194e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
195e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
196e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
197e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
198e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
199e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
200e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long endTime = alertCursor.getLong(ALERT_INDEX_END);
201e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final Uri alertUri = ContentUris
202e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
203e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
204e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int state = alertCursor.getInt(ALERT_INDEX_STATE);
2057321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
206e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
207e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (DEBUG) {
208e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
209e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
210e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " declined:" + declined + " beginTime:" + beginTime
211e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " endTime:" + endTime);
212146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2130e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
214e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                ContentValues values = new ContentValues();
215e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newState = -1;
216e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
217e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Uncomment for the behavior of clearing out alerts after the
218e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // events ended. b/1880369
219e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //
220e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // if (endTime < currentTime) {
221e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //     newState = CalendarAlerts.DISMISSED;
222e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // } else
223e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
2243dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                // Remove declined events
2253dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                if (!declined) {
2263dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    // Don't count duplicate alerts for the same event
2273dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    if (eventIds.put(eventId, beginTime) == null) {
2283dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                        numReminders++;
2293dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan                    }
2303dd7e40b48c91913b02cab6b06ce5a07a9654709Michael Chan
231fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (state == CalendarAlerts.STATE_SCHEDULED) {
232fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        newState = CalendarAlerts.STATE_FIRED;
233e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        numFired++;
234e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
235e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // Record the received time in the CalendarAlerts table.
236e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // This is useful for finding bugs that cause alarms to be
237e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // missed or delayed.
238e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
239e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    }
240e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                } else {
241fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    newState = CalendarAlerts.STATE_DISMISSED;
242146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
243146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
244e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Update row if state changed
245e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (newState != -1) {
246e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.STATE, newState);
247e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    state = newState;
248146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2490e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
250fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state == CalendarAlerts.STATE_FIRED) {
251e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // Record the time posting to notification manager.
252e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // This is used for debugging missed alarms.
253e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
254146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2550e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
256e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Write row to if anything changed
257e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (values.size() > 0) cr.update(alertUri, values, null, null);
2580e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
259fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (state != CalendarAlerts.STATE_FIRED) {
260e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    continue;
261e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
262146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
263e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Pick an Event title for the notification panel by the latest
264e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // alertTime and give prefer accepted events in case of ties.
265e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newStatus;
266e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                switch (status) {
267e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_ACCEPTED:
268e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 2;
269e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
270e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_TENTATIVE:
271e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 1;
272e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
273e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    default:
274e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 0;
275e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
276e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
277e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // TODO Prioritize by "primary" calendar
278e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Assumes alerts are sorted by begin time in reverse
279e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (notificationEventName == null
280e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        || (notificationEventBegin <= beginTime &&
281e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                                notificationEventStatus < newStatus)) {
282e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventLocation = location;
283e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventBegin = beginTime;
2849881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    notificationEventEnd = endTime;
2859881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    notificationEventId = eventId;
286e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventStatus = newStatus;
2877321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                    notificationEventAllDay = allDay;
288146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2899881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                if (numReminders == 1) {
2909881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    notificationEventName = eventName;
2919881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                } else {
2929881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    notificationEventName = eventName + ", " + notificationEventName;
2939881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                }
294146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
295146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        } finally {
296e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (alertCursor != null) {
297e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                alertCursor.close();
298e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
299146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3000e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
301ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang        boolean quietUpdate = numFired == 0;
302cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang        boolean highPriority = numFired > 0 && doPopup;
303e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        postNotification(context, prefs, notificationEventName, notificationEventLocation,
3047321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan                numReminders, quietUpdate, highPriority, notificationEventBegin,
3059881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                notificationEventEnd, notificationEventId, notificationEventAllDay);
306e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
307e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        return true;
308e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
309e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
310e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static void postNotification(Context context, SharedPreferences prefs,
3119881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson            String eventName, String location, int numReminders, boolean quietUpdate,
3129881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson            boolean highPriority, long startMillis, long endMillis, long id, boolean allDay) {
313e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
314e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders
315c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (quietUpdate ? " QUIET" : " loud")
316c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang                    + (highPriority ? " high-priority" : ""));
317146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3180e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3190e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        NotificationManager nm =
320e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
321146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
322e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (numReminders == 0) {
3239881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson            nm.cancel(AlertUtils.NOTIFICATION_ID);
324e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
325146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3260e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
327e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName,
3289881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                location, numReminders, highPriority, startMillis, endMillis, id, allDay);
329e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        notification.defaults |= Notification.DEFAULT_LIGHTS;
330e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
331e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // Quietly update notification bar. Nothing new. Maybe something just got deleted.
332e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!quietUpdate) {
333e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Flash ticker in status bar
334e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            notification.tickerText = eventName;
335e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (!TextUtils.isEmpty(location)) {
336e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.tickerText = eventName + " - " + location;
337e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
338e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
339e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Generate either a pop-up dialog, status bar notification, or
340e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // neither. Pop-up dialog and status bar notification may include a
341e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // sound, an alert, or both. A status bar notification also includes
342e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // a toast.
3431fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma
3441fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Find out the circumstances under which to vibrate.
3451fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            // Migrate from pre-Froyo boolean setting if necessary.
3461fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            String vibrateWhen; // "always" or "silent" or "never"
3474b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
3481fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            {
3491fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // Look up Froyo setting
3501fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen =
3514b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
3524b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa            } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
3531fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
3541fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                boolean vibrate =
3554b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
3561fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = vibrate ?
3571fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_true) :
3581fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                    context.getString(R.string.prefDefault_alerts_vibrate_false);
3591fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            } else {
3601fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                // No setting. Use Froyo-defined default.
3611fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen);
3621fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            }
3631fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateAlways = vibrateWhen.equals("always");
3641fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean vibrateSilent = vibrateWhen.equals("silent");
3651fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            AudioManager audioManager =
3661fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
3671fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            boolean nowSilent =
3681fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma                audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
369e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
370e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Possibly generate a vibration
3711fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma            if (vibrateAlways || (vibrateSilent && nowSilent)) {
372e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.defaults |= Notification.DEFAULT_VIBRATE;
373e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
3740e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3750e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // Possibly generate a sound. If 'Silent' is chosen, the ringtone
3760e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // string will be empty.
3770e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            String reminderRingtone = prefs.getString(
3784b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa                    GeneralPreferences.KEY_ALERTS_RINGTONE, null);
3790e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
3800e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan                    .parse(reminderRingtone);
381146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
382146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
3839881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        nm.notify(AlertUtils.NOTIFICATION_ID, notification);
384146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3850e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
386146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private void doTimeChanged() {
387146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        ContentResolver cr = getContentResolver();
388146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Object service = getSystemService(Context.ALARM_SERVICE);
389146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        AlarmManager manager = (AlarmManager) service;
390fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO Move this into Provider
391fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        rescheduleMissedAlarms(cr, this, manager);
392e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
393146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3940e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
395fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String SORT_ORDER_ALARMTIME_ASC =
396fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME + " ASC";
397fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
398fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    private static final String WHERE_RESCHEDULE_MISSED_ALARMS =
399fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.STATE
400fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "="
401fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.STATE_SCHEDULED
402fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
403fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
404fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + "<?"
405fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
406fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.ALARM_TIME
407fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + ">?"
408fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + " AND "
409fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            + CalendarContract.CalendarAlerts.END + ">=?";
410fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
411fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    /**
412fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * Searches the CalendarAlerts table for alarms that should have fired but
413fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * have not and then reschedules them. This method can be called at boot
414fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * time to restore alarms that may have been lost due to a phone reboot.
415fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     *
416fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param cr the ContentResolver
417fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param context the Context
418fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     * @param manager the AlarmManager
419fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik     */
420fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    public static final void rescheduleMissedAlarms(ContentResolver cr, Context context,
421fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            AlarmManager manager) {
422fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // Get all the alerts that have been scheduled but have not fired
423fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // and should have fired by now and are not too old.
424fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long now = System.currentTimeMillis();
425fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        long ancient = now - DateUtils.DAY_IN_MILLIS;
426fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        String[] projection = new String[] {
427fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            CalendarContract.CalendarAlerts.ALARM_TIME,
428fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        };
429fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
430fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // TODO: construct an explicit SQL query so that we can add
431fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        // "GROUPBY" instead of doing a sort and de-dup
432fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
433fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
434fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Long.toString(now), Long.toString(ancient), Long.toString(now)
435fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }), SORT_ORDER_ALARMTIME_ASC);
436fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (cursor == null) {
437fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            return;
438fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
439fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
440fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        if (DEBUG) {
441fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            Log.d(TAG, "missed alarms found: " + cursor.getCount());
442fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
443fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
444fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        try {
445fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            long alarmTime = -1;
446fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
447fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            while (cursor.moveToNext()) {
448fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                long newAlarmTime = cursor.getLong(0);
449fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                if (alarmTime != newAlarmTime) {
450fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    if (DEBUG) {
451fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                        Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
452fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    }
4539881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                    AlertUtils.scheduleAlarm(context, manager, newAlarmTime);
454fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                    alarmTime = newAlarmTime;
455fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik                }
456fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            }
457fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        } finally {
458fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik            cursor.close();
459fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik        }
460fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik    }
461fa292a0db2a6f04255c75a57908b17ba48a96183RoboErik
462146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private final class ServiceHandler extends Handler {
463146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public ServiceHandler(Looper looper) {
464146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            super(looper);
465146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
4660e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
467146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        @Override
468146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public void handleMessage(Message msg) {
469146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            processMessage(msg);
470146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // NOTE: We MUST not call stopSelf() directly, since we need to
471146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // make sure the wake lock acquired by AlertReceiver is released.
472146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
4730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        }
474e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
475146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
476146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
477146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onCreate() {
478146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        HandlerThread thread = new HandlerThread("AlertService",
479146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Process.THREAD_PRIORITY_BACKGROUND);
480146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        thread.start();
4810e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
482146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper = thread.getLooper();
483146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceHandler = new ServiceHandler(mServiceLooper);
484146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
485146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
486146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
487c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff    public int onStartCommand(Intent intent, int flags, int startId) {
488c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        if (intent != null) {
489c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            Message msg = mServiceHandler.obtainMessage();
490c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.arg1 = startId;
491c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.obj = intent.getExtras();
492c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            mServiceHandler.sendMessage(msg);
493c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        }
494c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        return START_REDELIVER_INTENT;
495146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
496146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
497146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
498146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onDestroy() {
499146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper.quit();
500146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
501146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
502146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
503146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public IBinder onBind(Intent intent) {
504146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        return null;
505146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
506146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project}
507