Home | History | Annotate | Download | only in alerts
      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");
     81         thr.start();
     82         sAsyncHandler = new Handler(thr.getLooper());
     83     }
     84 
     85     @Override
     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      */
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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 
    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