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