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