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