Controller.java revision 6d8bfa67c438ee18921d13d8bfba876aacaa9ff6
1/* 2 * Copyright (C) 2009 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; 18 19import com.android.email.mail.AuthenticationFailedException; 20import com.android.email.mail.MessagingException; 21import com.android.email.mail.Store; 22import com.android.email.mail.store.Pop3Store.Pop3Message; 23import com.android.email.provider.AttachmentProvider; 24import com.android.email.provider.EmailContent; 25import com.android.email.provider.EmailContent.Account; 26import com.android.email.provider.EmailContent.Attachment; 27import com.android.email.provider.EmailContent.Mailbox; 28import com.android.email.provider.EmailContent.MailboxColumns; 29import com.android.email.provider.EmailContent.Message; 30import com.android.email.provider.EmailContent.MessageColumns; 31import com.android.email.service.EmailServiceStatus; 32import com.android.email.service.IEmailService; 33import com.android.email.service.IEmailServiceCallback; 34 35import android.content.ContentResolver; 36import android.content.ContentUris; 37import android.content.ContentValues; 38import android.content.Context; 39import android.database.Cursor; 40import android.net.Uri; 41import android.os.RemoteException; 42import android.text.TextUtils; 43import android.util.Log; 44 45import java.io.File; 46import java.io.FileNotFoundException; 47import java.io.IOException; 48import java.io.InputStream; 49import java.util.HashSet; 50import java.util.concurrent.ConcurrentHashMap; 51 52/** 53 * New central controller/dispatcher for Email activities that may require remote operations. 54 * Handles disambiguating between legacy MessagingController operations and newer provider/sync 55 * based code. 56 */ 57public class Controller { 58 59 private static Controller sInstance; 60 private final Context mContext; 61 private Context mProviderContext; 62 private final MessagingController mLegacyController; 63 private final LegacyListener mLegacyListener = new LegacyListener(); 64 private final ServiceCallback mServiceCallback = new ServiceCallback(); 65 private final HashSet<Result> mListeners = new HashSet<Result>(); 66 /*package*/ final ConcurrentHashMap<Long, Boolean> mLegacyControllerMap = 67 new ConcurrentHashMap<Long, Boolean>(); 68 69 70 // Note that 0 is a syntactically valid account key; however there can never be an account 71 // with id = 0, so attempts to restore the account will return null. Null values are 72 // handled properly within the code, so this won't cause any issues. 73 private static final long ATTACHMENT_MAILBOX_ACCOUNT_KEY = 0; 74 /*package*/ static final String ATTACHMENT_MAILBOX_SERVER_ID = "__attachment_mailbox__"; 75 /*package*/ static final String ATTACHMENT_MESSAGE_UID_PREFIX = "__attachment_message__"; 76 private static final String WHERE_TYPE_ATTACHMENT = 77 MailboxColumns.TYPE + "=" + Mailbox.TYPE_ATTACHMENT; 78 private static final String WHERE_MAILBOX_KEY = MessageColumns.MAILBOX_KEY + "=?"; 79 80 private static String[] MESSAGEID_TO_ACCOUNTID_PROJECTION = new String[] { 81 EmailContent.RECORD_ID, 82 EmailContent.MessageColumns.ACCOUNT_KEY 83 }; 84 private static int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1; 85 86 private static String[] MESSAGEID_TO_MAILBOXID_PROJECTION = new String[] { 87 EmailContent.RECORD_ID, 88 EmailContent.MessageColumns.MAILBOX_KEY 89 }; 90 private static int MESSAGEID_TO_MAILBOXID_COLUMN_MAILBOXID = 1; 91 92 protected Controller(Context _context) { 93 mContext = _context.getApplicationContext(); 94 mProviderContext = _context; 95 mLegacyController = MessagingController.getInstance(mProviderContext); 96 mLegacyController.addListener(mLegacyListener); 97 } 98 99 /** 100 * Gets or creates the singleton instance of Controller. 101 */ 102 public synchronized static Controller getInstance(Context _context) { 103 if (sInstance == null) { 104 sInstance = new Controller(_context); 105 } 106 return sInstance; 107 } 108 109 /** 110 * Inject a mock controller. Used only for testing. Affects future calls to getInstance(). 111 * 112 * Tests that use this method MUST clean it up by calling this method again with null. 113 */ 114 public synchronized static void injectMockControllerForTest(Controller mockController) { 115 sInstance = mockController; 116 } 117 118 /** 119 * For testing only: Inject a different context for provider access. This will be 120 * used internally for access the underlying provider (e.g. getContentResolver().query()). 121 * @param providerContext the provider context to be used by this instance 122 */ 123 public void setProviderContext(Context providerContext) { 124 mProviderContext = providerContext; 125 } 126 127 /** 128 * Any UI code that wishes for callback results (on async ops) should register their callback 129 * here (typically from onResume()). Unregistered callbacks will never be called, to prevent 130 * problems when the command completes and the activity has already paused or finished. 131 * @param listener The callback that may be used in action methods 132 */ 133 public void addResultCallback(Result listener) { 134 synchronized (mListeners) { 135 mListeners.add(listener); 136 } 137 } 138 139 /** 140 * Any UI code that no longer wishes for callback results (on async ops) should unregister 141 * their callback here (typically from onPause()). Unregistered callbacks will never be called, 142 * to prevent problems when the command completes and the activity has already paused or 143 * finished. 144 * @param listener The callback that may no longer be used 145 */ 146 public void removeResultCallback(Result listener) { 147 synchronized (mListeners) { 148 mListeners.remove(listener); 149 } 150 } 151 152 private boolean isActiveResultCallback(Result listener) { 153 synchronized (mListeners) { 154 return mListeners.contains(listener); 155 } 156 } 157 158 /** 159 * Delete all Messages that live in the attachment mailbox 160 */ 161 public void deleteAttachmentMessages() { 162 // Note: There should only be one attachment mailbox at present 163 ContentResolver resolver = mProviderContext.getContentResolver(); 164 Cursor c = null; 165 try { 166 c = resolver.query(Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, 167 WHERE_TYPE_ATTACHMENT, null, null); 168 while (c.moveToNext()) { 169 long mailboxId = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 170 // Must delete attachments BEFORE messages 171 AttachmentProvider.deleteAllMailboxAttachmentFiles(mProviderContext, 0, mailboxId); 172 resolver.delete(Message.CONTENT_URI, WHERE_MAILBOX_KEY, 173 new String[] {Long.toString(mailboxId)}); 174 } 175 } finally { 176 if (c != null) { 177 c.close(); 178 } 179 } 180 } 181 182 /** 183 * Returns the attachment Mailbox (where we store eml attachment Emails), creating one 184 * if necessary 185 * @return the account's temporary Mailbox 186 */ 187 public Mailbox getAttachmentMailbox() { 188 Cursor c = mProviderContext.getContentResolver().query(Mailbox.CONTENT_URI, 189 Mailbox.CONTENT_PROJECTION, WHERE_TYPE_ATTACHMENT, null, null); 190 try { 191 if (c.moveToFirst()) { 192 return new Mailbox().restore(c); 193 } 194 } finally { 195 c.close(); 196 } 197 Mailbox m = new Mailbox(); 198 m.mAccountKey = ATTACHMENT_MAILBOX_ACCOUNT_KEY; 199 m.mServerId = ATTACHMENT_MAILBOX_SERVER_ID; 200 m.mFlagVisible = false; 201 m.mDisplayName = ATTACHMENT_MAILBOX_SERVER_ID; 202 m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; 203 m.mType = Mailbox.TYPE_ATTACHMENT; 204 m.save(mProviderContext); 205 return m; 206 } 207 208 /** 209 * Create a Message from the Uri and store it in the attachment mailbox 210 * @param uri the uri containing message content 211 * @return the Message or null 212 */ 213 public Message loadMessageFromUri(Uri uri) { 214 Mailbox mailbox = getAttachmentMailbox(); 215 if (mailbox == null) return null; 216 try { 217 InputStream is = mProviderContext.getContentResolver().openInputStream(uri); 218 try { 219 // First, create a Pop3Message from the attachment and then parse it 220 Pop3Message pop3Message = new Pop3Message( 221 ATTACHMENT_MESSAGE_UID_PREFIX + System.currentTimeMillis(), null); 222 pop3Message.parse(is); 223 // Now, pull out the header fields 224 Message msg = new Message(); 225 LegacyConversions.updateMessageFields(msg, pop3Message, 0, mailbox.mId); 226 // Commit the message to the local store 227 msg.save(mProviderContext); 228 // Setup the rest of the message and mark it completely loaded 229 mLegacyController.copyOneMessageToProvider(pop3Message, msg, 230 Message.FLAG_LOADED_COMPLETE, mProviderContext); 231 // Restore the complete message and return it 232 return Message.restoreMessageWithId(mProviderContext, msg.mId); 233 } catch (MessagingException e) { 234 } catch (IOException e) { 235 } 236 } catch (FileNotFoundException e) { 237 } 238 return null; 239 } 240 241 /** 242 * Enable/disable logging for external sync services 243 * 244 * Generally this should be called by anybody who changes Email.DEBUG 245 */ 246 public void serviceLogging(int debugEnabled) { 247 IEmailService service = ExchangeUtils.getExchangeEmailService(mContext, mServiceCallback); 248 try { 249 service.setLogging(debugEnabled); 250 } catch (RemoteException e) { 251 // TODO Change exception handling to be consistent with however this method 252 // is implemented for other protocols 253 Log.d("updateMailboxList", "RemoteException" + e); 254 } 255 } 256 257 /** 258 * Request a remote update of mailboxes for an account. 259 */ 260 public void updateMailboxList(final long accountId) { 261 Utility.runAsync(new Runnable() { 262 @Override 263 public void run() { 264 final IEmailService service = getServiceForAccount(accountId); 265 if (service != null) { 266 // Service implementation 267 try { 268 service.updateFolderList(accountId); 269 } catch (RemoteException e) { 270 // TODO Change exception handling to be consistent with however this method 271 // is implemented for other protocols 272 Log.d("updateMailboxList", "RemoteException" + e); 273 } 274 } else { 275 // MessagingController implementation 276 mLegacyController.listFolders(accountId, mLegacyListener); 277 } 278 } 279 }); 280 } 281 282 /** 283 * Request a remote update of a mailbox. For use by the timed service. 284 * 285 * Functionally this is quite similar to updateMailbox(), but it's a separate API and 286 * separate callback in order to keep UI callbacks from affecting the service loop. 287 */ 288 public void serviceCheckMail(final long accountId, final long mailboxId, final long tag) { 289 IEmailService service = getServiceForAccount(accountId); 290 if (service != null) { 291 // Service implementation 292// try { 293 // TODO this isn't quite going to work, because we're going to get the 294 // generic (UI) callbacks and not the ones we need to restart the ol' service. 295 // service.startSync(mailboxId, tag); 296 mLegacyListener.checkMailFinished(mContext, accountId, mailboxId, tag); 297// } catch (RemoteException e) { 298 // TODO Change exception handling to be consistent with however this method 299 // is implemented for other protocols 300// Log.d("updateMailbox", "RemoteException" + e); 301// } 302 } else { 303 // MessagingController implementation 304 Utility.runAsync(new Runnable() { 305 public void run() { 306 mLegacyController.checkMail(accountId, tag, mLegacyListener); 307 } 308 }); 309 } 310 } 311 312 /** 313 * Request a remote update of a mailbox. 314 * 315 * The contract here should be to try and update the headers ASAP, in order to populate 316 * a simple message list. We should also at this point queue up a background task of 317 * downloading some/all of the messages in this mailbox, but that should be interruptable. 318 */ 319 public void updateMailbox(final long accountId, final long mailboxId) { 320 321 IEmailService service = getServiceForAccount(accountId); 322 if (service != null) { 323 // Service implementation 324 try { 325 service.startSync(mailboxId); 326 } catch (RemoteException e) { 327 // TODO Change exception handling to be consistent with however this method 328 // is implemented for other protocols 329 Log.d("updateMailbox", "RemoteException" + e); 330 } 331 } else { 332 // MessagingController implementation 333 Utility.runAsync(new Runnable() { 334 public void run() { 335 // TODO shouldn't be passing fully-build accounts & mailboxes into APIs 336 Account account = 337 EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); 338 Mailbox mailbox = 339 EmailContent.Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); 340 if (account == null || mailbox == null) { 341 return; 342 } 343 mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener); 344 } 345 }); 346 } 347 } 348 349 /** 350 * Request that any final work necessary be done, to load a message. 351 * 352 * Note, this assumes that the caller has already checked message.mFlagLoaded and that 353 * additional work is needed. There is no optimization here for a message which is already 354 * loaded. 355 * 356 * @param messageId the message to load 357 * @param callback the Controller callback by which results will be reported 358 */ 359 public void loadMessageForView(final long messageId) { 360 361 // Split here for target type (Service or MessagingController) 362 IEmailService service = getServiceForMessage(messageId); 363 if (service != null) { 364 // There is no service implementation, so we'll just jam the value, log the error, 365 // and get out of here. 366 Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId); 367 ContentValues cv = new ContentValues(); 368 cv.put(MessageColumns.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE); 369 mProviderContext.getContentResolver().update(uri, cv, null, null); 370 Log.d(Email.LOG_TAG, "Unexpected loadMessageForView() for service-based message."); 371 synchronized (mListeners) { 372 for (Result listener : mListeners) { 373 listener.loadMessageForViewCallback(null, messageId, 100); 374 } 375 } 376 } else { 377 // MessagingController implementation 378 Utility.runAsync(new Runnable() { 379 public void run() { 380 mLegacyController.loadMessageForView(messageId, mLegacyListener); 381 } 382 }); 383 } 384 } 385 386 387 /** 388 * Saves the message to a mailbox of given type. 389 * This is a synchronous operation taking place in the same thread as the caller. 390 * Upon return the message.mId is set. 391 * @param message the message (must have the mAccountId set). 392 * @param mailboxType the mailbox type (e.g. Mailbox.TYPE_DRAFTS). 393 */ 394 public void saveToMailbox(final EmailContent.Message message, final int mailboxType) { 395 long accountId = message.mAccountKey; 396 long mailboxId = findOrCreateMailboxOfType(accountId, mailboxType); 397 message.mMailboxKey = mailboxId; 398 message.save(mProviderContext); 399 } 400 401 /** 402 * @param accountId the account id 403 * @param mailboxType the mailbox type (e.g. EmailContent.Mailbox.TYPE_TRASH) 404 * @return the id of the mailbox. The mailbox is created if not existing. 405 * Returns Mailbox.NO_MAILBOX if the accountId or mailboxType are negative. 406 * Does not validate the input in other ways (e.g. does not verify the existence of account). 407 */ 408 public long findOrCreateMailboxOfType(long accountId, int mailboxType) { 409 if (accountId < 0 || mailboxType < 0) { 410 return Mailbox.NO_MAILBOX; 411 } 412 long mailboxId = 413 Mailbox.findMailboxOfType(mProviderContext, accountId, mailboxType); 414 return mailboxId == Mailbox.NO_MAILBOX ? createMailbox(accountId, mailboxType) : mailboxId; 415 } 416 417 /** 418 * Returns the server-side name for a specific mailbox. 419 * 420 * @param mailboxType the mailbox type 421 * @return the resource string corresponding to the mailbox type, empty if not found. 422 */ 423 /* package */ String getMailboxServerName(int mailboxType) { 424 int resId = -1; 425 switch (mailboxType) { 426 case Mailbox.TYPE_INBOX: 427 resId = R.string.mailbox_name_server_inbox; 428 break; 429 case Mailbox.TYPE_OUTBOX: 430 resId = R.string.mailbox_name_server_outbox; 431 break; 432 case Mailbox.TYPE_DRAFTS: 433 resId = R.string.mailbox_name_server_drafts; 434 break; 435 case Mailbox.TYPE_TRASH: 436 resId = R.string.mailbox_name_server_trash; 437 break; 438 case Mailbox.TYPE_SENT: 439 resId = R.string.mailbox_name_server_sent; 440 break; 441 case Mailbox.TYPE_JUNK: 442 resId = R.string.mailbox_name_server_junk; 443 break; 444 } 445 return resId != -1 ? mContext.getString(resId) : ""; 446 } 447 448 /** 449 * Create a mailbox given the account and mailboxType. 450 * TODO: Does this need to be signaled explicitly to the sync engines? 451 */ 452 /* package */ long createMailbox(long accountId, int mailboxType) { 453 if (accountId < 0 || mailboxType < 0) { 454 String mes = "Invalid arguments " + accountId + ' ' + mailboxType; 455 Log.e(Email.LOG_TAG, mes); 456 throw new RuntimeException(mes); 457 } 458 Mailbox box = new Mailbox(); 459 box.mAccountKey = accountId; 460 box.mType = mailboxType; 461 box.mSyncInterval = EmailContent.Account.CHECK_INTERVAL_NEVER; 462 box.mFlagVisible = true; 463 box.mDisplayName = getMailboxServerName(mailboxType); 464 box.save(mProviderContext); 465 return box.mId; 466 } 467 468 /** 469 * Send a message: 470 * - move the message to Outbox (the message is assumed to be in Drafts). 471 * - EAS service will take it from there 472 * - trigger send for POP/IMAP 473 * @param messageId the id of the message to send 474 */ 475 public void sendMessage(long messageId, long accountId) { 476 ContentResolver resolver = mProviderContext.getContentResolver(); 477 if (accountId == -1) { 478 accountId = lookupAccountForMessage(messageId); 479 } 480 if (accountId == -1) { 481 // probably the message was not found 482 if (Email.LOGD) { 483 Email.log("no account found for message " + messageId); 484 } 485 return; 486 } 487 488 // Move to Outbox 489 long outboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_OUTBOX); 490 ContentValues cv = new ContentValues(); 491 cv.put(EmailContent.MessageColumns.MAILBOX_KEY, outboxId); 492 493 // does this need to be SYNCED_CONTENT_URI instead? 494 Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId); 495 resolver.update(uri, cv, null, null); 496 497 // Split here for target type (Service or MessagingController) 498 IEmailService service = getServiceForMessage(messageId); 499 if (service != null) { 500 // We just need to be sure the callback is installed, if this is the first call 501 // to the service. 502 try { 503 service.setCallback(mServiceCallback); 504 } catch (RemoteException re) { 505 // OK - not a critical callback here 506 } 507 } else { 508 // for IMAP & POP only, (attempt to) send the message now 509 final EmailContent.Account account = 510 EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); 511 if (account == null) { 512 return; 513 } 514 final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); 515 Utility.runAsync(new Runnable() { 516 public void run() { 517 mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); 518 } 519 }); 520 } 521 } 522 523 /** 524 * Try to send all pending messages for a given account 525 * 526 * @param accountId the account for which to send messages (-1 for all accounts) 527 * @param callback 528 */ 529 public void sendPendingMessages(long accountId) { 530 // 1. make sure we even have an outbox, exit early if not 531 final long outboxId = 532 Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_OUTBOX); 533 if (outboxId == Mailbox.NO_MAILBOX) { 534 return; 535 } 536 537 // 2. dispatch as necessary 538 IEmailService service = getServiceForAccount(accountId); 539 if (service != null) { 540 // Service implementation 541 try { 542 service.startSync(outboxId); 543 } catch (RemoteException e) { 544 // TODO Change exception handling to be consistent with however this method 545 // is implemented for other protocols 546 Log.d("updateMailbox", "RemoteException" + e); 547 } 548 } else { 549 // MessagingController implementation 550 final EmailContent.Account account = 551 EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); 552 if (account == null) { 553 return; 554 } 555 final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); 556 Utility.runAsync(new Runnable() { 557 public void run() { 558 mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); 559 } 560 }); 561 } 562 } 563 564 /** 565 * Call {@link #sendPendingMessages} for all accounts. 566 */ 567 public void sendPendingMessagesForAllAccounts(final Context context) { 568 Utility.runAsync(new Runnable() { 569 public void run() { 570 Cursor c = context.getContentResolver().query(Account.CONTENT_URI, 571 Account.ID_PROJECTION, null, null, null); 572 try { 573 while (c.moveToNext()) { 574 long accountId = c.getLong(Account.ID_PROJECTION_COLUMN); 575 sendPendingMessages(accountId); 576 } 577 } finally { 578 c.close(); 579 } 580 } 581 }); 582 } 583 584 /** 585 * Reset visible limits for all accounts. 586 * For each account: 587 * look up limit 588 * write limit into all mailboxes for that account 589 */ 590 public void resetVisibleLimits() { 591 Utility.runAsync(new Runnable() { 592 public void run() { 593 ContentResolver resolver = mProviderContext.getContentResolver(); 594 Cursor c = null; 595 try { 596 c = resolver.query( 597 Account.CONTENT_URI, 598 Account.ID_PROJECTION, 599 null, null, null); 600 while (c.moveToNext()) { 601 long accountId = c.getLong(Account.ID_PROJECTION_COLUMN); 602 Account account = Account.restoreAccountWithId(mProviderContext, accountId); 603 if (account != null) { 604 Store.StoreInfo info = Store.StoreInfo.getStoreInfo( 605 account.getStoreUri(mProviderContext), mContext); 606 if (info != null && info.mVisibleLimitDefault > 0) { 607 int limit = info.mVisibleLimitDefault; 608 ContentValues cv = new ContentValues(); 609 cv.put(MailboxColumns.VISIBLE_LIMIT, limit); 610 resolver.update(Mailbox.CONTENT_URI, cv, 611 MailboxColumns.ACCOUNT_KEY + "=?", 612 new String[] { Long.toString(accountId) }); 613 } 614 } 615 } 616 } finally { 617 if (c != null) { 618 c.close(); 619 } 620 } 621 } 622 }); 623 } 624 625 /** 626 * Increase the load count for a given mailbox, and trigger a refresh. Applies only to 627 * IMAP and POP. 628 * 629 * @param mailboxId the mailbox 630 * @param callback 631 */ 632 public void loadMoreMessages(final long mailboxId) { 633 Utility.runAsync(new Runnable() { 634 public void run() { 635 Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); 636 if (mailbox == null) { 637 return; 638 } 639 Account account = Account.restoreAccountWithId(mProviderContext, 640 mailbox.mAccountKey); 641 if (account == null) { 642 return; 643 } 644 Store.StoreInfo info = Store.StoreInfo.getStoreInfo( 645 account.getStoreUri(mProviderContext), mContext); 646 if (info != null && info.mVisibleLimitIncrement > 0) { 647 // Use provider math to increment the field 648 ContentValues cv = new ContentValues();; 649 cv.put(EmailContent.FIELD_COLUMN_NAME, MailboxColumns.VISIBLE_LIMIT); 650 cv.put(EmailContent.ADD_COLUMN_NAME, info.mVisibleLimitIncrement); 651 Uri uri = ContentUris.withAppendedId(Mailbox.ADD_TO_FIELD_URI, mailboxId); 652 mProviderContext.getContentResolver().update(uri, cv, null, null); 653 // Trigger a refresh using the new, longer limit 654 mailbox.mVisibleLimit += info.mVisibleLimitIncrement; 655 mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener); 656 } 657 } 658 }); 659 } 660 661 /** 662 * @param messageId the id of message 663 * @return the accountId corresponding to the given messageId, or -1 if not found. 664 */ 665 private long lookupAccountForMessage(long messageId) { 666 ContentResolver resolver = mProviderContext.getContentResolver(); 667 Cursor c = resolver.query(EmailContent.Message.CONTENT_URI, 668 MESSAGEID_TO_ACCOUNTID_PROJECTION, EmailContent.RECORD_ID + "=?", 669 new String[] { Long.toString(messageId) }, null); 670 try { 671 return c.moveToFirst() 672 ? c.getLong(MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID) 673 : -1; 674 } finally { 675 c.close(); 676 } 677 } 678 679 /** 680 * Delete a single attachment entry from the DB given its id. 681 * Does not delete any eventual associated files. 682 */ 683 public void deleteAttachment(long attachmentId) { 684 ContentResolver resolver = mProviderContext.getContentResolver(); 685 Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId); 686 resolver.delete(uri, null, null); 687 } 688 689 /** 690 * Delete a single message by moving it to the trash, or deleting it from the trash 691 * 692 * This function has no callback, no result reporting, because the desired outcome 693 * is reflected entirely by changes to one or more cursors. 694 * 695 * @param messageId The id of the message to "delete". 696 * @param accountId The id of the message's account, or -1 if not known by caller 697 * 698 * TODO: Move out of UI thread 699 * TODO: "get account a for message m" should be a utility 700 * TODO: "get mailbox of type n for account a" should be a utility 701 */ 702 public void deleteMessage(long messageId, long accountId) { 703 ContentResolver resolver = mProviderContext.getContentResolver(); 704 705 // 1. Look up acct# for message we're deleting 706 if (accountId == -1) { 707 accountId = lookupAccountForMessage(messageId); 708 } 709 if (accountId == -1) { 710 return; 711 } 712 713 // 2. Confirm that there is a trash mailbox available. If not, create one 714 long trashMailboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_TRASH); 715 716 // 3. Are we moving to trash or deleting? It depends on where the message currently sits. 717 long sourceMailboxId = -1; 718 Cursor c = resolver.query(EmailContent.Message.CONTENT_URI, 719 MESSAGEID_TO_MAILBOXID_PROJECTION, EmailContent.RECORD_ID + "=?", 720 new String[] { Long.toString(messageId) }, null); 721 try { 722 sourceMailboxId = c.moveToFirst() 723 ? c.getLong(MESSAGEID_TO_MAILBOXID_COLUMN_MAILBOXID) 724 : -1; 725 } finally { 726 c.close(); 727 } 728 729 // 4. Drop non-essential data for the message (e.g. attachment files) 730 AttachmentProvider.deleteAllAttachmentFiles(mProviderContext, accountId, messageId); 731 732 Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); 733 734 // 5. Perform "delete" as appropriate 735 if (sourceMailboxId == trashMailboxId) { 736 // 5a. Delete from trash 737 resolver.delete(uri, null, null); 738 } else { 739 // 5b. Move to trash 740 ContentValues cv = new ContentValues(); 741 cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId); 742 resolver.update(uri, cv, null, null); 743 } 744 745 // 6. Service runs automatically, MessagingController needs a kick 746 Account account = Account.restoreAccountWithId(mProviderContext, accountId); 747 if (account == null) { 748 return; // isMessagingController returns false for null, but let's make it clear. 749 } 750 if (isMessagingController(account)) { 751 final long syncAccountId = accountId; 752 Utility.runAsync(new Runnable() { 753 public void run() { 754 mLegacyController.processPendingActions(syncAccountId); 755 } 756 }); 757 } 758 } 759 760 /** 761 * Set/clear the unread status of a message 762 * 763 * TODO db ops should not be in this thread. queue it up. 764 * 765 * @param messageId the message to update 766 * @param isRead the new value for the isRead flag 767 */ 768 public void setMessageRead(final long messageId, boolean isRead) { 769 ContentValues cv = new ContentValues(); 770 cv.put(EmailContent.MessageColumns.FLAG_READ, isRead); 771 Uri uri = ContentUris.withAppendedId( 772 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 773 mProviderContext.getContentResolver().update(uri, cv, null, null); 774 775 // Service runs automatically, MessagingController needs a kick 776 final Message message = Message.restoreMessageWithId(mProviderContext, messageId); 777 if (message == null) { 778 return; 779 } 780 Account account = Account.restoreAccountWithId(mProviderContext, message.mAccountKey); 781 if (account == null) { 782 return; // isMessagingController returns false for null, but let's make it clear. 783 } 784 if (isMessagingController(account)) { 785 Utility.runAsync(new Runnable() { 786 public void run() { 787 mLegacyController.processPendingActions(message.mAccountKey); 788 } 789 }); 790 } 791 } 792 793 /** 794 * Set/clear the favorite status of a message 795 * 796 * TODO db ops should not be in this thread. queue it up. 797 * 798 * @param messageId the message to update 799 * @param isFavorite the new value for the isFavorite flag 800 */ 801 public void setMessageFavorite(final long messageId, boolean isFavorite) { 802 ContentValues cv = new ContentValues(); 803 cv.put(EmailContent.MessageColumns.FLAG_FAVORITE, isFavorite); 804 Uri uri = ContentUris.withAppendedId( 805 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 806 mProviderContext.getContentResolver().update(uri, cv, null, null); 807 808 // Service runs automatically, MessagingController needs a kick 809 final Message message = Message.restoreMessageWithId(mProviderContext, messageId); 810 if (message == null) { 811 return; 812 } 813 Account account = Account.restoreAccountWithId(mProviderContext, message.mAccountKey); 814 if (account == null) { 815 return; // isMessagingController returns false for null, but let's make it clear. 816 } 817 if (isMessagingController(account)) { 818 Utility.runAsync(new Runnable() { 819 public void run() { 820 mLegacyController.processPendingActions(message.mAccountKey); 821 } 822 }); 823 } 824 } 825 826 /** 827 * Respond to a meeting invitation. 828 * 829 * @param messageId the id of the invitation being responded to 830 * @param response the code representing the response to the invitation 831 */ 832 public void sendMeetingResponse(final long messageId, final int response) { 833 // Split here for target type (Service or MessagingController) 834 IEmailService service = getServiceForMessage(messageId); 835 if (service != null) { 836 // Service implementation 837 try { 838 service.sendMeetingResponse(messageId, response); 839 } catch (RemoteException e) { 840 // TODO Change exception handling to be consistent with however this method 841 // is implemented for other protocols 842 Log.e("onDownloadAttachment", "RemoteException", e); 843 } 844 } 845 } 846 847 /** 848 * Request that an attachment be loaded. It will be stored at a location controlled 849 * by the AttachmentProvider. 850 * 851 * @param attachmentId the attachment to load 852 * @param messageId the owner message 853 * @param mailboxId the owner mailbox 854 * @param accountId the owner account 855 */ 856 public void loadAttachment(final long attachmentId, final long messageId, final long mailboxId, 857 final long accountId) { 858 859 File saveToFile = AttachmentProvider.getAttachmentFilename(mProviderContext, 860 accountId, attachmentId); 861 Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId); 862 863 if (saveToFile.exists() && attachInfo.mContentUri != null) { 864 // The attachment has already been downloaded, so we will just "pretend" to download it 865 synchronized (mListeners) { 866 for (Result listener : mListeners) { 867 listener.loadAttachmentCallback(null, messageId, attachmentId, 0); 868 } 869 for (Result listener : mListeners) { 870 listener.loadAttachmentCallback(null, messageId, attachmentId, 100); 871 } 872 } 873 return; 874 } 875 876 // Split here for target type (Service or MessagingController) 877 IEmailService service = getServiceForMessage(messageId); 878 if (service != null) { 879 // Service implementation 880 try { 881 service.loadAttachment(attachInfo.mId, saveToFile.getAbsolutePath(), 882 AttachmentProvider.getAttachmentUri(accountId, attachmentId).toString()); 883 } catch (RemoteException e) { 884 // TODO Change exception handling to be consistent with however this method 885 // is implemented for other protocols 886 Log.e("onDownloadAttachment", "RemoteException", e); 887 } 888 } else { 889 // MessagingController implementation 890 Utility.runAsync(new Runnable() { 891 public void run() { 892 mLegacyController.loadAttachment(accountId, messageId, mailboxId, attachmentId, 893 mLegacyListener); 894 } 895 }); 896 } 897 } 898 899 /** 900 * For a given message id, return a service proxy if applicable, or null. 901 * 902 * @param messageId the message of interest 903 * @result service proxy, or null if n/a 904 */ 905 private IEmailService getServiceForMessage(long messageId) { 906 // TODO make this more efficient, caching the account, smaller lookup here, etc. 907 Message message = Message.restoreMessageWithId(mProviderContext, messageId); 908 if (message == null) { 909 return null; 910 } 911 return getServiceForAccount(message.mAccountKey); 912 } 913 914 /** 915 * For a given account id, return a service proxy if applicable, or null. 916 * 917 * @param accountId the message of interest 918 * @result service proxy, or null if n/a 919 */ 920 private IEmailService getServiceForAccount(long accountId) { 921 if (isMessagingController(accountId)) return null; 922 return getExchangeEmailService(); 923 } 924 925 private IEmailService getExchangeEmailService() { 926 return ExchangeUtils.getExchangeEmailService(mContext, mServiceCallback); 927 } 928 929 /** 930 * Simple helper to determine if legacy MessagingController should be used 931 */ 932 public boolean isMessagingController(EmailContent.Account account) { 933 if (account == null) return false; 934 return isMessagingController(account.mId); 935 } 936 937 public boolean isMessagingController(long accountId) { 938 Boolean isLegacyController = mLegacyControllerMap.get(accountId); 939 if (isLegacyController == null) { 940 String protocol = Account.getProtocol(mProviderContext, accountId); 941 isLegacyController = ("pop3".equals(protocol) || "imap".equals(protocol)); 942 mLegacyControllerMap.put(accountId, isLegacyController); 943 } 944 return isLegacyController; 945 } 946 947 /** 948 * Delete an account. 949 */ 950 public void deleteAccount(final long accountId) { 951 Utility.runAsync(new Runnable() { 952 public void run() { 953 deleteAccountSync(accountId); 954 } 955 }); 956 } 957 958 /** 959 * Delete an account synchronously. Intended to be used only by unit tests. 960 */ 961 public void deleteAccountSync(long accountId) { 962 try { 963 mLegacyControllerMap.remove(accountId); 964 // Get the account URI. 965 final Account account = Account.restoreAccountWithId(mContext, accountId); 966 if (account == null) { 967 return; // Already deleted? 968 } 969 970 final String accountUri = account.getStoreUri(mContext); 971 // Delete Remote store at first. 972 if (!TextUtils.isEmpty(accountUri)) { 973 Store.getInstance(accountUri, mContext, null).delete(); 974 // Remove the Store instance from cache. 975 Store.removeInstance(accountUri); 976 } 977 978 Uri uri = ContentUris.withAppendedId( 979 EmailContent.Account.CONTENT_URI, accountId); 980 mContext.getContentResolver().delete(uri, null, null); 981 982 // Update the backup (side copy) of the accounts 983 AccountBackupRestore.backupAccounts(mContext); 984 985 // Release or relax device administration, if relevant 986 SecurityPolicy.getInstance(mContext).reducePolicies(); 987 988 Email.setServicesEnabled(mContext); 989 } catch (Exception e) { 990 Log.w(Email.LOG_TAG, "Exception while deleting account", e); 991 } finally { 992 synchronized (mListeners) { 993 for (Result l : mListeners) { 994 l.deleteAccountCallback(accountId); 995 } 996 } 997 } 998 } 999 1000 /** 1001 * Simple callback for synchronous commands. For many commands, this can be largely ignored 1002 * and the result is observed via provider cursors. The callback will *not* necessarily be 1003 * made from the UI thread, so you may need further handlers to safely make UI updates. 1004 */ 1005 public static abstract class Result { 1006 /** 1007 * Callback for updateMailboxList 1008 * 1009 * @param result If null, the operation completed without error 1010 * @param accountId The account being operated on 1011 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 1012 */ 1013 public void updateMailboxListCallback(MessagingException result, long accountId, 1014 int progress) { 1015 } 1016 1017 /** 1018 * Callback for updateMailbox. Note: This looks a lot like checkMailCallback, but 1019 * it's a separate call used only by UI's, so we can keep things separate. 1020 * 1021 * @param result If null, the operation completed without error 1022 * @param accountId The account being operated on 1023 * @param mailboxId The mailbox being operated on 1024 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 1025 * @param numNewMessages the number of new messages delivered 1026 */ 1027 public void updateMailboxCallback(MessagingException result, long accountId, 1028 long mailboxId, int progress, int numNewMessages) { 1029 } 1030 1031 /** 1032 * Callback for loadMessageForView 1033 * 1034 * @param result if null, the attachment completed - if non-null, terminating with failure 1035 * @param messageId the message which contains the attachment 1036 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 1037 */ 1038 public void loadMessageForViewCallback(MessagingException result, long messageId, 1039 int progress) { 1040 } 1041 1042 /** 1043 * Callback for loadAttachment 1044 * 1045 * @param result if null, the attachment completed - if non-null, terminating with failure 1046 * @param messageId the message which contains the attachment 1047 * @param attachmentId the attachment being loaded 1048 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 1049 */ 1050 public void loadAttachmentCallback(MessagingException result, long messageId, 1051 long attachmentId, int progress) { 1052 } 1053 1054 /** 1055 * Callback for checkmail. Note: This looks a lot like updateMailboxCallback, but 1056 * it's a separate call used only by the automatic checker service, so we can keep 1057 * things separate. 1058 * 1059 * @param result If null, the operation completed without error 1060 * @param accountId The account being operated on 1061 * @param mailboxId The mailbox being operated on (may be unknown at start) 1062 * @param progress 0 for "starting", no updates, 100 for complete 1063 * @param tag the same tag that was passed to serviceCheckMail() 1064 */ 1065 public void serviceCheckMailCallback(MessagingException result, long accountId, 1066 long mailboxId, int progress, long tag) { 1067 } 1068 1069 /** 1070 * Callback for sending pending messages. This will be called once to start the 1071 * group, multiple times for messages, and once to complete the group. 1072 * 1073 * @param result If null, the operation completed without error 1074 * @param accountId The account being operated on 1075 * @param messageId The being sent (may be unknown at start) 1076 * @param progress 0 for "starting", 100 for complete 1077 */ 1078 public void sendMailCallback(MessagingException result, long accountId, 1079 long messageId, int progress) { 1080 } 1081 1082 /** 1083 * Callback from {@link Controller#deleteAccount}. 1084 */ 1085 public void deleteAccountCallback(long accountId) { 1086 } 1087 } 1088 1089 /** 1090 * Support for receiving callbacks from MessagingController and dealing with UI going 1091 * out of scope. 1092 */ 1093 private class LegacyListener extends MessagingListener { 1094 1095 @Override 1096 public void listFoldersStarted(long accountId) { 1097 synchronized (mListeners) { 1098 for (Result l : mListeners) { 1099 l.updateMailboxListCallback(null, accountId, 0); 1100 } 1101 } 1102 } 1103 1104 @Override 1105 public void listFoldersFailed(long accountId, String message) { 1106 synchronized (mListeners) { 1107 for (Result l : mListeners) { 1108 l.updateMailboxListCallback(new MessagingException(message), accountId, 0); 1109 } 1110 } 1111 } 1112 1113 @Override 1114 public void listFoldersFinished(long accountId) { 1115 synchronized (mListeners) { 1116 for (Result l : mListeners) { 1117 l.updateMailboxListCallback(null, accountId, 100); 1118 } 1119 } 1120 } 1121 1122 @Override 1123 public void synchronizeMailboxStarted(long accountId, long mailboxId) { 1124 synchronized (mListeners) { 1125 for (Result l : mListeners) { 1126 l.updateMailboxCallback(null, accountId, mailboxId, 0, 0); 1127 } 1128 } 1129 } 1130 1131 @Override 1132 public void synchronizeMailboxFinished(long accountId, long mailboxId, 1133 int totalMessagesInMailbox, int numNewMessages) { 1134 synchronized (mListeners) { 1135 for (Result l : mListeners) { 1136 l.updateMailboxCallback(null, accountId, mailboxId, 100, numNewMessages); 1137 } 1138 } 1139 } 1140 1141 @Override 1142 public void synchronizeMailboxFailed(long accountId, long mailboxId, Exception e) { 1143 MessagingException me; 1144 if (e instanceof MessagingException) { 1145 me = (MessagingException) e; 1146 } else { 1147 me = new MessagingException(e.toString()); 1148 } 1149 synchronized (mListeners) { 1150 for (Result l : mListeners) { 1151 l.updateMailboxCallback(me, accountId, mailboxId, 0, 0); 1152 } 1153 } 1154 } 1155 1156 @Override 1157 public void checkMailStarted(Context context, long accountId, long tag) { 1158 synchronized (mListeners) { 1159 for (Result l : mListeners) { 1160 l.serviceCheckMailCallback(null, accountId, -1, 0, tag); 1161 } 1162 } 1163 } 1164 1165 @Override 1166 public void checkMailFinished(Context context, long accountId, long folderId, long tag) { 1167 synchronized (mListeners) { 1168 for (Result l : mListeners) { 1169 l.serviceCheckMailCallback(null, accountId, folderId, 100, tag); 1170 } 1171 } 1172 } 1173 1174 @Override 1175 public void loadMessageForViewStarted(long messageId) { 1176 synchronized (mListeners) { 1177 for (Result listener : mListeners) { 1178 listener.loadMessageForViewCallback(null, messageId, 0); 1179 } 1180 } 1181 } 1182 1183 @Override 1184 public void loadMessageForViewFinished(long messageId) { 1185 synchronized (mListeners) { 1186 for (Result listener : mListeners) { 1187 listener.loadMessageForViewCallback(null, messageId, 100); 1188 } 1189 } 1190 } 1191 1192 @Override 1193 public void loadMessageForViewFailed(long messageId, String message) { 1194 synchronized (mListeners) { 1195 for (Result listener : mListeners) { 1196 listener.loadMessageForViewCallback(new MessagingException(message), 1197 messageId, 0); 1198 } 1199 } 1200 } 1201 1202 @Override 1203 public void loadAttachmentStarted(long accountId, long messageId, long attachmentId, 1204 boolean requiresDownload) { 1205 synchronized (mListeners) { 1206 for (Result listener : mListeners) { 1207 listener.loadAttachmentCallback(null, messageId, attachmentId, 0); 1208 } 1209 } 1210 } 1211 1212 @Override 1213 public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) { 1214 synchronized (mListeners) { 1215 for (Result listener : mListeners) { 1216 listener.loadAttachmentCallback(null, messageId, attachmentId, 100); 1217 } 1218 } 1219 } 1220 1221 @Override 1222 public void loadAttachmentFailed(long accountId, long messageId, long attachmentId, 1223 String reason) { 1224 synchronized (mListeners) { 1225 for (Result listener : mListeners) { 1226 listener.loadAttachmentCallback(new MessagingException(reason), 1227 messageId, attachmentId, 0); 1228 } 1229 } 1230 } 1231 1232 @Override 1233 synchronized public void sendPendingMessagesStarted(long accountId, long messageId) { 1234 synchronized (mListeners) { 1235 for (Result listener : mListeners) { 1236 listener.sendMailCallback(null, accountId, messageId, 0); 1237 } 1238 } 1239 } 1240 1241 @Override 1242 synchronized public void sendPendingMessagesCompleted(long accountId) { 1243 synchronized (mListeners) { 1244 for (Result listener : mListeners) { 1245 listener.sendMailCallback(null, accountId, -1, 100); 1246 } 1247 } 1248 } 1249 1250 @Override 1251 synchronized public void sendPendingMessagesFailed(long accountId, long messageId, 1252 Exception reason) { 1253 MessagingException me; 1254 if (reason instanceof MessagingException) { 1255 me = (MessagingException) reason; 1256 } else { 1257 me = new MessagingException(reason.toString()); 1258 } 1259 synchronized (mListeners) { 1260 for (Result listener : mListeners) { 1261 listener.sendMailCallback(me, accountId, messageId, 0); 1262 } 1263 } 1264 } 1265 } 1266 1267 /** 1268 * Service callback for service operations 1269 */ 1270 private class ServiceCallback extends IEmailServiceCallback.Stub { 1271 1272 private final static boolean DEBUG_FAIL_DOWNLOADS = false; // do not check in "true" 1273 1274 public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 1275 int progress) { 1276 MessagingException result = mapStatusToException(statusCode); 1277 switch (statusCode) { 1278 case EmailServiceStatus.SUCCESS: 1279 progress = 100; 1280 break; 1281 case EmailServiceStatus.IN_PROGRESS: 1282 if (DEBUG_FAIL_DOWNLOADS && progress > 75) { 1283 result = new MessagingException( 1284 String.valueOf(EmailServiceStatus.CONNECTION_ERROR)); 1285 } 1286 // discard progress reports that look like sentinels 1287 if (progress < 0 || progress >= 100) { 1288 return; 1289 } 1290 break; 1291 } 1292 synchronized (mListeners) { 1293 for (Result listener : mListeners) { 1294 listener.loadAttachmentCallback(result, messageId, attachmentId, progress); 1295 } 1296 } 1297 } 1298 1299 /** 1300 * Note, this is an incomplete implementation of this callback, because we are 1301 * not getting things back from Service in quite the same way as from MessagingController. 1302 * However, this is sufficient for basic "progress=100" notification that message send 1303 * has just completed. 1304 */ 1305 public void sendMessageStatus(long messageId, String subject, int statusCode, 1306 int progress) { 1307// Log.d(Email.LOG_TAG, "sendMessageStatus: messageId=" + messageId 1308// + " statusCode=" + statusCode + " progress=" + progress); 1309// Log.d(Email.LOG_TAG, "sendMessageStatus: subject=" + subject); 1310 long accountId = -1; // This should be in the callback 1311 MessagingException result = mapStatusToException(statusCode); 1312 switch (statusCode) { 1313 case EmailServiceStatus.SUCCESS: 1314 progress = 100; 1315 break; 1316 case EmailServiceStatus.IN_PROGRESS: 1317 // discard progress reports that look like sentinels 1318 if (progress < 0 || progress >= 100) { 1319 return; 1320 } 1321 break; 1322 } 1323// Log.d(Email.LOG_TAG, "result=" + result + " messageId=" + messageId 1324// + " progress=" + progress); 1325 synchronized(mListeners) { 1326 for (Result listener : mListeners) { 1327 listener.sendMailCallback(result, accountId, messageId, progress); 1328 } 1329 } 1330 } 1331 1332 public void syncMailboxListStatus(long accountId, int statusCode, int progress) { 1333 MessagingException result = mapStatusToException(statusCode); 1334 switch (statusCode) { 1335 case EmailServiceStatus.SUCCESS: 1336 progress = 100; 1337 break; 1338 case EmailServiceStatus.IN_PROGRESS: 1339 // discard progress reports that look like sentinels 1340 if (progress < 0 || progress >= 100) { 1341 return; 1342 } 1343 break; 1344 } 1345 synchronized(mListeners) { 1346 for (Result listener : mListeners) { 1347 listener.updateMailboxListCallback(result, accountId, progress); 1348 } 1349 } 1350 } 1351 1352 public void syncMailboxStatus(long mailboxId, int statusCode, int progress) { 1353 MessagingException result = mapStatusToException(statusCode); 1354 switch (statusCode) { 1355 case EmailServiceStatus.SUCCESS: 1356 progress = 100; 1357 break; 1358 case EmailServiceStatus.IN_PROGRESS: 1359 // discard progress reports that look like sentinels 1360 if (progress < 0 || progress >= 100) { 1361 return; 1362 } 1363 break; 1364 } 1365 // TODO where do we get "number of new messages" as well? 1366 // TODO should pass this back instead of looking it up here 1367 // TODO smaller projection 1368 Mailbox mbx = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); 1369 // The mailbox could have disappeared if the server commanded it 1370 if (mbx == null) return; 1371 long accountId = mbx.mAccountKey; 1372 synchronized(mListeners) { 1373 for (Result listener : mListeners) { 1374 listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0); 1375 } 1376 } 1377 } 1378 1379 private MessagingException mapStatusToException(int statusCode) { 1380 switch (statusCode) { 1381 case EmailServiceStatus.SUCCESS: 1382 case EmailServiceStatus.IN_PROGRESS: 1383 return null; 1384 1385 case EmailServiceStatus.LOGIN_FAILED: 1386 return new AuthenticationFailedException(""); 1387 1388 case EmailServiceStatus.CONNECTION_ERROR: 1389 return new MessagingException(MessagingException.IOERROR); 1390 1391 case EmailServiceStatus.SECURITY_FAILURE: 1392 return new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED); 1393 1394 case EmailServiceStatus.MESSAGE_NOT_FOUND: 1395 case EmailServiceStatus.ATTACHMENT_NOT_FOUND: 1396 case EmailServiceStatus.FOLDER_NOT_DELETED: 1397 case EmailServiceStatus.FOLDER_NOT_RENAMED: 1398 case EmailServiceStatus.FOLDER_NOT_CREATED: 1399 case EmailServiceStatus.REMOTE_EXCEPTION: 1400 // TODO: define exception code(s) & UI string(s) for server-side errors 1401 default: 1402 return new MessagingException(String.valueOf(statusCode)); 1403 } 1404 } 1405 } 1406} 1407