UIControllerTwoPane.java revision 075feb45562429cb8c7e19b43dc91e9778afeb24
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.getMailboxId(); 491 } 492 493 public long getMessageId() { 494 return mMessageId; 495 } 496 497 /** 498 * @return true if an account is selected, or the current view is the combined view. 499 */ 500 public boolean isAccountSelected() { 501 return getUIAccountId() != NO_ACCOUNT; 502 } 503 504 public boolean isMailboxSelected() { 505 return getMessageListMailboxId() != NO_MAILBOX; 506 } 507 508 public boolean isMessageSelected() { 509 return getMessageId() != NO_MESSAGE; 510 } 511 512 /** 513 * @return true if refresh is in progress for the current mailbox. 514 */ 515 public boolean isRefreshInProgress() { 516 long messageListMailboxId = getMessageListMailboxId(); 517 return (messageListMailboxId >= 0) 518 && mRefreshManager.isMessageListRefreshing(messageListMailboxId); 519 } 520 521 /** 522 * @return true if the UI should enable the "refresh" command. 523 */ 524 public boolean isRefreshEnabled() { 525 // - Don't show for combined inboxes, but 526 // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list 527 return -1 != getActualAccountId(); 528 } 529 530 /** 531 * Called by the host activity at the end of {@link Activity#onCreate}. 532 */ 533 public void onActivityCreated() { 534 mRefreshManager.registerListener(mRefreshListener); 535 loadAccounts(); 536 } 537 538 /** 539 * Install all the fragments kept in {@link #mRestoredFragments}. 540 * 541 * Must be called at the end of {@link EmailActivity#onCreate}. 542 */ 543 public void installRestoredFragments() { 544 mHoldFragmentInstallation = false; 545 546 // Install all the fragments restored by the framework. 547 for (Fragment fragment : mRestoredFragments) { 548 installFragment(fragment); 549 } 550 mRestoredFragments.clear(); 551 } 552 553 /** 554 * Called by {@link EmailActivity} when a {@link Fragment} is attached. 555 * 556 * If the activity has already been created, we initialize the fragment here. Otherwise we 557 * keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's 558 * onCreate. 559 */ 560 public void onAttachFragment(Fragment fragment) { 561 if (mHoldFragmentInstallation) { 562 // Fragment being restored by the framework during the activity recreation. 563 mRestoredFragments.add(fragment); 564 return; 565 } 566 installFragment(fragment); 567 } 568 569 /** 570 * Called from {@link EmailActivity#onStart}. 571 */ 572 public void onStart() { 573 if (isMessageSelected()) { 574 updateMessageOrderManager(); 575 } 576 } 577 578 /** 579 * Called from {@link EmailActivity#onResume}. 580 */ 581 public void onResume() { 582 updateActionBar(); 583 } 584 585 /** 586 * Called from {@link EmailActivity#onPause}. 587 */ 588 public void onPause() { 589 } 590 591 /** 592 * Called from {@link EmailActivity#onStop}. 593 */ 594 public void onStop() { 595 stopMessageOrderManager(); 596 } 597 598 /** 599 * Called from {@link EmailActivity#onDestroy}. 600 */ 601 public void onDestroy() { 602 mHoldFragmentInstallation = true; // No more fragment installation. 603 mTaskTracker.cancellAllInterrupt(); 604 closeMailboxFinder(); 605 mRefreshManager.unregisterListener(mRefreshListener); 606 } 607 608 public void onSaveInstanceState(Bundle outState) { 609 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 610 Log.d(Logging.LOG_TAG, "" + this + " onSaveInstanceState"); 611 } 612 outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId); 613 outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxListMailboxId); 614 outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId); 615 if (!mMailboxStack.isEmpty()) { 616 // Save the mailbox stack 617 long[] mailboxIds = Utility.toPrimitiveLongArray(mMailboxStack); 618 outState.putLongArray(BUNDLE_KEY_MAILBOX_STACK, mailboxIds); 619 } 620 } 621 622 public void restoreInstanceState(Bundle savedInstanceState) { 623 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 624 Log.d(Logging.LOG_TAG, "" + this + " restoreInstanceState"); 625 } 626 mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, NO_ACCOUNT); 627 mMailboxListMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, NO_MAILBOX); 628 mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, NO_MESSAGE); 629 long[] mailboxIds = savedInstanceState.getLongArray(BUNDLE_KEY_MAILBOX_STACK); 630 if (mailboxIds != null) { 631 // Restore the mailbox stack; ugly hack to get around 'Long' versus 'long' 632 mMailboxStack.clear(); 633 for (long id : mailboxIds) { 634 mMailboxStack.push(id); 635 } 636 } 637 638 // STOPSHIP If MailboxFinder is still running, it needs restarting after loadState(). 639 // This probably means we need to start MailboxFinder if mMailboxId == -1. 640 } 641 642 private void installFragment(Fragment fragment) { 643 if (fragment instanceof MailboxListFragment) { 644 mMailboxListFragment = (MailboxListFragment) fragment; 645 mMailboxListFragment.setCallback(this); 646 } else if (fragment instanceof MessageListFragment) { 647 mMessageListFragment = (MessageListFragment) fragment; 648 mMessageListFragment.setCallback(this); 649 } else if (fragment instanceof MessageViewFragment) { 650 mMessageViewFragment = (MessageViewFragment) fragment; 651 mMessageViewFragment.setCallback(this); 652 } else { 653 // Ignore -- uninteresting fragments such as dialogs. 654 } 655 } 656 657 private FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) { 658 if (mMailboxListFragment != null) { 659 ft.remove(mMailboxListFragment); 660 mMailboxListFragment.setCallback(null); 661 mMailboxListFragment = null; 662 } 663 return ft; 664 } 665 666 private FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) { 667 if (mMessageListFragment != null) { 668 ft.remove(mMessageListFragment); 669 mMessageListFragment.setCallback(null); 670 mMessageListFragment = null; 671 } 672 return ft; 673 } 674 675 private FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) { 676 if (mMessageViewFragment != null) { 677 ft.remove(mMessageViewFragment); 678 mMessageViewFragment.setCallback(null); 679 mMessageViewFragment = null; 680 } 681 return ft; 682 } 683 684 private void commitFragmentTransaction(FragmentTransaction ft) { 685 ft.commit(); 686 mActivity.getFragmentManager().executePendingTransactions(); 687 } 688 689 /** 690 * Show the default view for the account. 691 * 692 * On two-pane, it's the account's root mailboxes on the left pane with Inbox on the right pane. 693 * 694 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 695 * Must never be {@link #NO_ACCOUNT}. 696 */ 697 public void openAccount(long accountId) { 698 mMailboxStack.clear(); 699 updateActionBar(); 700 open(accountId, NO_MAILBOX, NO_MESSAGE); 701 } 702 703 /** 704 * Opens the given mailbox. on two-pane, this will update both the mailbox list and the 705 * message list. 706 * 707 * NOTE: It's assumed that the mailbox is associated with the specified account. If the 708 * mailbox is not associated with the account, the behaviour is undefined. 709 */ 710 private void openMailbox(long accountId, long mailboxId) { 711 updateActionBar(); 712 updateMailboxList(accountId, mailboxId, true, true); 713 updateMessageList(mailboxId, true, true); 714 } 715 716 /** 717 * Loads the given account and optionally selects the given mailbox and message. Used to open 718 * a particular view at a request from outside of the activity, such as the widget. 719 * 720 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 721 * Must never be {@link #NO_ACCOUNT}. 722 * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox. 723 * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message. 724 */ 725 public void open(long accountId, long mailboxId, long messageId) { 726 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 727 Log.d(Logging.LOG_TAG, "" + this + " open accountId=" + accountId 728 + " mailboxId=" + mailboxId + " messageId=" + messageId); 729 } 730 if (accountId == NO_ACCOUNT) { 731 throw new IllegalArgumentException(); 732 } else if (mailboxId == NO_MAILBOX) { 733 updateMailboxList(accountId, NO_MAILBOX, true, true); 734 735 // Show the appropriate message list 736 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 737 // When opening the Combined view, the right pane will be "combined inbox". 738 updateMessageList(Mailbox.QUERY_ALL_INBOXES, true, true); 739 } else { 740 // Try to find the inbox for the account 741 closeMailboxFinder(); 742 mMailboxFinder = new MailboxFinder(mActivity, mAccountId, Mailbox.TYPE_INBOX, this); 743 mMailboxFinder.startLookup(); 744 } 745 } else if (messageId == NO_MESSAGE) { 746 // STOPSHIP Use the appropriate parent mailbox ID 747 updateMailboxList(accountId, mailboxId, true, true); 748 updateMessageList(mailboxId, true, true); 749 } else { 750 // STOPSHIP Use the appropriate parent mailbox ID 751 updateMailboxList(accountId, mailboxId, false, true); 752 updateMessageList(mailboxId, false, true); 753 updateMessageView(messageId); 754 } 755 } 756 757 /** 758 * Pre-fragment transaction check. 759 * 760 * @throw IllegalStateException if updateXxx methods can't be called in the current state. 761 */ 762 private void preFragmentTransactionCheck() { 763 if (mHoldFragmentInstallation) { 764 // Code assumes mMailboxListFragment/etc are set right within the 765 // commitFragmentTransaction() call (because we use synchronous transaction), 766 // so updateXxx() can't be called if fragments are not installable yet. 767 throw new IllegalStateException(); 768 } 769 } 770 771 /** 772 * Loads the given account and optionally selects the given mailbox and message. If the 773 * specified account is already selected, no actions will be performed unless 774 * <code>forceReload</code> is <code>true</code>. 775 * 776 * @param accountId ID of the account to load. Must never be {@link #NO_ACCOUNT}. 777 * @param parentMailboxId ID of the mailbox to use as the parent mailbox. Pass 778 * {@link #NO_MAILBOX} to show the root mailboxes. 779 * @param changeVisiblePane if true, the message view will be hidden. 780 * @param clearDependentPane if true, the message list and the message view will be cleared 781 */ 782 783 // TODO The name "updateMailboxList" is misleading, as it also updates members such as 784 // mAccountId. We need better structure but let's do that after refactoring 785 // MailboxListFragment.onMailboxSelected, and removed the UI callbacks such as 786 // TargetActivity.onAccountChanged. 787 788 private void updateMailboxList(long accountId, long parentMailboxId, 789 boolean changeVisiblePane, boolean clearDependentPane) { 790 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 791 Log.d(Logging.LOG_TAG, "" + this + " updateMailboxList accountId=" + accountId 792 + " parentMailboxId=" + parentMailboxId); 793 } 794 preFragmentTransactionCheck(); 795 if (accountId == NO_ACCOUNT) { 796 throw new InvalidParameterException(); 797 } 798 799 // TODO Check if the current fragment has been initialized with the same parameters, and 800 // then return. 801 802 mAccountId = accountId; 803 mMailboxListMailboxId = parentMailboxId; 804 805 // Open mailbox list, remove message list / message view 806 final FragmentManager fm = mActivity.getFragmentManager(); 807 final FragmentTransaction ft = fm.beginTransaction(); 808 uninstallMailboxListFragment(ft); 809 if (clearDependentPane) { 810 mMessageId = NO_MESSAGE; 811 uninstallMessageListFragment(ft); 812 uninstallMessageViewFragment(ft); 813 } 814 ft.add(mThreePane.getLeftPaneId(), 815 MailboxListFragment.newInstance(getUIAccountId(), parentMailboxId)); 816 commitFragmentTransaction(ft); 817 818 if (changeVisiblePane) { 819 mThreePane.showLeftPane(); 820 } 821 updateRefreshProgress(); 822 } 823 824 /** 825 * Go back to a mailbox list view. If a message view is currently active, it will 826 * be hidden. 827 */ 828 private void goBackToMailbox() { 829 if (isMessageSelected()) { 830 mThreePane.showLeftPane(); // Show mailbox list 831 } 832 } 833 834 /** 835 * Selects the specified mailbox and optionally loads a message within it. If a message is 836 * not loaded, a list of the messages contained within the mailbox is shown. Otherwise the 837 * given message is shown. If <code>navigateToMailbox<code> is <code>true</code>, the 838 * mailbox is navigated to and any contained mailboxes are shown. 839 * 840 * @param mailboxId ID of the mailbox to load. Must never be <code>0</code> or <code>-1</code>. 841 * @param changeVisiblePane if true, the message view will be hidden. 842 * @param clearDependentPane if true, the message view will be cleared 843 */ 844 private void updateMessageList(long mailboxId, boolean changeVisiblePane, 845 boolean clearDependentPane) { 846 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 847 Log.d(Logging.LOG_TAG, "" + this + " updateMessageList mMailboxId=" + mailboxId); 848 } 849 preFragmentTransactionCheck(); 850 if (mailboxId == 0 || mailboxId == -1) { 851 throw new InvalidParameterException(); 852 } 853 854 // TODO Check if the current fragment has been initialized with the same parameters, and 855 // then return. 856 857 final FragmentManager fm = mActivity.getFragmentManager(); 858 final FragmentTransaction ft = fm.beginTransaction(); 859 uninstallMessageListFragment(ft); 860 if (clearDependentPane) { 861 uninstallMessageViewFragment(ft); 862 mMessageId = NO_MESSAGE; 863 } 864 ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(mailboxId)); 865 commitFragmentTransaction(ft); 866 867 if (changeVisiblePane) { 868 mThreePane.showLeftPane(); 869 } 870 871 // TODO We shouldn't select the mailbox when we're updating the message list. These two 872 // functions should be done separately. Find a better location for this call to be done. 873 mMailboxListFragment.setSelectedMailbox(mailboxId); 874 updateRefreshProgress(); 875 } 876 877 /** 878 * Show a message on the message view. 879 * 880 * @param messageId ID of the mailbox to load. Must never be {@link #NO_MESSAGE}. 881 */ 882 private void updateMessageView(long messageId) { 883 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 884 Log.d(Logging.LOG_TAG, "" + this + " updateMessageView messageId=" + messageId); 885 } 886 preFragmentTransactionCheck(); 887 if (messageId == NO_MESSAGE) { 888 throw new InvalidParameterException(); 889 } 890 891 // TODO Check if the current fragment has been initialized with the same parameters, and 892 // then return. 893 894 // Update member 895 mMessageId = messageId; 896 897 // Open message 898 final FragmentManager fm = mActivity.getFragmentManager(); 899 final FragmentTransaction ft = fm.beginTransaction(); 900 uninstallMessageViewFragment(ft); 901 ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId)); 902 commitFragmentTransaction(ft); 903 904 mThreePane.showRightPane(); // Show message view 905 906 mMessageListFragment.setSelectedMessage(mMessageId); 907 } 908 909 private void closeMailboxFinder() { 910 if (mMailboxFinder != null) { 911 mMailboxFinder.cancel(); 912 mMailboxFinder = null; 913 } 914 } 915 916 private class CommandButtonCallback implements MessageCommandButtonView.Callback { 917 @Override 918 public void onMoveToNewer() { 919 moveToNewer(); 920 } 921 922 @Override 923 public void onMoveToOlder() { 924 moveToOlder(); 925 } 926 } 927 928 private void onCurrentMessageGone() { 929 switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) { 930 case Preferences.AUTO_ADVANCE_NEWER: 931 if (moveToNewer()) return; 932 break; 933 case Preferences.AUTO_ADVANCE_OLDER: 934 if (moveToOlder()) return; 935 break; 936 } 937 // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST. Go back to message list. 938 goBackToMailbox(); 939 } 940 941 /** 942 * Potentially create a new {@link MessageOrderManager}; if it's not already started or if 943 * the account has changed, and sync it to the current message. 944 */ 945 private void updateMessageOrderManager() { 946 if (!isMailboxSelected()) { 947 return; 948 } 949 final long mailboxId = getMessageListMailboxId(); 950 if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) { 951 stopMessageOrderManager(); 952 mOrderManager = 953 new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback); 954 } 955 if (isMessageSelected()) { 956 mOrderManager.moveTo(getMessageId()); 957 } 958 } 959 960 private class MessageOrderManagerCallback implements MessageOrderManager.Callback { 961 @Override 962 public void onMessagesChanged() { 963 updateNavigationArrows(); 964 } 965 966 @Override 967 public void onMessageNotFound() { 968 // Current message gone. 969 goBackToMailbox(); 970 } 971 } 972 973 /** 974 * Stop {@link MessageOrderManager}. 975 */ 976 private void stopMessageOrderManager() { 977 if (mOrderManager != null) { 978 mOrderManager.close(); 979 mOrderManager = null; 980 } 981 } 982 983 /** 984 * Disable/enable the move-to-newer/older buttons. 985 */ 986 private void updateNavigationArrows() { 987 if (mOrderManager == null) { 988 // shouldn't happen, but just in case 989 mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0); 990 } else { 991 mMessageCommandButtons.enableNavigationButtons( 992 mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(), 993 mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount()); 994 } 995 } 996 997 private boolean moveToOlder() { 998 if ((mOrderManager != null) && mOrderManager.moveToOlder()) { 999 updateMessageView(mOrderManager.getCurrentMessageId()); 1000 return true; 1001 } 1002 return false; 1003 } 1004 1005 private boolean moveToNewer() { 1006 if ((mOrderManager != null) && mOrderManager.moveToNewer()) { 1007 updateMessageView(mOrderManager.getCurrentMessageId()); 1008 return true; 1009 } 1010 return false; 1011 } 1012 1013 /** 1014 * Load account list for the action bar. 1015 * 1016 * If there's only one account configured, show the account name in the action bar. 1017 * If more than one account are configured, show a spinner in the action bar, and select the 1018 * current account. 1019 */ 1020 private void loadAccounts() { 1021 if (mAccountsSelectorAdapter == null) { 1022 mAccountsSelectorAdapter = new AccountSelectorAdapter(mActivity); 1023 } 1024 mActivity.getLoaderManager().initLoader(LOADER_ID_ACCOUNT_LIST, null, 1025 new LoaderCallbacks<Cursor>() { 1026 @Override 1027 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 1028 return AccountSelectorAdapter.createLoader(mActivity); 1029 } 1030 1031 @Override 1032 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 1033 updateAccountList(data); 1034 } 1035 1036 @Override 1037 public void onLoaderReset(Loader<Cursor> loader) { 1038 mAccountsSelectorAdapter.swapCursor(null); 1039 } 1040 }); 1041 } 1042 1043 /** 1044 * Called when the LOADER_ID_ACCOUNT_LIST loader loads the data. Update the account spinner 1045 * on the action bar. 1046 */ 1047 private void updateAccountList(Cursor accountsCursor) { 1048 final int count = accountsCursor.getCount(); 1049 if (count == 0) { 1050 // Open Welcome, which in turn shows the adding a new account screen. 1051 Welcome.actionStart(mActivity); 1052 mActivity.finish(); 1053 return; 1054 } 1055 1056 // If ony one acount, don't show dropdown. 1057 final ActionBar ab = mActionBar; 1058 if (count == 1) { 1059 accountsCursor.moveToFirst(); 1060 1061 // Show the account name as the title. 1062 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); 1063 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 1064 ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(accountsCursor)); 1065 return; 1066 } 1067 1068 // Find the currently selected account, and select it. 1069 int defaultSelection = 0; 1070 if (isAccountSelected()) { 1071 accountsCursor.moveToPosition(-1); 1072 int i = 0; 1073 while (accountsCursor.moveToNext()) { 1074 final long accountId = AccountSelectorAdapter.getAccountId(accountsCursor); 1075 if (accountId == getUIAccountId()) { 1076 defaultSelection = i; 1077 break; 1078 } 1079 i++; 1080 } 1081 } 1082 1083 // Update the dropdown list. 1084 mAccountsSelectorAdapter.swapCursor(accountsCursor); 1085 1086 // Don't show the title. 1087 ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); 1088 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 1089 ab.setListNavigationCallbacks(mAccountsSelectorAdapter, mActionBarNavigationCallback); 1090 ab.setSelectedNavigationItem(defaultSelection); 1091 } 1092 1093 private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener { 1094 @Override 1095 public boolean onNavigationItemSelected(int itemPosition, long accountId) { 1096 // TODO openAccount should do the check eventually, but it's necessary for now. 1097 if (accountId != getUIAccountId()) { 1098 openAccount(accountId); 1099 } 1100 return true; 1101 } 1102 } 1103 1104 /** 1105 * Handles {@link android.app.Activity#onCreateOptionsMenu} callback. 1106 */ 1107 public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { 1108 inflater.inflate(R.menu.message_list_xl_option, menu); 1109 return true; 1110 } 1111 1112 /** 1113 * Handles {@link android.app.Activity#onPrepareOptionsMenu} callback. 1114 */ 1115 public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { 1116 ActivityHelper.updateRefreshMenuIcon(menu.findItem(R.id.refresh), 1117 isRefreshEnabled(), 1118 isRefreshInProgress()); 1119 return true; 1120 } 1121 1122 /** 1123 * Handles {@link android.app.Activity#onOptionsItemSelected} callback. 1124 * 1125 * @return true if the option item is handled. 1126 */ 1127 public boolean onOptionsItemSelected(MenuItem item) { 1128 switch (item.getItemId()) { 1129 case android.R.id.home: 1130 // Comes from the action bar when the app icon on the left is pressed. 1131 // It works like a back press, but it won't close the activity. 1132 return onBackPressed(false); 1133 case R.id.compose: 1134 return onCompose(); 1135 case R.id.refresh: 1136 onRefresh(); 1137 return true; 1138 case R.id.account_settings: 1139 return onAccountSettings(); 1140 } 1141 return false; 1142 } 1143 1144 /** 1145 * Performs the back action. 1146 * 1147 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 1148 * <code>true</code> if it's caused by the "home" icon click on the action bar. 1149 */ 1150 public boolean onBackPressed(boolean isSystemBackKey) { 1151 if (mThreePane.onBackPressed(isSystemBackKey)) { 1152 return true; 1153 } else if (!mMailboxStack.isEmpty()) { 1154 long mailboxId = mMailboxStack.pop(); 1155 if (mailboxId == NO_MAILBOX) { 1156 // No mailbox; reload the top-level message list 1157 openAccount(mAccountId); 1158 } else { 1159 openMailbox(mAccountId, mailboxId); 1160 } 1161 return true; 1162 } 1163 return false; 1164 } 1165 1166 /** 1167 * Handles the "Compose" option item. Opens the message compose activity. 1168 */ 1169 private boolean onCompose() { 1170 if (!isAccountSelected()) { 1171 return false; // this shouldn't really happen 1172 } 1173 MessageCompose.actionCompose(mActivity, getActualAccountId()); 1174 return true; 1175 } 1176 1177 /** 1178 * Handles the "Compose" option item. Opens the settings activity. 1179 */ 1180 private boolean onAccountSettings() { 1181 AccountSettingsXL.actionSettings(mActivity, getActualAccountId()); 1182 return true; 1183 } 1184 1185 /** 1186 * Handles the "refresh" option item. Opens the settings activity. 1187 */ 1188 // TODO used by experimental code in the activity -- otherwise can be private. 1189 public void onRefresh() { 1190 // Cancel previously running instance if any. 1191 new RefreshTask(mTaskTracker, mActivity, getActualAccountId(), 1192 getMessageListMailboxId()).cancelPreviousAndExecuteParallel(); 1193 } 1194 1195 /** 1196 * Start/stop the "refresh" animation on the action bar according to the current refresh state. 1197 * 1198 * (We start the animation if {@link UIControllerTwoPane#isRefreshInProgress} returns true, 1199 * and stop otherwise.) 1200 */ 1201 private void updateRefreshProgress() { 1202 mActivity.invalidateOptionsMenu(); 1203 } 1204 1205 private class RefreshListener 1206 implements RefreshManager.Listener { 1207 @Override 1208 public void onMessagingError(final long accountId, long mailboxId, final String message) { 1209 updateRefreshProgress(); 1210 } 1211 1212 @Override 1213 public void onRefreshStatusChanged(long accountId, long mailboxId) { 1214 updateRefreshProgress(); 1215 } 1216 } 1217 1218 /** 1219 * Class to handle refresh. 1220 * 1221 * When the user press "refresh", 1222 * <ul> 1223 * <li>Refresh the current mailbox, if it's refreshable. (e.g. don't refresh combined inbox, 1224 * drafts, etc. 1225 * <li>Refresh the mailbox list, if it hasn't been refreshed in the last 1226 * {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 1227 * <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last 1228 * {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 1229 * </ul> 1230 */ 1231 /* package */ static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> { 1232 private final Clock mClock; 1233 private final Context mContext; 1234 private final long mAccountId; 1235 private final long mMailboxId; 1236 private final RefreshManager mRefreshManager; 1237 /* package */ long mInboxId; 1238 1239 public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 1240 long mailboxId) { 1241 this(tracker, context, accountId, mailboxId, Clock.INSTANCE, 1242 RefreshManager.getInstance(context)); 1243 } 1244 1245 /* package */ RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId, 1246 long mailboxId, Clock clock, RefreshManager refreshManager) { 1247 super(tracker); 1248 mClock = clock; 1249 mContext = context; 1250 mRefreshManager = refreshManager; 1251 mAccountId = accountId; 1252 mMailboxId = mailboxId; 1253 } 1254 1255 /** 1256 * Do DB access on a worker thread. 1257 */ 1258 @Override 1259 protected Boolean doInBackground(Void... params) { 1260 mInboxId = Account.getInboxId(mContext, mAccountId); 1261 return Mailbox.isRefreshable(mContext, mMailboxId); 1262 } 1263 1264 /** 1265 * Do the actual refresh. 1266 */ 1267 @Override 1268 protected void onPostExecute(Boolean isCurrentMailboxRefreshable) { 1269 if (isCancelled() || isCurrentMailboxRefreshable == null) { 1270 return; 1271 } 1272 if (isCurrentMailboxRefreshable) { 1273 mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false); 1274 } 1275 // Refresh mailbox list 1276 if (mAccountId != -1) { 1277 if (shouldRefreshMailboxList()) { 1278 mRefreshManager.refreshMailboxList(mAccountId); 1279 } 1280 } 1281 // Refresh inbox 1282 if (shouldAutoRefreshInbox()) { 1283 mRefreshManager.refreshMessageList(mAccountId, mInboxId, false); 1284 } 1285 } 1286 1287 /** 1288 * @return true if the mailbox list of the current account hasn't been refreshed 1289 * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}. 1290 */ 1291 /* package */ boolean shouldRefreshMailboxList() { 1292 if (mRefreshManager.isMailboxListRefreshing(mAccountId)) { 1293 return false; 1294 } 1295 final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId) 1296 + MAILBOX_REFRESH_MIN_INTERVAL; 1297 if (nextRefreshTime > mClock.getTime()) { 1298 return false; 1299 } 1300 return true; 1301 } 1302 1303 /** 1304 * @return true if the inbox of the current account hasn't been refreshed 1305 * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}. 1306 */ 1307 /* package */ boolean shouldAutoRefreshInbox() { 1308 if (mInboxId == mMailboxId) { 1309 return false; // Current ID == inbox. No need to auto-refresh. 1310 } 1311 if (mRefreshManager.isMessageListRefreshing(mInboxId)) { 1312 return false; 1313 } 1314 final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId) 1315 + INBOX_AUTO_REFRESH_MIN_INTERVAL; 1316 if (nextRefreshTime > mClock.getTime()) { 1317 return false; 1318 } 1319 return true; 1320 } 1321 } 1322} 1323