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