AlertService.java revision e2ae1ef8decfddcc4e5802483e92cab93c6fc67c
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
17146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpackage com.android.calendar;
18146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
19146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.AlarmManager;
20146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Notification;
21146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.NotificationManager;
22146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Service;
23146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentResolver;
24e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport android.content.ContentUris;
25146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentValues;
26146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Context;
27146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Intent;
28146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.SharedPreferences;
29146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.database.Cursor;
30146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.net.Uri;
31146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Bundle;
32146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Handler;
33146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.HandlerThread;
34146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.IBinder;
35146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Looper;
36146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Message;
37146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Process;
38146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.preference.PreferenceManager;
39146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.provider.Calendar.Attendees;
40146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.provider.Calendar.CalendarAlerts;
41146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.text.TextUtils;
42146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.util.Log;
43e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
44e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport java.util.HashMap;
45146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
46146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/**
47146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * This service is used to handle calendar event reminders.
48146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */
49146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpublic class AlertService extends Service {
50e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    static final boolean DEBUG = true;
51146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final String TAG = "AlertService";
520e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
53146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile Looper mServiceLooper;
54146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private volatile ServiceHandler mServiceHandler;
550e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
560e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan    private static final String[] ALERT_PROJECTION = new String[] {
57146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts._ID,                     // 0
58146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_ID,                // 1
59146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.STATE,                   // 2
60146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.TITLE,                   // 3
61146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.EVENT_LOCATION,          // 4
62146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
63146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALL_DAY,                 // 6
64146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.ALARM_TIME,              // 7
65146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.MINUTES,                 // 8
66146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.BEGIN,                   // 9
67e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        CalendarAlerts.END,                     // 10
68146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
690e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
70146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ID = 0;
71146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_ID = 1;
72146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_STATE = 2;
73146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_TITLE = 3;
74146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_EVENT_LOCATION = 4;
75146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
76146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALL_DAY = 6;
77146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_ALARM_TIME = 7;
78146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_MINUTES = 8;
79146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private static final int ALERT_INDEX_BEGIN = 9;
80e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final int ALERT_INDEX_END = 10;
81146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
82e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
83e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
84146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
85e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
86e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Integer.toString(CalendarAlerts.FIRED), Integer.toString(CalendarAlerts.SCHEDULED)
87146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    };
880e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
89e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
900e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
918af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project    @SuppressWarnings("deprecation")
928af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project    void processMessage(Message msg) {
93146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Bundle bundle = (Bundle) msg.obj;
940e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
95146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // On reboot, update the notification bar with the contents of the
96146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        // CalendarAlerts table.
97146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        String action = bundle.getString("action");
98e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
99e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "" + bundle.getLong(android.provider.Calendar.CalendarAlerts.ALARM_TIME)
100e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    + " Action = " + action);
101e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
102e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
103146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        if (action.equals(Intent.ACTION_BOOT_COMPLETED)
104146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                || action.equals(Intent.ACTION_TIME_CHANGED)) {
105146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            doTimeChanged();
106146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            return;
107146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
108146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
109e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!action.equals(android.provider.Calendar.EVENT_REMINDER_ACTION)
110e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                && !action.equals(Intent.ACTION_LOCALE_CHANGED)) {
111e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.w(TAG, "Invalid action: " + action);
112e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
113146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
114146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
115e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
116e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
117605a0901134c0840b2fcf0514b4c1f8bc10dc7e0Michael Chan
118e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    static boolean updateAlertNotification(Context context) {
119e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        ContentResolver cr = context.getContentResolver();
120e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        final long currentTime = System.currentTimeMillis();
1210e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
122e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        Cursor alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION, ACTIVE_ALERTS_SELECTION
123e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                + currentTime, ACTIVE_ALERTS_SELECTION_ARGS, ACTIVE_ALERTS_SORT);
1240e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
125e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (alertCursor == null || alertCursor.getCount() == 0) {
126146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            if (alertCursor != null) {
127146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                alertCursor.close();
128146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
129e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
130e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
131e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            NotificationManager nm =
132e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
133e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
134e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return false;
135146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1360e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
137e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
138e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "alert count:" + alertCursor.getCount());
139146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
1400e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
141e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventName = null;
142e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        String notificationEventLocation = null;
143e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        long notificationEventBegin = 0;
144e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int notificationEventStatus = 0;
145e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
146e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numReminders = 0;
147e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        int numFired = 0;
148146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        try {
149e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            while (alertCursor.moveToNext()) {
150e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
151e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
152e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
153e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
154e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
155e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
156e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
157e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
158e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
159e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long endTime = alertCursor.getLong(ALERT_INDEX_END);
160e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final Uri alertUri = ContentUris
161e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
162e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
163e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int state = alertCursor.getInt(ALERT_INDEX_STATE);
164e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
165e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (DEBUG) {
166e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
167e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
168e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " declined:" + declined + " beginTime:" + beginTime
169e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                            + " endTime:" + endTime);
170146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
1710e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
172e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                ContentValues values = new ContentValues();
173e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newState = -1;
174e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
175e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Uncomment for the behavior of clearing out alerts after the
176e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // events ended. b/1880369
177e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //
178e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // if (endTime < currentTime) {
179e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                //     newState = CalendarAlerts.DISMISSED;
180e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // } else
181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
182e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Remove declined events and duplicate alerts for the same event
183e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (!declined && eventIds.put(eventId, beginTime) == null) {
184e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    numReminders++;
185e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    if (state == CalendarAlerts.SCHEDULED) {
186e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newState = CalendarAlerts.FIRED;
187e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        numFired++;
188e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
189e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // Record the received time in the CalendarAlerts table.
190e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // This is useful for finding bugs that cause alarms to be
191e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        // missed or delayed.
192e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
193e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    }
194e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                } else {
195e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    newState = CalendarAlerts.DISMISSED;
196e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    if (DEBUG) {
197e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        if (!declined) Log.d(TAG, "dropping dup alert for event " + eventId);
198146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                    }
199146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
200146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
201e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Update row if state changed
202e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (newState != -1) {
203e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.STATE, newState);
204e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    state = newState;
205146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2060e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
207e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (state == CalendarAlerts.FIRED) {
208e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // Record the time posting to notification manager.
209e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    // This is used for debugging missed alarms.
210e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
211146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
2120e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
213e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Write row to if anything changed
214e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (values.size() > 0) cr.update(alertUri, values, null, null);
2150e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
216e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (state != CalendarAlerts.FIRED) {
217e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    continue;
218e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
219146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
220e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Pick an Event title for the notification panel by the latest
221e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // alertTime and give prefer accepted events in case of ties.
222e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                int newStatus;
223e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                switch (status) {
224e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_ACCEPTED:
225e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 2;
226e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
227e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    case Attendees.ATTENDEE_STATUS_TENTATIVE:
228e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 1;
229e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        break;
230e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    default:
231e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        newStatus = 0;
232e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                }
233e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
234e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // TODO Prioritize by "primary" calendar
235e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                // Assumes alerts are sorted by begin time in reverse
236e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                if (notificationEventName == null
237e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                        || (notificationEventBegin <= beginTime &&
238e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                                notificationEventStatus < newStatus)) {
239e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventName = eventName;
240e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventLocation = location;
241e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventBegin = beginTime;
242e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    notificationEventStatus = newStatus;
243146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                }
244146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
245146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        } finally {
246e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (alertCursor != null) {
247e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                alertCursor.close();
248e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
249146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2500e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
251e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
252146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        String reminderType = prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_TYPE,
253146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                CalendarPreferenceActivity.ALERT_TYPE_STATUS_BAR);
2540e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
255e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // TODO check for this before adding stuff to the alerts table.
256146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_OFF)) {
257e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (DEBUG) {
258146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Log.d(TAG, "alert preference is OFF");
259146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            }
260e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return true;
261e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
262e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
263e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        postNotification(context, prefs, notificationEventName, notificationEventLocation,
264e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                numReminders, numFired == 0 /* quiet update */);
265e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
266e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (numFired > 0 && reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_ALERTS)) {
267e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Intent alertIntent = new Intent();
268e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            alertIntent.setClass(context, AlertActivity.class);
269e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            alertIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
270e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            context.startActivity(alertIntent);
271e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        }
272e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
273e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        return true;
274e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
275e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
276e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    private static void postNotification(Context context, SharedPreferences prefs,
277e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            String eventName, String location, int numReminders, boolean quietUpdate) {
278e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (DEBUG) {
279e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders
280e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    + (quietUpdate ? " QUIET" : " loud"));
281146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2820e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
2830e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        NotificationManager nm =
284e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
285146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
286e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (numReminders == 0) {
287e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            nm.cancel(0);
288e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            return;
289146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
2900e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
291e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName,
292e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                location, numReminders);
293e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        notification.defaults |= Notification.DEFAULT_LIGHTS;
294e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
295e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        // Quietly update notification bar. Nothing new. Maybe something just got deleted.
296e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        if (!quietUpdate) {
297e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Flash ticker in status bar
298e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            notification.tickerText = eventName;
299e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (!TextUtils.isEmpty(location)) {
300e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.tickerText = eventName + " - " + location;
301e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
302e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
303e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Generate either a pop-up dialog, status bar notification, or
304e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // neither. Pop-up dialog and status bar notification may include a
305e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // sound, an alert, or both. A status bar notification also includes
306e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // a toast.
307e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            boolean reminderVibrate =
308e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                    prefs.getBoolean(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE, false);
309e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan
310e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            // Possibly generate a vibration
311e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            if (reminderVibrate) {
312e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan                notification.defaults |= Notification.DEFAULT_VIBRATE;
313e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan            }
3140e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
3150e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // Possibly generate a sound. If 'Silent' is chosen, the ringtone
3160e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            // string will be empty.
3170e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            String reminderRingtone = prefs.getString(
3180e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan                    CalendarPreferenceActivity.KEY_ALERTS_RINGTONE, null);
3190e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan            notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
3200e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan                    .parse(reminderRingtone);
321146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
322146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
323146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        nm.notify(0, notification);
324146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3250e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
326146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private void doTimeChanged() {
327146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        ContentResolver cr = getContentResolver();
328146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        Object service = getSystemService(Context.ALARM_SERVICE);
329146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        AlarmManager manager = (AlarmManager) service;
330146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        CalendarAlerts.rescheduleMissedAlarms(cr, this, manager);
331e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan        updateAlertNotification(this);
332146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
3330e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
334146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    private final class ServiceHandler extends Handler {
335146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public ServiceHandler(Looper looper) {
336146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            super(looper);
337146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        }
3380e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
339146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        @Override
340146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        public void handleMessage(Message msg) {
341146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            processMessage(msg);
342146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // NOTE: We MUST not call stopSelf() directly, since we need to
343146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            // make sure the wake lock acquired by AlertReceiver is released.
344146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
3450e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan        }
346e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan    }
347146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
348146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
349146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onCreate() {
350146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        HandlerThread thread = new HandlerThread("AlertService",
351146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project                Process.THREAD_PRIORITY_BACKGROUND);
352146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        thread.start();
3530e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan
354146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper = thread.getLooper();
355146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceHandler = new ServiceHandler(mServiceLooper);
356146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
357146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
358146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
359c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff    public int onStartCommand(Intent intent, int flags, int startId) {
360c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        if (intent != null) {
361c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            Message msg = mServiceHandler.obtainMessage();
362c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.arg1 = startId;
363c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            msg.obj = intent.getExtras();
364c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff            mServiceHandler.sendMessage(msg);
365c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        }
366c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff        return START_REDELIVER_INTENT;
367146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
368146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
369146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
370146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public void onDestroy() {
371146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        mServiceLooper.quit();
372146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
373146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project
374146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    @Override
375146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    public IBinder onBind(Intent intent) {
376146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project        return null;
377146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project    }
378146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project}
379