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