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