| 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 | |
| 17 | package android.support.v2.app; |
| 18 | |
| 19 | import android.app.Activity; |
| 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
| 22 | import android.content.res.Configuration; |
| 23 | import android.content.res.TypedArray; |
| 24 | import android.os.Bundle; |
| 25 | import android.os.Handler; |
| 26 | import android.os.Message; |
| 27 | import android.os.Parcelable; |
| 28 | import android.util.AttributeSet; |
| 29 | import android.util.Log; |
| 30 | import android.view.KeyEvent; |
| 31 | import android.view.Menu; |
| 32 | import android.view.MenuItem; |
| 33 | import android.view.View; |
| 34 | import android.view.Window; |
| 35 | |
| 36 | import java.io.FileDescriptor; |
| 37 | import java.io.PrintWriter; |
| 38 | import java.util.ArrayList; |
| 39 | import java.util.HashMap; |
| 40 | |
| 41 | /** |
| 42 | * Base class for activities that want to use the support-based Fragment and |
| 43 | * Loader APIs. |
| 44 | * |
| 45 | * <p>Known limitations:</p> |
| 46 | * <ul> |
| 47 | * <li> <p>When using the <fragment> tag, this implementation can not |
| 48 | * use the parent view's ID as the new fragment's ID. You must explicitly |
| 49 | * specify an ID (or tag) in the <fragment>.</p> |
| 50 | * <li> <p>Prior to Honeycomb (3.0), an activity's state was saved before pausing. |
| 51 | * Fragments are a significant amount of new state, and dynamic enough that one |
| 52 | * often wants them to change between pausing and stopping. These classes |
| 53 | * throw an exception if you try to change the fragment state after it has been |
| 54 | * saved, to avoid accidental loss of UI state. However this is too restrictive |
| 55 | * prior to Honeycomb, where the state is saved before pausing. To address this, |
| 56 | * when running on platforms prior to Honeycomb an exception will not be thrown |
| 57 | * if you change fragments between the state save and the activity being stopped. |
| 58 | * This means that is some cases if the activity is restored from its last saved |
| 59 | * state, this may be a snapshot slightly before what the user last saw.</p> |
| 60 | * </ul> |
| 61 | */ |
| 62 | public class FragmentActivity extends Activity { |
| 63 | private static final String TAG = "FragmentActivity"; |
| 64 | |
| 65 | private static final String FRAGMENTS_TAG = "android:support:fragments"; |
| 66 | |
| 67 | // This is the SDK API version of Honeycomb (3.0). |
| 68 | private static final int HONEYCOMB = 11; |
| 69 | |
| 70 | static final int MSG_REALLY_STOPPED = 1; |
| 71 | |
| 72 | final Handler mHandler = new Handler() { |
| 73 | @Override |
| 74 | public void handleMessage(Message msg) { |
| 75 | switch (msg.what) { |
| 76 | case MSG_REALLY_STOPPED: |
| 77 | if (mStopped) { |
| 78 | doReallyStop(false); |
| 79 | } |
| 80 | break; |
| 81 | default: |
| 82 | super.handleMessage(msg); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | }; |
| 87 | final FragmentManagerImpl mFragments = new FragmentManagerImpl(); |
| 88 | |
| 89 | boolean mResumed; |
| 90 | boolean mStopped; |
| 91 | boolean mReallyStopped; |
| 92 | |
| 93 | boolean mOptionsMenuInvalidated; |
| 94 | |
| 95 | boolean mCheckedForLoaderManager; |
| 96 | boolean mLoadersStarted; |
| 97 | HCSparseArray<LoaderManagerImpl> mAllLoaderManagers; |
| 98 | LoaderManagerImpl mLoaderManager; |
| 99 | |
| 100 | static final class NonConfigurationInstances { |
| 101 | Object activity; |
| 102 | HashMap<String, Object> children; |
| 103 | ArrayList<Fragment> fragments; |
| 104 | HCSparseArray<LoaderManagerImpl> loaders; |
| 105 | } |
| 106 | |
| 107 | static class FragmentTag { |
| 108 | public static final int[] Fragment = { |
| 109 | 0x01010003, 0x010100d0, 0x010100d1 |
| 110 | }; |
| 111 | public static final int Fragment_id = 1; |
| 112 | public static final int Fragment_name = 0; |
| 113 | public static final int Fragment_tag = 2; |
| 114 | } |
| 115 | |
| 116 | // ------------------------------------------------------------------------ |
| 117 | // HOOKS INTO ACTIVITY |
| 118 | // ------------------------------------------------------------------------ |
| 119 | |
| 120 | /** |
| 121 | * Dispatch incoming result to the correct fragment. |
| 122 | */ |
| 123 | @Override |
| 124 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| 125 | int index = requestCode>>16; |
| 126 | if (index != 0) { |
| 127 | index--; |
| 128 | if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { |
| 129 | Log.w(TAG, "Activity result fragment index out of range: 0x" |
| 130 | + Integer.toHexString(requestCode)); |
| 131 | return; |
| 132 | } |
| 133 | Fragment frag = mFragments.mActive.get(index); |
| 134 | if (frag == null) { |
| 135 | Log.w(TAG, "Activity result no fragment exists for index: 0x" |
| 136 | + Integer.toHexString(requestCode)); |
| 137 | } |
| 138 | frag.onActivityResult(requestCode&0xffff, resultCode, data); |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | super.onActivityResult(requestCode, resultCode, data); |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Take care of popping the fragment back stack or finishing the activity |
| 147 | * as appropriate. |
| 148 | */ |
| 149 | public void onBackPressed() { |
| 150 | if (!mFragments.popBackStackImmediate()) { |
| 151 | finish(); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Dispatch configuration change to all fragments. |
| 157 | */ |
| 158 | @Override |
| 159 | public void onConfigurationChanged(Configuration newConfig) { |
| 160 | super.onConfigurationChanged(newConfig); |
| 161 | mFragments.dispatchConfigurationChanged(newConfig); |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Perform initialization of all fragments and loaders. |
| 166 | */ |
| 167 | @Override |
| 168 | protected void onCreate(Bundle savedInstanceState) { |
| 169 | mFragments.attachActivity(this); |
| 170 | // Old versions of the platform didn't do this! |
| 171 | if (getLayoutInflater().getFactory() == null) { |
| 172 | getLayoutInflater().setFactory(this); |
| 173 | } |
| 174 | |
| 175 | super.onCreate(savedInstanceState); |
| 176 | |
| 177 | NonConfigurationInstances nc = (NonConfigurationInstances) |
| 178 | getLastNonConfigurationInstance(); |
| 179 | if (nc != null) { |
| 180 | mAllLoaderManagers = nc.loaders; |
| 181 | } |
| 182 | if (savedInstanceState != null) { |
| 183 | Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); |
| 184 | mFragments.restoreAllState(p, nc != null ? nc.fragments : null); |
| 185 | } |
| 186 | mFragments.dispatchCreate(); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Dispatch to Fragment.onCreateOptionsMenu(). |
| 191 | */ |
| 192 | @Override |
| 193 | public boolean onCreatePanelMenu(int featureId, Menu menu) { |
| 194 | if (featureId == Window.FEATURE_OPTIONS_PANEL) { |
| 195 | boolean show = super.onCreatePanelMenu(featureId, menu); |
| 196 | show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater()); |
| 197 | //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 198 | if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 199 | return show; |
| 200 | } |
| 201 | // Prior to Honeycomb, the framework can't invalidate the options |
| 202 | // menu, so we must always say we have one in case the app later |
| 203 | // invalidates it and needs to have it shown. |
| 204 | return true; |
| 205 | } |
| 206 | return super.onCreatePanelMenu(featureId, menu); |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Add support for inflating the <fragment> tag. |
| 211 | */ |
| 212 | @Override |
| 213 | public View onCreateView(String name, Context context, AttributeSet attrs) { |
| 214 | if (!"fragment".equals(name)) { |
| 215 | return super.onCreateView(name, context, attrs); |
| 216 | } |
| 217 | |
| 218 | String fname = attrs.getAttributeValue(null, "class"); |
| 219 | TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment); |
| 220 | if (fname == null) { |
| 221 | fname = a.getString(FragmentTag.Fragment_name); |
| 222 | } |
| 223 | int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID); |
| 224 | String tag = a.getString(FragmentTag.Fragment_tag); |
| 225 | a.recycle(); |
| 226 | |
| 227 | View parent = null; // NOTE: no way to get parent pre-Honeycomb. |
| 228 | int containerId = parent != null ? parent.getId() : 0; |
| 229 | if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { |
| 230 | throw new IllegalArgumentException(attrs.getPositionDescription() |
| 231 | + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); |
| 232 | } |
| 233 | |
| 234 | // If we restored from a previous state, we may already have |
| 235 | // instantiated this fragment from the state and should use |
| 236 | // that instance instead of making a new one. |
| 237 | Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null; |
| 238 | if (fragment == null && tag != null) { |
| 239 | fragment = mFragments.findFragmentByTag(tag); |
| 240 | } |
| 241 | if (fragment == null && containerId != View.NO_ID) { |
| 242 | fragment = mFragments.findFragmentById(containerId); |
| 243 | } |
| 244 | |
| 245 | if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" |
| 246 | + Integer.toHexString(id) + " fname=" + fname |
| 247 | + " existing=" + fragment); |
| 248 | if (fragment == null) { |
| 249 | fragment = Fragment.instantiate(this, fname); |
| 250 | fragment.mFromLayout = true; |
| 251 | fragment.mFragmentId = id != 0 ? id : containerId; |
| 252 | fragment.mContainerId = containerId; |
| 253 | fragment.mTag = tag; |
| 254 | fragment.mInLayout = true; |
| 255 | fragment.mImmediateActivity = this; |
| 256 | fragment.mFragmentManager = mFragments; |
| 257 | fragment.onInflate(this, attrs, fragment.mSavedFragmentState); |
| 258 | mFragments.addFragment(fragment, true); |
| 259 | |
| 260 | } else if (fragment.mInLayout) { |
| 261 | // A fragment already exists and it is not one we restored from |
| 262 | // previous state. |
| 263 | throw new IllegalArgumentException(attrs.getPositionDescription() |
| 264 | + ": Duplicate id 0x" + Integer.toHexString(id) |
| 265 | + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) |
| 266 | + " with another fragment for " + fname); |
| 267 | } else { |
| 268 | // This fragment was retained from a previous instance; get it |
| 269 | // going now. |
| 270 | fragment.mInLayout = true; |
| 271 | fragment.mImmediateActivity = this; |
| 272 | // If this fragment is newly instantiated (either right now, or |
| 273 | // from last saved state), then give it the attributes to |
| 274 | // initialize itself. |
| 275 | if (!fragment.mRetaining) { |
| 276 | fragment.onInflate(this, attrs, fragment.mSavedFragmentState); |
| 277 | } |
| 278 | mFragments.moveToState(fragment); |
| 279 | } |
| 280 | |
| 281 | if (fragment.mView == null) { |
| 282 | throw new IllegalStateException("Fragment " + fname |
| 283 | + " did not create a view."); |
| 284 | } |
| 285 | if (id != 0) { |
| 286 | fragment.mView.setId(id); |
| 287 | } |
| 288 | if (fragment.mView.getTag() == null) { |
| 289 | fragment.mView.setTag(tag); |
| 290 | } |
| 291 | return fragment.mView; |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Destroy all fragments and loaders. |
| 296 | */ |
| 297 | @Override |
| 298 | protected void onDestroy() { |
| 299 | super.onDestroy(); |
| 300 | |
| 301 | doReallyStop(false); |
| 302 | |
| 303 | mFragments.dispatchDestroy(); |
| 304 | if (mLoaderManager != null) { |
| 305 | mLoaderManager.doDestroy(); |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Take care of calling onBackPressed() for pre-Eclair platforms. |
| 311 | */ |
| 312 | @Override |
| 313 | public boolean onKeyDown(int keyCode, KeyEvent event) { |
| 314 | //v4 if (android.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */ |
| 315 | if (android.support.v2.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */ |
| 316 | && keyCode == KeyEvent.KEYCODE_BACK |
| 317 | && event.getRepeatCount() == 0) { |
| 318 | // Take care of calling this method on earlier versions of |
| 319 | // the platform where it doesn't exist. |
| 320 | onBackPressed(); |
| 321 | return true; |
| 322 | } |
| 323 | |
| 324 | return super.onKeyDown(keyCode, event); |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Dispatch onLowMemory() to all fragments. |
| 329 | */ |
| 330 | @Override |
| 331 | public void onLowMemory() { |
| 332 | super.onLowMemory(); |
| 333 | mFragments.dispatchLowMemory(); |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Dispatch context and options menu to fragments. |
| 338 | */ |
| 339 | @Override |
| 340 | public boolean onMenuItemSelected(int featureId, MenuItem item) { |
| 341 | if (super.onMenuItemSelected(featureId, item)) { |
| 342 | return true; |
| 343 | } |
| 344 | |
| 345 | switch (featureId) { |
| 346 | case Window.FEATURE_OPTIONS_PANEL: |
| 347 | return mFragments.dispatchOptionsItemSelected(item); |
| 348 | |
| 349 | case Window.FEATURE_CONTEXT_MENU: |
| 350 | return mFragments.dispatchContextItemSelected(item); |
| 351 | |
| 352 | default: |
| 353 | return false; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | /** |
| 358 | * Call onOptionsMenuClosed() on fragments. |
| 359 | */ |
| 360 | @Override |
| 361 | public void onPanelClosed(int featureId, Menu menu) { |
| 362 | switch (featureId) { |
| 363 | case Window.FEATURE_OPTIONS_PANEL: |
| 364 | mFragments.dispatchOptionsMenuClosed(menu); |
| 365 | break; |
| 366 | } |
| 367 | super.onPanelClosed(featureId, menu); |
| 368 | } |
| 369 | |
| 370 | /** |
| 371 | * Dispatch onPause() to fragments. |
| 372 | */ |
| 373 | @Override |
| 374 | protected void onPause() { |
| 375 | super.onPause(); |
| 376 | mResumed = false; |
| 377 | mFragments.dispatchPause(); |
| 378 | } |
| 379 | |
| 380 | /** |
| 381 | * Dispatch onActivityCreated() on fragments. |
| 382 | */ |
| 383 | @Override |
| 384 | protected void onPostCreate(Bundle savedInstanceState) { |
| 385 | super.onPostCreate(savedInstanceState); |
| 386 | mFragments.dispatchActivityCreated(); |
| 387 | } |
| 388 | |
| 389 | /** |
| 390 | * Dispatch onResume() to fragments. |
| 391 | */ |
| 392 | @Override |
| 393 | protected void onPostResume() { |
| 394 | super.onPostResume(); |
| 395 | mFragments.dispatchResume(); |
| 396 | mFragments.execPendingActions(); |
| 397 | } |
| 398 | |
| 399 | /** |
| 400 | * Dispatch onPrepareOptionsMenu() to fragments. |
| 401 | */ |
| 402 | @Override |
| 403 | public boolean onPreparePanel(int featureId, View view, Menu menu) { |
| 404 | if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { |
| 405 | if (mOptionsMenuInvalidated) { |
| 406 | mOptionsMenuInvalidated = false; |
| 407 | menu.clear(); |
| 408 | onCreatePanelMenu(featureId, menu); |
| 409 | } |
| 410 | boolean goforit = super.onPreparePanel(featureId, view, menu); |
| 411 | goforit |= mFragments.dispatchPrepareOptionsMenu(menu); |
| 412 | return goforit && menu.hasVisibleItems(); |
| 413 | } |
| 414 | return super.onPreparePanel(featureId, view, menu); |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * Ensure any outstanding fragment transactions have been committed. |
| 419 | */ |
| 420 | @Override |
| 421 | protected void onResume() { |
| 422 | super.onResume(); |
| 423 | mResumed = true; |
| 424 | mFragments.execPendingActions(); |
| 425 | } |
| 426 | |
| 427 | /** |
| 428 | * Retain all appropriate fragment and loader state. You can NOT |
| 429 | * override this yourself! |
| 430 | */ |
| 431 | @Override |
| 432 | public final Object onRetainNonConfigurationInstance() { |
| 433 | if (mStopped) { |
| 434 | doReallyStop(true); |
| 435 | } |
| 436 | |
| 437 | ArrayList<Fragment> fragments = mFragments.retainNonConfig(); |
| 438 | boolean retainLoaders = false; |
| 439 | if (mAllLoaderManagers != null) { |
| 440 | // prune out any loader managers that were already stopped and so |
| 441 | // have nothing useful to retain. |
| 442 | for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { |
| 443 | LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); |
| 444 | if (lm.mRetaining) { |
| 445 | retainLoaders = true; |
| 446 | } else { |
| 447 | lm.doDestroy(); |
| 448 | mAllLoaderManagers.removeAt(i); |
| 449 | } |
| 450 | } |
| 451 | } |
| 452 | if (fragments == null && !retainLoaders) { |
| 453 | return null; |
| 454 | } |
| 455 | |
| 456 | NonConfigurationInstances nci = new NonConfigurationInstances(); |
| 457 | nci.activity = null; |
| 458 | nci.children = null; |
| 459 | nci.fragments = fragments; |
| 460 | nci.loaders = mAllLoaderManagers; |
| 461 | return nci; |
| 462 | } |
| 463 | |
| 464 | /** |
| 465 | * Save all appropriate fragment state. |
| 466 | */ |
| 467 | @Override |
| 468 | protected void onSaveInstanceState(Bundle outState) { |
| 469 | super.onSaveInstanceState(outState); |
| 470 | Parcelable p = mFragments.saveAllState(); |
| 471 | if (p != null) { |
| 472 | outState.putParcelable(FRAGMENTS_TAG, p); |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | /** |
| 477 | * Dispatch onStart() to all fragments. Ensure any created loaders are |
| 478 | * now started. |
| 479 | */ |
| 480 | @Override |
| 481 | protected void onStart() { |
| 482 | super.onStart(); |
| 483 | |
| 484 | mStopped = false; |
| 485 | mHandler.removeMessages(MSG_REALLY_STOPPED); |
| 486 | |
| 487 | mFragments.noteStateNotSaved(); |
| 488 | mFragments.execPendingActions(); |
| 489 | |
| 490 | |
| 491 | if (!mLoadersStarted) { |
| 492 | mLoadersStarted = true; |
| 493 | if (mLoaderManager != null) { |
| 494 | mLoaderManager.doStart(); |
| 495 | } else if (!mCheckedForLoaderManager) { |
| 496 | mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); |
| 497 | } |
| 498 | mCheckedForLoaderManager = true; |
| 499 | } |
| 500 | // NOTE: HC onStart goes here. |
| 501 | |
| 502 | mFragments.dispatchStart(); |
| 503 | if (mAllLoaderManagers != null) { |
| 504 | for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { |
| 505 | mAllLoaderManagers.valueAt(i).finishRetain(); |
| 506 | } |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | /** |
| 511 | * Dispatch onStop() to all fragments. Ensure all loaders are stopped. |
| 512 | */ |
| 513 | @Override |
| 514 | protected void onStop() { |
| 515 | super.onStop(); |
| 516 | |
| 517 | mStopped = true; |
| 518 | mHandler.sendEmptyMessage(MSG_REALLY_STOPPED); |
| 519 | |
| 520 | mFragments.dispatchStop(); |
| 521 | } |
| 522 | |
| 523 | // ------------------------------------------------------------------------ |
| 524 | // NEW METHODS |
| 525 | // ------------------------------------------------------------------------ |
| 526 | |
| 527 | void supportInvalidateOptionsMenu() { |
| 528 | //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 529 | if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 530 | // If we are running on HC or greater, we can use the framework |
| 531 | // API to invalidate the options menu. |
| 532 | ActivityCompatHoneycomb.invalidateOptionsMenu(this); |
| 533 | return; |
| 534 | } |
| 535 | |
| 536 | // Whoops, older platform... we'll use a hack, to manually rebuild |
| 537 | // the options menu the next time it is prepared. |
| 538 | mOptionsMenuInvalidated = true; |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * Print the Activity's state into the given stream. This gets invoked if |
| 543 | * you run "adb shell dumpsys activity <activity_component_name>". |
| 544 | * |
| 545 | * @param prefix Desired prefix to prepend at each line of output. |
| 546 | * @param fd The raw file descriptor that the dump is being sent to. |
| 547 | * @param writer The PrintWriter to which you should dump your state. This will be |
| 548 | * closed for you after you return. |
| 549 | * @param args additional arguments to the dump request. |
| 550 | */ |
| 551 | public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { |
| 552 | //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 553 | if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) { |
| 554 | // XXX This can only work if we can call the super-class impl. :/ |
| 555 | //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args); |
| 556 | } |
| 557 | writer.print(prefix); writer.print("Local FragmentActivity "); |
| 558 | writer.print(Integer.toHexString(System.identityHashCode(this))); |
| 559 | writer.println(" State:"); |
| 560 | String innerPrefix = prefix + " "; |
| 561 | writer.print(innerPrefix); writer.print("mResumed="); |
| 562 | writer.print(mResumed); writer.print(" mStopped="); |
| 563 | writer.print(mStopped); writer.print(" mReallyStopped="); |
| 564 | writer.println(mReallyStopped); |
| 565 | writer.print(innerPrefix); writer.print("mLoadersStarted="); |
| 566 | writer.println(mLoadersStarted); |
| 567 | if (mLoaderManager != null) { |
| 568 | writer.print(prefix); writer.print("Loader Manager "); |
| 569 | writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); |
| 570 | writer.println(":"); |
| 571 | mLoaderManager.dump(prefix + " ", fd, writer, args); |
| 572 | } |
| 573 | mFragments.dump(prefix, fd, writer, args); |
| 574 | } |
| 575 | |
| 576 | void doReallyStop(boolean retaining) { |
| 577 | if (!mReallyStopped) { |
| 578 | mReallyStopped = true; |
| 579 | mHandler.removeMessages(MSG_REALLY_STOPPED); |
| 580 | onReallyStop(retaining); |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | /** |
| 585 | * Pre-HC, we didn't have a way to determine whether an activity was |
| 586 | * being stopped for a config change or not until we saw |
| 587 | * onRetainNonConfigurationInstance() called after onStop(). However |
| 588 | * we need to know this, to know whether to retain fragments. This will |
| 589 | * tell us what we need to know. |
| 590 | */ |
| 591 | void onReallyStop(boolean retaining) { |
| 592 | if (mLoadersStarted) { |
| 593 | mLoadersStarted = false; |
| 594 | if (mLoaderManager != null) { |
| 595 | if (!retaining) { |
| 596 | mLoaderManager.doStop(); |
| 597 | } else { |
| 598 | mLoaderManager.doRetain(); |
| 599 | } |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | mFragments.dispatchReallyStop(retaining); |
| 604 | } |
| 605 | |
| 606 | // ------------------------------------------------------------------------ |
| 607 | // FRAGMENT SUPPORT |
| 608 | // ------------------------------------------------------------------------ |
| 609 | |
| 610 | /** |
| 611 | * Called when a fragment is attached to the activity. |
| 612 | */ |
| 613 | public void onAttachFragment(Fragment fragment) { |
| 614 | } |
| 615 | |
| 616 | /** |
| 617 | * Return the FragmentManager for interacting with fragments associated |
| 618 | * with this activity. |
| 619 | */ |
| 620 | public FragmentManager getSupportFragmentManager() { |
| 621 | return mFragments; |
| 622 | } |
| 623 | |
| 624 | /** |
| 625 | * Modifies the standard behavior to allow results to be delivered to fragments. |
| 626 | * This imposes a restriction that requestCode be <= 0xffff. |
| 627 | */ |
| 628 | @Override |
| 629 | public void startActivityForResult(Intent intent, int requestCode) { |
| 630 | if (requestCode != -1 && (requestCode&0xffff0000) != 0) { |
| 631 | throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); |
| 632 | } |
| 633 | super.startActivityForResult(intent, requestCode); |
| 634 | } |
| 635 | |
| 636 | /** |
| 637 | * Called by Fragment.startActivityForResult() to implement its behavior. |
| 638 | */ |
| 639 | public void startActivityFromFragment(Fragment fragment, Intent intent, |
| 640 | int requestCode) { |
| 641 | if (requestCode == -1) { |
| 642 | super.startActivityForResult(intent, -1); |
| 643 | return; |
| 644 | } |
| 645 | if ((requestCode&0xffff0000) != 0) { |
| 646 | throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); |
| 647 | } |
| 648 | super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); |
| 649 | } |
| 650 | |
| 651 | void invalidateSupportFragmentIndex(int index) { |
| 652 | //Log.v(TAG, "invalidateFragmentIndex: index=" + index); |
| 653 | if (mAllLoaderManagers != null) { |
| 654 | LoaderManagerImpl lm = mAllLoaderManagers.get(index); |
| 655 | if (lm != null) { |
| 656 | lm.doDestroy(); |
| 657 | } |
| 658 | mAllLoaderManagers.remove(index); |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | // ------------------------------------------------------------------------ |
| 663 | // LOADER SUPPORT |
| 664 | // ------------------------------------------------------------------------ |
| 665 | |
| 666 | /** |
| 667 | * Return the LoaderManager for this fragment, creating it if needed. |
| 668 | */ |
| 669 | public LoaderManager getSupportLoaderManager() { |
| 670 | if (mLoaderManager != null) { |
| 671 | return mLoaderManager; |
| 672 | } |
| 673 | mCheckedForLoaderManager = true; |
| 674 | mLoaderManager = getLoaderManager(-1, mLoadersStarted, true); |
| 675 | return mLoaderManager; |
| 676 | } |
| 677 | |
| 678 | LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) { |
| 679 | if (mAllLoaderManagers == null) { |
| 680 | mAllLoaderManagers = new HCSparseArray<LoaderManagerImpl>(); |
| 681 | } |
| 682 | LoaderManagerImpl lm = mAllLoaderManagers.get(index); |
| 683 | if (lm == null) { |
| 684 | if (create) { |
| 685 | lm = new LoaderManagerImpl(this, started); |
| 686 | mAllLoaderManagers.put(index, lm); |
| 687 | } |
| 688 | } else { |
| 689 | lm.updateActivity(this); |
| 690 | } |
| 691 | return lm; |
| 692 | } |
| 693 | } |