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