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