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