AlertService.java revision 7321a0630aca3e5093d12f0e4f55da77620f53ed
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; 2123e7da3eacee7bceb105cdfc7b5329c7a43846d5Mason Tangimport com.android.calendar.R.string; 22146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 23146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.AlarmManager; 24146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Notification; 25146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.NotificationManager; 26146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.app.Service; 27146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentResolver; 28e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport android.content.ContentUris; 29146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.ContentValues; 30146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Context; 31146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Intent; 32146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.SharedPreferences; 33146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.database.Cursor; 341fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shumaimport android.media.AudioManager; 35146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.net.Uri; 36146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Bundle; 37146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Handler; 38146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.HandlerThread; 39146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.IBinder; 40146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Looper; 41146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Message; 42146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.os.Process; 43146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.provider.Calendar.Attendees; 44146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.provider.Calendar.CalendarAlerts; 45146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.text.TextUtils; 46146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.util.Log; 47e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 48e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chanimport java.util.HashMap; 49146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 50146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/** 51146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * This service is used to handle calendar event reminders. 52146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */ 53146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpublic class AlertService extends Service { 54e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan static final boolean DEBUG = true; 55146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final String TAG = "AlertService"; 560e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 57146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private volatile Looper mServiceLooper; 58146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private volatile ServiceHandler mServiceHandler; 590e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 600e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan private static final String[] ALERT_PROJECTION = new String[] { 61146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts._ID, // 0 62146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.EVENT_ID, // 1 63146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.STATE, // 2 64146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.TITLE, // 3 65146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.EVENT_LOCATION, // 4 66146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.SELF_ATTENDEE_STATUS, // 5 67146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.ALL_DAY, // 6 68146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.ALARM_TIME, // 7 69146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.MINUTES, // 8 70146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.BEGIN, // 9 71e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan CalendarAlerts.END, // 10 72146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project }; 730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 74146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_ID = 0; 75146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_EVENT_ID = 1; 76146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_STATE = 2; 77146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_TITLE = 3; 78146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_EVENT_LOCATION = 4; 79146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5; 80146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_ALL_DAY = 6; 81146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_ALARM_TIME = 7; 82146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_MINUTES = 8; 83146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private static final int ALERT_INDEX_BEGIN = 9; 84e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan private static final int ALERT_INDEX_END = 10; 85146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 86e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR " 87e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<="; 88146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 89e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] { 90e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Integer.toString(CalendarAlerts.FIRED), Integer.toString(CalendarAlerts.SCHEDULED) 91146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project }; 920e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 93e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC"; 940e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 958af2529989a9b10a0bb84736695c22fc02a17a4aThe Android Open Source Project void processMessage(Message msg) { 96146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project Bundle bundle = (Bundle) msg.obj; 970e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 98146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project // On reboot, update the notification bar with the contents of the 99146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project // CalendarAlerts table. 100146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project String action = bundle.getString("action"); 101e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 102e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Log.d(TAG, "" + bundle.getLong(android.provider.Calendar.CalendarAlerts.ALARM_TIME) 103e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + " Action = " + action); 104e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 105e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 106146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project if (action.equals(Intent.ACTION_BOOT_COMPLETED) 107146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project || action.equals(Intent.ACTION_TIME_CHANGED)) { 108146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project doTimeChanged(); 109146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project return; 110146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 111146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 112e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (!action.equals(android.provider.Calendar.EVENT_REMINDER_ACTION) 113e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan && !action.equals(Intent.ACTION_LOCALE_CHANGED)) { 114e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Log.w(TAG, "Invalid action: " + action); 115e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan return; 116146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 117146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 118e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan updateAlertNotification(this); 119e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 120605a0901134c0840b2fcf0514b4c1f8bc10dc7e0Michael Chan 121e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan static boolean updateAlertNotification(Context context) { 122e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan ContentResolver cr = context.getContentResolver(); 123e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long currentTime = System.currentTimeMillis(); 1240e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 125e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Cursor alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION, ACTIVE_ALERTS_SELECTION 126e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + currentTime, ACTIVE_ALERTS_SELECTION_ARGS, ACTIVE_ALERTS_SORT); 1270e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 128e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (alertCursor == null || alertCursor.getCount() == 0) { 129146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project if (alertCursor != null) { 130146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project alertCursor.close(); 131146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 132e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 133e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) Log.d(TAG, "No fired or scheduled alerts"); 134e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan NotificationManager nm = 135e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 136e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan nm.cancel(0); 137e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan return false; 138146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 1390e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 140e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 141e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Log.d(TAG, "alert count:" + alertCursor.getCount()); 142146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 1430e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 144e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan String notificationEventName = null; 145e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan String notificationEventLocation = null; 146e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan long notificationEventBegin = 0; 147e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int notificationEventStatus = 0; 1487321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan boolean notificationEventAllDay = true; 149e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan HashMap<Long, Long> eventIds = new HashMap<Long, Long>(); 150e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int numReminders = 0; 151e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int numFired = 0; 152146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project try { 153e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan while (alertCursor.moveToNext()) { 154e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long alertId = alertCursor.getLong(ALERT_INDEX_ID); 155e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID); 156e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES); 157e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final String eventName = alertCursor.getString(ALERT_INDEX_TITLE); 158e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION); 159e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS); 160e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED; 161e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN); 162e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long endTime = alertCursor.getLong(ALERT_INDEX_END); 163e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final Uri alertUri = ContentUris 164e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan .withAppendedId(CalendarAlerts.CONTENT_URI, alertId); 165e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME); 166e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int state = alertCursor.getInt(ALERT_INDEX_STATE); 1677321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0; 168e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 169e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 170e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId 171e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + " eventId:" + eventId + " state: " + state + " minutes:" + minutes 172e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + " declined:" + declined + " beginTime:" + beginTime 173e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan + " endTime:" + endTime); 174146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 1750e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 176e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan ContentValues values = new ContentValues(); 177e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int newState = -1; 178e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 179e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Uncomment for the behavior of clearing out alerts after the 180e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // events ended. b/1880369 181e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // 182e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // if (endTime < currentTime) { 183e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // newState = CalendarAlerts.DISMISSED; 184e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // } else 185e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 186e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Remove declined events and duplicate alerts for the same event 187e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (!declined && eventIds.put(eventId, beginTime) == null) { 188e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan numReminders++; 189e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (state == CalendarAlerts.SCHEDULED) { 190e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan newState = CalendarAlerts.FIRED; 191e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan numFired++; 192e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 193e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Record the received time in the CalendarAlerts table. 194e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // This is useful for finding bugs that cause alarms to be 195e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // missed or delayed. 196e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan values.put(CalendarAlerts.RECEIVED_TIME, currentTime); 197e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 198e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } else { 199e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan newState = CalendarAlerts.DISMISSED; 200e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 201e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (!declined) Log.d(TAG, "dropping dup alert for event " + eventId); 202146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 203146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 204146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 205e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Update row if state changed 206e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (newState != -1) { 207e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan values.put(CalendarAlerts.STATE, newState); 208e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan state = newState; 209146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 2100e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 211e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (state == CalendarAlerts.FIRED) { 212e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Record the time posting to notification manager. 213e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // This is used for debugging missed alarms. 214e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan values.put(CalendarAlerts.NOTIFY_TIME, currentTime); 215146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 2160e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 217e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Write row to if anything changed 218e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (values.size() > 0) cr.update(alertUri, values, null, null); 2190e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 220e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (state != CalendarAlerts.FIRED) { 221e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan continue; 222e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 223146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 224e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Pick an Event title for the notification panel by the latest 225e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // alertTime and give prefer accepted events in case of ties. 226e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan int newStatus; 227e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan switch (status) { 228e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan case Attendees.ATTENDEE_STATUS_ACCEPTED: 229e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan newStatus = 2; 230e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan break; 231e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan case Attendees.ATTENDEE_STATUS_TENTATIVE: 232e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan newStatus = 1; 233e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan break; 234e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan default: 235e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan newStatus = 0; 236e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 237e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 238e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // TODO Prioritize by "primary" calendar 239e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Assumes alerts are sorted by begin time in reverse 240e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (notificationEventName == null 241e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan || (notificationEventBegin <= beginTime && 242e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notificationEventStatus < newStatus)) { 243e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notificationEventName = eventName; 244e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notificationEventLocation = location; 245e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notificationEventBegin = beginTime; 246e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notificationEventStatus = newStatus; 2477321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan notificationEventAllDay = allDay; 248146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 249146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 250146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } finally { 251e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (alertCursor != null) { 252e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan alertCursor.close(); 253e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 254146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 2550e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 2564b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 257cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang 2584b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true); 2594b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false); 2600e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 261e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // TODO check for this before adding stuff to the alerts table. 262cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang if (!doAlert) { 263e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 264146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project Log.d(TAG, "alert preference is OFF"); 265146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 266e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan return true; 267e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 268e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 269ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang boolean quietUpdate = numFired == 0; 270cca9ecb23b079c47856af22f89f7a6f3dec7a492Mason Tang boolean highPriority = numFired > 0 && doPopup; 271e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan postNotification(context, prefs, notificationEventName, notificationEventLocation, 2727321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan numReminders, quietUpdate, highPriority, notificationEventBegin, 2737321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan notificationEventAllDay); 274e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 275e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan return true; 276e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 277e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 278e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan private static void postNotification(Context context, SharedPreferences prefs, 279ccf4a12bc3b25c46d592d3f9116f52751b96010cMason Tang String eventName, String location, int numReminders, 2807321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan boolean quietUpdate, boolean highPriority, long startMillis, boolean allDay) { 281e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (DEBUG) { 282e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders 283c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang + (quietUpdate ? " QUIET" : " loud") 284c9656c9e42d9bb640688648b9dfe8d9f4e16b47dMason Tang + (highPriority ? " high-priority" : "")); 285146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 2860e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 2870e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan NotificationManager nm = 288e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 289146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 290e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (numReminders == 0) { 291e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan nm.cancel(0); 292e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan return; 293146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 2940e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 295e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName, 2967321a0630aca3e5093d12f0e4f55da77620f53edMichael Chan location, numReminders, highPriority, startMillis, allDay); 297e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notification.defaults |= Notification.DEFAULT_LIGHTS; 298e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 299e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Quietly update notification bar. Nothing new. Maybe something just got deleted. 300e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (!quietUpdate) { 301e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Flash ticker in status bar 302e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notification.tickerText = eventName; 303e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan if (!TextUtils.isEmpty(location)) { 304e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notification.tickerText = eventName + " - " + location; 305e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 306e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 307e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Generate either a pop-up dialog, status bar notification, or 308e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // neither. Pop-up dialog and status bar notification may include a 309e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // sound, an alert, or both. A status bar notification also includes 310e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // a toast. 3111fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma 3121fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma // Find out the circumstances under which to vibrate. 3131fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma // Migrate from pre-Froyo boolean setting if necessary. 3141fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma String vibrateWhen; // "always" or "silent" or "never" 3154b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN)) 3161fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma { 3171fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma // Look up Froyo setting 3181fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma vibrateWhen = 3194b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null); 3204b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) { 3211fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value. 3221fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma boolean vibrate = 3234b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false); 3241fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma vibrateWhen = vibrate ? 3251fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma context.getString(R.string.prefDefault_alerts_vibrate_true) : 3261fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma context.getString(R.string.prefDefault_alerts_vibrate_false); 3271fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma } else { 3281fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma // No setting. Use Froyo-defined default. 3291fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen); 3301fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma } 3311fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma boolean vibrateAlways = vibrateWhen.equals("always"); 3321fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma boolean vibrateSilent = vibrateWhen.equals("silent"); 3331fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma AudioManager audioManager = 3341fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 3351fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma boolean nowSilent = 3361fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE; 337e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan 338e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan // Possibly generate a vibration 3391fec2207219842a71fbbb8567cd968ab61ce3c1cJim Shuma if (vibrateAlways || (vibrateSilent && nowSilent)) { 340e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan notification.defaults |= Notification.DEFAULT_VIBRATE; 341e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 3420e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 3430e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan // Possibly generate a sound. If 'Silent' is chosen, the ringtone 3440e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan // string will be empty. 3450e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan String reminderRingtone = prefs.getString( 3464b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa GeneralPreferences.KEY_ALERTS_RINGTONE, null); 3470e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri 3480e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan .parse(reminderRingtone); 349146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 350146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 351146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project nm.notify(0, notification); 352146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 3530e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 354146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private void doTimeChanged() { 355146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project ContentResolver cr = getContentResolver(); 356146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project Object service = getSystemService(Context.ALARM_SERVICE); 357146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project AlarmManager manager = (AlarmManager) service; 358146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project CalendarAlerts.rescheduleMissedAlarms(cr, this, manager); 359e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan updateAlertNotification(this); 360146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 3610e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 362146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project private final class ServiceHandler extends Handler { 363146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public ServiceHandler(Looper looper) { 364146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project super(looper); 365146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 3660e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 367146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project @Override 368146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public void handleMessage(Message msg) { 369146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project processMessage(msg); 370146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project // NOTE: We MUST not call stopSelf() directly, since we need to 371146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project // make sure the wake lock acquired by AlertReceiver is released. 372146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project AlertReceiver.finishStartingService(AlertService.this, msg.arg1); 3730e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan } 374e2ae1ef8decfddcc4e5802483e92cab93c6fc67cMichael Chan } 375146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 376146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project @Override 377146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public void onCreate() { 378146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project HandlerThread thread = new HandlerThread("AlertService", 379146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project Process.THREAD_PRIORITY_BACKGROUND); 380146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project thread.start(); 3810e7235b00fdf47c773592a324c4a62ef95d1dcf4Michael Chan 382146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project mServiceLooper = thread.getLooper(); 383146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project mServiceHandler = new ServiceHandler(mServiceLooper); 384146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 385146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 386146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project @Override 387c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff public int onStartCommand(Intent intent, int flags, int startId) { 388c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff if (intent != null) { 389c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff Message msg = mServiceHandler.obtainMessage(); 390c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff msg.arg1 = startId; 391c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff msg.obj = intent.getExtras(); 392c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff mServiceHandler.sendMessage(msg); 393c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff } 394c1dc950c9b5756937a1df44463cc09fdf0649420Ken Shirriff return START_REDELIVER_INTENT; 395146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 396146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 397146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project @Override 398146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public void onDestroy() { 399146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project mServiceLooper.quit(); 400146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 401146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 402146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project @Override 403146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public IBinder onBind(Intent intent) { 404146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project return null; 405146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 406146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project} 407