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