UIControllerTwoPane.java revision ea56ccf6ddbbcd6cbbc000822328f2fad1d3ff98
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.email.activity.setup.AccountSettingsXL; 26import com.android.emailcommon.Logging; 27import com.android.emailcommon.provider.EmailContent.Account; 28import com.android.emailcommon.provider.EmailContent.Mailbox; 29import com.android.emailcommon.utility.EmailAsyncTask; 30import com.android.emailcommon.utility.Utility; 31 32import android.app.ActionBar; 33import android.app.Activity; 34import android.app.Fragment; 35import android.app.FragmentManager; 36import android.app.FragmentTransaction; 37import android.app.LoaderManager.LoaderCallbacks; 38import android.content.Context; 39import android.content.Loader; 40import android.database.Cursor; 41import android.os.Bundle; 42import android.util.Log; 43import android.view.LayoutInflater; 44import android.view.Menu; 45import android.view.MenuInflater; 46import android.view.MenuItem; 47import android.view.View; 48import android.widget.TextView; 49 50import java.security.InvalidParameterException; 51import java.util.ArrayList; 52import java.util.Set; 53import java.util.Stack; 54 55/** 56 * UI Controller for x-large devices. Supports a multi-pane layout. 57 * 58 * Note: Always use {@link #commitFragmentTransaction} to commit fragment transactions. Currently 59 * we use synchronous transactions only, but we may want to switch back to asynchronous later. 60 * 61 * TODO: Test it. It's testable if we implement MockFragmentTransaction, which may be too early 62 * to do so at this point. (API may not be stable enough yet.) 63 * 64 * TODO Consider extracting out a separate class to manage the action bar 65 */ 66class UIControllerTwoPane implements 67 MailboxFinder.Callback, 68 ThreePaneLayout.Callback, 69 MailboxListFragment.Callback, 70 MessageListFragment.Callback, 71 MessageViewFragment.Callback { 72 private static final String BUNDLE_KEY_ACCOUNT_ID = "UIControllerTwoPane.state.account_id"; 73 private static final String BUNDLE_KEY_MAILBOX_ID = "UIControllerTwoPane.state.mailbox_id"; 74 private static final String BUNDLE_KEY_MESSAGE_ID = "UIControllerTwoPane.state.message_id"; 75 private static final String BUNDLE_KEY_MAILBOX_STACK 76 = "UIControllerTwoPane.state.mailbox_stack"; 77 78 /* package */ static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds 79 /* package */ static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds 80 81 private static final int LOADER_ID_ACCOUNT_LIST 82 = EmailActivity.UI_CONTROLLER_LOADER_ID_BASE + 0; 83 84 /** No account selected */ 85 static final long NO_ACCOUNT = -1; 86 /** No mailbox selected */ 87 static final long NO_MAILBOX = -1; 88 /** No message selected */ 89 static final long NO_MESSAGE = -1; 90 /** Current account id */ 91 private long mAccountId = NO_ACCOUNT; 92 93 // TODO Remove this instance variable and replace it with a call to mMessageListFragment to 94 // retrieve it's mailbox ID. There's no reason we should be duplicating data 95 /** 96 * The id of the currently viewed mailbox in the mailbox list fragment. 97 * IMPORTANT: Do not confuse this with the value returned by {@link #getMessageListMailboxId()} 98 * which is the mailbox id associated with the message list fragment. The two may be different. 99 */ 100 private long mMailboxListMailboxId = NO_MAILBOX; 101 102 /** Current message id */ 103 private long mMessageId = NO_MESSAGE; 104 105 // Action bar 106 private ActionBar mActionBar; 107 private AccountSelectorAdapter mAccountsSelectorAdapter; 108 private final ActionBarNavigationCallback mActionBarNavigationCallback = 109 new ActionBarNavigationCallback(); 110 private View mActionBarMailboxNameView; 111 private TextView mActionBarMailboxName; 112 private TextView mActionBarUnreadCount; 113 114 // Other UI elements 115 private ThreePaneLayout mThreePane; 116 117 /** 118 * Fragments that are installed. 119 * 120 * A fragment is installed when: 121 * - it is attached to the activity 122 * - the parent activity is created 123 * - and it is not scheduled to be removed. 124 * 125 * We set callbacks to fragments only when they are installed. 126 */ 127 private MailboxListFragment mMailboxListFragment; 128 private MessageListFragment mMessageListFragment; 129 private MessageViewFragment mMessageViewFragment; 130 131 private MessageCommandButtonView mMessageCommandButtons; 132 133 private MailboxFinder mMailboxFinder; 134 135 private final RefreshManager mRefreshManager; 136 private final RefreshListener mRefreshListener = new RefreshListener(); 137 private MessageOrderManager mOrderManager; 138 private final MessageOrderManagerCallback mMessageOrderManagerCallback = 139 new MessageOrderManagerCallback(); 140 /** Mailbox IDs that the user has navigated away from; used to provide "back" functionality */ 141 private final Stack<Long> mMailboxStack = new Stack<Long>(); 142 143 /** 144 * List of fragments that are restored by the framework while the activity is being re-created 145 * for configuration changes (e.g. screen rotation). We'll install them later when the activity 146 * is created in {@link #installRestoredFragments()}. 147 */ 148 private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>(); 149 150 /** 151 * Whether fragment installation should be hold. 152 * We hold installing fragments until {@link #installRestoredFragments()} is called. 153 */ 154 private boolean mHoldFragmentInstallation = true; 155 156 /** The owner activity */ 157 private final EmailActivity mActivity; 158 159 private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); 160 161 public UIControllerTwoPane(EmailActivity activity) { 162 mActivity = activity; 163 mRefreshManager = RefreshManager.getInstance(mActivity); 164 } 165 166 // MailboxFinder$Callback 167 @Override 168 public void onAccountNotFound() { 169 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 170 Log.d(Logging.LOG_TAG, "" + this + " onAccountNotFound()"); 171 } 172 // Shouldn't happen 173 } 174 175 @Override 176 public void onAccountSecurityHold(long accountId) { 177 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 178 Log.d(Logging.LOG_TAG, "" + this + " onAccountSecurityHold()"); 179 } 180 mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId, 181 true)); 182 } 183 184 @Override 185 public void onMailboxFound(long accountId, long mailboxId) { 186 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 187 Log.d(Logging.LOG_TAG, "" + this + " onMailboxFound()"); 188 } 189 updateMessageList(mailboxId, true, true); 190 } 191 192 @Override 193 public void onMailboxNotFound(long accountId) { 194 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 195 Log.d(Logging.LOG_TAG, "" + this + " onMailboxNotFound()"); 196 } 197 // TODO: handle more gracefully. 198 Log.e(Logging.LOG_TAG, "unable to find mailbox for account " + accountId); 199 } 200 201 @Override 202 public void onMailboxNotFound() { 203 // TODO: handle more gracefully. 204 Log.e(Logging.LOG_TAG, "unable to find mailbox"); 205 } 206 207 // ThreePaneLayoutCallback 208 @Override 209 public void onVisiblePanesChanged(int previousVisiblePanes) { 210 211 updateActionBar(); 212 213 // If the right pane is gone, remove the message view. 214 final int visiblePanes = mThreePane.getVisiblePanes(); 215 if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) && 216 ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) { 217 // Message view just got hidden 218 mMessageId = NO_MESSAGE; 219 if (mMessageListFragment != null) { 220 mMessageListFragment.setSelectedMessage(NO_MESSAGE); 221 } 222 uninstallMessageViewFragment(mActivity.getFragmentManager().beginTransaction()) 223 .commit(); 224 } 225 // Disable CAB when the message list is not visible. 226 if (mMessageListFragment != null) { 227 mMessageListFragment.onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0); 228 } 229 } 230 231 /** 232 * Update the action bar according to the current state. 233 * 234 * - Show/hide the "back" button next to the "Home" icon. 235 * - Show/hide the current mailbox name. 236 */ 237 private void updateActionBar() { 238 final int visiblePanes = mThreePane.getVisiblePanes(); 239 240 // If the left pane (mailbox list pane) is hidden, the back action on action bar will be 241 // enabled, and we also show the current mailbox name. 242 final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0); 243 boolean displayUp = leftPaneHidden || !mMailboxStack.isEmpty(); 244 mActionBar.setDisplayOptions(displayUp ? ActionBar.DISPLAY_HOME_AS_UP : 0, 245 ActionBar.DISPLAY_HOME_AS_UP); 246 mActionBarMailboxNameView.setVisibility(leftPaneHidden ? View.VISIBLE : View.GONE); 247 } 248 249 // MailboxListFragment$Callback 250 @Override 251 public void onMailboxSelected(long accountId, long mailboxId, boolean navigate, 252 boolean dragDrop) { 253 if (dragDrop) { 254 // We don't want to change the message list for D&D. 255 256 // STOPSHIP fixit: the new mailbox list created here doesn't know D&D is in progress. 257 258 updateMailboxList(accountId, mailboxId, true, 259 false /* don't clear message list and message view */); 260 } else if (mailboxId == NO_MAILBOX) { 261 // reload the top-level message list. Always implies navigate. 262 openAccount(accountId); 263 } else if (navigate) { 264 if (mMailboxStack.isEmpty() || mailboxId != mMailboxListMailboxId) { 265 // Don't navigate to the same mailbox id twice in a row 266 mMailboxStack.push(mMailboxListMailboxId); 267 openMailbox(accountId, mailboxId); 268 } 269 } else { 270 updateMessageList(mailboxId, true, true); 271 } 272 } 273 274 @Override 275 public void onAccountSelected(long accountId) { 276 // TODO openAccount should do the check eventually, but it's necessary for now. 277 if (accountId != getUIAccountId()) { 278 openAccount(accountId); 279 loadAccounts(); // update account spinner 280 } 281 } 282 283 @Override 284 public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) { 285 mActionBarMailboxName.setText(mailboxName); 286 287 // Note on action bar, we show only "unread count". Some mailboxes such as Outbox don't 288 // have the idea of "unread count", in which case we just omit the count. 289 mActionBarUnreadCount.setText( 290 UiUtilities.getMessageCountForUi(mActivity, unreadCount, true)); 291 } 292 293 // MessageListFragment$Callback 294 @Override 295 public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId, 296 int type) { 297 if (type == MessageListFragment.Callback.TYPE_DRAFT) { 298 MessageCompose.actionEditDraft(mActivity, messageId); 299 } else { 300 updateMessageView(messageId); 301 } 302 } 303 304 @Override 305 public void onEnterSelectionMode(boolean enter) { 306 } 307 308 /** 309 * Apply the auto-advance policy upon initation of a batch command that could potentially 310 * affect the currently selected conversation. 311 */ 312 @Override 313 public void onAdvancingOpAccepted(Set<Long> affectedMessages) { 314 if (!isMessageSelected()) { 315 // Do nothing if message view is not visible. 316 return; 317 } 318 319 int autoAdvanceDir = Preferences.getPreferences(mActivity).getAutoAdvanceDirection(); 320 if ((autoAdvanceDir == Preferences.AUTO_ADVANCE_MESSAGE_LIST) || (mOrderManager == null)) { 321 if (affectedMessages.contains(getMessageId())) { 322 goBackToMailbox(); 323 } 324 return; 325 } 326 327 // Navigate to the first unselected item in the appropriate direction. 328 switch (autoAdvanceDir) { 329 case Preferences.AUTO_ADVANCE_NEWER: 330 while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) { 331 if (!mOrderManager.moveToNewer()) { 332 goBackToMailbox(); 333 return; 334 } 335 } 336 updateMessageView(mOrderManager.getCurrentMessageId()); 337 break; 338 339 case Preferences.AUTO_ADVANCE_OLDER: 340 while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) { 341 if (!mOrderManager.moveToOlder()) { 342 goBackToMailbox(); 343 return; 344 } 345 } 346 updateMessageView(mOrderManager.getCurrentMessageId()); 347 break; 348 } 349 } 350 351 @Override 352 public void onListLoaded() { 353 } 354 355 // MessageViewFragment$Callback 356 @Override 357 public void onMessageViewShown(int mailboxType) { 358 updateMessageOrderManager(); 359 updateNavigationArrows(); 360 } 361 362 @Override 363 public void onMessageViewGone() { 364 stopMessageOrderManager(); 365 } 366 367 @Override 368 public boolean onUrlInMessageClicked(String url) { 369 return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId()); 370 } 371 372 @Override 373 public void onMessageSetUnread() { 374 goBackToMailbox(); 375 } 376 377 @Override 378 public void onMessageNotExists() { 379 goBackToMailbox(); 380 } 381 382 @Override 383 public void onLoadMessageStarted() { 384 // TODO Any nice UI for this? 385 } 386 387 @Override 388 public void onLoadMessageFinished() { 389 // TODO Any nice UI for this? 390 } 391 392 @Override 393 public void onLoadMessageError(String errorMessage) { 394 } 395 396 @Override 397 public void onRespondedToInvite(int response) { 398 onCurrentMessageGone(); 399 } 400 401 @Override 402 public void onCalendarLinkClicked(long epochEventStartTime) { 403 ActivityHelper.openCalendar(mActivity, epochEventStartTime); 404 } 405 406 @Override 407 public void onBeforeMessageGone() { 408 onCurrentMessageGone(); 409 } 410 411 @Override 412 public void onForward() { 413 MessageCompose.actionForward(mActivity, getMessageId()); 414 } 415 416 @Override 417 public void onReply() { 418 MessageCompose.actionReply(mActivity, getMessageId(), false); 419 } 420 421 @Override 422 public void onReplyAll() { 423 MessageCompose.actionReply(mActivity, getMessageId(), true); 424 } 425 426 /** 427 * Must be called just after the activity sets up the content view. 428 * 429 * (Due to the complexity regarding class/activity initialization order, we can't do this in 430 * the constructor.) TODO this should no longer be true when we merge activities. 431 */ 432 public void onActivityViewReady() { 433 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 434 Log.d(Logging.LOG_TAG, "" + this + " onActivityViewReady"); 435 } 436 // Set up action bar 437 mActionBar = mActivity.getActionBar(); 438 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME); 439 440 // Set a view for the current mailbox to the action bar. 441 final LayoutInflater inflater = LayoutInflater.from(mActivity); 442 mActionBarMailboxNameView = inflater.inflate(R.layout.action_bar_current_mailbox, null); 443 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM); 444 final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams( 445 ActionBar.LayoutParams.WRAP_CONTENT, 446 ActionBar.LayoutParams.MATCH_PARENT); 447 customViewLayout.setMargins(mActivity.getResources().getDimensionPixelSize( 448 R.dimen.action_bar_mailbox_name_left_margin) , 0, 0, 0); 449 mActionBar.setCustomView(mActionBarMailboxNameView, customViewLayout); 450 451 mActionBarMailboxName = 452 (TextView) mActionBarMailboxNameView.findViewById(R.id.mailbox_name); 453 mActionBarUnreadCount = 454 (TextView) mActionBarMailboxNameView.findViewById(R.id.unread_count); 455 456 457 // Set up content 458 mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane); 459 mThreePane.setCallback(this); 460 461 mMessageCommandButtons = mThreePane.getMessageCommandButtons(); 462 mMessageCommandButtons.setCallback(new CommandButtonCallback()); 463 } 464 465 /** 466 * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 467 * 468 * @see #getActualAccountId() 469 */ 470 public long getUIAccountId() { 471 return mAccountId; 472 } 473 474 /** 475 * @return the currently selected account ID. If the current view is the combined view, 476 * it'll return {@link #NO_ACCOUNT}. 477 * 478 * @see #getUIAccountId() 479 */ 480 public long getActualAccountId() { 481 return mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW ? NO_ACCOUNT : mAccountId; 482 } 483 484 /** 485 * Returns the id of the mailbox used for the message list fragment. 486 * IMPORTANT: Do not confuse this with {@link #mMailboxListMailboxId} which is the id used 487 * for the mailbox list. The two may be different. 488 */ 489 public long getMessageListMailboxId() { 490 return (mMessageListFragment == null) 491 ? Mailbox.NO_MAILBOX 492 : mMessageListFragment.getMailboxId(); 493 } 494 495 public long getMessageId() { 496 return mMessageId; 497 } 498 499 /** 500 * @return true if an account is selected, or the current view is the combined view. 501 */ 502 public boolean isAccountSelected() { 503 return getUIAccountId() != NO_ACCOUNT; 504 } 505 506 public boolean isMailboxSelected() { 507 return getMessageListMailboxId() != NO_MAILBOX; 508 } 509 510 public boolean isMessageSelected() { 511 return getMessageId() != NO_MESSAGE; 512 } 513 514 /** 515 * @return true if refresh is in progress for the current mailbox. 516 */ 517 public boolean isRefreshInProgress() { 518 long messageListMailboxId = getMessageListMailboxId(); 519 return (messageListMailboxId >= 0) 520 && mRefreshManager.isMessageListRefreshing(messageListMailboxId); 521 } 522 523 /** 524 * @return true if the UI should enable the "refresh" command. 525 */ 526 public boolean isRefreshEnabled() { 527 // - Don't show for combined inboxes, but 528 // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list 529 return -1 != getActualAccountId(); 530 } 531 532 /** 533 * Called by the host activity at the end of {@link Activity#onCreate}. 534 */ 535 public void onActivityCreated() { 536 mRefreshManager.registerListener(mRefreshListener); 537 loadAccounts(); 538 } 539 540 /** 541 * Install all the fragments kept in {@link #mRestoredFragments}. 542 * 543 * Must be called at the end of {@link EmailActivity#onCreate}. 544 */ 545 public void installRestoredFragments() { 546 mHoldFragmentInstallation = false; 547 548 // Install all the fragments restored by the framework. 549 for (Fragment fragment : mRestoredFragments) { 550 installFragment(fragment); 551 } 552 mRestoredFragments.clear(); 553 } 554 555 /** 556 * Called by {@link EmailActivity} when a {@link Fragment} is attached. 557 * 558 * If the activity has already been created, we initialize the fragment here. Otherwise we 559 * keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's 560 * onCreate. 561 */ 562 public void onAttachFragment(Fragment fragment) { 563 if (mHoldFragmentInstallation) { 564 // Fragment being restored by the framework during the activity recreation. 565 mRestoredFragments.add(fragment); 566 return; 567 } 568 installFragment(fragment); 569 } 570 571 /** 572 * Called from {@link EmailActivity#onStart}. 573 */ 574 public void onStart() { 575 if (isMessageSelected()) { 576 updateMessageOrderManager(); 577 } 578 } 579 580 /** 581 * Called from {@link EmailActivity#onResume}. 582 */ 583 public void onResume() { 584 updateActionBar(); 585 } 586 587 /** 588 * Called from {@link EmailActivity#onPause}. 589 */ 590 public void onPause() { 591 } 592 593 /** 594 * Called from {@link EmailActivity#onStop}. 595 */ 596 public void onStop() { 597 stopMessageOrderManager(); 598 } 599 600 /** 601 * Called from {@link EmailActivity#onDestroy}. 602 */ 603 public void onDestroy() { 604 mHoldFragmentInstallation = true; // No more fragment installation. 605 mTaskTracker.cancellAllInterrupt(); 606 closeMailboxFinder(); 607 mRefreshManager.unregisterListener(mRefreshListener); 608 } 609 610 public void onSaveInstanceState(Bundle outState) { 611 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 612 Log.d(Logging.LOG_TAG, "" + this + " onSaveInstanceState"); 613 } 614 outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId); 615 outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxListMailboxId); 616 outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId); 617 if (!mMailboxStack.isEmpty()) { 618 // Save the mailbox stack 619 long[] mailboxIds = Utility.toPrimitiveLongArray(mMailboxStack); 620 outState.putLongArray(BUNDLE_KEY_MAILBOX_STACK, mailboxIds); 621 } 622 } 623 624 public void restoreInstanceState(Bundle savedInstanceState) { 625 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 626 Log.d(Logging.LOG_TAG, "" + this + " restoreInstanceState"); 627 } 628 mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, NO_ACCOUNT); 629 mMailboxListMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, NO_MAILBOX); 630 mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, NO_MESSAGE); 631 long[] mailboxIds = savedInstanceState.getLongArray(BUNDLE_KEY_MAILBOX_STACK); 632 if (mailboxIds != null) { 633 // Restore the mailbox stack; ugly hack to get around 'Long' versus 'long' 634 mMailboxStack.clear(); 635 for (long id : mailboxIds) { 636 mMailboxStack.push(id); 637 } 638 } 639 640 // STOPSHIP If MailboxFinder is still running, it needs restarting after loadState(). 641 // This probably means we need to start MailboxFinder if mMailboxId == -1. 642 } 643 644 private void installFragment(Fragment fragment) { 645 if (fragment instanceof MailboxListFragment) { 646 mMailboxListFragment = (MailboxListFragment) fragment; 647 mMailboxListFragment.setCallback(this); 648 } else if (fragment instanceof MessageListFragment) { 649 mMessageListFragment = (MessageListFragment) fragment; 650 mMessageListFragment.setCallback(this); 651 } else if (fragment instanceof MessageViewFragment) { 652 mMessageViewFragment = (MessageViewFragment) fragment; 653 mMessageViewFragment.setCallback(this); 654 } else { 655 // Ignore -- uninteresting fragments such as dialogs. 656 } 657 } 658 659 private FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) { 660 if (mMailboxListFragment != null) { 661 ft.remove(mMailboxListFragment); 662 mMailboxListFragment.setCallback(null); 663 mMailboxListFragment = null; 664 } 665 return ft; 666 } 667 668 private FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) { 669 if (mMessageListFragment != null) { 670 ft.remove(mMessageListFragment); 671 mMessageListFragment.setCallback(null); 672 mMessageListFragment = null; 673 } 674 return ft; 675 } 676 677 private FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) { 678 if (mMessageViewFragment != null) { 679 ft.remove(mMessageViewFragment); 680 mMessageViewFragment.setCallback(null); 681 mMessageViewFragment = null; 682 } 683 return ft; 684 } 685 686 private void commitFragmentTransaction(FragmentTransaction ft) { 687 ft.commit(); 688 mActivity.getFragmentManager().executePendingTransactions(); 689 } 690 691 /** 692 * Show the default view for the account. 693 * 694 * On two-pane, it's the account's root mailboxes on the left pane with Inbox on the right pane. 695 * 696 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 697 * Must never be {@link #NO_ACCOUNT}. 698 */ 699 public void openAccount(long accountId) { 700 mMailboxStack.clear(); 701 updateActionBar(); 702 open(accountId, NO_MAILBOX, NO_MESSAGE); 703 } 704 705 /** 706 * Opens the given mailbox. on two-pane, this will update both the mailbox list and the 707 * message list. 708 * 709 * NOTE: It's assumed that the mailbox is associated with the specified account. If the 710 * mailbox is not associated with the account, the behaviour is undefined. 711 */ 712 private void openMailbox(long accountId, long mailboxId) { 713 updateActionBar(); 714 updateMailboxList(accountId, mailboxId, true, true); 715 updateMessageList(mailboxId, true, true); 716 } 717 718 /** 719 * Loads the given account and optionally selects the given mailbox and message. Used to open 720 * a particular view at a request from outside of the activity, such as the widget. 721 * 722 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 723 * Must never be {@link #NO_ACCOUNT}. 724 * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox. 725 * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message. 726 */ 727 public void open(long accountId, long mailboxId, long messageId) { 728 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 729 Log.d(Logging.LOG_TAG, "" + this + " open accountId=" + accountId 730 + " mailboxId=" + mailboxId + " messageId=" + messageId); 731 } 732 if (accountId == NO_ACCOUNT) { 733 throw new IllegalArgumentException(); 734 } else if (mailboxId == NO_MAILBOX) { 735 updateMailboxList(accountId, NO_MAILBOX, true, true); 736 737 // Show the appropriate message list 738 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 739 // When opening the Combined view, the right pane will be "combined inbox". 740 updateMessageList(Mailbox.QUERY_ALL_INBOXES, true, true); 741 } else { 742 // Try to find the inbox for the account 743 closeMailboxFinder(); 744 mMailboxFinder = new MailboxFinder(mActivity, mAccountId, Mailbox.TYPE_INBOX, this); 745 mMailboxFinder.startLookup(); 746 } 747 } else if (messageId == NO_MESSAGE) { 748 // STOPSHIP Use the appropriate parent mailbox ID 749 updateMailboxList(accountId, mailboxId, true, true); 750 updateMessageList(mailboxId, true, true); 751 } else { 752 // STOPSHIP Use the appropriate parent mailbox ID 753 updateMailboxList(accountId, mailboxId, false, true); 754 updateMessageList(mailboxId, false, true); 755 updateMessageView(messageId); 756 } 757 } 758 759 /** 760 * Pre-fragment transaction check. 761 * 762 * @throw IllegalStateException if updateXxx methods can't be called in the current state. 763 */ 764 private void preFragmentTransactionCheck() { 765 if (mHoldFragmentInstallation) { 766 // Code assumes mMailboxListFragment/etc are set right within the 767 // commitFragmentTransaction() call (because we use synchronous transaction), 768 // so updateXxx() can't be called if fragments are not installable yet. 769 throw new IllegalStateException(); 770 } 771 } 772 773 /** 774 * Loads the given account and optionally selects the given mailbox and message. If the 775 * specified account is already selected, no actions will be performed unless 776 * <code>forceReload</code> is <code>true</code>. 777 * 778 * @param accountId ID of the account to load. Must never be {@link #NO_ACCOUNT}. 779 * @param parentMailboxId ID of the mailbox to use as the parent mailbox. Pass 780 * {@link #NO_MAILBOX} to show the root mailboxes. 781 * @param changeVisiblePane if true, the message view will be hidden. 782 * @param clearDependentPane if true, the message list and the message view will be cleared 783 */ 784 785 // TODO The name "updateMailboxList" is misleading, as it also updates members such as 786 // mAccountId. We need better structure but let's do that after refactoring 787 // MailboxListFragment.onMailboxSelected, and removed the UI callbacks such as 788 // TargetActivity.onAccountChanged. 789 790 private void updateMailboxList(long accountId, long parentMailboxId, 791 boolean changeVisiblePane, boolean clearDependentPane) { 792 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 793 Log.d(Logging.LOG_TAG, "" + this + " updateMailboxList accountId=" + accountId 794 + " parentMailboxId=" + parentMailboxId); 795 } 796 preFragmentTransactionCheck(); 797 if (accountId == NO_ACCOUNT) { 798 throw new InvalidParameterException(); 799 } 800 801 // TODO Check if the current fragment has been initialized with the same parameters, and 802 // then return. 803 804 mAccountId = accountId; 805 mMailboxListMailboxId = parentMailboxId; 806 807 // Open mailbox list, remove message list / message view 808 final FragmentManager fm = mActivity.getFragmentManager(); 809 final FragmentTransaction ft = fm.beginTransaction(); 810 uninstallMailboxListFragment(ft); 811 if (clearDependentPane) { 812 mMessageId = NO_MESSAGE; 813 uninstallMessageListFragment(ft); 814 uninstallMessageViewFragment(ft); 815 } 816 ft.add(mThreePane.getLeftPaneId(), 817 MailboxListFragment.newInstance(getUIAccountId(), parentMailboxId)); 818 commitFragmentTransaction(ft); 819 820 if (changeVisiblePane) { 821 mThreePane.showLeftPane(); 822 } 823 updateRefreshProgress(); 824 } 825 826 /** 827 * Go back to a mailbox list view. If a message view is currently active, it will 828 * be hidden. 829 */ 830 private void goBackToMailbox() { 831 if (isMessageSelected()) { 832 mThreePane.showLeftPane(); // Show mailbox list 833 } 834 } 835 836 /** 837 * Selects the specified mailbox and optionally loads a message within it. If a message is 838 * not loaded, a list of the messages contained within the mailbox is shown. Otherwise the 839 * given message is shown. If <code>navigateToMailbox<code> is <code>true</code>, the 840 * mailbox is navigated to and any contained mailboxes are shown. 841 * 842 * @param mailboxId ID of the mailbox to load. Must never be <code>0</code> or <code>-1</code>. 843 * @param changeVisiblePane if true, the message view will be hidden. 844 * @param clearDependentPane if true, the message view will be cleared 845 */ 846 private void updateMessageList(long mailboxId, boolean changeVisiblePane, 847 boolean clearDependentPane) { 848 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 849 Log.d(Logging.LOG_TAG, "" + this + " updateMessageList mMailboxId=" + mailboxId); 850 } 851 preFragmentTransactionCheck(); 852 if (mailboxId == 0 || mailboxId == -1) { 853 throw new InvalidParameterException(); 854 } 855 856 // TODO Check if the current fragment has been initialized with the same parameters, and 857 // then return. 858 859 final FragmentManager fm = mActivity.getFragmentManager(); 860 final FragmentTransaction ft = fm.beginTransaction(); 861 uninstallMessageListFragment(ft); 862 if (clearDependentPane) { 863 uninstallMessageViewFragment(ft); 864 mMessageId = NO_MESSAGE; 865 } 866 ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(mailboxId)); 867 commitFragmentTransaction(ft); 868 869 if (changeVisiblePane) { 870 mThreePane.showLeftPane(); 871 } 872 873 // TODO We shouldn't select the mailbox when we're updating the message list. These two 874 // functions should be done separately. Find a better location for this call to be done. 875 mMailboxListFragment.setSelectedMailbox(mailboxId); 876 updateRefreshProgress(); 877 } 878 879 /** 880 * Show a message on the message view. 881 * 882 * @param messageId ID of the mailbox to load. Must never be {@link #NO_MESSAGE}. 883 */ 884 private void updateMessageView(long messageId) { 885 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 886 Log.d(Logging.LOG_TAG, "" + this + " updateMessageView messageId=" + messageId); 887 } 888 preFragmentTransactionCheck(); 889 if (messageId == NO_MESSAGE) { 890 throw new InvalidParameterException(); 891 } 892 893 // TODO Check if the current fragment has been initialized with the same parameters, and 894 // then return. 895 896 // Update member 897 mMessageId = messageId; 898 899 // Open message 900 final FragmentManager fm = mActivity.getFragmentManager(); 901 final FragmentTransaction ft = fm.beginTransaction(); 902 uninstallMessageViewFragment(ft); 903 ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId)); 904 commitFragmentTransaction(ft); 905 906 mThreePane.showRightPane(); // Show message view 907 908 mMessageListFragment.setSelectedMessage(mMessageId); 909 } 910 911 private void closeMailboxFinder() { 912 if (mMailboxFinder != null) { 913 mMailboxFinder.cancel(); 914 mMailboxFinder = null; 915 } 916 } 917 918 private class CommandButtonCallback implements MessageCommandButtonView.Callback { 919 @Override 920 public void onMoveToNewer() { 921 moveToNewer(); 922 } 923 924 @Override 925 public void onMoveToOlder() { 926 moveToOlder(); 927 } 928 } 929 930 private void onCurrentMessageGone() { 931 switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) { 932 case Preferences.AUTO_ADVANCE_NEWER: 933 if (moveToNewer()) return; 934 break; 935 case Preferences.AUTO_ADVANCE_OLDER: 936 if (moveToOlder()) return; 937 break; 938 } 939 // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST. Go back to message list. 940 goBackToMailbox(); 941 } 942 943 /** 944 * Potentially create a new {@link MessageOrderManager}; if it's not already started or if 945 * the account has changed, and sync it to the current message. 946 */ 947 private void updateMessageOrderManager() { 948 if (!isMailboxSelected()) { 949 return; 950 } 951 final long mailboxId = getMessageListMailboxId(); 952 if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) { 953 stopMessageOrderManager(); 954 mOrderManager = 955 new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback); 956 } 957 if (isMessageSelected()) { 958 mOrderManager.moveTo(getMessageId()); 959 } 960 } 961 962 private class MessageOrderManagerCallback implements MessageOrderManager.Callback { 963 @Override 964 public void onMessagesChanged() { 965 updateNavigationArrows(); 966 } 967 968 @Override 969 public void onMessageNotFound() { 970 // Current message gone. 971 goBackToMailbox(); 972 } 973 } 974 975 /** 976 * Stop {@link MessageOrderManager}. 977 */ 978 private void stopMessageOrderManager() { 979 if (mOrderManager != null) { 980 mOrderManager.close(); 981 mOrderManager = null; 982 } 983 } 984 985 /** 986 * Disable/enable the move-to-newer/older buttons. 987 */ 988 private void updateNavigationArrows() { 989 if (mOrderManager == null) { 990 // shouldn't happen, but just in case 991 mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0); 992 } else { 993 mMessageCommandButtons.enableNavigationButtons( 994 mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(), 995 mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount()); 996 } 997 } 998 999 private boolean moveToOlder() { 1000 if ((mOrderManager != null) && mOrderManager.moveToOlder()) { 1001 updateMessageView(mOrderManager.getCurrentMessageId()); 1002 return true; 1003 } 1004 return false; 1005 } 1006 1007 private boolean moveToNewer() { 1008 if ((mOrderManager != null) && mOrderManager.moveToNewer()) { 1009 updateMessageView(mOrderManager.getCurrentMessageId()); 1010 return true; 1011 } 1012 return false; 1013 } 1014 1015 /** 1016 * Load account list for the action bar. 1017 * 1018 * If there's only one account configured, show the account name in the action bar. 1019 * If more than one account are configured, show a spinner in the action bar, and select the 1020 * current account. 1021 */ 1022 private void loadAccounts() { 1023 if (mAccountsSelectorAdapter == null) { 1024 mAccountsSelectorAdapter = new AccountSelectorAdapter(mActivity); 1025 } 1026 mActivity.getLoaderManager().initLoader(LOADER_ID_ACCOUNT_LIST, null, 1027 new LoaderCallbacks<Cursor>() { 1028 @Override 1029 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 1030 return AccountSelectorAdapter.createLoader(mActivity); 1031 } 1032 1033 @Override 1034 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 1035 updateAccountList(data); 1036 } 1037 1038 @Override 1039 public void onLoaderReset(Loader<Cursor> loader) { 1040 mAccountsSelectorAdapter.swapCursor(null); 1041 } 1042 }); 1043 } 1044 1045 /** 1046 * Called when the LOADER_ID_ACCOUNT_LIST loader loads the data. Update the account spinner 1047 * on the action bar. 1048 */ 1049 private void updateAccountList(Cursor accountsCursor) { 1050 final int count = accountsCursor.getCount(); 1051 if (count == 0) { 1052 // Open Welcome, which in turn shows the adding a new account screen. 1053 Welcome.actionStart(mActivity); 1054 mActivity.finish(); 1055 return; 1056 } 1057 1058 // If ony one acount, don't show dropdown. 1059 final ActionBar ab = mActionBar; 1060 if (count == 1) { 1061 accountsCursor.moveToFirst(); 1062 1063 // Show the account name as the title. 1064 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); 1065 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 1066 ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(accountsCursor)); 1067 return; 1068 } 1069 1070 // Find the currently selected account, and select it. 1071 int defaultSelection = 0; 1072 if (isAccountSelected()) { 1073 accountsCursor.moveToPosition(-1); 1074 int i = 0; 1075 while (accountsCursor.moveToNext()) { 1076 final long accountId = AccountSelectorAdapter.getAccountId(accountsCursor); 1077 if (accountId == getUIAccountId()) { 1078 defaultSelection = i; 1079 break; 1080 } 1081 i++; 1082 } 1083 } 1084 1085 // Update the dropdown list. 1086 mAccountsSelectorAdapter.swapCursor(accountsCursor); 1087 1088 // Don't show the title. 1089 ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); 1090 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 1091 ab.setListNavigationCallbacks(mAccountsSelectorAdapter, mActionBarNavigationCallback); 1092 ab.setSelectedNavigationItem(defaultSelection); 1093 } 1094 1095 private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener { 1096 @Override 1097 public boolean onNavigationItemSelected(int itemPosition, long accountId) { 1098 // TODO openAccount should do the check eventually, but it's necessary for now. 1099 if (accountId != getUIAccountId()) { 1100 openAccount(accountId); 1101 } 1102 return true; 1103 } 1104 } 1105 1106 /** 1107 * Handles {@link android.app.Activity#onCreateOptionsMenu} callback. 1108 */ 1109 public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { 1110 inflater.inflate(R.menu.message_list_xl_option, menu); 1111 return true; 1112 } 1113 1114 /** 1115 * Handles {@link android.app.Activity#onPrepareOptionsMenu} callback. 1116 */ 1117 public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { 1118 ActivityHelper.updateRefreshMenuIcon(menu.findItem(R.id.refresh), 1119 isRefreshEnabled(), 1120 isRefreshInProgress()); 1121 return true; 1122 } 1123 1124 /** 1125 * Handles {@link android.app.Activity#onOptionsItemSelected} callback. 1126 * 1127 * @return true if the option item is handled. 1128 */ 1129 public boolean onOptionsItemSelected(MenuItem item) { 1130 switch (item.getItemId()) { 1131 case android.R.id.home: 1132 // Comes from the action bar when the app icon on the left is pressed. 1133 // It works like a back press, but it won't close the activity. 1134 return onBackPressed(false); 1135 case R.id.compose: 1136 return onCompose(); 1137 case R.id.refresh: 1138 onRefresh(); 1139 return true; 1140 case R.id.account_settings: 1141 return onAccountSettings(); 1142 } 1143 return false; 1144 } 1145 1146 /** 1147 * Performs the back action. 1148 * 1149 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 1150 * <code>true</code> if it's caused by the "home" icon click on the action bar. 1151 */ 1152 public boolean onBackPressed(boolean isSystemBackKey) { 1153 if (mThreePane.onBackPressed(isSystemBackKey)) { 1154 return true; 1155 } else if (!mMailboxStack.isEmpty()) { 1156 long mailboxId = mMailboxStack.pop(); 1157 if (mailboxId == NO_MAILBOX) { 1158 // No mailbox; reload the top-level message list 1159 openAccount(mAccountId); 1160 } else { 1161 openMailbox(mAccountId, mailboxId); 1162 } 1163 return true; 1164 } 1165 return false; 1166 } 1167 1168 /** 1169 * Handles the "Compose" option item. Opens the message compose activity. 1170 */ 1171 private boolean onCompose() { 1172 if (!isAccountSelected()) { 1173 return false; // this shouldn't really happen 1174 } 1175 MessageCompose.actionCompose(mActivity, getActualAccountId()); 1176 return true; 1177 } 1178 1179 /** 1180 * Handles the "Compose" option item. Opens the settings activity. 1181 */ 1182 private boolean onAccountSettings() { 1183 AccountSettingsXL.actionSettings(mActivity, getActualAccountId()); 1184 return true; 1185 } 1186 1187 /** 1188 * Handles the "refresh" option item. Opens the settings activity. 1189 */ 1190 // TODO used by experimental code in the activity -- otherwise can be private. 1191 public void onRefresh() { 1192 // Cancel previously running instance if any. 1193 new RefreshTask(mTaskTracker, mActivity, getActualAccountId(), 1194 getMessageListMailboxId()).cancelPreviousAndExecuteParallel(); 1195 } 1196 1197 /** 1198 * Start/stop the "refresh" animation on the action bar according to the current refresh state. 1199 * 1200 * (We start the animation if {@link UIControllerTwoPane#isRefreshInProgress} returns true, 1201 * and stop otherwise.) 1202 */ 1203 private void updateRefreshProgress() { 1204 mActivity.invalidateOptionsMenu(); 1205 } 1206 1207 private class RefreshListener 1208 implements RefreshManager.Listener { 1209 @Override 1210 public void onMessagingError(final long accountId, long mailboxId, final String message) { 1211 updateRefreshProgress(); 1212 } 1213 1214 @Override 1215 public void onRefreshStatusChanged(long accountId, long mailboxId) { 1216 updateRefreshProgress(); 1217 } 1218 } 1219 1220 /** 1221 * Class to handle refresh. 1222 * 1223 * When the user press "refresh", 1224 * <ul> 1225 * <li>Refresh the current mailbox, if it's refreshable. (e.g. don't refresh combined inbox, 1226 * drafts, etc. 1227 * <li>Refresh the mailbox list, if it hasn't been refreshed in the last 1228 * {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 1229 * <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last 1230 * {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 1231 * </ul> 1232 */ 1233 /* package */ static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> { 1234 private final Clock mClock; 1235 private final Context mContext; 1236 private final long mAccountId; 1237 private final long mMailboxId; 1238 private final RefreshManager mRefreshManager; 1239 /* package */ long mInboxId; 1240 1241 public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 1242 long mailboxId) { 1243 this(tracker, context, accountId, mailboxId, Clock.INSTANCE, 1244 RefreshManager.getInstance(context)); 1245 } 1246 1247 /* package */ RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 1248 long mailboxId, Clock clock, RefreshManager refreshManager) { 1249 super(tracker); 1250 mClock = clock; 1251 mContext = context; 1252 mRefreshManager = refreshManager; 1253 mAccountId = accountId; 1254 mMailboxId = mailboxId; 1255 } 1256 1257 /** 1258 * Do DB access on a worker thread. 1259 */ 1260 @Override 1261 protected Boolean doInBackground(Void... params) { 1262 mInboxId = Account.getInboxId(mContext, mAccountId); 1263 return Mailbox.isRefreshable(mContext, mMailboxId); 1264 } 1265 1266 /** 1267 * Do the actual refresh. 1268 */ 1269 @Override 1270 protected void onPostExecute(Boolean isCurrentMailboxRefreshable) { 1271 if (isCancelled() || isCurrentMailboxRefreshable == null) { 1272 return; 1273 } 1274 if (isCurrentMailboxRefreshable) { 1275 mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false); 1276 } 1277 // Refresh mailbox list 1278 if (mAccountId != -1) { 1279 if (shouldRefreshMailboxList()) { 1280 mRefreshManager.refreshMailboxList(mAccountId); 1281 } 1282 } 1283 // Refresh inbox 1284 if (shouldAutoRefreshInbox()) { 1285 mRefreshManager.refreshMessageList(mAccountId, mInboxId, false); 1286 } 1287 } 1288 1289 /** 1290 * @return true if the mailbox list of the current account hasn't been refreshed 1291 * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 1292 */ 1293 /* package */ boolean shouldRefreshMailboxList() { 1294 if (mRefreshManager.isMailboxListRefreshing(mAccountId)) { 1295 return false; 1296 } 1297 final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId) 1298 + MAILBOX_REFRESH_MIN_INTERVAL; 1299 if (nextRefreshTime > mClock.getTime()) { 1300 return false; 1301 } 1302 return true; 1303 } 1304 1305 /** 1306 * @return true if the inbox of the current account hasn't been refreshed 1307 * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 1308 */ 1309 /* package */ boolean shouldAutoRefreshInbox() { 1310 if (mInboxId == mMailboxId) { 1311 return false; // Current ID == inbox. No need to auto-refresh. 1312 } 1313 if (mRefreshManager.isMessageListRefreshing(mInboxId)) { 1314 return false; 1315 } 1316 final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId) 1317 + INBOX_AUTO_REFRESH_MIN_INTERVAL; 1318 if (nextRefreshTime > mClock.getTime()) { 1319 return false; 1320 } 1321 return true; 1322 } 1323 } 1324} 1325