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