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