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 17 package com.android.calendar.alerts; 18 19 import com.android.calendar.R; 20 import com.android.calendar.Utils; 21 22 import android.app.Notification; 23 import android.app.Notification.Builder; 24 import android.app.PendingIntent; 25 import android.app.Service; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.net.Uri; 31 import android.os.PowerManager; 32 import android.text.TextUtils; 33 import android.text.format.DateFormat; 34 import android.text.format.DateUtils; 35 import android.text.format.Time; 36 import android.util.Log; 37 38 import java.util.Locale; 39 import java.util.TimeZone; 40 41 /** 42 * Receives android.intent.action.EVENT_REMINDER intents and handles 43 * event reminders. The intent URI specifies an alert id in the 44 * CalendarAlerts database table. This class also receives the 45 * BOOT_COMPLETED intent so that it can add a status bar notification 46 * if there are Calendar event alarms that have not been dismissed. 47 * It also receives the TIME_CHANGED action so that it can fire off 48 * snoozed alarms that have become ready. The real work is done in 49 * the AlertService class. 50 * 51 * To trigger this code after pushing the apk to device: 52 * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER" 53 * -n "com.android.calendar/.alerts.AlertReceiver" 54 */ 55 public class AlertReceiver extends BroadcastReceiver { 56 private static final String TAG = "AlertReceiver"; 57 58 private static final String DELETE_ACTION = "delete"; 59 60 static final Object mStartingServiceSync = new Object(); 61 static PowerManager.WakeLock mStartingService; 62 63 public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"; 64 65 @Override 66 public void onReceive(Context context, Intent intent) { 67 if (AlertService.DEBUG) { 68 Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); 69 } 70 71 if (DELETE_ACTION.equals(intent.getAction())) { 72 73 /* The user has clicked the "Clear All Notifications" 74 * buttons so dismiss all Calendar alerts. 75 */ 76 // TODO Grab a wake lock here? 77 Intent serviceIntent = new Intent(context, DismissAllAlarmsService.class); 78 context.startService(serviceIntent); 79 } else { 80 Intent i = new Intent(); 81 i.setClass(context, AlertService.class); 82 i.putExtras(intent); 83 i.putExtra("action", intent.getAction()); 84 Uri uri = intent.getData(); 85 86 // This intent might be a BOOT_COMPLETED so it might not have a Uri. 87 if (uri != null) { 88 i.putExtra("uri", uri.toString()); 89 } 90 beginStartingService(context, i); 91 } 92 } 93 94 /** 95 * Start the service to process the current event notifications, acquiring 96 * the wake lock before returning to ensure that the service will run. 97 */ 98 public static void beginStartingService(Context context, Intent intent) { 99 synchronized (mStartingServiceSync) { 100 if (mStartingService == null) { 101 PowerManager pm = 102 (PowerManager)context.getSystemService(Context.POWER_SERVICE); 103 mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 104 "StartingAlertService"); 105 mStartingService.setReferenceCounted(false); 106 } 107 mStartingService.acquire(); 108 context.startService(intent); 109 } 110 } 111 112 /** 113 * Called back by the service when it has finished processing notifications, 114 * releasing the wake lock if the service is now stopping. 115 */ 116 public static void finishStartingService(Service service, int startId) { 117 synchronized (mStartingServiceSync) { 118 if (mStartingService != null) { 119 if (service.stopSelfResult(startId)) { 120 mStartingService.release(); 121 } 122 } 123 } 124 } 125 126 /** 127 * Creates an alert notification. If high priority, this will set 128 * FLAG_HIGH_PRIORITY on the resulting notification and attach the a pending 129 * intent. Otherwise, it creates a standard notification. 130 */ 131 public static Notification makeNewAlertNotification(Context context, 132 String title, String location, int numReminders, 133 boolean highPriority, long startMillis, boolean allDay) { 134 Resources res = context.getResources(); 135 136 // Create an intent triggered by clicking on the status icon. 137 Intent clickIntent = new Intent(); 138 clickIntent.setClass(context, AlertActivity.class); 139 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 140 141 // Create an intent triggered by clicking on the "Clear All Notifications" button 142 Intent deleteIntent = new Intent(); 143 deleteIntent.setClass(context, AlertReceiver.class); 144 deleteIntent.setAction(DELETE_ACTION); 145 146 if (title == null || title.length() == 0) { 147 title = res.getString(R.string.no_title_label); 148 } 149 150 Builder bob = new Notification.Builder(context); 151 bob.setContentTitle(title); 152 bob.setSmallIcon(R.drawable.stat_notify_calendar); 153 154 PendingIntent pendingClickIntent = PendingIntent.getActivity( 155 context, 0, clickIntent, 0); 156 bob.setContentIntent(pendingClickIntent); 157 bob.setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0)); 158 if (highPriority) { 159 bob.setFullScreenIntent(pendingClickIntent, true); 160 } 161 162 if (numReminders > 1) { 163 bob.setNumber(numReminders); 164 } 165 166 // Format the second line which shows time and location. 167 // 168 // 1) Show time only for non-all day events 169 // 2) No date for today 170 // 3) Show "tomorrow" for tomorrow 171 // 4) Show date for days beyond that 172 173 String tz = Utils.getTimeZone(context, null); 174 Time time = new Time(tz); 175 time.setToNow(); 176 int today = Time.getJulianDay(time.toMillis(false), time.gmtoff); 177 time.set(startMillis); 178 int eventDay = Time.getJulianDay(time.toMillis(false), time.gmtoff); 179 180 int flags = DateUtils.FORMAT_ABBREV_ALL; 181 if (!allDay) { 182 flags |= DateUtils.FORMAT_SHOW_TIME; 183 if (DateFormat.is24HourFormat(context)) { 184 flags |= DateUtils.FORMAT_24HOUR; 185 } 186 } else { 187 flags |= DateUtils.FORMAT_UTC; 188 } 189 190 if (eventDay > today + 1) { 191 flags |= DateUtils.FORMAT_SHOW_DATE; 192 } 193 194 StringBuilder sb = new StringBuilder(Utils.formatDateRange(context, startMillis, 195 startMillis, flags)); 196 197 if (!allDay && tz != Time.getCurrentTimezone()) { 198 // Assumes time was set to the current tz 199 time.set(startMillis); 200 boolean isDST = time.isDst != 0; 201 sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName( 202 isDST, TimeZone.SHORT, Locale.getDefault())); 203 } 204 205 if (eventDay == today + 1) { 206 // Tomorrow 207 sb.append(", "); 208 sb.append(context.getString(R.string.tomorrow)); 209 } 210 211 String loc; 212 if (location != null && !TextUtils.isEmpty(loc = location.trim())) { 213 sb.append(", "); 214 sb.append(loc); 215 } 216 bob.setContentText(sb.toString()); 217 218 return bob.getNotification(); 219 } 220 } 221 222