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