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