MailService.java revision d16b4b921fef0a79a12fd90a5fad353413ba80ea
1/*
2 * Copyright (C) 2008 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.email.service;
18
19import com.android.email.Controller;
20import com.android.email.Email;
21import com.android.email.R;
22import com.android.email.activity.MessageList;
23import com.android.email.mail.MessagingException;
24import com.android.email.provider.EmailContent.Account;
25import com.android.email.provider.EmailContent.AccountColumns;
26import com.android.email.provider.EmailContent.Mailbox;
27
28import android.app.AlarmManager;
29import android.app.Notification;
30import android.app.NotificationManager;
31import android.app.PendingIntent;
32import android.app.Service;
33import android.content.ContentUris;
34import android.content.ContentValues;
35import android.content.Context;
36import android.content.Intent;
37import android.database.Cursor;
38import android.net.Uri;
39import android.os.IBinder;
40import android.os.SystemClock;
41import android.util.Config;
42import android.util.Log;
43
44import java.util.HashMap;
45
46/**
47 * Background service for refreshing non-push email accounts.
48 */
49public class MailService extends Service {
50    /** DO NOT CHECK IN "TRUE" */
51    private static final boolean DEBUG_FORCE_QUICK_REFRESH = false;     // force 1-minute refresh
52
53    private static final String LOG_TAG = "Email-MailService";
54
55    public static int NEW_MESSAGE_NOTIFICATION_ID = 1;
56
57    private static final String ACTION_CHECK_MAIL =
58        "com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
59    private static final String ACTION_RESCHEDULE =
60        "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE";
61    private static final String ACTION_CANCEL =
62        "com.android.email.intent.action.MAIL_SERVICE_CANCEL";
63    private static final String ACTION_NOTIFY_MAIL =
64        "com.android.email.intent.action.MAIL_SERVICE_NOTIFY";
65
66    private static final String EXTRA_CHECK_ACCOUNT = "com.android.email.intent.extra.ACCOUNT";
67    private static final String EXTRA_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
68    private static final String EXTRA_DEBUG_WATCHDOG = "com.android.email.intent.extra.WATCHDOG";
69
70    private static final int WATCHDOG_DELAY = 10 * 60 * 1000;   // 10 minutes
71
72    private static final String[] NEW_MESSAGE_COUNT_PROJECTION =
73        new String[] {AccountColumns.NEW_MESSAGE_COUNT};
74
75    private Controller.Result mControllerCallback = new ControllerResults();
76
77    private int mStartId;
78
79    /**
80     * Access must be synchronized, because there are accesses from the Controller callback
81     */
82    private static HashMap<Long,AccountSyncReport> mSyncReports =
83        new HashMap<Long,AccountSyncReport>();
84
85    /**
86     * Simple template used for clearing new message count in accounts
87     */
88    static ContentValues mClearNewMessages;
89    static {
90        mClearNewMessages = new ContentValues();
91        mClearNewMessages.put(Account.NEW_MESSAGE_COUNT, 0);
92    }
93
94    public static void actionReschedule(Context context) {
95        Intent i = new Intent();
96        i.setClass(context, MailService.class);
97        i.setAction(MailService.ACTION_RESCHEDULE);
98        context.startService(i);
99    }
100
101    public static void actionCancel(Context context)  {
102        Intent i = new Intent();
103        i.setClass(context, MailService.class);
104        i.setAction(MailService.ACTION_CANCEL);
105        context.startService(i);
106    }
107
108    /**
109     * Reset new message counts for one or all accounts.  This clears both our local copy and
110     * the values (if any) stored in the account records.
111     *
112     * @param accountId account to clear, or -1 for all accounts
113     */
114    public static void resetNewMessageCount(Context context, long accountId) {
115        synchronized (mSyncReports) {
116            for (AccountSyncReport report : mSyncReports.values()) {
117                if (accountId == -1 || accountId == report.accountId) {
118                    report.numNewMessages = 0;
119                }
120            }
121        }
122        // now do the database - all accounts, or just one of them
123        Uri uri;
124        if (accountId == -1) {
125            uri = Account.CONTENT_URI;
126        } else {
127            uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
128        }
129        context.getContentResolver().update(uri, mClearNewMessages, null, null);
130    }
131
132    /**
133     * Entry point for asynchronous message services (e.g. push mode) to post notifications of new
134     * messages.  This assumes that the push provider has already synced the messages into the
135     * appropriate database - this simply triggers the notification mechanism.
136     *
137     * @param context a context
138     * @param accountId the id of the account that is reporting new messages
139     * @param newCount the number of new messages
140     */
141    public static void actionNotifyNewMessages(Context context, long accountId) {
142        Intent i = new Intent(ACTION_NOTIFY_MAIL);
143        i.setClass(context, MailService.class);
144        i.putExtra(EXTRA_CHECK_ACCOUNT, accountId);
145        context.startService(i);
146    }
147
148    @Override
149    public int onStartCommand(Intent intent, int flags, int startId) {
150        super.onStartCommand(intent, flags, startId);
151
152        // TODO this needs to be passed through the controller and back to us
153        this.mStartId = startId;
154        String action = intent.getAction();
155
156        Controller controller = Controller.getInstance(getApplication());
157        controller.addResultCallback(mControllerCallback);
158
159        if (ACTION_CHECK_MAIL.equals(action)) {
160            // If we have the data, restore the last-sync-times for each account
161            // These are cached in the wakeup intent in case the process was killed.
162            restoreSyncReports(intent);
163
164            // Sync a specific account if given
165            AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
166            long checkAccountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1);
167            if (Config.LOGD && Email.DEBUG) {
168                Log.d(LOG_TAG, "action: check mail for id=" + checkAccountId);
169            }
170            if (checkAccountId >= 0) {
171                setWatchdog(checkAccountId, alarmManager);
172            }
173            // if no account given, or the given account cannot be synced - reschedule
174            if (checkAccountId == -1 || !syncOneAccount(controller, checkAccountId, startId)) {
175                // Prevent runaway on the current account by pretending it updated
176                if (checkAccountId != -1) {
177                    updateAccountReport(checkAccountId, 0);
178                }
179                // Find next account to sync, and reschedule
180                reschedule(alarmManager);
181                stopSelf(startId);
182            }
183        }
184        else if (ACTION_CANCEL.equals(action)) {
185            if (Config.LOGD && Email.DEBUG) {
186                Log.d(LOG_TAG, "action: cancel");
187            }
188            cancel();
189            stopSelf(startId);
190        }
191        else if (ACTION_RESCHEDULE.equals(action)) {
192            if (Config.LOGD && Email.DEBUG) {
193                Log.d(LOG_TAG, "action: reschedule");
194            }
195            // As a precaution, clear any outstanding Email notifications
196            // We could be smarter and only do this when the list of accounts changes,
197            // but that's an edge condition and this is much safer.
198            NotificationManager notificationManager =
199                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
200            notificationManager.cancel(NEW_MESSAGE_NOTIFICATION_ID);
201
202            // When called externally, we refresh the sync reports table to pick up
203            // any changes in the account list or account settings
204            refreshSyncReports();
205            // Finally, scan for the next needing update, and set an alarm for it
206            AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
207            reschedule(alarmManager);
208            stopSelf(startId);
209        } else if (ACTION_NOTIFY_MAIL.equals(action)) {
210            long accountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1);
211            // Get the current new message count
212            Cursor c = getContentResolver().query(
213                    ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
214                    NEW_MESSAGE_COUNT_PROJECTION, null, null, null);
215            int newMessageCount = 0;
216            try {
217                if (c.moveToFirst()) {
218                    newMessageCount = c.getInt(0);
219                } else {
220                    // If the account no longer exists, set to -1 (which is handled below)
221                    accountId = -1;
222                }
223            } finally {
224                c.close();
225            }
226            if (Config.LOGD && Email.DEBUG) {
227                Log.d(LOG_TAG, "notify accountId=" + Long.toString(accountId)
228                        + " count=" + newMessageCount);
229            }
230            if (accountId != -1) {
231                updateAccountReport(accountId, newMessageCount);
232                notifyNewMessages(accountId);
233            }
234            stopSelf(startId);
235        }
236
237        // Returning START_NOT_STICKY means that if a mail check is killed (e.g. due to memory
238        // pressure, there will be no explicit restart.  This is OK;  Note that we set a watchdog
239        // alarm before each mailbox check.  If the mailbox check never completes, the watchdog
240        // will fire and get things running again.
241        return START_NOT_STICKY;
242    }
243
244    @Override
245    public IBinder onBind(Intent intent) {
246        return null;
247    }
248
249    @Override
250    public void onDestroy() {
251        super.onDestroy();
252        Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback);
253    }
254
255    private void cancel() {
256        AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
257        PendingIntent pi = createAlarmIntent(-1, null, false);
258        alarmMgr.cancel(pi);
259    }
260
261    /**
262     * Refresh the sync reports, to pick up any changes in the account list or account settings.
263     */
264    private void refreshSyncReports() {
265        synchronized (mSyncReports) {
266            // Make shallow copy of sync reports so we can recover the prev sync times
267            HashMap<Long,AccountSyncReport> oldSyncReports =
268                new HashMap<Long,AccountSyncReport>(mSyncReports);
269
270            // Delete the sync reports to force a refresh from live account db data
271            mSyncReports.clear();
272            setupSyncReportsLocked(-1);
273
274            // Restore prev-sync & next-sync times for any reports in the new list
275            for (AccountSyncReport newReport : mSyncReports.values()) {
276                AccountSyncReport oldReport = oldSyncReports.get(newReport.accountId);
277                if (oldReport != null) {
278                    newReport.prevSyncTime = oldReport.prevSyncTime;
279                    if (newReport.syncInterval > 0 && newReport.prevSyncTime != 0) {
280                        newReport.nextSyncTime =
281                            newReport.prevSyncTime + (newReport.syncInterval * 1000 * 60);
282                    }
283                }
284            }
285        }
286    }
287
288    /**
289     * Create and send an alarm with the entire list.  This also sends a list of known last-sync
290     * times with the alarm, so if we are killed between alarms, we don't lose this info.
291     *
292     * @param alarmMgr passed in so we can mock for testing.
293     */
294    /* package */ void reschedule(AlarmManager alarmMgr) {
295        // restore the reports if lost
296        setupSyncReports(-1);
297        synchronized (mSyncReports) {
298            int numAccounts = mSyncReports.size();
299            long[] accountInfo = new long[numAccounts * 2];     // pairs of { accountId, lastSync }
300            int accountInfoIndex = 0;
301
302            long nextCheckTime = Long.MAX_VALUE;
303            AccountSyncReport nextAccount = null;
304            long timeNow = SystemClock.elapsedRealtime();
305
306            for (AccountSyncReport report : mSyncReports.values()) {
307                if (report.syncInterval <= 0) {                         // no timed checks - skip
308                    continue;
309                }
310                long prevSyncTime = report.prevSyncTime;
311                long nextSyncTime = report.nextSyncTime;
312
313                // select next account to sync
314                if ((prevSyncTime == 0) || (nextSyncTime < timeNow)) {  // never checked, or overdue
315                    nextCheckTime = 0;
316                    nextAccount = report;
317                } else if (nextSyncTime < nextCheckTime) {              // next to be checked
318                    nextCheckTime = nextSyncTime;
319                    nextAccount = report;
320                }
321                // collect last-sync-times for all accounts
322                // this is using pairs of {long,long} to simplify passing in a bundle
323                accountInfo[accountInfoIndex++] = report.accountId;
324                accountInfo[accountInfoIndex++] = report.prevSyncTime;
325            }
326
327            // Clear out any unused elements in the array
328            while (accountInfoIndex < accountInfo.length) {
329                accountInfo[accountInfoIndex++] = -1;
330            }
331
332            // set/clear alarm as needed
333            long idToCheck = (nextAccount == null) ? -1 : nextAccount.accountId;
334            PendingIntent pi = createAlarmIntent(idToCheck, accountInfo, false);
335
336            if (nextAccount == null) {
337                alarmMgr.cancel(pi);
338                if (Config.LOGD && Email.DEBUG) {
339                    Log.d(LOG_TAG, "reschedule: alarm cancel - no account to check");
340                }
341            } else {
342                alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckTime, pi);
343                if (Config.LOGD && Email.DEBUG) {
344                    Log.d(LOG_TAG, "reschedule: alarm set at " + nextCheckTime
345                            + " for " + nextAccount);
346                }
347            }
348        }
349    }
350
351    /**
352     * Create a watchdog alarm and set it.  This is used in case a mail check fails (e.g. we are
353     * killed by the system due to memory pressure.)  Normally, a mail check will complete and
354     * the watchdog will be replaced by the call to reschedule().
355     * @param accountId the account we were trying to check
356     * @param alarmMgr system alarm manager
357     */
358    private void setWatchdog(long accountId, AlarmManager alarmMgr) {
359        PendingIntent pi = createAlarmIntent(accountId, null, true);
360        long timeNow = SystemClock.elapsedRealtime();
361        long nextCheckTime = timeNow + WATCHDOG_DELAY;
362        alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckTime, pi);
363    }
364
365    /**
366     * Return a pending intent for use by this alarm.  Most of the fields must be the same
367     * (in order for the intent to be recognized by the alarm manager) but the extras can
368     * be different, and are passed in here as parameters.
369     */
370    /* package */ PendingIntent createAlarmIntent(long checkId, long[] accountInfo,
371            boolean isWatchdog) {
372        Intent i = new Intent();
373        i.setClassName("com.android.email", "com.android.email.service.MailService");
374        i.setAction(ACTION_CHECK_MAIL);
375        i.putExtra(EXTRA_CHECK_ACCOUNT, checkId);
376        i.putExtra(EXTRA_ACCOUNT_INFO, accountInfo);
377        if (isWatchdog) {
378            i.putExtra(EXTRA_DEBUG_WATCHDOG, true);
379        }
380        PendingIntent pi = PendingIntent.getService(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
381        return pi;
382    }
383
384    /**
385     * Start a controller sync for a specific account
386     *
387     * @param controller The controller to do the sync work
388     * @param checkAccountId the account Id to try and check
389     * @param startId the id of this service launch
390     * @return true if mail checking has started, false if it could not (e.g. bad account id)
391     */
392    private boolean syncOneAccount(Controller controller, long checkAccountId, int startId) {
393        long inboxId = Mailbox.findMailboxOfType(this, checkAccountId, Mailbox.TYPE_INBOX);
394        if (inboxId == Mailbox.NO_MAILBOX) {
395            return false;
396        } else {
397            controller.serviceCheckMail(checkAccountId, inboxId, startId, mControllerCallback);
398            return true;
399        }
400    }
401
402    /**
403     * Note:  Times are relative to SystemClock.elapsedRealtime()
404     */
405    private static class AccountSyncReport {
406        long accountId;
407        long prevSyncTime;      // 0 == unknown
408        long nextSyncTime;      // 0 == ASAP  -1 == don't sync
409        int numNewMessages;
410
411        int syncInterval;
412        boolean notify;
413        boolean vibrate;
414        Uri ringtoneUri;
415
416        String displayName;     // temporary, for debug logging
417
418
419        @Override
420        public String toString() {
421            return displayName + ": id=" + accountId + " prevSync=" + prevSyncTime
422                    + " nextSync=" + nextSyncTime + " numNew=" + numNewMessages;
423        }
424    }
425
426    /**
427     * scan accounts to create a list of { acct, prev sync, next sync, #new }
428     * use this to create a fresh copy.  assumes all accounts need sync
429     *
430     * @param accountId -1 will rebuild the list if empty.  other values will force loading
431     *   of a single account (e.g if it was created after the original list population)
432     */
433    /* package */ void setupSyncReports(long accountId) {
434        synchronized (mSyncReports) {
435            setupSyncReportsLocked(accountId);
436        }
437    }
438
439    /**
440     * Handle the work of setupSyncReports.  Must be synchronized on mSyncReports.
441     */
442    private void setupSyncReportsLocked(long accountId) {
443        if (accountId == -1) {
444            // -1 == reload the list if empty, otherwise exit immediately
445            if (mSyncReports.size() > 0) {
446                return;
447            }
448        } else {
449            // load a single account if it doesn't already have a sync record
450            if (mSyncReports.containsKey(accountId)) {
451                return;
452            }
453        }
454
455        // setup to add a single account or all accounts
456        Uri uri;
457        if (accountId == -1) {
458            uri = Account.CONTENT_URI;
459        } else {
460            uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
461        }
462
463        // TODO use a narrower projection here
464        Cursor c = getContentResolver().query(uri, Account.CONTENT_PROJECTION,
465                null, null, null);
466        try {
467            while (c.moveToNext()) {
468                AccountSyncReport report = new AccountSyncReport();
469                int syncInterval = c.getInt(Account.CONTENT_SYNC_INTERVAL_COLUMN);
470                int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN);
471                String ringtoneString = c.getString(Account.CONTENT_RINGTONE_URI_COLUMN);
472
473                // For debugging only
474                if (DEBUG_FORCE_QUICK_REFRESH && syncInterval >= 0) {
475                    syncInterval = 1;
476                }
477
478                report.accountId = c.getLong(Account.CONTENT_ID_COLUMN);
479                report.prevSyncTime = 0;
480                report.nextSyncTime = (syncInterval > 0) ? 0 : -1;  // 0 == ASAP -1 == no sync
481                report.numNewMessages = 0;
482
483                report.syncInterval = syncInterval;
484                report.notify = (flags & Account.FLAGS_NOTIFY_NEW_MAIL) != 0;
485                report.vibrate = (flags & Account.FLAGS_VIBRATE) != 0;
486                report.ringtoneUri = (ringtoneString == null) ? null
487                        : Uri.parse(ringtoneString);
488
489                report.displayName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
490
491                // TODO lookup # new in inbox
492                mSyncReports.put(report.accountId, report);
493            }
494        } finally {
495            c.close();
496        }
497    }
498
499    /**
500     * Update list with a single account's sync times and unread count
501     *
502     * @param accountId the account being updated
503     * @param newCount the number of new messages, or -1 if not being reported (don't update)
504     * @return the report for the updated account, or null if it doesn't exist (e.g. deleted)
505     */
506    /* package */ AccountSyncReport updateAccountReport(long accountId, int newCount) {
507        // restore the reports if lost
508        setupSyncReports(accountId);
509        synchronized (mSyncReports) {
510            AccountSyncReport report = mSyncReports.get(accountId);
511            if (report == null) {
512                // discard result - there is no longer an account with this id
513                Log.d(LOG_TAG, "No account to update for id=" + Long.toString(accountId));
514                return null;
515            }
516
517            // report found - update it (note - editing the report while in-place in the hashmap)
518            report.prevSyncTime = SystemClock.elapsedRealtime();
519            if (report.syncInterval > 0) {
520                report.nextSyncTime = report.prevSyncTime + (report.syncInterval * 1000 * 60);
521            }
522            if (newCount != -1) {
523                report.numNewMessages = newCount;
524            }
525            if (Config.LOGD && Email.DEBUG) {
526                Log.d(LOG_TAG, "update account " + report.toString());
527            }
528            return report;
529        }
530    }
531
532    /**
533     * when we receive an alarm, update the account sync reports list if necessary
534     * this will be the case when if we have restarted the process and lost the data
535     * in the global.
536     *
537     * @param restoreIntent the intent with the list
538     */
539    /* package */ void restoreSyncReports(Intent restoreIntent) {
540        // restore the reports if lost
541        setupSyncReports(-1);
542        synchronized (mSyncReports) {
543            long[] accountInfo = restoreIntent.getLongArrayExtra(EXTRA_ACCOUNT_INFO);
544            if (accountInfo == null) {
545                Log.d(LOG_TAG, "no data in intent to restore");
546                return;
547            }
548            int accountInfoIndex = 0;
549            int accountInfoLimit = accountInfo.length;
550            while (accountInfoIndex < accountInfoLimit) {
551                long accountId = accountInfo[accountInfoIndex++];
552                long prevSync = accountInfo[accountInfoIndex++];
553                AccountSyncReport report = mSyncReports.get(accountId);
554                if (report != null) {
555                    if (report.prevSyncTime == 0) {
556                        report.prevSyncTime = prevSync;
557                        if (report.syncInterval > 0 && report.prevSyncTime != 0) {
558                            report.nextSyncTime =
559                                report.prevSyncTime + (report.syncInterval * 1000 * 60);
560                        }
561                    }
562                }
563            }
564        }
565    }
566
567    class ControllerResults implements Controller.Result {
568
569        public void loadMessageForViewCallback(MessagingException result, long messageId,
570                int progress) {
571        }
572
573        public void loadAttachmentCallback(MessagingException result, long messageId,
574                long attachmentId, int progress) {
575        }
576
577        public void updateMailboxCallback(MessagingException result, long accountId,
578                long mailboxId, int progress, int numNewMessages) {
579            if (result != null || progress == 100) {
580                // We only track the inbox here in the service - ignore other mailboxes
581                long inboxId = Mailbox.findMailboxOfType(MailService.this,
582                        accountId, Mailbox.TYPE_INBOX);
583                if (mailboxId == inboxId) {
584                    if (progress == 100) {
585                        updateAccountReport(accountId, numNewMessages);
586                        if (numNewMessages > 0) {
587                            notifyNewMessages(accountId);
588                        }
589                    } else {
590                        updateAccountReport(accountId, -1);
591                    }
592                }
593                // Call the global refresh tracker for all mailboxes
594                Email.updateMailboxRefreshTime(mailboxId);
595            }
596        }
597
598        public void updateMailboxListCallback(MessagingException result, long accountId,
599                int progress) {
600        }
601
602        public void serviceCheckMailCallback(MessagingException result, long accountId,
603                long mailboxId, int progress, long tag) {
604            if (result != null || progress == 100) {
605                if (result != null) {
606                    // the checkmail ended in an error.  force an update of the refresh
607                    // time, so we don't just spin on this account
608                    updateAccountReport(accountId, -1);
609                }
610                AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
611                reschedule(alarmManager);
612                int serviceId = MailService.this.mStartId;
613                if (tag != 0) {
614                    serviceId = (int) tag;
615                }
616                stopSelf(serviceId);
617            }
618        }
619
620        public void sendMailCallback(MessagingException result, long accountId, long messageId,
621                int progress) {
622        }
623    }
624
625    /**
626     * Prepare notifications for a given new account having received mail
627     * The notification is organized around the account that has the new mail (e.g. selecting
628     * the alert preferences) but the notification will include a summary if other
629     * accounts also have new mail.
630     */
631    private void notifyNewMessages(long accountId) {
632        boolean notify = false;
633        boolean vibrate = false;
634        Uri ringtone = null;
635        int accountsWithNewMessages = 0;
636        int numNewMessages = 0;
637        String reportName = null;
638        synchronized (mSyncReports) {
639            for (AccountSyncReport report : mSyncReports.values()) {
640                if (report.numNewMessages == 0) {
641                    continue;
642                }
643                numNewMessages += report.numNewMessages;
644                accountsWithNewMessages += 1;
645                if (report.accountId == accountId) {
646                    notify = report.notify;
647                    vibrate = report.vibrate;
648                    ringtone = report.ringtoneUri;
649                    reportName = report.displayName;
650                }
651            }
652        }
653        if (!notify) {
654            return;
655        }
656
657        // set up to post a notification
658        Intent intent;
659        String reportString;
660
661        if (accountsWithNewMessages == 1) {
662            // Prepare a report for a single account
663            // "12 unread (gmail)"
664            reportString = getResources().getQuantityString(
665                    R.plurals.notification_new_one_account_fmt, numNewMessages,
666                    numNewMessages, reportName);
667            intent = MessageList.actionHandleAccountIntent(this,
668                    accountId, -1, Mailbox.TYPE_INBOX);
669        } else {
670            // Prepare a report for multiple accounts
671            // "4 accounts"
672            reportString = getResources().getQuantityString(
673                    R.plurals.notification_new_multi_account_fmt, accountsWithNewMessages,
674                    accountsWithNewMessages);
675            intent = MessageList.actionHandleAccountIntent(this,
676                    -1, Mailbox.QUERY_ALL_INBOXES, -1);
677        }
678
679        // prepare appropriate pending intent, set up notification, and send
680        PendingIntent pending =
681            PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
682
683        Notification notification = new Notification(
684                R.drawable.stat_notify_email_generic,
685                getString(R.string.notification_new_title),
686                System.currentTimeMillis());
687        notification.setLatestEventInfo(this,
688                getString(R.string.notification_new_title),
689                reportString,
690                pending);
691
692        notification.sound = ringtone;
693        // Use same code here as in Gmail and GTalk for vibration
694        if (vibrate) {
695            notification.defaults |= Notification.DEFAULT_VIBRATE;
696        }
697
698        // This code is identical to that used by Gmail and GTalk for notifications
699        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
700        notification.ledARGB = 0xff00ff00;
701        notification.ledOnMS = 500;
702        notification.ledOffMS = 2000;
703
704        NotificationManager notificationManager =
705            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
706        notificationManager.notify(NEW_MESSAGE_NOTIFICATION_ID, notification);
707    }
708}
709