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 android.app.Notification; 20 import android.app.PendingIntent; 21 import android.app.Service; 22 import android.content.BroadcastReceiver; 23 import android.content.ContentUris; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Resources; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.PowerManager; 32 import android.provider.CalendarContract.Attendees; 33 import android.provider.CalendarContract.Calendars; 34 import android.provider.CalendarContract.Events; 35 import android.text.SpannableStringBuilder; 36 import android.text.TextUtils; 37 import android.text.style.RelativeSizeSpan; 38 import android.text.style.TextAppearanceSpan; 39 import android.util.Log; 40 41 import com.android.calendar.R; 42 import com.android.calendar.Utils; 43 import com.android.calendar.alerts.AlertService.NotificationWrapper; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.regex.Pattern; 48 49 /** 50 * Receives android.intent.action.EVENT_REMINDER intents and handles 51 * event reminders. The intent URI specifies an alert id in the 52 * CalendarAlerts database table. This class also receives the 53 * BOOT_COMPLETED intent so that it can add a status bar notification 54 * if there are Calendar event alarms that have not been dismissed. 55 * It also receives the TIME_CHANGED action so that it can fire off 56 * snoozed alarms that have become ready. The real work is done in 57 * the AlertService class. 58 * 59 * To trigger this code after pushing the apk to device: 60 * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER" 61 * -n "com.android.calendar/.alerts.AlertReceiver" 62 */ 63 public class AlertReceiver extends BroadcastReceiver { 64 private static final String TAG = "AlertReceiver"; 65 66 private static final String DELETE_ALL_ACTION = "com.android.calendar.DELETEALL"; 67 private static final String MAIL_ACTION = "com.android.calendar.MAIL"; 68 private static final String EXTRA_EVENT_ID = "eventid"; 69 70 static final Object mStartingServiceSync = new Object(); 71 static PowerManager.WakeLock mStartingService; 72 private static final Pattern mBlankLinePattern = Pattern.compile("^\\s*$[\n\r]", 73 Pattern.MULTILINE); 74 75 public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders"; 76 private static final int NOTIFICATION_DIGEST_MAX_LENGTH = 3; 77 78 private static Handler sAsyncHandler; 79 static { 80 HandlerThread thr = new HandlerThread("AlertReceiver async"); thr.start()81 thr.start(); 82 sAsyncHandler = new Handler(thr.getLooper()); 83 } 84 85 @Override onReceive(final Context context, final Intent intent)86 public void onReceive(final Context context, final Intent intent) { 87 if (AlertService.DEBUG) { 88 Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); 89 } 90 if (DELETE_ALL_ACTION.equals(intent.getAction())) { 91 92 /* The user has clicked the "Clear All Notifications" 93 * buttons so dismiss all Calendar alerts. 94 */ 95 // TODO Grab a wake lock here? 96 Intent serviceIntent = new Intent(context, DismissAlarmsService.class); 97 context.startService(serviceIntent); 98 } else if (MAIL_ACTION.equals(intent.getAction())) { 99 // Close the notification shade. 100 Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 101 context.sendBroadcast(closeNotificationShadeIntent); 102 103 // Now start the email intent. 104 final long eventId = intent.getLongExtra(EXTRA_EVENT_ID, -1); 105 if (eventId != -1) { 106 Intent i = new Intent(context, QuickResponseActivity.class); 107 i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, eventId); 108 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 109 context.startActivity(i); 110 } 111 } else { 112 Intent i = new Intent(); 113 i.setClass(context, AlertService.class); 114 i.putExtras(intent); 115 i.putExtra("action", intent.getAction()); 116 Uri uri = intent.getData(); 117 118 // This intent might be a BOOT_COMPLETED so it might not have a Uri. 119 if (uri != null) { 120 i.putExtra("uri", uri.toString()); 121 } 122 beginStartingService(context, i); 123 } 124 } 125 126 /** 127 * Start the service to process the current event notifications, acquiring 128 * the wake lock before returning to ensure that the service will run. 129 */ beginStartingService(Context context, Intent intent)130 public static void beginStartingService(Context context, Intent intent) { 131 synchronized (mStartingServiceSync) { 132 if (mStartingService == null) { 133 PowerManager pm = 134 (PowerManager)context.getSystemService(Context.POWER_SERVICE); 135 mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 136 "StartingAlertService"); 137 mStartingService.setReferenceCounted(false); 138 } 139 mStartingService.acquire(); 140 context.startService(intent); 141 } 142 } 143 144 /** 145 * Called back by the service when it has finished processing notifications, 146 * releasing the wake lock if the service is now stopping. 147 */ finishStartingService(Service service, int startId)148 public static void finishStartingService(Service service, int startId) { 149 synchronized (mStartingServiceSync) { 150 if (mStartingService != null) { 151 if (service.stopSelfResult(startId)) { 152 mStartingService.release(); 153 } 154 } 155 } 156 } 157 createClickEventIntent(Context context, long eventId, long startMillis, long endMillis, int notificationId)158 private static PendingIntent createClickEventIntent(Context context, long eventId, 159 long startMillis, long endMillis, int notificationId) { 160 return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId, 161 "com.android.calendar.CLICK", true); 162 } 163 createDeleteEventIntent(Context context, long eventId, long startMillis, long endMillis, int notificationId)164 private static PendingIntent createDeleteEventIntent(Context context, long eventId, 165 long startMillis, long endMillis, int notificationId) { 166 return createDismissAlarmsIntent(context, eventId, startMillis, endMillis, notificationId, 167 "com.android.calendar.DELETE", false); 168 } 169 createDismissAlarmsIntent(Context context, long eventId, long startMillis, long endMillis, int notificationId, String action, boolean showEvent)170 private static PendingIntent createDismissAlarmsIntent(Context context, long eventId, 171 long startMillis, long endMillis, int notificationId, String action, 172 boolean showEvent) { 173 Intent intent = new Intent(); 174 intent.setClass(context, DismissAlarmsService.class); 175 intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId); 176 intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); 177 intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); 178 intent.putExtra(AlertUtils.SHOW_EVENT_KEY, showEvent); 179 intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId); 180 181 // Must set a field that affects Intent.filterEquals so that the resulting 182 // PendingIntent will be a unique instance (the 'extras' don't achieve this). 183 // This must be unique for the click event across all reminders (so using 184 // event ID + startTime should be unique). This also must be unique from 185 // the delete event (which also uses DismissAlarmsService). 186 Uri.Builder builder = Events.CONTENT_URI.buildUpon(); 187 ContentUris.appendId(builder, eventId); 188 ContentUris.appendId(builder, startMillis); 189 intent.setData(builder.build()); 190 intent.setAction(action); 191 return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 192 } 193 createSnoozeIntent(Context context, long eventId, long startMillis, long endMillis, int notificationId)194 private static PendingIntent createSnoozeIntent(Context context, long eventId, 195 long startMillis, long endMillis, int notificationId) { 196 Intent intent = new Intent(); 197 intent.setClass(context, SnoozeAlarmsService.class); 198 intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId); 199 intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); 200 intent.putExtra(AlertUtils.EVENT_END_KEY, endMillis); 201 intent.putExtra(AlertUtils.NOTIFICATION_ID_KEY, notificationId); 202 203 Uri.Builder builder = Events.CONTENT_URI.buildUpon(); 204 ContentUris.appendId(builder, eventId); 205 ContentUris.appendId(builder, startMillis); 206 intent.setData(builder.build()); 207 return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 208 } 209 createAlertActivityIntent(Context context)210 private static PendingIntent createAlertActivityIntent(Context context) { 211 Intent clickIntent = new Intent(); 212 clickIntent.setClass(context, AlertActivity.class); 213 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 214 return PendingIntent.getActivity(context, 0, clickIntent, 215 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 216 } 217 makeBasicNotification(Context context, String title, String summaryText, long startMillis, long endMillis, long eventId, int notificationId, boolean doPopup)218 public static NotificationWrapper makeBasicNotification(Context context, String title, 219 String summaryText, long startMillis, long endMillis, long eventId, 220 int notificationId, boolean doPopup) { 221 222 Notification n = makeBasicNotificationBuilder(context, title, summaryText, startMillis, 223 endMillis, eventId, notificationId, doPopup, false, false).build(); 224 225 return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup); 226 } 227 makeBasicNotificationBuilder(Context context, String title, String summaryText, long startMillis, long endMillis, long eventId, int notificationId, boolean doPopup, boolean highPriority, boolean addActionButtons)228 private static Notification.Builder makeBasicNotificationBuilder(Context context, String title, 229 String summaryText, long startMillis, long endMillis, long eventId, 230 int notificationId, boolean doPopup, boolean highPriority, boolean addActionButtons) { 231 Resources resources = context.getResources(); 232 if (title == null || title.length() == 0) { 233 title = resources.getString(R.string.no_title_label); 234 } 235 236 // Create an intent triggered by clicking on the status icon, that dismisses the 237 // notification and shows the event. 238 PendingIntent clickIntent = createClickEventIntent(context, eventId, startMillis, 239 endMillis, notificationId); 240 241 // Create a delete intent triggered by dismissing the notification. 242 PendingIntent deleteIntent = createDeleteEventIntent(context, eventId, startMillis, 243 endMillis, notificationId); 244 245 // Create the base notification. 246 Notification.Builder notificationBuilder = new Notification.Builder(context); 247 notificationBuilder.setContentTitle(title); 248 notificationBuilder.setContentText(summaryText); 249 notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar); 250 notificationBuilder.setContentIntent(clickIntent); 251 notificationBuilder.setDeleteIntent(deleteIntent); 252 if (addActionButtons) { 253 // Create a snooze button. TODO: change snooze to 10 minutes. 254 PendingIntent snoozeIntent = createSnoozeIntent(context, eventId, startMillis, 255 endMillis, notificationId); 256 notificationBuilder.addAction(R.drawable.ic_alarm_holo_dark, 257 resources.getString(R.string.snooze_label), snoozeIntent); 258 259 // Create an email button. 260 PendingIntent emailIntent = createBroadcastMailIntent(context, eventId, title); 261 if (emailIntent != null) { 262 notificationBuilder.addAction(R.drawable.ic_menu_email_holo_dark, 263 resources.getString(R.string.email_guests_label), emailIntent); 264 } 265 } 266 if (doPopup) { 267 notificationBuilder.setFullScreenIntent(createAlertActivityIntent(context), true); 268 } 269 270 // Turn off timestamp. 271 notificationBuilder.setWhen(0); 272 273 // Setting to a higher priority will encourage notification manager to expand the 274 // notification. 275 if (highPriority) { 276 notificationBuilder.setPriority(Notification.PRIORITY_HIGH); 277 } else { 278 notificationBuilder.setPriority(Notification.PRIORITY_DEFAULT); 279 } 280 return notificationBuilder; 281 } 282 283 /** 284 * Creates an expanding notification. The initial expanded state is decided by 285 * the notification manager based on the priority. 286 */ makeExpandingNotification(Context context, String title, String summaryText, String description, long startMillis, long endMillis, long eventId, int notificationId, boolean doPopup, boolean highPriority)287 public static NotificationWrapper makeExpandingNotification(Context context, String title, 288 String summaryText, String description, long startMillis, long endMillis, long eventId, 289 int notificationId, boolean doPopup, boolean highPriority) { 290 Notification.Builder basicBuilder = makeBasicNotificationBuilder(context, title, 291 summaryText, startMillis, endMillis, eventId, notificationId, 292 doPopup, highPriority, true); 293 294 // Create an expanded notification 295 Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle( 296 basicBuilder); 297 if (description != null) { 298 description = mBlankLinePattern.matcher(description).replaceAll(""); 299 description = description.trim(); 300 } 301 CharSequence text; 302 if (TextUtils.isEmpty(description)) { 303 text = summaryText; 304 } else { 305 SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); 306 stringBuilder.append(summaryText); 307 stringBuilder.append("\n\n"); 308 stringBuilder.setSpan(new RelativeSizeSpan(0.5f), summaryText.length(), 309 stringBuilder.length(), 0); 310 stringBuilder.append(description); 311 text = stringBuilder; 312 } 313 expandedBuilder.bigText(text); 314 315 return new NotificationWrapper(expandedBuilder.build(), notificationId, eventId, 316 startMillis, endMillis, doPopup); 317 } 318 319 /** 320 * Creates an expanding digest notification for expired events. 321 */ makeDigestNotification(Context context, ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle, boolean expandable)322 public static NotificationWrapper makeDigestNotification(Context context, 323 ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle, 324 boolean expandable) { 325 if (notificationInfos == null || notificationInfos.size() < 1) { 326 return null; 327 } 328 329 Resources res = context.getResources(); 330 int numEvents = notificationInfos.size(); 331 long[] eventIds = new long[notificationInfos.size()]; 332 for (int i = 0; i < notificationInfos.size(); i++) { 333 eventIds[i] = notificationInfos.get(i).eventId; 334 } 335 336 // Create an intent triggered by clicking on the status icon that shows the alerts list. 337 PendingIntent pendingClickIntent = createAlertActivityIntent(context); 338 339 // Create an intent triggered by dismissing the digest notification that clears all 340 // expired events. 341 Intent deleteIntent = new Intent(); 342 deleteIntent.setClass(context, DismissAlarmsService.class); 343 deleteIntent.setAction(DELETE_ALL_ACTION); 344 deleteIntent.putExtra(AlertUtils.EVENT_IDS_KEY, eventIds); 345 PendingIntent pendingDeleteIntent = PendingIntent.getService(context, 0, deleteIntent, 346 PendingIntent.FLAG_UPDATE_CURRENT); 347 348 if (digestTitle == null || digestTitle.length() == 0) { 349 digestTitle = res.getString(R.string.no_title_label); 350 } 351 352 Notification.Builder notificationBuilder = new Notification.Builder(context); 353 notificationBuilder.setContentText(digestTitle); 354 notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_multiple); 355 notificationBuilder.setContentIntent(pendingClickIntent); 356 notificationBuilder.setDeleteIntent(pendingDeleteIntent); 357 String nEventsStr = res.getQuantityString(R.plurals.Nevents, numEvents, numEvents); 358 notificationBuilder.setContentTitle(nEventsStr); 359 360 // Set to min priority to encourage the notification manager to collapse it. 361 notificationBuilder.setPriority(Notification.PRIORITY_MIN); 362 363 Notification n; 364 365 if (expandable) { 366 // Multiple reminders. Combine into an expanded digest notification. 367 Notification.InboxStyle expandedBuilder = new Notification.InboxStyle( 368 notificationBuilder); 369 int i = 0; 370 for (AlertService.NotificationInfo info : notificationInfos) { 371 if (i < NOTIFICATION_DIGEST_MAX_LENGTH) { 372 String name = info.eventName; 373 if (TextUtils.isEmpty(name)) { 374 name = context.getResources().getString(R.string.no_title_label); 375 } 376 String timeLocation = AlertUtils.formatTimeLocation(context, info.startMillis, 377 info.allDay, info.location); 378 379 TextAppearanceSpan primaryTextSpan = new TextAppearanceSpan(context, 380 R.style.NotificationPrimaryText); 381 TextAppearanceSpan secondaryTextSpan = new TextAppearanceSpan(context, 382 R.style.NotificationSecondaryText); 383 384 // Event title in bold. 385 SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); 386 stringBuilder.append(name); 387 stringBuilder.setSpan(primaryTextSpan, 0, stringBuilder.length(), 0); 388 stringBuilder.append(" "); 389 390 // Followed by time and location. 391 int secondaryIndex = stringBuilder.length(); 392 stringBuilder.append(timeLocation); 393 stringBuilder.setSpan(secondaryTextSpan, secondaryIndex, stringBuilder.length(), 394 0); 395 expandedBuilder.addLine(stringBuilder); 396 i++; 397 } else { 398 break; 399 } 400 } 401 402 // If there are too many to display, add "+X missed events" for the last line. 403 int remaining = numEvents - i; 404 if (remaining > 0) { 405 String nMoreEventsStr = res.getQuantityString(R.plurals.N_remaining_events, 406 remaining, remaining); 407 // TODO: Add highlighting and icon to this last entry once framework allows it. 408 expandedBuilder.setSummaryText(nMoreEventsStr); 409 } 410 411 // Remove the title in the expanded form (redundant with the listed items). 412 expandedBuilder.setBigContentTitle(""); 413 414 n = expandedBuilder.build(); 415 } else { 416 n = notificationBuilder.build(); 417 } 418 419 NotificationWrapper nw = new NotificationWrapper(n); 420 if (AlertService.DEBUG) { 421 for (AlertService.NotificationInfo info : notificationInfos) { 422 nw.add(new NotificationWrapper(null, 0, info.eventId, info.startMillis, 423 info.endMillis, false)); 424 } 425 } 426 return nw; 427 } 428 429 private static final String[] ATTENDEES_PROJECTION = new String[] { 430 Attendees.ATTENDEE_EMAIL, // 0 431 Attendees.ATTENDEE_STATUS, // 1 432 }; 433 private static final int ATTENDEES_INDEX_EMAIL = 0; 434 private static final int ATTENDEES_INDEX_STATUS = 1; 435 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; 436 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " 437 + Attendees.ATTENDEE_EMAIL + " ASC"; 438 439 private static final String[] EVENT_PROJECTION = new String[] { 440 Calendars.OWNER_ACCOUNT, // 0 441 Calendars.ACCOUNT_NAME, // 1 442 Events.TITLE, // 2 443 }; 444 private static final int EVENT_INDEX_OWNER_ACCOUNT = 0; 445 private static final int EVENT_INDEX_ACCOUNT_NAME = 1; 446 private static final int EVENT_INDEX_TITLE = 2; 447 getEventCursor(Context context, long eventId)448 private static Cursor getEventCursor(Context context, long eventId) { 449 return context.getContentResolver().query( 450 ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION, 451 null, null, null); 452 } 453 getAttendeesCursor(Context context, long eventId)454 private static Cursor getAttendeesCursor(Context context, long eventId) { 455 return context.getContentResolver().query(Attendees.CONTENT_URI, 456 ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) }, 457 ATTENDEES_SORT_ORDER); 458 } 459 460 /** 461 * Creates a broadcast pending intent that fires to AlertReceiver when the email button 462 * is clicked. 463 */ createBroadcastMailIntent(Context context, long eventId, String eventTitle)464 private static PendingIntent createBroadcastMailIntent(Context context, long eventId, 465 String eventTitle) { 466 // Query for viewer account. 467 String syncAccount = null; 468 Cursor eventCursor = getEventCursor(context, eventId); 469 try { 470 if (eventCursor != null && eventCursor.moveToFirst()) { 471 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 472 } 473 } finally { 474 if (eventCursor != null) { 475 eventCursor.close(); 476 } 477 } 478 479 // Query attendees to see if there are any to email. 480 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 481 try { 482 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 483 do { 484 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 485 if (Utils.isEmailableFrom(email, syncAccount)) { 486 // Send intent back to ourself first for a couple reasons: 487 // 1) Workaround issue where clicking action button in notification does 488 // not automatically close the notification shade. 489 // 2) Attendees list in email will always be up to date. 490 Intent broadcastIntent = new Intent(MAIL_ACTION); 491 broadcastIntent.setClass(context, AlertReceiver.class); 492 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); 493 return PendingIntent.getBroadcast(context, 494 Long.valueOf(eventId).hashCode(), broadcastIntent, 495 PendingIntent.FLAG_CANCEL_CURRENT); 496 } 497 } while (attendeesCursor.moveToNext()); 498 } 499 return null; 500 501 } finally { 502 if (attendeesCursor != null) { 503 attendeesCursor.close(); 504 } 505 } 506 } 507 508 /** 509 * Creates an Intent for emailing the attendees of the event. Returns null if there 510 * are no emailable attendees. 511 */ createEmailIntent(Context context, long eventId, String body)512 static Intent createEmailIntent(Context context, long eventId, String body) { 513 // TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to 514 // be shared with EventInfoFragment. 515 516 // Query for the owner account(s). 517 String ownerAccount = null; 518 String syncAccount = null; 519 String eventTitle = null; 520 Cursor eventCursor = getEventCursor(context, eventId); 521 try { 522 if (eventCursor != null && eventCursor.moveToFirst()) { 523 ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT); 524 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 525 eventTitle = eventCursor.getString(EVENT_INDEX_TITLE); 526 } 527 } finally { 528 if (eventCursor != null) { 529 eventCursor.close(); 530 } 531 } 532 if (TextUtils.isEmpty(eventTitle)) { 533 eventTitle = context.getResources().getString(R.string.no_title_label); 534 } 535 536 // Query for the attendees. 537 List<String> toEmails = new ArrayList<String>(); 538 List<String> ccEmails = new ArrayList<String>(); 539 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 540 try { 541 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 542 do { 543 int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 544 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 545 switch(status) { 546 case Attendees.ATTENDEE_STATUS_DECLINED: 547 addIfEmailable(ccEmails, email, syncAccount); 548 break; 549 default: 550 addIfEmailable(toEmails, email, syncAccount); 551 } 552 } while (attendeesCursor.moveToNext()); 553 } 554 } finally { 555 if (attendeesCursor != null) { 556 attendeesCursor.close(); 557 } 558 } 559 560 Intent intent = null; 561 if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) { 562 intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body, 563 toEmails, ccEmails, ownerAccount); 564 } 565 566 if (intent == null) { 567 return null; 568 } 569 else { 570 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 571 return intent; 572 } 573 } 574 addIfEmailable(List<String> emailList, String email, String syncAccount)575 private static void addIfEmailable(List<String> emailList, String email, String syncAccount) { 576 if (Utils.isEmailableFrom(email, syncAccount)) { 577 emailList.add(email); 578 } 579 } 580 } 581