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