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