AlertReceiver.java revision 7ffa24cba2977925b737e6bd39be59ba39609611
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 Events.ORGANIZER, // 3 479 }; 480 private static final int EVENT_INDEX_OWNER_ACCOUNT = 0; 481 private static final int EVENT_INDEX_ACCOUNT_NAME = 1; 482 private static final int EVENT_INDEX_TITLE = 2; 483 private static final int EVENT_INDEX_ORGANIZER = 3; 484 485 private static Cursor getEventCursor(Context context, long eventId) { 486 return context.getContentResolver().query( 487 ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION, 488 null, null, null); 489 } 490 491 private static Cursor getAttendeesCursor(Context context, long eventId) { 492 return context.getContentResolver().query(Attendees.CONTENT_URI, 493 ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) }, 494 ATTENDEES_SORT_ORDER); 495 } 496 497 /** 498 * Creates a broadcast pending intent that fires to AlertReceiver when the email button 499 * is clicked. 500 */ 501 private static PendingIntent createBroadcastMailIntent(Context context, long eventId, 502 String eventTitle) { 503 // Query for viewer account. 504 String syncAccount = null; 505 Cursor eventCursor = getEventCursor(context, eventId); 506 try { 507 if (eventCursor != null && eventCursor.moveToFirst()) { 508 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 509 } 510 } finally { 511 if (eventCursor != null) { 512 eventCursor.close(); 513 } 514 } 515 516 // Query attendees to see if there are any to email. 517 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 518 try { 519 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 520 do { 521 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 522 if (Utils.isEmailableFrom(email, syncAccount)) { 523 // Send intent back to ourself first for a couple reasons: 524 // 1) Workaround issue where clicking action button in notification does 525 // not automatically close the notification shade. 526 // 2) Attendees list in email will always be up to date. 527 Intent broadcastIntent = new Intent(MAIL_ACTION); 528 broadcastIntent.setClass(context, AlertReceiver.class); 529 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); 530 return PendingIntent.getBroadcast(context, 531 Long.valueOf(eventId).hashCode(), broadcastIntent, 532 PendingIntent.FLAG_CANCEL_CURRENT); 533 } 534 } while (attendeesCursor.moveToNext()); 535 } 536 return null; 537 538 } finally { 539 if (attendeesCursor != null) { 540 attendeesCursor.close(); 541 } 542 } 543 } 544 545 /** 546 * Creates an Intent for emailing the attendees of the event. Returns null if there 547 * are no emailable attendees. 548 */ 549 static Intent createEmailIntent(Context context, long eventId, String body) { 550 // TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to 551 // be shared with EventInfoFragment. 552 553 // Query for the owner account(s). 554 String ownerAccount = null; 555 String syncAccount = null; 556 String eventTitle = null; 557 String eventOrganizer = null; 558 Cursor eventCursor = getEventCursor(context, eventId); 559 try { 560 if (eventCursor != null && eventCursor.moveToFirst()) { 561 ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT); 562 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 563 eventTitle = eventCursor.getString(EVENT_INDEX_TITLE); 564 eventOrganizer = eventCursor.getString(EVENT_INDEX_ORGANIZER); 565 } 566 } finally { 567 if (eventCursor != null) { 568 eventCursor.close(); 569 } 570 } 571 if (TextUtils.isEmpty(eventTitle)) { 572 eventTitle = context.getResources().getString(R.string.no_title_label); 573 } 574 575 // Query for the attendees. 576 List<String> toEmails = new ArrayList<String>(); 577 List<String> ccEmails = new ArrayList<String>(); 578 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 579 try { 580 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 581 do { 582 int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 583 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 584 switch(status) { 585 case Attendees.ATTENDEE_STATUS_DECLINED: 586 addIfEmailable(ccEmails, email, syncAccount); 587 break; 588 default: 589 addIfEmailable(toEmails, email, syncAccount); 590 } 591 } while (attendeesCursor.moveToNext()); 592 } 593 } finally { 594 if (attendeesCursor != null) { 595 attendeesCursor.close(); 596 } 597 } 598 599 // Add organizer only if no attendees to email (the case when too many attendees 600 // in the event to sync or show). 601 if (toEmails.size() == 0 && ccEmails.size() == 0 && eventOrganizer != null) { 602 addIfEmailable(toEmails, eventOrganizer, syncAccount); 603 } 604 605 Intent intent = null; 606 if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) { 607 intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body, 608 toEmails, ccEmails, ownerAccount); 609 } 610 611 if (intent == null) { 612 return null; 613 } 614 else { 615 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 616 return intent; 617 } 618 } 619 620 private static void addIfEmailable(List<String> emailList, String email, String syncAccount) { 621 if (Utils.isEmailableFrom(email, syncAccount)) { 622 emailList.add(email); 623 } 624 } 625} 626