Controller.java revision 0d1078363581db8caded06cf94e729e88a88761a
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.MessagingException; 20import com.android.email.mail.Store; 21import com.android.email.provider.AttachmentProvider; 22import com.android.email.provider.EmailContent; 23import com.android.email.provider.EmailContent.Account; 24import com.android.email.provider.EmailContent.Attachment; 25import com.android.email.provider.EmailContent.Mailbox; 26import com.android.email.provider.EmailContent.Message; 27import com.android.email.service.EmailServiceProxy; 28import com.android.exchange.EmailServiceStatus; 29import com.android.exchange.IEmailService; 30import com.android.exchange.IEmailServiceCallback; 31import com.android.exchange.SyncManager; 32 33import android.content.ContentResolver; 34import android.content.ContentUris; 35import android.content.ContentValues; 36import android.content.Context; 37import android.database.Cursor; 38import android.net.Uri; 39import android.os.RemoteException; 40import android.util.Log; 41 42import java.io.File; 43import java.util.HashSet; 44 45/** 46 * New central controller/dispatcher for Email activities that may require remote operations. 47 * Handles disambiguating between legacy MessagingController operations and newer provider/sync 48 * based code. 49 */ 50public class Controller { 51 52 static Controller sInstance; 53 private Context mContext; 54 private Context mProviderContext; 55 private MessagingController mLegacyController; 56 private LegacyListener mLegacyListener = new LegacyListener(); 57 private ServiceCallback mServiceCallback = new ServiceCallback(); 58 private HashSet<Result> mListeners = new HashSet<Result>(); 59 60 private static String[] MESSAGEID_TO_ACCOUNTID_PROJECTION = new String[] { 61 EmailContent.RECORD_ID, 62 EmailContent.MessageColumns.ACCOUNT_KEY 63 }; 64 private static int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1; 65 66 protected Controller(Context _context) { 67 mContext = _context; 68 mProviderContext = _context; 69 mLegacyController = MessagingController.getInstance(mContext); 70 mLegacyController.addListener(mLegacyListener); 71 } 72 73 /** 74 * Gets or creates the singleton instance of Controller. 75 * @param _context The context that will be used for all underlying system access 76 */ 77 public synchronized static Controller getInstance(Context _context) { 78 if (sInstance == null) { 79 sInstance = new Controller(_context); 80 } 81 return sInstance; 82 } 83 84 /** 85 * For testing only: Inject a different context for provider access. This will be 86 * used internally for access the underlying provider (e.g. getContentResolver().query()). 87 * @param providerContext the provider context to be used by this instance 88 */ 89 public void setProviderContext(Context providerContext) { 90 mProviderContext = providerContext; 91 } 92 93 /** 94 * Any UI code that wishes for callback results (on async ops) should register their callback 95 * here (typically from onResume()). Unregistered callbacks will never be called, to prevent 96 * problems when the command completes and the activity has already paused or finished. 97 * @param listener The callback that may be used in action methods 98 */ 99 public void addResultCallback(Result listener) { 100 synchronized (mListeners) { 101 mListeners.add(listener); 102 } 103 } 104 105 /** 106 * Any UI code that no longer wishes for callback results (on async ops) should unregister 107 * their callback here (typically from onPause()). Unregistered callbacks will never be called, 108 * to prevent problems when the command completes and the activity has already paused or 109 * finished. 110 * @param listener The callback that may no longer be used 111 */ 112 public void removeResultCallback(Result listener) { 113 synchronized (mListeners) { 114 mListeners.remove(listener); 115 } 116 } 117 118 private boolean isActiveResultCallback(Result listener) { 119 synchronized (mListeners) { 120 return mListeners.contains(listener); 121 } 122 } 123 124 /** 125 * Enable/disable logging for external sync services 126 * 127 * Generally this should be called by anybody who changes Email.DEBUG 128 */ 129 public void serviceLogging(int debugEnabled) { 130 IEmailService service = 131 new EmailServiceProxy(mContext, SyncManager.class, mServiceCallback); 132 try { 133 service.setLogging(debugEnabled); 134 } catch (RemoteException e) { 135 // TODO Change exception handling to be consistent with however this method 136 // is implemented for other protocols 137 Log.d("updateMailboxList", "RemoteException" + e); 138 } 139 } 140 141 /** 142 * Request a remote update of mailboxes for an account. 143 * 144 * TODO: Clean up threading in MessagingController cases (or perhaps here in Controller) 145 */ 146 public void updateMailboxList(final long accountId, final Result callback) { 147 148 IEmailService service = getServiceForAccount(accountId); 149 if (service != null) { 150 // Service implementation 151 try { 152 service.updateFolderList(accountId); 153 } catch (RemoteException e) { 154 // TODO Change exception handling to be consistent with however this method 155 // is implemented for other protocols 156 Log.d("updateMailboxList", "RemoteException" + e); 157 } 158 } else { 159 // MessagingController implementation 160 new Thread() { 161 @Override 162 public void run() { 163 mLegacyController.listFolders(accountId, mLegacyListener); 164 } 165 }.start(); 166 } 167 } 168 169 /** 170 * Request a remote update of a mailbox. For use by the timed service. 171 * 172 * Functionally this is quite similar to updateMailbox(), but it's a separate API and 173 * separate callback in order to keep UI callbacks from affecting the service loop. 174 */ 175 public void serviceCheckMail(final long accountId, final long mailboxId, final long tag, 176 final Result callback) { 177 IEmailService service = getServiceForAccount(accountId); 178 if (service != null) { 179 // Service implementation 180// try { 181 // TODO this isn't quite going to work, because we're going to get the 182 // generic (UI) callbacks and not the ones we need to restart the ol' service. 183 // service.startSync(mailboxId, tag); 184 callback.serviceCheckMailCallback(null, accountId, mailboxId, 100, tag); 185// } catch (RemoteException e) { 186 // TODO Change exception handling to be consistent with however this method 187 // is implemented for other protocols 188// Log.d("updateMailbox", "RemoteException" + e); 189// } 190 } else { 191 // MessagingController implementation 192 new Thread() { 193 @Override 194 public void run() { 195 mLegacyController.checkMail(accountId, tag, mLegacyListener); 196 } 197 }.start(); 198 } 199 } 200 201 /** 202 * Request a remote update of a mailbox. 203 * 204 * The contract here should be to try and update the headers ASAP, in order to populate 205 * a simple message list. We should also at this point queue up a background task of 206 * downloading some/all of the messages in this mailbox, but that should be interruptable. 207 */ 208 public void updateMailbox(final long accountId, final long mailboxId, final Result callback) { 209 210 IEmailService service = getServiceForAccount(accountId); 211 if (service != null) { 212 // Service implementation 213 try { 214 service.startSync(mailboxId); 215 } catch (RemoteException e) { 216 // TODO Change exception handling to be consistent with however this method 217 // is implemented for other protocols 218 Log.d("updateMailbox", "RemoteException" + e); 219 } 220 } else { 221 // MessagingController implementation 222 new Thread() { 223 @Override 224 public void run() { 225 // TODO shouldn't be passing fully-build accounts & mailboxes into APIs 226 Account account = 227 EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); 228 Mailbox mailbox = 229 EmailContent.Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); 230 mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener); 231 } 232 }.start(); 233 } 234 } 235 236 /** 237 * Saves the message to a mailbox of given type. 238 * This is a synchronous operation taking place in the same thread as the caller. 239 * Upon return the message.mId is set. 240 * @param message the message (must have the mAccountId set). 241 * @param mailboxType the mailbox type (e.g. Mailbox.TYPE_DRAFTS). 242 */ 243 public void saveToMailbox(final EmailContent.Message message, final int mailboxType) { 244 long accountId = message.mAccountKey; 245 long mailboxId = findOrCreateMailboxOfType(accountId, mailboxType); 246 message.mMailboxKey = mailboxId; 247 message.save(mContext); 248 } 249 250 /** 251 * @param accountId the account id 252 * @param mailboxType the mailbox type (e.g. EmailContent.Mailbox.TYPE_TRASH) 253 * @return the id of the mailbox. The mailbox is created if not existing. 254 * Returns Mailbox.NO_MAILBOX if the accountId or mailboxType are negative. 255 * Does not validate the input in other ways (e.g. does not verify the existence of account). 256 */ 257 public long findOrCreateMailboxOfType(long accountId, int mailboxType) { 258 if (accountId < 0 || mailboxType < 0) { 259 return Mailbox.NO_MAILBOX; 260 } 261 long mailboxId = 262 Mailbox.findMailboxOfType(mProviderContext, accountId, mailboxType); 263 return mailboxId == Mailbox.NO_MAILBOX ? createMailbox(accountId, mailboxType) : mailboxId; 264 } 265 266 /** 267 * @param mailboxType the mailbox type 268 * @return the resource string corresponding to the mailbox type, empty if not found. 269 */ 270 /* package */ String getSpecialMailboxDisplayName(int mailboxType) { 271 int resId = -1; 272 switch (mailboxType) { 273 case Mailbox.TYPE_INBOX: 274 // TODO: there is no special_mailbox_display_name_inbox; why? 275 resId = R.string.special_mailbox_name_inbox; 276 break; 277 case Mailbox.TYPE_OUTBOX: 278 resId = R.string.special_mailbox_display_name_outbox; 279 break; 280 case Mailbox.TYPE_DRAFTS: 281 resId = R.string.special_mailbox_display_name_drafts; 282 break; 283 case Mailbox.TYPE_TRASH: 284 resId = R.string.special_mailbox_display_name_trash; 285 break; 286 case Mailbox.TYPE_SENT: 287 resId = R.string.special_mailbox_display_name_sent; 288 break; 289 } 290 return resId != -1 ? mContext.getString(resId) : ""; 291 } 292 293 /** 294 * Create a mailbox given the account and mailboxType. 295 * TODO: Does this need to be signaled explicitly to the sync engines? 296 * As this method is only used internally ('private'), it does not 297 * validate its inputs (accountId and mailboxType). 298 */ 299 /* package */ long createMailbox(long accountId, int mailboxType) { 300 if (accountId < 0 || mailboxType < 0) { 301 String mes = "Invalid arguments " + accountId + ' ' + mailboxType; 302 Log.e(Email.LOG_TAG, mes); 303 throw new RuntimeException(mes); 304 } 305 Mailbox box = new Mailbox(); 306 box.mAccountKey = accountId; 307 box.mType = mailboxType; 308 box.mSyncInterval = EmailContent.Account.CHECK_INTERVAL_NEVER; 309 box.mFlagVisible = true; 310 box.mDisplayName = getSpecialMailboxDisplayName(mailboxType); 311 box.save(mProviderContext); 312 return box.mId; 313 } 314 315 /** 316 * Send a message: 317 * - move the message to Outbox (the message is assumed to be in Drafts). 318 * - perform any necessary notification 319 * @param messageId the id of the message to send 320 */ 321 public void sendMessage(long messageId, long accountId) { 322 ContentResolver resolver = mProviderContext.getContentResolver(); 323 if (accountId == -1) { 324 accountId = lookupAccountForMessage(messageId); 325 } 326 if (accountId == -1) { 327 // probably the message was not found 328 if (Email.LOGD) { 329 Email.log("no account found for message " + messageId); 330 } 331 return; 332 } 333 334 // Move to Outbox 335 long outboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_OUTBOX); 336 ContentValues cv = new ContentValues(); 337 cv.put(EmailContent.MessageColumns.MAILBOX_KEY, outboxId); 338 339 // does this need to be SYNCED_CONTENT_URI instead? 340 Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId); 341 resolver.update(uri, cv, null, null); 342 343 // TODO: notifications 344 } 345 346 /** 347 * @param messageId the id of message 348 * @return the accountId corresponding to the given messageId, or -1 if not found. 349 */ 350 private long lookupAccountForMessage(long messageId) { 351 ContentResolver resolver = mProviderContext.getContentResolver(); 352 Cursor c = resolver.query(EmailContent.Message.CONTENT_URI, 353 MESSAGEID_TO_ACCOUNTID_PROJECTION, EmailContent.RECORD_ID + "=?", 354 new String[] { Long.toString(messageId) }, null); 355 try { 356 return c.moveToFirst() 357 ? c.getLong(MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID) 358 : -1; 359 } finally { 360 c.close(); 361 } 362 } 363 364 /** 365 * Delete a single message by moving it to the trash. 366 * 367 * This function has no callback, no result reporting, because the desired outcome 368 * is reflected entirely by changes to one or more cursors. 369 * 370 * @param messageId The id of the message to "delete". 371 * @param accountId The id of the message's account, or -1 if not known by caller 372 * 373 * TODO: Move out of UI thread 374 * TODO: "get account a for message m" should be a utility 375 * TODO: "get mailbox of type n for account a" should be a utility 376 */ 377 public void deleteMessage(long messageId, long accountId) { 378 ContentResolver resolver = mProviderContext.getContentResolver(); 379 380 // 1. Look up acct# for message we're deleting 381 Cursor c = null; 382 if (accountId == -1) { 383 accountId = lookupAccountForMessage(messageId); 384 } 385 if (accountId == -1) { 386 return; 387 } 388 389 // 2. Confirm that there is a trash mailbox available 390 // 3. If there's no trash mailbox, create one 391 // TODO: Does this need to be signaled explicitly to the sync engines? 392 long trashMailboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_TRASH); 393 394 // 4. Change the mailbox key for the message we're "deleting" 395 ContentValues cv = new ContentValues(); 396 cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId); 397 Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); 398 resolver.update(uri, cv, null, null); 399 400 // 5. Drop non-essential data for the message (e.g. attachments) 401 // TODO: find the actual files (if any, if loaded) & delete them 402 c = null; 403 try { 404 c = resolver.query(EmailContent.Attachment.CONTENT_URI, 405 EmailContent.Attachment.CONTENT_PROJECTION, 406 EmailContent.AttachmentColumns.MESSAGE_KEY + "=?", 407 new String[] { Long.toString(messageId) }, null); 408 while (c.moveToNext()) { 409 // delete any associated storage 410 // delete row? 411 } 412 } finally { 413 if (c != null) c.close(); 414 } 415 416 // 6. For IMAP/POP3 we may need to kick off an immediate delete (depends on acct settings) 417 // TODO write this 418 } 419 420 /** 421 * Set/clear the unread status of a message 422 * 423 * @param messageId the message to update 424 * @param isRead the new value for the isRead flag 425 */ 426 public void setMessageRead(long messageId, boolean isRead) { 427 // TODO this should not be in this thread. queue it up. 428 // TODO Also, it needs to update the read/unread count in the mailbox 429 // TODO kick off service/messagingcontroller actions 430 431 ContentValues cv = new ContentValues(); 432 cv.put(EmailContent.MessageColumns.FLAG_READ, isRead); 433 Uri uri = ContentUris.withAppendedId( 434 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 435 mProviderContext.getContentResolver().update(uri, cv, null, null); 436 } 437 438 /** 439 * Set/clear the favorite status of a message 440 * 441 * @param messageId the message to update 442 * @param isFavorite the new value for the isFavorite flag 443 */ 444 public void setMessageFavorite(long messageId, boolean isFavorite) { 445 // TODO this should not be in this thread. queue it up. 446 // TODO kick off service/messagingcontroller actions 447 448 ContentValues cv = new ContentValues(); 449 cv.put(EmailContent.MessageColumns.FLAG_FAVORITE, isFavorite); 450 Uri uri = ContentUris.withAppendedId( 451 EmailContent.Message.SYNCED_CONTENT_URI, messageId); 452 mProviderContext.getContentResolver().update(uri, cv, null, null); 453 } 454 455 /** 456 * Request that an attachment be loaded. It will be stored at a location controlled 457 * by the AttachmentProvider. 458 * 459 * @param attachmentId the attachment to load 460 * @param messageId the owner message 461 * @param mailboxId the owner mailbox 462 * @param accountId the owner account 463 * @param callback the Controller callback by which results will be reported 464 */ 465 public void loadAttachment(final long attachmentId, final long messageId, final long mailboxId, 466 final long accountId, final Result callback) { 467 468 File saveToFile = AttachmentProvider.getAttachmentFilename(mContext, 469 accountId, attachmentId); 470 if (saveToFile.exists()) { 471 // The attachment has already been downloaded, so we will just "pretend" to download it 472 synchronized (mListeners) { 473 for (Result listener : mListeners) { 474 listener.loadAttachmentCallback(null, messageId, attachmentId, 0); 475 } 476 for (Result listener : mListeners) { 477 listener.loadAttachmentCallback(null, messageId, attachmentId, 100); 478 } 479 } 480 return; 481 } 482 483 Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId); 484 485 // Split here for target type (Service or MessagingController) 486 IEmailService service = getServiceForMessage(messageId); 487 if (service != null) { 488 // Service implementation 489 try { 490 service.loadAttachment(attachInfo.mId, saveToFile.getAbsolutePath(), 491 AttachmentProvider.getAttachmentUri(accountId, attachmentId).toString()); 492 } catch (RemoteException e) { 493 // TODO Change exception handling to be consistent with however this method 494 // is implemented for other protocols 495 Log.e("onDownloadAttachment", "RemoteException", e); 496 } 497 } else { 498 // MessagingController implementation 499 new Thread() { 500 @Override 501 public void run() { 502 mLegacyController.loadAttachment(accountId, messageId, mailboxId, attachmentId, 503 mLegacyListener); 504 } 505 }.start(); 506 } 507 } 508 509 /** 510 * For a given message id, return a service proxy if applicable, or null. 511 * 512 * @param messageId the message of interest 513 * @result service proxy, or null if n/a 514 */ 515 private IEmailService getServiceForMessage(long messageId) { 516 // TODO make this more efficient, caching the account, smaller lookup here, etc. 517 Message message = Message.restoreMessageWithId(mProviderContext, messageId); 518 return getServiceForAccount(message.mAccountKey); 519 } 520 521 /** 522 * For a given account id, return a service proxy if applicable, or null. 523 * 524 * @param accountId the message of interest 525 * @result service proxy, or null if n/a 526 */ 527 private IEmailService getServiceForAccount(long accountId) { 528 // TODO make this more efficient, caching the account, MUCH smaller lookup here, etc. 529 Account account = EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); 530 if (isMessagingController(account)) { 531 return null; 532 } else { 533 return new EmailServiceProxy(mContext, SyncManager.class, mServiceCallback); 534 } 535 } 536 537 /** 538 * Simple helper to determine if legacy MessagingController should be used 539 * 540 * TODO this should not require a full account, just an accountId 541 * TODO this should use a cache because we'll be doing this a lot 542 */ 543 private boolean isMessagingController(EmailContent.Account account) { 544 Store.StoreInfo info = 545 Store.StoreInfo.getStoreInfo(account.getStoreUri(mContext), mContext); 546 String scheme = info.mScheme; 547 548 return ("pop3".equals(scheme) || "imap".equals(scheme)); 549 } 550 551 /** 552 * Simple callback for synchronous commands. For many commands, this can be largely ignored 553 * and the result is observed via provider cursors. The callback will *not* necessarily be 554 * made from the UI thread, so you may need further handlers to safely make UI updates. 555 */ 556 public interface Result { 557 /** 558 * Callback for updateMailboxList 559 * 560 * @param result If null, the operation completed without error 561 * @param accountId The account being operated on 562 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 563 */ 564 public void updateMailboxListCallback(MessagingException result, long accountId, 565 int progress); 566 567 /** 568 * Callback for updateMailbox. Note: This looks a lot like checkMailCallback, but 569 * it's a separate call used only by UI's, so we can keep things separate. 570 * 571 * @param result If null, the operation completed without error 572 * @param accountId The account being operated on 573 * @param mailboxId The mailbox being operated on 574 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 575 * @param numNewMessages the number of new messages delivered 576 */ 577 public void updateMailboxCallback(MessagingException result, long accountId, 578 long mailboxId, int progress, int numNewMessages); 579 580 /** 581 * Callback for loadAttachment 582 * 583 * @param result if null, the attachment completed - if non-null, terminating with failure 584 * @param messageId the message which contains the attachment 585 * @param attachmentId the attachment being loaded 586 * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete 587 */ 588 public void loadAttachmentCallback(MessagingException result, long messageId, 589 long attachmentId, int progress); 590 591 /** 592 * Callback for checkmail. Note: This looks a lot like updateMailboxCallback, but 593 * it's a separate call used only by the automatic checker service, so we can keep 594 * things separate. 595 * 596 * @param result If null, the operation completed without error 597 * @param accountId The account being operated on 598 * @param mailboxId The mailbox being operated on (may be unknown at start) 599 * @param progress 0 for "starting", no updates, 100 for complete 600 * @param tag the same tag that was passed to serviceCheckMail() 601 */ 602 public void serviceCheckMailCallback(MessagingException result, long accountId, 603 long mailboxId, int progress, long tag); 604 } 605 606 /** 607 * Support for receiving callbacks from MessagingController and dealing with UI going 608 * out of scope. 609 */ 610 private class LegacyListener extends MessagingListener { 611 612 @Override 613 public void listFoldersStarted(EmailContent.Account account) { 614 synchronized (mListeners) { 615 for (Result l : mListeners) { 616 l.updateMailboxListCallback(null, account.mId, 0); 617 } 618 } 619 } 620 621 @Override 622 public void listFoldersFailed(EmailContent.Account account, String message) { 623 synchronized (mListeners) { 624 for (Result l : mListeners) { 625 l.updateMailboxListCallback(new MessagingException(message), account.mId, 0); 626 } 627 } 628 } 629 630 @Override 631 public void listFoldersFinished(EmailContent.Account account) { 632 synchronized (mListeners) { 633 for (Result l : mListeners) { 634 l.updateMailboxListCallback(null, account.mId, 100); 635 } 636 } 637 } 638 639 @Override 640 public void synchronizeMailboxStarted(EmailContent.Account account, 641 EmailContent.Mailbox folder) { 642 synchronized (mListeners) { 643 for (Result l : mListeners) { 644 l.updateMailboxCallback(null, account.mId, folder.mId, 0, 0); 645 } 646 } 647 } 648 649 @Override 650 public void synchronizeMailboxFinished(EmailContent.Account account, 651 EmailContent.Mailbox folder, int totalMessagesInMailbox, int numNewMessages) { 652 synchronized (mListeners) { 653 for (Result l : mListeners) { 654 l.updateMailboxCallback(null, account.mId, folder.mId, 100, numNewMessages); 655 } 656 } 657 } 658 659 @Override 660 public void synchronizeMailboxFailed(EmailContent.Account account, 661 EmailContent.Mailbox folder, Exception e) { 662 MessagingException me; 663 if (e instanceof MessagingException) { 664 me = (MessagingException) e; 665 } else { 666 me = new MessagingException(e.toString()); 667 } 668 synchronized (mListeners) { 669 for (Result l : mListeners) { 670 l.updateMailboxCallback(me, account.mId, folder.mId, 0, 0); 671 } 672 } 673 } 674 675 @Override 676 public void checkMailStarted(Context context, long accountId, long tag) { 677 synchronized (mListeners) { 678 for (Result l : mListeners) { 679 l.serviceCheckMailCallback(null, accountId, -1, 0, tag); 680 } 681 } 682 } 683 684 @Override 685 public void checkMailFinished(Context context, long accountId, long folderId, long tag) { 686 synchronized (mListeners) { 687 for (Result l : mListeners) { 688 l.serviceCheckMailCallback(null, accountId, folderId, 100, tag); 689 } 690 } 691 } 692 693 @Override 694 public void loadAttachmentStarted(long accountId, long messageId, long attachmentId, 695 boolean requiresDownload) { 696 synchronized (mListeners) { 697 for (Result listener : mListeners) { 698 listener.loadAttachmentCallback(null, messageId, attachmentId, 0); 699 } 700 } 701 } 702 703 @Override 704 public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) { 705 synchronized (mListeners) { 706 for (Result listener : mListeners) { 707 listener.loadAttachmentCallback(null, messageId, attachmentId, 100); 708 } 709 } 710 } 711 712 @Override 713 public void loadAttachmentFailed(long accountId, long messageId, long attachmentId, 714 String reason) { 715 synchronized (mListeners) { 716 for (Result listener : mListeners) { 717 listener.loadAttachmentCallback(new MessagingException(reason), 718 messageId, attachmentId, 0); 719 } 720 } 721 } 722 } 723 724 /** 725 * Service callback for service operations 726 */ 727 private class ServiceCallback extends IEmailServiceCallback.Stub { 728 729 private final static boolean DEBUG_FAIL_DOWNLOADS = false; // do not check in "true" 730 731 public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 732 int progress) { 733 MessagingException result = null; 734 switch (statusCode) { 735 case EmailServiceStatus.SUCCESS: 736 progress = 100; 737 break; 738 case EmailServiceStatus.IN_PROGRESS: 739 if (DEBUG_FAIL_DOWNLOADS && progress > 75) { 740 result = new MessagingException( 741 String.valueOf(EmailServiceStatus.CONNECTION_ERROR)); 742 } 743 // discard progress reports that look like sentinels 744 if (progress < 0 || progress >= 100) { 745 return; 746 } 747 break; 748 default: 749 result = new MessagingException(String.valueOf(statusCode)); 750 break; 751 } 752 synchronized (mListeners) { 753 for (Result listener : mListeners) { 754 listener.loadAttachmentCallback(result, messageId, attachmentId, progress); 755 } 756 } 757 } 758 759 public void sendMessageStatus(long messageId, int statusCode, int progress) { 760 // TODO Auto-generated method stub 761 762 } 763 764 public void syncMailboxListStatus(long accountId, int statusCode, int progress) { 765 MessagingException result= null; 766 switch (statusCode) { 767 case EmailServiceStatus.SUCCESS: 768 progress = 100; 769 break; 770 case EmailServiceStatus.IN_PROGRESS: 771 // discard progress reports that look like sentinels 772 if (progress < 0 || progress >= 100) { 773 return; 774 } 775 break; 776 default: 777 result = new MessagingException(String.valueOf(statusCode)); 778 break; 779 } 780 synchronized(mListeners) { 781 for (Result listener : mListeners) { 782 listener.updateMailboxListCallback(result, accountId, progress); 783 } 784 } 785 } 786 787 public void syncMailboxStatus(long mailboxId, int statusCode, int progress) { 788 MessagingException result= null; 789 switch (statusCode) { 790 case EmailServiceStatus.SUCCESS: 791 progress = 100; 792 break; 793 case EmailServiceStatus.IN_PROGRESS: 794 // discard progress reports that look like sentinels 795 if (progress < 0 || progress >= 100) { 796 return; 797 } 798 break; 799 default: 800 result = new MessagingException(String.valueOf(statusCode)); 801 break; 802 } 803 // TODO where do we get "number of new messages" as well? 804 // TODO should pass this back instead of looking it up here 805 // TODO smaller projection 806 Mailbox mbx = Mailbox.restoreMailboxWithId(mContext, mailboxId); 807 // The mailbox could have disappeared if the server commanded it 808 if (mbx == null) return; 809 long accountId = mbx.mAccountKey; 810 synchronized(mListeners) { 811 for (Result listener : mListeners) { 812 listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0); 813 } 814 } 815 } 816 } 817} 818