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