AlertReceiver.java revision 9881907c47b2658fa85954bfb339c4b1eab9fc8e
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.calendar.alerts; 18 19import com.android.calendar.R; 20import com.android.calendar.Utils; 21 22import android.app.Notification; 23import android.app.Notification.Builder; 24import android.app.PendingIntent; 25import android.app.Service; 26import android.content.BroadcastReceiver; 27import android.content.Context; 28import android.content.Intent; 29import android.content.res.Resources; 30import android.net.Uri; 31import android.os.PowerManager; 32import android.text.TextUtils; 33import android.text.format.DateFormat; 34import android.text.format.DateUtils; 35import android.text.format.Time; 36import android.util.Log; 37import android.view.View; 38import android.widget.RemoteViews; 39 40import java.util.Locale; 41import java.util.TimeZone; 42 43/** 44 * Receives android.intent.action.EVENT_REMINDER intents and handles 45 * event reminders. The intent URI specifies an alert id in the 46 * CalendarAlerts database table. This class also receives the 47 * BOOT_COMPLETED intent so that it can add a status bar notification 48 * if there are Calendar event alarms that have not been dismissed. 49 * It also receives the TIME_CHANGED action so that it can fire off 50 * snoozed alarms that have become ready. The real work is done in 51 * the AlertService class. 52 * 53 * To trigger this code after pushing the apk to device: 54 * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER" 55 * -n "com.android.calendar/.alerts.AlertReceiver" 56 */ 57public class AlertReceiver extends BroadcastReceiver { 58 private static final String TAG = "AlertReceiver"; 59 60 private static final String DELETE_ACTION = "delete"; 61 62 static final Object mStartingServiceSync = new Object(); 63 static PowerManager.WakeLock mStartingService; 64 65 public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"; 66 67 @Override 68 public void onReceive(Context context, Intent intent) { 69 if (AlertService.DEBUG) { 70 Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); 71 } 72 if (DELETE_ACTION.equals(intent.getAction())) { 73 74 /* The user has clicked the "Clear All Notifications" 75 * buttons so dismiss all Calendar alerts. 76 */ 77 // TODO Grab a wake lock here? 78 Intent serviceIntent = new Intent(context, DismissAlarmsService.class); 79 context.startService(serviceIntent); 80 } else { 81 Intent i = new Intent(); 82 i.setClass(context, AlertService.class); 83 i.putExtras(intent); 84 i.putExtra("action", intent.getAction()); 85 Uri uri = intent.getData(); 86 87 // This intent might be a BOOT_COMPLETED so it might not have a Uri. 88 if (uri != null) { 89 i.putExtra("uri", uri.toString()); 90 } 91 beginStartingService(context, i); 92 } 93 } 94 95 /** 96 * Start the service to process the current event notifications, acquiring 97 * the wake lock before returning to ensure that the service will run. 98 */ 99 public static void beginStartingService(Context context, Intent intent) { 100 synchronized (mStartingServiceSync) { 101 if (mStartingService == null) { 102 PowerManager pm = 103 (PowerManager)context.getSystemService(Context.POWER_SERVICE); 104 mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 105 "StartingAlertService"); 106 mStartingService.setReferenceCounted(false); 107 } 108 mStartingService.acquire(); 109 context.startService(intent); 110 } 111 } 112 113 /** 114 * Called back by the service when it has finished processing notifications, 115 * releasing the wake lock if the service is now stopping. 116 */ 117 public static void finishStartingService(Service service, int startId) { 118 synchronized (mStartingServiceSync) { 119 if (mStartingService != null) { 120 if (service.stopSelfResult(startId)) { 121 mStartingService.release(); 122 } 123 } 124 } 125 } 126 127 /** 128 * Creates an alert notification. If high priority, this will set 129 * FLAG_HIGH_PRIORITY on the resulting notification and attach the a pending 130 * intent. Otherwise, it creates a standard notification. 131 */ 132 public static Notification makeNewAlertNotification(Context context, 133 String title, String location, int numReminders, 134 boolean highPriority, long startMillis, long endMillis, long id, 135 boolean allDay) { 136 Resources res = context.getResources(); 137 // Create an intent triggered by clicking on the status icon. 138 // For a notification with one event, dismiss the notification and show event 139 // For a notification with more than one alert, show the alerts list. 140 Intent clickIntent = new Intent(); 141 PendingIntent pendingClickIntent; 142 if (numReminders == 1) { 143 clickIntent.setClass(context, DismissAlarmsService.class); 144 clickIntent.putExtra(AlertUtils.EVENT_ID_KEY, id); 145 clickIntent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); 146 clickIntent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); 147 clickIntent.putExtra(AlertUtils.SHOW_EVENT_KEY, true); 148 } else { 149 clickIntent.setClass(context, AlertActivity.class); 150 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 151 } 152 pendingClickIntent = PendingIntent.getService(context, 0, clickIntent, 153 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 154 155 // Create an intent triggered by clicking on the "Clear All Notifications" button or 156 // by dismissing the notification 157 Intent deleteIntent = new Intent(); 158 deleteIntent.setClass(context, AlertReceiver.class); 159 deleteIntent.setAction(DELETE_ACTION); 160 if (numReminders == 1) { 161 deleteIntent.putExtra(AlertUtils.EVENT_ID_KEY, id); 162 deleteIntent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); 163 deleteIntent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); 164 } 165 166 if (title == null || title.length() == 0) { 167 title = res.getString(R.string.no_title_label); 168 } 169 170 Builder bob = new Notification.Builder(context); 171 172 bob.setContentTitle(title); 173 bob.setSmallIcon(R.drawable.stat_notify_calendar); 174 175 bob.setContentIntent(pendingClickIntent); 176 bob.setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0)); 177 if (highPriority) { 178 bob.setFullScreenIntent(pendingClickIntent, true); 179 } 180 181 // Format the second line which shows time and location for single alert or the 182 // number of events for multiple alerts 183 // 1) Show time only for non-all day events 184 // 2) No date for today 185 // 3) Show "tomorrow" for tomorrow 186 // 4) Show date for days beyond that 187 188 String eventTextString; 189 if (numReminders > 1) { 190 String eventsFormat = res.getQuantityString(R.plurals.Nevents, 191 numReminders); 192 eventTextString = String.format(eventsFormat, numReminders); 193 } else { 194 String tz = Utils.getTimeZone(context, null); 195 Time time = new Time(tz); 196 time.setToNow(); 197 int today = Time.getJulianDay(time.toMillis(false), time.gmtoff); 198 time.set(startMillis); 199 int eventDay = Time.getJulianDay(time.toMillis(false), time.gmtoff); 200 201 int flags = DateUtils.FORMAT_ABBREV_ALL; 202 if (!allDay) { 203 flags |= DateUtils.FORMAT_SHOW_TIME; 204 if (DateFormat.is24HourFormat(context)) { 205 flags |= DateUtils.FORMAT_24HOUR; 206 } 207 } else { 208 flags |= DateUtils.FORMAT_UTC; 209 } 210 211 if (eventDay > today + 1) { 212 flags |= DateUtils.FORMAT_SHOW_DATE; 213 } 214 215 StringBuilder sb = new StringBuilder(Utils.formatDateRange(context, startMillis, 216 startMillis, flags)); 217 218 if (!allDay && tz != Time.getCurrentTimezone()) { 219 // Assumes time was set to the current tz 220 time.set(startMillis); 221 boolean isDST = time.isDst != 0; 222 sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName( 223 isDST, TimeZone.SHORT, Locale.getDefault())); 224 } 225 226 if (eventDay == today + 1) { 227 // Tomorrow 228 sb.append(", "); 229 sb.append(context.getString(R.string.tomorrow)); 230 } 231 232 String loc; 233 if (location != null && !TextUtils.isEmpty(loc = location.trim())) { 234 sb.append(", "); 235 sb.append(loc); 236 } 237 eventTextString = sb.toString(); 238 } 239 240 Notification notification = bob.getNotification(); 241 RemoteViews contentView = new RemoteViews(context.getPackageName(), R.layout.notification); 242 contentView.setTextViewText(R.id.title, title); 243 contentView.setTextViewText(R.id.text, eventTextString); 244 if (numReminders > 1) { 245 contentView.setViewVisibility(R.id.snooze_button, View.GONE); 246 } else { 247 contentView.setViewVisibility(R.id.snooze_button, View.VISIBLE); 248 // Create an intent triggered by clicking on the snooze button. 249 Intent snoozeIntent = new Intent(); 250 snoozeIntent.setClass(context, SnoozeAlarmsService.class); 251 snoozeIntent.putExtra(AlertUtils.EVENT_ID_KEY, id); 252 snoozeIntent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); 253 snoozeIntent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); 254 PendingIntent pendingSnoozeIntent = PendingIntent.getService(context, 0, snoozeIntent, 255 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 256 contentView.setOnClickPendingIntent(R.id.snooze_button, pendingSnoozeIntent); 257 } 258 notification.contentView = contentView; 259 return notification; 260 } 261} 262