UIControllerBase.java revision 78c450ab14bc040f84b65563456433b809f2db02
1/* 2 * Copyright (C) 2011 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 android.app.Activity; 20import android.app.Fragment; 21import android.app.FragmentManager; 22import android.app.FragmentTransaction; 23import android.os.Bundle; 24import android.util.Log; 25import android.view.Menu; 26import android.view.MenuInflater; 27import android.view.MenuItem; 28 29import com.android.email.Email; 30import com.android.email.MessageListContext; 31import com.android.email.R; 32import com.android.email.RefreshManager; 33import com.android.email.activity.setup.AccountSettings; 34import com.android.emailcommon.Logging; 35import com.android.emailcommon.provider.Account; 36import com.android.emailcommon.provider.EmailContent.Message; 37import com.android.emailcommon.provider.HostAuth; 38import com.android.emailcommon.provider.Mailbox; 39import com.android.emailcommon.utility.EmailAsyncTask; 40import com.google.common.base.Objects; 41 42import java.util.LinkedList; 43import java.util.List; 44 45/** 46 * Base class for the UI controller. 47 */ 48abstract class UIControllerBase implements MailboxListFragment.Callback, 49 MessageListFragment.Callback, MessageViewFragment.Callback { 50 static final boolean DEBUG_FRAGMENTS = false; // DO NOT SUBMIT WITH TRUE 51 52 static final String KEY_LIST_CONTEXT = "UIControllerBase.listContext"; 53 54 /** The owner activity */ 55 final EmailActivity mActivity; 56 final FragmentManager mFragmentManager; 57 58 protected final ActionBarController mActionBarController; 59 60 final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); 61 62 final RefreshManager mRefreshManager; 63 64 /** 65 * Fragments that are installed. 66 * 67 * A fragment is installed in {@link Fragment#onActivityCreated} and uninstalled in 68 * {@link Fragment#onDestroyView}, using {@link FragmentInstallable} callbacks. 69 * 70 * This means fragments in the back stack are *not* installed. 71 * 72 * We set callbacks to fragments only when they are installed. 73 * 74 * @see FragmentInstallable 75 */ 76 private MailboxListFragment mMailboxListFragment; 77 private MessageListFragment mMessageListFragment; 78 private MessageViewFragment mMessageViewFragment; 79 80 /** 81 * To avoid double-deleting a fragment (which will cause a runtime exception), 82 * we put a fragment in this list when we {@link FragmentTransaction#remove(Fragment)} it, 83 * and remove from the list when we actually uninstall it. 84 */ 85 private final List<Fragment> mRemovedFragments = new LinkedList<Fragment>(); 86 87 /** 88 * The active context for the current MessageList. 89 * In some UI layouts such as the one-pane view, the message list may not be visible, but is 90 * on the backstack. This list context will still be accessible in those cases. 91 * 92 * Should be set using {@link #setListContext(MessageListContext)}. 93 */ 94 protected MessageListContext mListContext; 95 96 private final RefreshManager.Listener mRefreshListener 97 = new RefreshManager.Listener() { 98 @Override 99 public void onMessagingError(final long accountId, long mailboxId, final String message) { 100 refreshActionBar(); 101 } 102 103 @Override 104 public void onRefreshStatusChanged(long accountId, long mailboxId) { 105 refreshActionBar(); 106 } 107 }; 108 109 public UIControllerBase(EmailActivity activity) { 110 mActivity = activity; 111 mFragmentManager = activity.getFragmentManager(); 112 mRefreshManager = RefreshManager.getInstance(mActivity); 113 mActionBarController = createActionBarController(activity); 114 if (DEBUG_FRAGMENTS) { 115 FragmentManager.enableDebugLogging(true); 116 } 117 } 118 119 /** 120 * Called by the base class to let a subclass create an {@link ActionBarController}. 121 */ 122 protected abstract ActionBarController createActionBarController(Activity activity); 123 124 /** @return the layout ID for the activity. */ 125 public abstract int getLayoutId(); 126 127 /** 128 * Must be called just after the activity sets up the content view. Used to initialize views. 129 * 130 * (Due to the complexity regarding class/activity initialization order, we can't do this in 131 * the constructor.) 132 */ 133 public void onActivityViewReady() { 134 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 135 Log.d(Logging.LOG_TAG, this + " onActivityViewReady"); 136 } 137 } 138 139 /** 140 * Called at the end of {@link EmailActivity#onCreate}. 141 */ 142 public void onActivityCreated() { 143 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 144 Log.d(Logging.LOG_TAG, this + " onActivityCreated"); 145 } 146 mRefreshManager.registerListener(mRefreshListener); 147 mActionBarController.onActivityCreated(); 148 } 149 150 /** 151 * Handles the {@link android.app.Activity#onStart} callback. 152 */ 153 public void onActivityStart() { 154 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 155 Log.d(Logging.LOG_TAG, this + " onActivityStart"); 156 } 157 } 158 159 /** 160 * Handles the {@link android.app.Activity#onResume} callback. 161 */ 162 public void onActivityResume() { 163 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 164 Log.d(Logging.LOG_TAG, this + " onActivityResume"); 165 } 166 refreshActionBar(); 167 } 168 169 /** 170 * Handles the {@link android.app.Activity#onPause} callback. 171 */ 172 public void onActivityPause() { 173 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 174 Log.d(Logging.LOG_TAG, this + " onActivityPause"); 175 } 176 } 177 178 /** 179 * Handles the {@link android.app.Activity#onStop} callback. 180 */ 181 public void onActivityStop() { 182 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 183 Log.d(Logging.LOG_TAG, this + " onActivityStop"); 184 } 185 } 186 187 /** 188 * Handles the {@link android.app.Activity#onDestroy} callback. 189 */ 190 public void onActivityDestroy() { 191 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 192 Log.d(Logging.LOG_TAG, this + " onActivityDestroy"); 193 } 194 mActionBarController.onActivityDestroy(); 195 mRefreshManager.unregisterListener(mRefreshListener); 196 mTaskTracker.cancellAllInterrupt(); 197 } 198 199 /** 200 * Handles the {@link android.app.Activity#onSaveInstanceState} callback. 201 */ 202 public void onSaveInstanceState(Bundle outState) { 203 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 204 Log.d(Logging.LOG_TAG, this + " onSaveInstanceState"); 205 } 206 mActionBarController.onSaveInstanceState(outState); 207 outState.putParcelable(KEY_LIST_CONTEXT, mListContext); 208 } 209 210 /** 211 * Handles the {@link android.app.Activity#onRestoreInstanceState} callback. 212 */ 213 public void onRestoreInstanceState(Bundle savedInstanceState) { 214 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 215 Log.d(Logging.LOG_TAG, this + " restoreInstanceState"); 216 } 217 mActionBarController.onRestoreInstanceState(savedInstanceState); 218 mListContext = savedInstanceState.getParcelable(KEY_LIST_CONTEXT); 219 } 220 221 /** 222 * Install a fragment. Must be caleld from the host activity's 223 * {@link FragmentInstallable#onInstallFragment}. 224 */ 225 public final void onInstallFragment(Fragment fragment) { 226 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 227 Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment); 228 } 229 if (fragment instanceof MailboxListFragment) { 230 installMailboxListFragment((MailboxListFragment) fragment); 231 } else if (fragment instanceof MessageListFragment) { 232 installMessageListFragment((MessageListFragment) fragment); 233 } else if (fragment instanceof MessageViewFragment) { 234 installMessageViewFragment((MessageViewFragment) fragment); 235 } else { 236 throw new IllegalArgumentException("Tried to install unknown fragment"); 237 } 238 } 239 240 /** Install fragment */ 241 protected void installMailboxListFragment(MailboxListFragment fragment) { 242 mMailboxListFragment = fragment; 243 mMailboxListFragment.setCallback(this); 244 refreshActionBar(); 245 } 246 247 /** Install fragment */ 248 protected void installMessageListFragment(MessageListFragment fragment) { 249 mMessageListFragment = fragment; 250 mMessageListFragment.setCallback(this); 251 refreshActionBar(); 252 } 253 254 /** Install fragment */ 255 protected void installMessageViewFragment(MessageViewFragment fragment) { 256 mMessageViewFragment = fragment; 257 mMessageViewFragment.setCallback(this); 258 refreshActionBar(); 259 } 260 261 /** 262 * Uninstall a fragment. Must be caleld from the host activity's 263 * {@link FragmentInstallable#onUninstallFragment}. 264 */ 265 public final void onUninstallFragment(Fragment fragment) { 266 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 267 Log.d(Logging.LOG_TAG, this + " onUninstallFragment fragment=" + fragment); 268 } 269 mRemovedFragments.remove(fragment); 270 if (fragment == mMailboxListFragment) { 271 uninstallMailboxListFragment(); 272 } else if (fragment == mMessageListFragment) { 273 uninstallMessageListFragment(); 274 } else if (fragment == mMessageViewFragment) { 275 uninstallMessageViewFragment(); 276 } else { 277 throw new IllegalArgumentException("Tried to uninstall unknown fragment"); 278 } 279 } 280 281 /** Uninstall {@link MailboxListFragment} */ 282 protected void uninstallMailboxListFragment() { 283 mMailboxListFragment.setCallback(null); 284 mMailboxListFragment = null; 285 } 286 287 /** Uninstall {@link MessageListFragment} */ 288 protected void uninstallMessageListFragment() { 289 mMessageListFragment.setCallback(null); 290 mMessageListFragment = null; 291 } 292 293 /** Uninstall {@link MessageViewFragment} */ 294 protected void uninstallMessageViewFragment() { 295 mMessageViewFragment.setCallback(null); 296 mMessageViewFragment = null; 297 } 298 299 /** 300 * If a {@link Fragment} is not already in {@link #mRemovedFragments}, 301 * {@link FragmentTransaction#remove} it and add to the list. 302 * 303 * Do nothing if {@code fragment} is null. 304 */ 305 protected final void removeFragment(FragmentTransaction ft, Fragment fragment) { 306 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 307 Log.d(Logging.LOG_TAG, this + " removeFragment fragment=" + fragment); 308 } 309 if (fragment == null) { 310 return; 311 } 312 if (!mRemovedFragments.contains(fragment)) { 313 ft.remove(fragment); 314 addFragmentToRemovalList(fragment); 315 } 316 } 317 318 /** 319 * Remove a {@link Fragment} from {@link #mRemovedFragments}. No-op if {@code fragment} is 320 * null. 321 * 322 * {@link #removeMailboxListFragment}, {@link #removeMessageListFragment} and 323 * {@link #removeMessageViewFragment} all call this, so subclasses don't have to do this when 324 * using them. 325 * 326 * However, unfortunately, subclasses have to call this manually when popping from the 327 * back stack to avoid double-delete. 328 */ 329 protected void addFragmentToRemovalList(Fragment fragment) { 330 if (fragment != null) { 331 mRemovedFragments.add(fragment); 332 } 333 } 334 335 /** 336 * Remove the fragment if it's installed. 337 */ 338 protected FragmentTransaction removeMailboxListFragment(FragmentTransaction ft) { 339 removeFragment(ft, mMailboxListFragment); 340 return ft; 341 } 342 343 /** 344 * Remove the fragment if it's installed. 345 */ 346 protected FragmentTransaction removeMessageListFragment(FragmentTransaction ft) { 347 removeFragment(ft, mMessageListFragment); 348 return ft; 349 } 350 351 /** 352 * Remove the fragment if it's installed. 353 */ 354 protected FragmentTransaction removeMessageViewFragment(FragmentTransaction ft) { 355 removeFragment(ft, mMessageViewFragment); 356 return ft; 357 } 358 359 /** @return true if a {@link MailboxListFragment} is installed. */ 360 protected final boolean isMailboxListInstalled() { 361 return mMailboxListFragment != null; 362 } 363 364 /** @return true if a {@link MessageListFragment} is installed. */ 365 protected final boolean isMessageListInstalled() { 366 return mMessageListFragment != null; 367 } 368 369 /** @return true if a {@link MessageViewFragment} is installed. */ 370 protected final boolean isMessageViewInstalled() { 371 return mMessageViewFragment != null; 372 } 373 374 /** @return the installed {@link MailboxListFragment} or null. */ 375 protected final MailboxListFragment getMailboxListFragment() { 376 return mMailboxListFragment; 377 } 378 379 /** @return the installed {@link MessageListFragment} or null. */ 380 protected final MessageListFragment getMessageListFragment() { 381 return mMessageListFragment; 382 } 383 384 /** @return the installed {@link MessageViewFragment} or null. */ 385 protected final MessageViewFragment getMessageViewFragment() { 386 return mMessageViewFragment; 387 } 388 389 /** 390 * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 391 * 392 * @see #getActualAccountId() 393 */ 394 public abstract long getUIAccountId(); 395 396 /** 397 * @return true if an account is selected, or the current view is the combined view. 398 */ 399 public final boolean isAccountSelected() { 400 return getUIAccountId() != Account.NO_ACCOUNT; 401 } 402 403 /** 404 * @return if an actual account is selected. (i.e. {@link Account#ACCOUNT_ID_COMBINED_VIEW} 405 * is not considered "actual".s) 406 */ 407 public final boolean isActualAccountSelected() { 408 return isAccountSelected() && (getUIAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW); 409 } 410 411 /** 412 * @return the currently selected account ID. If the current view is the combined view, 413 * it'll return {@link Account#NO_ACCOUNT}. 414 * 415 * @see #getUIAccountId() 416 */ 417 public final long getActualAccountId() { 418 return isActualAccountSelected() ? getUIAccountId() : Account.NO_ACCOUNT; 419 } 420 421 /** 422 * Show the default view for the given account. 423 * 424 * No-op if the given account is already selected. 425 * 426 * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 427 * Must never be {@link Account#NO_ACCOUNT}. 428 */ 429 public final void switchAccount(long accountId) { 430 431 if (Account.isSecurityHold(mActivity, accountId)) { 432 ActivityHelper.showSecurityHoldDialog(mActivity, accountId); 433 mActivity.finish(); 434 return; 435 } 436 437 if (accountId == getUIAccountId()) { 438 // Do nothing if the account is already selected. Not even going back to the inbox. 439 return; 440 } 441 442 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 443 openMailbox(accountId, Mailbox.QUERY_ALL_INBOXES); 444 } else { 445 long inboxId = Mailbox.findMailboxOfType(mActivity, accountId, Mailbox.TYPE_INBOX); 446 if (inboxId == Mailbox.NO_MAILBOX) { 447 // The account doesn't have Inbox yet... Redirect to Welcome and let it wait for 448 // the initial sync... 449 Log.w(Logging.LOG_TAG, "Account " + accountId +" doesn't have Inbox. Redirecting" 450 + " to Welcome..."); 451 Welcome.actionOpenAccountInbox(mActivity, accountId); 452 mActivity.finish(); 453 return; 454 } else { 455 openMailbox(accountId, inboxId); 456 } 457 } 458 } 459 460 /** 461 * Returns the id of the parent mailbox used for the mailbox list fragment. 462 * 463 * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with 464 * {@link #getMessageListMailboxId()} 465 */ 466 protected long getMailboxListMailboxId() { 467 return isMailboxListInstalled() ? getMailboxListFragment().getSelectedMailboxId() 468 : Mailbox.NO_MAILBOX; 469 } 470 471 /** 472 * Returns the id of the mailbox used for the message list fragment. 473 * 474 * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with 475 * {@link #getMessageListMailboxId()} 476 */ 477 protected long getMessageListMailboxId() { 478 return isMessageListInstalled() ? getMessageListFragment().getMailboxId() 479 : Mailbox.NO_MAILBOX; 480 } 481 482 /** 483 * Shortcut for {@link #open} with {@link Message#NO_MESSAGE}. 484 */ 485 protected final void openMailbox(long accountId, long mailboxId) { 486 open(MessageListContext.forMailbox(accountId, mailboxId), Message.NO_MESSAGE); 487 } 488 489 /** 490 * Opens a given list 491 * @param listContext the list context for the message list to open 492 * @param messageId if specified and not {@link Message#NO_MESSAGE}, will open the message 493 * in the message list. 494 */ 495 public final void open(final MessageListContext listContext, final long messageId) { 496 setListContext(listContext); 497 openInternal(listContext, messageId); 498 499 if (listContext.isSearch()) { 500 mActionBarController.enterSearchMode(listContext.getSearchParams().mFilter); 501 } 502 } 503 504 /** 505 * Sets the internal value of the list context for the message list. 506 */ 507 protected void setListContext(MessageListContext listContext) { 508 if (Objects.equal(listContext, mListContext)) { 509 return; 510 } 511 512 // TODO: remove this when the search mailbox no longer shows up on the list 513 // Special case search. Since the search mailbox shows up in the mailbox list, the mailbox 514 // list can give us a callback to open that mailbox, and it will look like a normal 515 // mailbox open instead of a search, blowing away a perfectly good list context. 516 if (mListContext != null 517 && mListContext.isSearch() 518 && mListContext.getMailboxId() == listContext.getMailboxId()) { 519 return; 520 } 521 522 mListContext = listContext; 523 } 524 525 protected abstract void openInternal( 526 final MessageListContext listContext, final long messageId); 527 528 /** 529 * Performs the back action. 530 * 531 * NOTE The method in the base class has precedence. Subclasses overriding this method MUST 532 * call super's method first. 533 * 534 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 535 * <code>false</code> if it's caused by the "home" icon click on the action bar. 536 */ 537 public boolean onBackPressed(boolean isSystemBackKey) { 538 if (mActionBarController.onBackPressed(isSystemBackKey)) { 539 return true; 540 } 541 return false; 542 } 543 544 /** 545 * Must be called from {@link Activity#onSearchRequested()}. 546 * This initiates the search entry mode - see {@link #onSearchSubmit} for when the search 547 * is actually submitted. 548 */ 549 public void onSearchRequested() { 550 mActionBarController.enterSearchMode(null); 551 } 552 553 /** @return true if the search menu option should be enabled based on the current UI. */ 554 protected boolean canSearch() { 555 return false; 556 } 557 558 /** 559 * Kicks off a search query, if the UI is in a state where a search is possible. 560 */ 561 protected void onSearchSubmit(final String queryTerm) { 562 final long accountId = getUIAccountId(); 563 if (!Account.isNormalAccount(accountId)) { 564 return; // Invalid account to search from. 565 } 566 567 // TODO: do a global search for EAS inbox. 568 // TODO: handle doing another search from a search result, in which case we should 569 // search the original mailbox that was searched, and not search in the search mailbox 570 final long mailboxId = getMessageListMailboxId(); 571 572 if (Email.DEBUG) { 573 Log.d(Logging.LOG_TAG, "Submitting search: " + queryTerm); 574 } 575 576 mActivity.startActivity(EmailActivity.createSearchIntent( 577 mActivity, accountId, mailboxId, queryTerm)); 578 579 580 // TODO: this causes a slight flicker. 581 // A new instance of the activity will sit on top. When the user exits search and 582 // returns to this activity, the search box should not be open then. 583 mActionBarController.exitSearchMode(); 584 } 585 586 /** 587 * Handles exiting of search entry mode. 588 */ 589 protected void onSearchExit() { 590 if ((mListContext != null) && mListContext.isSearch()) { 591 mActivity.finish(); 592 } 593 } 594 595 /** 596 * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback. 597 */ 598 public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { 599 inflater.inflate(R.menu.email_activity_options, menu); 600 return true; 601 } 602 603 /** 604 * Handles the {@link android.app.Activity#onPrepareOptionsMenu} callback. 605 */ 606 public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { 607 608 // Update the refresh button. 609 MenuItem item = menu.findItem(R.id.refresh); 610 if (isRefreshEnabled()) { 611 item.setVisible(true); 612 if (isRefreshInProgress()) { 613 item.setActionView(R.layout.action_bar_indeterminate_progress); 614 } else { 615 item.setActionView(null); 616 } 617 } else { 618 item.setVisible(false); 619 } 620 621 // Deal with protocol-specific menu options. 622 boolean isEas = false; 623 boolean accountSearchable = false; 624 long accountId = getActualAccountId(); 625 if (accountId > 0) { 626 Account account = Account.restoreAccountWithId(mActivity, accountId); 627 if (account != null) { 628 String protocol = account.getProtocol(mActivity); 629 if (HostAuth.SCHEME_EAS.equals(protocol)) { 630 isEas = true; 631 } 632 accountSearchable = (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) != 0; 633 } 634 } 635 636 // TODO: Should use an isSyncable call to prevent drafts/outbox from allowing this 637 menu.findItem(R.id.search).setVisible(accountSearchable && canSearch()); 638 menu.findItem(R.id.sync_lookback).setVisible(isEas); 639 menu.findItem(R.id.sync_frequency).setVisible(isEas); 640 641 return true; 642 } 643 644 /** 645 * Handles the {@link android.app.Activity#onOptionsItemSelected} callback. 646 * 647 * @return true if the option item is handled. 648 */ 649 public boolean onOptionsItemSelected(MenuItem item) { 650 switch (item.getItemId()) { 651 case android.R.id.home: 652 // Comes from the action bar when the app icon on the left is pressed. 653 // It works like a back press, but it won't close the activity. 654 return onBackPressed(false); 655 case R.id.compose: 656 return onCompose(); 657 case R.id.refresh: 658 onRefresh(); 659 return true; 660 case R.id.account_settings: 661 return onAccountSettings(); 662 case R.id.search: 663 onSearchRequested(); 664 return true; 665 } 666 return false; 667 } 668 669 /** 670 * Opens the message compose activity. 671 */ 672 private boolean onCompose() { 673 if (!isAccountSelected()) { 674 return false; // this shouldn't really happen 675 } 676 MessageCompose.actionCompose(mActivity, getActualAccountId()); 677 return true; 678 } 679 680 /** 681 * Handles the "Settings" option item. Opens the settings activity. 682 */ 683 private boolean onAccountSettings() { 684 AccountSettings.actionSettings(mActivity, getActualAccountId()); 685 return true; 686 } 687 688 /** 689 * @return the ID of the message in focus and visible, if any. Returns 690 * {@link Message#NO_MESSAGE} if no message is opened. 691 */ 692 protected long getMessageId() { 693 return isMessageViewInstalled() 694 ? getMessageViewFragment().getMessageId() 695 : Message.NO_MESSAGE; 696 } 697 698 699 /** 700 * STOPSHIP For experimental UI. Remove this. 701 * 702 * @return mailbox ID for "mailbox settings" option. 703 */ 704 public abstract long getMailboxSettingsMailboxId(); 705 706 /** 707 * STOPSHIP For experimental UI. Make it abstract protected. 708 * 709 * Performs "refesh". 710 */ 711 public abstract void onRefresh(); 712 713 /** 714 * @return true if refresh is in progress for the current mailbox. 715 */ 716 protected abstract boolean isRefreshInProgress(); 717 718 /** 719 * @return true if the UI should enable the "refresh" command. 720 */ 721 protected abstract boolean isRefreshEnabled(); 722 723 /** 724 * Refresh the action bar and menu items, including the "refreshing" icon. 725 */ 726 protected void refreshActionBar() { 727 if (mActionBarController != null) { 728 mActionBarController.refresh(); 729 } 730 mActivity.invalidateOptionsMenu(); 731 } 732 733 734 @Override 735 public String toString() { 736 return getClass().getSimpleName(); // Shown on logcat 737 } 738} 739