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