AlertReceiver.java revision 487d52c2789114e0ee3e7ce85694611b8d59dd70
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, int priority) { 223 Notification n = buildBasicNotification(new Notification.Builder(context), 224 context, title, summaryText, startMillis, endMillis, eventId, notificationId, 225 doPopup, priority, 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, int priority, 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 // Should be one of the values in Notification (ie. Notification.PRIORITY_HIGH, etc). 262 // A higher priority will encourage notification manager to expand it. 263 notificationBuilder.setPriority(priority); 264 } 265 266 if (addActionButtons) { 267 // Create snooze intent. TODO: change snooze to 10 minutes. 268 PendingIntent snoozeIntent = createSnoozeIntent(context, eventId, startMillis, 269 endMillis, notificationId); 270 271 // Create email intent for emailing attendees. 272 PendingIntent emailIntent = createBroadcastMailIntent(context, eventId, title); 273 274 if (Utils.isJellybeanOrLater()) { 275 notificationBuilder.addAction(R.drawable.ic_alarm_holo_dark, 276 resources.getString(R.string.snooze_label), snoozeIntent); 277 if (emailIntent != null) { 278 notificationBuilder.addAction(R.drawable.ic_menu_email_holo_dark, 279 resources.getString(R.string.email_guests_label), emailIntent); 280 } 281 } else { 282 // Old-style notification (pre-JB). Use custom view with buttons to provide 283 // JB-like functionality (snooze/email). 284 Notification n = notificationBuilder.getNotification(); 285 286 // Use custom view with buttons to provide JB-like functionality (snooze/email). 287 RemoteViews contentView = new RemoteViews(context.getPackageName(), 288 R.layout.notification); 289 contentView.setTextViewText(R.id.title, title); 290 contentView.setTextViewText(R.id.text, summaryText); 291 contentView.setViewVisibility(R.id.snooze_button, View.VISIBLE); 292 contentView.setOnClickPendingIntent(R.id.snooze_button, snoozeIntent); 293 if (emailIntent == null) { 294 contentView.setViewVisibility(R.id.email_button, View.GONE); 295 } else { 296 contentView.setViewVisibility(R.id.email_button, View.VISIBLE); 297 contentView.setOnClickPendingIntent(R.id.email_button, emailIntent); 298 } 299 n.contentView = contentView; 300 return n; 301 } 302 } 303 return notificationBuilder.getNotification(); 304 } 305 306 /** 307 * Creates an expanding notification. The initial expanded state is decided by 308 * the notification manager based on the priority. 309 */ 310 public static NotificationWrapper makeExpandingNotification(Context context, String title, 311 String summaryText, String description, long startMillis, long endMillis, long eventId, 312 int notificationId, boolean doPopup, int priority) { 313 Notification.Builder basicBuilder = new Notification.Builder(context); 314 Notification notification = buildBasicNotification(basicBuilder, context, title, 315 summaryText, startMillis, endMillis, eventId, notificationId, doPopup, 316 priority, true); 317 if (Utils.isJellybeanOrLater()) { 318 // Create a new-style expanded notification 319 Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle( 320 basicBuilder); 321 if (description != null) { 322 description = mBlankLinePattern.matcher(description).replaceAll(""); 323 description = description.trim(); 324 } 325 CharSequence text; 326 if (TextUtils.isEmpty(description)) { 327 text = summaryText; 328 } else { 329 SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); 330 stringBuilder.append(summaryText); 331 stringBuilder.append("\n\n"); 332 stringBuilder.setSpan(new RelativeSizeSpan(0.5f), summaryText.length(), 333 stringBuilder.length(), 0); 334 stringBuilder.append(description); 335 text = stringBuilder; 336 } 337 expandedBuilder.bigText(text); 338 notification = expandedBuilder.build(); 339 } 340 return new NotificationWrapper(notification, notificationId, eventId, startMillis, 341 endMillis, doPopup); 342 } 343 344 /** 345 * Creates an expanding digest notification for expired events. 346 */ 347 public static NotificationWrapper makeDigestNotification(Context context, 348 ArrayList<AlertService.NotificationInfo> notificationInfos, String digestTitle, 349 boolean expandable) { 350 if (notificationInfos == null || notificationInfos.size() < 1) { 351 return null; 352 } 353 354 Resources res = context.getResources(); 355 int numEvents = notificationInfos.size(); 356 long[] eventIds = new long[notificationInfos.size()]; 357 for (int i = 0; i < notificationInfos.size(); i++) { 358 eventIds[i] = notificationInfos.get(i).eventId; 359 } 360 361 // Create an intent triggered by clicking on the status icon that shows the alerts list. 362 PendingIntent pendingClickIntent = createAlertActivityIntent(context); 363 364 // Create an intent triggered by dismissing the digest notification that clears all 365 // expired events. 366 Intent deleteIntent = new Intent(); 367 deleteIntent.setClass(context, DismissAlarmsService.class); 368 deleteIntent.setAction(DELETE_ALL_ACTION); 369 deleteIntent.putExtra(AlertUtils.EVENT_IDS_KEY, eventIds); 370 PendingIntent pendingDeleteIntent = PendingIntent.getService(context, 0, deleteIntent, 371 PendingIntent.FLAG_UPDATE_CURRENT); 372 373 if (digestTitle == null || digestTitle.length() == 0) { 374 digestTitle = res.getString(R.string.no_title_label); 375 } 376 377 Notification.Builder notificationBuilder = new Notification.Builder(context); 378 notificationBuilder.setContentText(digestTitle); 379 notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_multiple); 380 notificationBuilder.setContentIntent(pendingClickIntent); 381 notificationBuilder.setDeleteIntent(pendingDeleteIntent); 382 String nEventsStr = res.getQuantityString(R.plurals.Nevents, numEvents, numEvents); 383 notificationBuilder.setContentTitle(nEventsStr); 384 385 Notification n; 386 if (Utils.isJellybeanOrLater()) { 387 // New-style notification... 388 389 // Set to min priority to encourage the notification manager to collapse it. 390 notificationBuilder.setPriority(Notification.PRIORITY_MIN); 391 392 if (expandable) { 393 // Multiple reminders. Combine into an expanded digest notification. 394 Notification.InboxStyle expandedBuilder = new Notification.InboxStyle( 395 notificationBuilder); 396 int i = 0; 397 for (AlertService.NotificationInfo info : notificationInfos) { 398 if (i < NOTIFICATION_DIGEST_MAX_LENGTH) { 399 String name = info.eventName; 400 if (TextUtils.isEmpty(name)) { 401 name = context.getResources().getString(R.string.no_title_label); 402 } 403 String timeLocation = AlertUtils.formatTimeLocation(context, 404 info.startMillis, info.allDay, info.location); 405 406 TextAppearanceSpan primaryTextSpan = new TextAppearanceSpan(context, 407 R.style.NotificationPrimaryText); 408 TextAppearanceSpan secondaryTextSpan = new TextAppearanceSpan(context, 409 R.style.NotificationSecondaryText); 410 411 // Event title in bold. 412 SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); 413 stringBuilder.append(name); 414 stringBuilder.setSpan(primaryTextSpan, 0, stringBuilder.length(), 0); 415 stringBuilder.append(" "); 416 417 // Followed by time and location. 418 int secondaryIndex = stringBuilder.length(); 419 stringBuilder.append(timeLocation); 420 stringBuilder.setSpan(secondaryTextSpan, secondaryIndex, 421 stringBuilder.length(), 0); 422 expandedBuilder.addLine(stringBuilder); 423 i++; 424 } else { 425 break; 426 } 427 } 428 429 // If there are too many to display, add "+X missed events" for the last line. 430 int remaining = numEvents - i; 431 if (remaining > 0) { 432 String nMoreEventsStr = res.getQuantityString(R.plurals.N_remaining_events, 433 remaining, remaining); 434 // TODO: Add highlighting and icon to this last entry once framework allows it. 435 expandedBuilder.setSummaryText(nMoreEventsStr); 436 } 437 438 // Remove the title in the expanded form (redundant with the listed items). 439 expandedBuilder.setBigContentTitle(""); 440 441 n = expandedBuilder.build(); 442 } else { 443 n = notificationBuilder.build(); 444 } 445 } else { 446 // Old style notification (pre-JB) 447 n = notificationBuilder.getNotification(); 448 } 449 450 NotificationWrapper nw = new NotificationWrapper(n); 451 if (AlertService.DEBUG) { 452 for (AlertService.NotificationInfo info : notificationInfos) { 453 nw.add(new NotificationWrapper(null, 0, info.eventId, info.startMillis, 454 info.endMillis, false)); 455 } 456 } 457 return nw; 458 } 459 460 private static final String[] ATTENDEES_PROJECTION = new String[] { 461 Attendees.ATTENDEE_EMAIL, // 0 462 Attendees.ATTENDEE_STATUS, // 1 463 }; 464 private static final int ATTENDEES_INDEX_EMAIL = 0; 465 private static final int ATTENDEES_INDEX_STATUS = 1; 466 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; 467 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " 468 + Attendees.ATTENDEE_EMAIL + " ASC"; 469 470 private static final String[] EVENT_PROJECTION = new String[] { 471 Calendars.OWNER_ACCOUNT, // 0 472 Calendars.ACCOUNT_NAME, // 1 473 Events.TITLE, // 2 474 }; 475 private static final int EVENT_INDEX_OWNER_ACCOUNT = 0; 476 private static final int EVENT_INDEX_ACCOUNT_NAME = 1; 477 private static final int EVENT_INDEX_TITLE = 2; 478 479 private static Cursor getEventCursor(Context context, long eventId) { 480 return context.getContentResolver().query( 481 ContentUris.withAppendedId(Events.CONTENT_URI, eventId), EVENT_PROJECTION, 482 null, null, null); 483 } 484 485 private static Cursor getAttendeesCursor(Context context, long eventId) { 486 return context.getContentResolver().query(Attendees.CONTENT_URI, 487 ATTENDEES_PROJECTION, ATTENDEES_WHERE, new String[] { Long.toString(eventId) }, 488 ATTENDEES_SORT_ORDER); 489 } 490 491 /** 492 * Creates a broadcast pending intent that fires to AlertReceiver when the email button 493 * is clicked. 494 */ 495 private static PendingIntent createBroadcastMailIntent(Context context, long eventId, 496 String eventTitle) { 497 // Query for viewer account. 498 String syncAccount = null; 499 Cursor eventCursor = getEventCursor(context, eventId); 500 try { 501 if (eventCursor != null && eventCursor.moveToFirst()) { 502 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 503 } 504 } finally { 505 if (eventCursor != null) { 506 eventCursor.close(); 507 } 508 } 509 510 // Query attendees to see if there are any to email. 511 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 512 try { 513 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 514 do { 515 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 516 if (Utils.isEmailableFrom(email, syncAccount)) { 517 // Send intent back to ourself first for a couple reasons: 518 // 1) Workaround issue where clicking action button in notification does 519 // not automatically close the notification shade. 520 // 2) Attendees list in email will always be up to date. 521 Intent broadcastIntent = new Intent(MAIL_ACTION); 522 broadcastIntent.setClass(context, AlertReceiver.class); 523 broadcastIntent.putExtra(EXTRA_EVENT_ID, eventId); 524 return PendingIntent.getBroadcast(context, 525 Long.valueOf(eventId).hashCode(), broadcastIntent, 526 PendingIntent.FLAG_CANCEL_CURRENT); 527 } 528 } while (attendeesCursor.moveToNext()); 529 } 530 return null; 531 532 } finally { 533 if (attendeesCursor != null) { 534 attendeesCursor.close(); 535 } 536 } 537 } 538 539 /** 540 * Creates an Intent for emailing the attendees of the event. Returns null if there 541 * are no emailable attendees. 542 */ 543 static Intent createEmailIntent(Context context, long eventId, String body) { 544 // TODO: Refactor to move query part into Utils.createEmailAttendeeIntent, to 545 // be shared with EventInfoFragment. 546 547 // Query for the owner account(s). 548 String ownerAccount = null; 549 String syncAccount = null; 550 String eventTitle = null; 551 Cursor eventCursor = getEventCursor(context, eventId); 552 try { 553 if (eventCursor != null && eventCursor.moveToFirst()) { 554 ownerAccount = eventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT); 555 syncAccount = eventCursor.getString(EVENT_INDEX_ACCOUNT_NAME); 556 eventTitle = eventCursor.getString(EVENT_INDEX_TITLE); 557 } 558 } finally { 559 if (eventCursor != null) { 560 eventCursor.close(); 561 } 562 } 563 if (TextUtils.isEmpty(eventTitle)) { 564 eventTitle = context.getResources().getString(R.string.no_title_label); 565 } 566 567 // Query for the attendees. 568 List<String> toEmails = new ArrayList<String>(); 569 List<String> ccEmails = new ArrayList<String>(); 570 Cursor attendeesCursor = getAttendeesCursor(context, eventId); 571 try { 572 if (attendeesCursor != null && attendeesCursor.moveToFirst()) { 573 do { 574 int status = attendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 575 String email = attendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 576 switch(status) { 577 case Attendees.ATTENDEE_STATUS_DECLINED: 578 addIfEmailable(ccEmails, email, syncAccount); 579 break; 580 default: 581 addIfEmailable(toEmails, email, syncAccount); 582 } 583 } while (attendeesCursor.moveToNext()); 584 } 585 } finally { 586 if (attendeesCursor != null) { 587 attendeesCursor.close(); 588 } 589 } 590 591 Intent intent = null; 592 if (ownerAccount != null && (toEmails.size() > 0 || ccEmails.size() > 0)) { 593 intent = Utils.createEmailAttendeesIntent(context.getResources(), eventTitle, body, 594 toEmails, ccEmails, ownerAccount); 595 } 596 597 if (intent == null) { 598 return null; 599 } 600 else { 601 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 602 return intent; 603 } 604 } 605 606 private static void addIfEmailable(List<String> emailList, String email, String syncAccount) { 607 if (Utils.isEmailableFrom(email, syncAccount)) { 608 emailList.add(email); 609 } 610 } 611} 612