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