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