UIControllerTwoPane.java revision d531dc3058c52b1e3ee2ee53eb8d63d6177d9f91
1/* 2 * Copyright (C) 2010 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.activity; 18 19import com.android.email.Clock; 20import com.android.email.Email; 21import com.android.email.Preferences; 22import com.android.email.R; 23import com.android.email.RefreshManager; 24import com.android.email.activity.setup.AccountSecurity; 25import com.android.emailcommon.Logging; 26import com.android.emailcommon.provider.EmailContent.Account; 27import com.android.emailcommon.provider.EmailContent.Message; 28import com.android.emailcommon.provider.Mailbox; 29import com.android.emailcommon.utility.EmailAsyncTask; 30import com.google.common.annotations.VisibleForTesting; 31 32import android.app.Activity; 33import android.app.FragmentManager; 34import android.app.FragmentTransaction; 35import android.content.Context; 36import android.os.Bundle; 37import android.util.Log; 38 39import java.util.Set; 40 41/** 42 * UI Controller for x-large devices. Supports a multi-pane layout. 43 */ 44class UIControllerTwoPane extends UIControllerBase implements 45 MailboxFinder.Callback, 46 ThreePaneLayout.Callback, 47 MailboxListFragment.Callback, 48 MessageListFragment.Callback, 49 MessageViewFragment.Callback { 50 @VisibleForTesting 51 static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds 52 53 @VisibleForTesting 54 static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds 55 56 private ActionBarController mActionBarController; 57 private final ActionBarControllerCallback mActionBarControllerCallback = 58 new ActionBarControllerCallback(); 59 60 // Other UI elements 61 private ThreePaneLayout mThreePane; 62 63 /** 64 * Fragments that are installed. 65 * 66 * A fragment is installed when: 67 * - it is attached to the activity 68 * - the parent activity is created 69 * - and it is not scheduled to be removed. 70 * 71 * We set callbacks to fragments only when they are installed. 72 */ 73 private MailboxListFragment mMailboxListFragment; 74 private MessageListFragment mMessageListFragment; 75 private MessageViewFragment mMessageViewFragment; 76 77 private MessageCommandButtonView mMessageCommandButtons; 78 79 private MailboxFinder mMailboxFinder; 80 81 private MessageOrderManager mOrderManager; 82 private final MessageOrderManagerCallback mMessageOrderManagerCallback = 83 new MessageOrderManagerCallback(); 84 85 /** 86 * The mailbox name selected on the mailbox list. 87 * Passed via {@link #onCurrentMailboxUpdated}. 88 */ 89 private String mCurrentMailboxName; 90 91 /** 92 * The unread count for the mailbox selected on the mailbox list. 93 * Passed via {@link #onCurrentMailboxUpdated}. 94 * 95 * 0 if the mailbox doesn't have the concept of "unread". e.g. Drafts. 96 */ 97 private int mCurrentMailboxUnreadCount; 98 99 public UIControllerTwoPane(EmailActivity activity) { 100 super(activity); 101 } 102 103 private void refreshActionBar() { 104 if (mActionBarController != null) { 105 mActionBarController.refresh(); 106 } 107 } 108 109 @Override 110 public int getLayoutId() { 111 return R.layout.email_activity_two_pane; 112 } 113 114 // MailboxFinder$Callback 115 @Override 116 public void onAccountNotFound() { 117 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 118 Log.d(Logging.LOG_TAG, this + " onAccountNotFound()"); 119 } 120 // Shouldn't happen 121 } 122 123 // MailboxFinder$Callback 124 @Override 125 public void onAccountSecurityHold(long accountId) { 126 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 127 Log.d(Logging.LOG_TAG, this + " onAccountSecurityHold()"); 128 } 129 mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId, 130 true)); 131 } 132 133 // MailboxFinder$Callback 134 @Override 135 public void onMailboxFound(long accountId, long mailboxId) { 136 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 137 Log.d(Logging.LOG_TAG, this + " onMailboxFound()"); 138 } 139 updateMessageList(accountId, mailboxId, true); 140 } 141 142 // MailboxFinder$Callback 143 @Override 144 public void onMailboxNotFound(long accountId) { 145 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 146 Log.d(Logging.LOG_TAG, this + " onMailboxNotFound()"); 147 } 148 Log.e(Logging.LOG_TAG, "unable to find mailbox for account " + accountId); 149 } 150 151 // ThreePaneLayoutCallback 152 @Override 153 public void onVisiblePanesChanged(int previousVisiblePanes) { 154 refreshActionBar(); 155 156 // If the right pane is gone, remove the message view. 157 final int visiblePanes = mThreePane.getVisiblePanes(); 158 159 if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) && 160 ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) { 161 // Message view just got hidden 162 unselectMessage(); 163 } 164 // Disable CAB when the message list is not visible. 165 if (isMessageListInstalled()) { 166 mMessageListFragment.onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0); 167 } 168 } 169 170 // MailboxListFragment$Callback 171 @Override 172 public void onMailboxSelected(long accountId, long mailboxId, boolean navigate) { 173 if ((accountId == Account.NO_ACCOUNT) || (mailboxId == Mailbox.NO_MAILBOX)) { 174 throw new IllegalArgumentException(); // Shouldn't happen. 175 } 176 if (navigate) { 177 if (mailboxId != getMailboxListMailboxId()) { 178 // Don't navigate to the same mailbox id twice in a row 179 openMailbox(accountId, mailboxId); 180 } 181 } else { 182 // Regular case -- just open the mailbox on the message list. 183 updateMessageList(accountId, mailboxId, true); 184 } 185 } 186 187 public void onMailboxSelectedForDnD(long mailboxId) { 188 // STOPSHIP the new mailbox list created here doesn't know D&D is in progress. b/4332725 189 190 updateMailboxList(getUIAccountId(), mailboxId, 191 false /* don't clear message list and message view */); 192 } 193 194 // MailboxListFragment$Callback 195 @Override 196 public void onAccountSelected(long accountId) { 197 switchAccount(accountId); 198 } 199 200 // MailboxListFragment$Callback 201 @Override 202 public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) { 203 mCurrentMailboxName = mailboxName; 204 mCurrentMailboxUnreadCount = unreadCount; 205 refreshActionBar(); 206 } 207 208 // MessageListFragment$Callback 209 @Override 210 public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId, 211 int type) { 212 if (type == MessageListFragment.Callback.TYPE_DRAFT) { 213 MessageCompose.actionEditDraft(mActivity, messageId); 214 } else { 215 updateMessageView(messageId); 216 mThreePane.showRightPane(); 217 } 218 } 219 220 // MessageListFragment$Callback 221 @Override 222 public void onMailboxNotFound() { 223 Log.e(Logging.LOG_TAG, "unable to find mailbox"); 224 } 225 226 // MessageListFragment$Callback 227 @Override 228 public void onEnterSelectionMode(boolean enter) { 229 } 230 231 // MessageListFragment$Callback 232 /** 233 * Apply the auto-advance policy upon initation of a batch command that could potentially 234 * affect the currently selected conversation. 235 */ 236 @Override 237 public void onAdvancingOpAccepted(Set<Long> affectedMessages) { 238 if (!isMessageViewInstalled()) { 239 // Do nothing if message view is not visible. 240 return; 241 } 242 243 int autoAdvanceDir = Preferences.getPreferences(mActivity).getAutoAdvanceDirection(); 244 if ((autoAdvanceDir == Preferences.AUTO_ADVANCE_MESSAGE_LIST) || (mOrderManager == null)) { 245 if (affectedMessages.contains(getMessageId())) { 246 goBackToMailbox(); 247 } 248 return; 249 } 250 251 // Navigate to the first unselected item in the appropriate direction. 252 switch (autoAdvanceDir) { 253 case Preferences.AUTO_ADVANCE_NEWER: 254 while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) { 255 if (!mOrderManager.moveToNewer()) { 256 goBackToMailbox(); 257 return; 258 } 259 } 260 updateMessageView(mOrderManager.getCurrentMessageId()); 261 break; 262 263 case Preferences.AUTO_ADVANCE_OLDER: 264 while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) { 265 if (!mOrderManager.moveToOlder()) { 266 goBackToMailbox(); 267 return; 268 } 269 } 270 updateMessageView(mOrderManager.getCurrentMessageId()); 271 break; 272 } 273 } 274 275 // MessageListFragment$Callback 276 @Override 277 public void onListLoaded() { 278 } 279 280 // MessageListFragment$Callback 281 @Override 282 public boolean onDragStarted() { 283 Log.w(Logging.LOG_TAG, "Drag started"); 284 285 if ((mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0) { 286 // Mailbox list hidden. D&D not allowed. 287 return false; 288 } 289 290 // STOPSHIP Save the current mailbox list 291 292 return true; 293 } 294 295 // MessageListFragment$Callback 296 @Override 297 public void onDragEnded() { 298 Log.w(Logging.LOG_TAG, "Drag ended"); 299 300 // STOPSHIP Restore the saved mailbox list 301 } 302 303 // MessageViewFragment$Callback 304 @Override 305 public void onMessageViewShown(int mailboxType) { 306 updateMessageOrderManager(); 307 updateNavigationArrows(); 308 } 309 310 // MessageViewFragment$Callback 311 @Override 312 public void onMessageViewGone() { 313 stopMessageOrderManager(); 314 } 315 316 // MessageViewFragment$Callback 317 @Override 318 public boolean onUrlInMessageClicked(String url) { 319 return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId()); 320 } 321 322 // MessageViewFragment$Callback 323 @Override 324 public void onMessageSetUnread() { 325 goBackToMailbox(); 326 } 327 328 // MessageViewFragment$Callback 329 @Override 330 public void onMessageNotExists() { 331 goBackToMailbox(); 332 } 333 334 // MessageViewFragment$Callback 335 @Override 336 public void onLoadMessageStarted() { 337 } 338 339 // MessageViewFragment$Callback 340 @Override 341 public void onLoadMessageFinished() { 342 } 343 344 // MessageViewFragment$Callback 345 @Override 346 public void onLoadMessageError(String errorMessage) { 347 } 348 349 // MessageViewFragment$Callback 350 @Override 351 public void onRespondedToInvite(int response) { 352 onCurrentMessageGone(); 353 } 354 355 // MessageViewFragment$Callback 356 @Override 357 public void onCalendarLinkClicked(long epochEventStartTime) { 358 ActivityHelper.openCalendar(mActivity, epochEventStartTime); 359 } 360 361 // MessageViewFragment$Callback 362 @Override 363 public void onBeforeMessageGone() { 364 onCurrentMessageGone(); 365 } 366 367 // MessageViewFragment$Callback 368 @Override 369 public void onForward() { 370 MessageCompose.actionForward(mActivity, getMessageId()); 371 } 372 373 // MessageViewFragment$Callback 374 @Override 375 public void onReply() { 376 MessageCompose.actionReply(mActivity, getMessageId(), false); 377 } 378 379 // MessageViewFragment$Callback 380 @Override 381 public void onReplyAll() { 382 MessageCompose.actionReply(mActivity, getMessageId(), true); 383 } 384 385 /** 386 * Must be called just after the activity sets up the content view. 387 */ 388 @Override 389 public void onActivityViewReady() { 390 super.onActivityViewReady(); 391 mActionBarController = new ActionBarController(mActivity, mActivity.getLoaderManager(), 392 mActivity.getActionBar(), mActionBarControllerCallback); 393 394 // Set up content 395 mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane); 396 mThreePane.setCallback(this); 397 398 mMessageCommandButtons = mThreePane.getMessageCommandButtons(); 399 mMessageCommandButtons.setCallback(new CommandButtonCallback()); 400 } 401 402 /** 403 * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 404 * 405 * @see #getActualAccountId() 406 */ 407 @Override 408 public long getUIAccountId() { 409 return isMailboxListInstalled() ? mMailboxListFragment.getAccountId() 410 :Account.NO_ACCOUNT; 411 } 412 413 /** 414 * Returns the id of the parent mailbox used for the mailbox list fragment. 415 * 416 * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with 417 * {@link #getMessageListMailboxId()} 418 */ 419 private long getMailboxListMailboxId() { 420 return isMailboxListInstalled() ? mMailboxListFragment.getParentMailboxId() 421 : Mailbox.NO_MAILBOX; 422 } 423 424 /** 425 * Returns the id of the mailbox used for the message list fragment. 426 * 427 * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with 428 * {@link #getMessageListMailboxId()} 429 */ 430 private long getMessageListMailboxId() { 431 return isMessageListInstalled() ? mMessageListFragment.getMailboxId() 432 : Message.NO_MESSAGE; 433 } 434 435 /* 436 * STOPSHIP Remove this -- see the base class method. 437 */ 438 @Override 439 public long getMailboxSettingsMailboxId() { 440 return getMessageListMailboxId(); 441 } 442 443 /* 444 * STOPSHIP Remove this -- see the base class method. 445 */ 446 @Override 447 public long getSearchMailboxId() { 448 return getMessageListMailboxId(); 449 } 450 451 private long getMessageId() { 452 return isMessageViewInstalled() ? mMessageViewFragment.getMessageId() 453 : Message.NO_MESSAGE; 454 } 455 456 private boolean isMailboxListInstalled() { 457 return mMailboxListFragment != null; 458 } 459 460 private boolean isMessageListInstalled() { 461 return mMessageListFragment != null; 462 } 463 464 private boolean isMessageViewInstalled() { 465 return mMessageViewFragment != null; 466 } 467 468 469 /** 470 * @return true if refresh is in progress for the current mailbox. 471 */ 472 @Override 473 protected boolean isRefreshInProgress() { 474 long messageListMailboxId = getMessageListMailboxId(); 475 return (messageListMailboxId >= 0) 476 && mRefreshManager.isMessageListRefreshing(messageListMailboxId); 477 } 478 479 /** 480 * @return true if the UI should enable the "refresh" command. 481 */ 482 @Override 483 protected boolean isRefreshEnabled() { 484 // - Don't show for combined inboxes, but 485 // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list 486 return getActualAccountId() != Account.NO_ACCOUNT; 487 } 488 489 /** 490 * Called by the host activity at the end of {@link Activity#onCreate}. 491 */ 492 @Override 493 public void onActivityCreated() { 494 super.onActivityCreated(); 495 mActionBarController.onActivityCreated(); 496 } 497 498 /** {@inheritDoc} */ 499 @Override 500 public void onActivityStart() { 501 super.onActivityStart(); 502 if (isMessageViewInstalled()) { 503 updateMessageOrderManager(); 504 } 505 } 506 507 /** {@inheritDoc} */ 508 @Override 509 public void onActivityResume() { 510 super.onActivityResume(); 511 refreshActionBar(); 512 } 513 514 /** {@inheritDoc} */ 515 @Override 516 public void onActivityPause() { 517 super.onActivityPause(); 518 } 519 520 /** {@inheritDoc} */ 521 @Override 522 public void onActivityStop() { 523 stopMessageOrderManager(); 524 super.onActivityStop(); 525 } 526 527 /** {@inheritDoc} */ 528 @Override 529 public void onActivityDestroy() { 530 closeMailboxFinder(); 531 super.onActivityDestroy(); 532 } 533 534 /** {@inheritDoc} */ 535 @Override 536 public void onSaveInstanceState(Bundle outState) { 537 super.onSaveInstanceState(outState); 538 539 // STOPSHIP If MailboxFinder is still running, it needs restarting after loadState(). 540 } 541 542 /** {@inheritDoc} */ 543 @Override 544 public void restoreInstanceState(Bundle savedInstanceState) { 545 super.restoreInstanceState(savedInstanceState); 546 547 // STOPSHIP If MailboxFinder is still running, it needs restarting after loadState(). 548 } 549 550 @Override 551 protected void installMailboxListFragment(MailboxListFragment fragment) { 552 mMailboxListFragment = fragment; 553 mMailboxListFragment.setCallback(this); 554 } 555 556 @Override 557 protected void installMessageListFragment(MessageListFragment fragment) { 558 mMessageListFragment = fragment; 559 mMessageListFragment.setCallback(this); 560 } 561 562 @Override 563 protected void installMessageViewFragment(MessageViewFragment fragment) { 564 mMessageViewFragment = fragment; 565 mMessageViewFragment.setCallback(this); 566 } 567 568 private FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) { 569 if (isMailboxListInstalled()) { 570 ft.remove(mMailboxListFragment); 571 mMailboxListFragment.setCallback(null); 572 mMailboxListFragment = null; 573 } 574 return ft; 575 } 576 577 private FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) { 578 if (isMessageListInstalled()) { 579 ft.remove(mMessageListFragment); 580 mMessageListFragment.setCallback(null); 581 mMessageListFragment = null; 582 } 583 return ft; 584 } 585 586 private FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) { 587 if (isMessageViewInstalled()) { 588 ft.remove(mMessageViewFragment); 589 mMessageViewFragment.setCallback(null); 590 mMessageViewFragment = null; 591 } 592 return ft; 593 } 594 595 /** 596 * {@inheritDoc} 597 */ 598 @Override 599 public void open(long accountId, long mailboxId, long messageId) { 600 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 601 Log.d(Logging.LOG_TAG, this + " open accountId=" + accountId 602 + " mailboxId=" + mailboxId + " messageId=" + messageId); 603 } 604 if (accountId == Account.NO_ACCOUNT) { 605 throw new IllegalArgumentException(); 606 } else if (mailboxId == Mailbox.NO_MAILBOX) { 607 updateMailboxList(accountId, Mailbox.NO_MAILBOX, true); 608 609 // Show the appropriate message list 610 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 611 // When opening the Combined view, the right pane will be "combined inbox". 612 updateMessageList(accountId, Mailbox.QUERY_ALL_INBOXES, true); 613 } else { 614 // Try to find the inbox for the account 615 closeMailboxFinder(); 616 mMailboxFinder = new MailboxFinder(mActivity, accountId, Mailbox.TYPE_INBOX, this); 617 mMailboxFinder.startLookup(); 618 } 619 mThreePane.showLeftPane(); 620 } else if (messageId == Message.NO_MESSAGE) { 621 // STOPSHIP Use the appropriate parent mailbox ID 622 updateMailboxList(accountId, mailboxId, true); 623 updateMessageList(accountId, mailboxId, true); 624 625 mThreePane.showLeftPane(); 626 } else { 627 // STOPSHIP Use the appropriate parent mailbox ID 628 updateMailboxList(accountId, mailboxId, true); 629 updateMessageList(accountId, mailboxId, true); 630 updateMessageView(messageId); 631 632 mThreePane.showRightPane(); 633 } 634 } 635 636 /** 637 * Pre-fragment transaction check. 638 * 639 * @throw IllegalStateException if updateXxx methods can't be called in the current state. 640 */ 641 private void preFragmentTransactionCheck() { 642 if (!isFragmentInstallable()) { 643 // Code assumes mMailboxListFragment/etc are set right within the 644 // commitFragmentTransaction() call (because we use synchronous transaction), 645 // so updateXxx() can't be called if fragments are not installable yet. 646 throw new IllegalStateException(); 647 } 648 } 649 650 /** 651 * Loads the given account and optionally selects the given mailbox and message. If the 652 * specified account is already selected, no actions will be performed unless 653 * <code>forceReload</code> is <code>true</code>. 654 * 655 * @param accountId ID of the account to load. Must never be {@link Account#NO_ACCOUNT}. 656 * @param parentMailboxId ID of the mailbox to use as the parent mailbox. Pass 657 * {@link Mailbox#NO_MAILBOX} to show the root mailboxes. 658 * @param clearDependentPane if true, the message list and the message view will be cleared 659 */ 660 private void updateMailboxList(long accountId, long parentMailboxId, 661 boolean clearDependentPane) { 662 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 663 Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId 664 + " parentMailboxId=" + parentMailboxId); 665 } 666 preFragmentTransactionCheck(); 667 if (accountId == Account.NO_ACCOUNT) { 668 throw new IllegalArgumentException(); 669 } 670 671 final FragmentManager fm = mActivity.getFragmentManager(); 672 final FragmentTransaction ft = fm.beginTransaction(); 673 if ((getUIAccountId() != accountId) 674 || (getMailboxListMailboxId() != parentMailboxId)) { 675 uninstallMailboxListFragment(ft); 676 ft.add(mThreePane.getLeftPaneId(), 677 MailboxListFragment.newInstance(accountId, parentMailboxId)); 678 } 679 if (clearDependentPane) { 680 uninstallMessageListFragment(ft); 681 uninstallMessageViewFragment(ft); 682 } 683 commitFragmentTransaction(ft); 684 685 // Update action bar / menu 686 updateRefreshProgress(); 687 refreshActionBar(); 688 } 689 690 /** 691 * Go back to a mailbox list view. If a message view is currently active, it will 692 * be hidden. 693 */ 694 private void goBackToMailbox() { 695 if (isMessageViewInstalled()) { 696 mThreePane.showLeftPane(); // Show mailbox list 697 } 698 } 699 700 /** 701 * Show the message list fragment for the given mailbox. 702 * 703 * @param accountId ID of the owner account for the mailbox. Must never be 704 * {@link Account#NO_ACCOUNT}. 705 * @param mailboxId ID of the mailbox to load. Must never be {@link Mailbox#NO_MAILBOX}. 706 * @param clearDependentPane if true, the message view will be cleared 707 * 708 * STOPSHIP Need to stop mailbox finder if it's still running 709 */ 710 private void updateMessageList(long accountId, long mailboxId, boolean clearDependentPane) { 711 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 712 Log.d(Logging.LOG_TAG, this + " updateMessageList mMailboxId=" + mailboxId); 713 } 714 preFragmentTransactionCheck(); 715 if (mailboxId == Mailbox.NO_MAILBOX) { 716 throw new IllegalArgumentException(); 717 } 718 719 final FragmentManager fm = mActivity.getFragmentManager(); 720 final FragmentTransaction ft = fm.beginTransaction(); 721 if (mailboxId != getMessageListMailboxId()) { 722 uninstallMessageListFragment(ft); 723 ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance( 724 accountId, mailboxId)); 725 } 726 if (clearDependentPane) { 727 uninstallMessageViewFragment(ft); 728 } 729 commitFragmentTransaction(ft); 730 731 mMailboxListFragment.setSelectedMailbox(mailboxId); 732 733 // Update action bar / menu 734 updateRefreshProgress(); 735 refreshActionBar(); 736 } 737 738 /** 739 * Show a message on the message view. 740 * 741 * @param messageId ID of the mailbox to load. Must never be {@link Message#NO_MESSAGE}. 742 */ 743 private void updateMessageView(long messageId) { 744 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 745 Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId); 746 } 747 preFragmentTransactionCheck(); 748 if (messageId == Message.NO_MESSAGE) { 749 throw new IllegalArgumentException(); 750 } 751 752 if (messageId == getMessageId()) { 753 return; // nothing to do. 754 } 755 // Open message 756 final FragmentManager fm = mActivity.getFragmentManager(); 757 final FragmentTransaction ft = fm.beginTransaction(); 758 uninstallMessageViewFragment(ft); 759 ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId)); 760 commitFragmentTransaction(ft); 761 762 mMessageListFragment.setSelectedMessage(messageId); 763 } 764 765 /** 766 * Remove the message view if shown. 767 */ 768 private void unselectMessage() { 769 commitFragmentTransaction(uninstallMessageViewFragment( 770 mActivity.getFragmentManager().beginTransaction())); 771 if (mMessageListFragment != null) { 772 mMessageListFragment.setSelectedMessage(Message.NO_MESSAGE); 773 } 774 } 775 776 private void closeMailboxFinder() { 777 if (mMailboxFinder != null) { 778 mMailboxFinder.cancel(); 779 mMailboxFinder = null; 780 } 781 } 782 783 private class CommandButtonCallback implements MessageCommandButtonView.Callback { 784 @Override 785 public void onMoveToNewer() { 786 moveToNewer(); 787 } 788 789 @Override 790 public void onMoveToOlder() { 791 moveToOlder(); 792 } 793 } 794 795 private void onCurrentMessageGone() { 796 switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) { 797 case Preferences.AUTO_ADVANCE_NEWER: 798 if (moveToNewer()) return; 799 break; 800 case Preferences.AUTO_ADVANCE_OLDER: 801 if (moveToOlder()) return; 802 break; 803 } 804 // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST. Go back to message list. 805 goBackToMailbox(); 806 } 807 808 /** 809 * Potentially create a new {@link MessageOrderManager}; if it's not already started or if 810 * the account has changed, and sync it to the current message. 811 */ 812 private void updateMessageOrderManager() { 813 if (!isMessageViewInstalled()) { 814 return; 815 } 816 final long mailboxId = getMessageListMailboxId(); 817 if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) { 818 stopMessageOrderManager(); 819 mOrderManager = 820 new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback); 821 } 822 mOrderManager.moveTo(getMessageId()); 823 } 824 825 private class MessageOrderManagerCallback implements MessageOrderManager.Callback { 826 @Override 827 public void onMessagesChanged() { 828 updateNavigationArrows(); 829 } 830 831 @Override 832 public void onMessageNotFound() { 833 // Current message gone. 834 goBackToMailbox(); 835 } 836 } 837 838 /** 839 * Stop {@link MessageOrderManager}. 840 */ 841 private void stopMessageOrderManager() { 842 if (mOrderManager != null) { 843 mOrderManager.close(); 844 mOrderManager = null; 845 } 846 } 847 848 /** 849 * Disable/enable the move-to-newer/older buttons. 850 */ 851 private void updateNavigationArrows() { 852 if (mOrderManager == null) { 853 // shouldn't happen, but just in case 854 mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0); 855 } else { 856 mMessageCommandButtons.enableNavigationButtons( 857 mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(), 858 mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount()); 859 } 860 } 861 862 private boolean moveToOlder() { 863 if ((mOrderManager != null) && mOrderManager.moveToOlder()) { 864 updateMessageView(mOrderManager.getCurrentMessageId()); 865 return true; 866 } 867 return false; 868 } 869 870 private boolean moveToNewer() { 871 if ((mOrderManager != null) && mOrderManager.moveToNewer()) { 872 updateMessageView(mOrderManager.getCurrentMessageId()); 873 return true; 874 } 875 return false; 876 } 877 878 /** {@inheritDoc} */ 879 @Override 880 public boolean onBackPressed(boolean isSystemBackKey) { 881 if (mThreePane.onBackPressed(isSystemBackKey)) { 882 return true; 883 } else if (navigateToParentMailboxList()) { 884 return true; 885 } 886 return false; 887 } 888 889 private boolean navigateToParentMailboxList() { 890 if (!isMailboxListInstalled() || mMailboxListFragment.isRoot()) { 891 return false; 892 } 893 super.navigateToParentMailboxList(mMailboxListFragment.getParentMailboxId()); 894 return true; 895 } 896 897 /** 898 * Handles the "refresh" option item. Opens the settings activity. 899 * TODO used by experimental code in the activity -- otherwise can be private. 900 */ 901 @Override 902 public void onRefresh() { 903 // Cancel previously running instance if any. 904 new RefreshTask(mTaskTracker, mActivity, getActualAccountId(), 905 getMessageListMailboxId()).cancelPreviousAndExecuteParallel(); 906 } 907 908 /** 909 * Class to handle refresh. 910 * 911 * When the user press "refresh", 912 * <ul> 913 * <li>Refresh the current mailbox, if it's refreshable. (e.g. don't refresh combined inbox, 914 * drafts, etc. 915 * <li>Refresh the mailbox list, if it hasn't been refreshed in the last 916 * {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 917 * <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last 918 * {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 919 * </ul> 920 */ 921 @VisibleForTesting 922 static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> { 923 private final Clock mClock; 924 private final Context mContext; 925 private final long mAccountId; 926 private final long mMailboxId; 927 private final RefreshManager mRefreshManager; 928 @VisibleForTesting 929 long mInboxId; 930 931 public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 932 long mailboxId) { 933 this(tracker, context, accountId, mailboxId, Clock.INSTANCE, 934 RefreshManager.getInstance(context)); 935 } 936 937 @VisibleForTesting 938 RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 939 long mailboxId, Clock clock, RefreshManager refreshManager) { 940 super(tracker); 941 mClock = clock; 942 mContext = context; 943 mRefreshManager = refreshManager; 944 mAccountId = accountId; 945 mMailboxId = mailboxId; 946 } 947 948 /** 949 * Do DB access on a worker thread. 950 */ 951 @Override 952 protected Boolean doInBackground(Void... params) { 953 mInboxId = Account.getInboxId(mContext, mAccountId); 954 return Mailbox.isRefreshable(mContext, mMailboxId); 955 } 956 957 /** 958 * Do the actual refresh. 959 */ 960 @Override 961 protected void onPostExecute(Boolean isCurrentMailboxRefreshable) { 962 if (isCancelled() || isCurrentMailboxRefreshable == null) { 963 return; 964 } 965 if (isCurrentMailboxRefreshable) { 966 mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false); 967 } 968 // Refresh mailbox list 969 if (mAccountId != Account.NO_ACCOUNT) { 970 if (shouldRefreshMailboxList()) { 971 mRefreshManager.refreshMailboxList(mAccountId); 972 } 973 } 974 // Refresh inbox 975 if (shouldAutoRefreshInbox()) { 976 mRefreshManager.refreshMessageList(mAccountId, mInboxId, false); 977 } 978 } 979 980 /** 981 * @return true if the mailbox list of the current account hasn't been refreshed 982 * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 983 */ 984 @VisibleForTesting 985 boolean shouldRefreshMailboxList() { 986 if (mRefreshManager.isMailboxListRefreshing(mAccountId)) { 987 return false; 988 } 989 final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId) 990 + MAILBOX_REFRESH_MIN_INTERVAL; 991 if (nextRefreshTime > mClock.getTime()) { 992 return false; 993 } 994 return true; 995 } 996 997 /** 998 * @return true if the inbox of the current account hasn't been refreshed 999 * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 1000 */ 1001 @VisibleForTesting 1002 boolean shouldAutoRefreshInbox() { 1003 if (mInboxId == mMailboxId) { 1004 return false; // Current ID == inbox. No need to auto-refresh. 1005 } 1006 if (mRefreshManager.isMessageListRefreshing(mInboxId)) { 1007 return false; 1008 } 1009 final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId) 1010 + INBOX_AUTO_REFRESH_MIN_INTERVAL; 1011 if (nextRefreshTime > mClock.getTime()) { 1012 return false; 1013 } 1014 return true; 1015 } 1016 } 1017 1018 private class ActionBarControllerCallback implements ActionBarController.Callback { 1019 @Override 1020 public String getCurrentMailboxName() { 1021 return mCurrentMailboxName; 1022 } 1023 1024 @Override 1025 public int getCurrentMailboxUnreadCount() { 1026 return mCurrentMailboxUnreadCount; 1027 } 1028 1029 @Override 1030 public long getUIAccountId() { 1031 return UIControllerTwoPane.this.getUIAccountId(); 1032 } 1033 1034 @Override 1035 public boolean isAccountSelected() { 1036 return UIControllerTwoPane.this.isAccountSelected(); 1037 } 1038 1039 @Override 1040 public void onAccountSelected(long accountId) { 1041 switchAccount(accountId); 1042 } 1043 1044 @Override 1045 public void onNoAccountsFound() { 1046 Welcome.actionStart(mActivity); 1047 mActivity.finish(); 1048 } 1049 1050 @Override 1051 public boolean shouldShowMailboxName() { 1052 // Show when the left pane is hidden. 1053 return (mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0; 1054 } 1055 1056 @Override 1057 public boolean shouldShowUp() { 1058 final int visiblePanes = mThreePane.getVisiblePanes(); 1059 final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0); 1060 return leftPaneHidden || (isMailboxListInstalled() && !mMailboxListFragment.isRoot()); 1061 } 1062 } 1063} 1064