+++ /dev/null
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v2.app;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Base class for activities that want to use the support-based Fragment and
- * Loader APIs.
- *
- * <p>Known limitations:</p>
- * <ul>
- * <li> <p>When using the <fragment> tag, this implementation can not
- * use the parent view's ID as the new fragment's ID. You must explicitly
- * specify an ID (or tag) in the <fragment>.</p>
- * <li> <p>Prior to Honeycomb (3.0), an activity's state was saved before pausing.
- * Fragments are a significant amount of new state, and dynamic enough that one
- * often wants them to change between pausing and stopping. These classes
- * throw an exception if you try to change the fragment state after it has been
- * saved, to avoid accidental loss of UI state. However this is too restrictive
- * prior to Honeycomb, where the state is saved before pausing. To address this,
- * when running on platforms prior to Honeycomb an exception will not be thrown
- * if you change fragments between the state save and the activity being stopped.
- * This means that is some cases if the activity is restored from its last saved
- * state, this may be a snapshot slightly before what the user last saw.</p>
- * </ul>
- */
-public class FragmentActivity extends Activity {
- private static final String TAG = "FragmentActivity";
-
- private static final String FRAGMENTS_TAG = "android:support:fragments";
-
- // This is the SDK API version of Honeycomb (3.0).
- private static final int HONEYCOMB = 11;
-
- static final int MSG_REALLY_STOPPED = 1;
-
- final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_REALLY_STOPPED:
- if (mStopped) {
- doReallyStop(false);
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
-
- };
- final FragmentManagerImpl mFragments = new FragmentManagerImpl();
-
- boolean mResumed;
- boolean mStopped;
- boolean mReallyStopped;
-
- boolean mOptionsMenuInvalidated;
-
- boolean mCheckedForLoaderManager;
- boolean mLoadersStarted;
- HCSparseArray<LoaderManagerImpl> mAllLoaderManagers;
- LoaderManagerImpl mLoaderManager;
-
- static final class NonConfigurationInstances {
- Object activity;
- HashMap<String, Object> children;
- ArrayList<Fragment> fragments;
- HCSparseArray<LoaderManagerImpl> loaders;
- }
-
- static class FragmentTag {
- public static final int[] Fragment = {
- 0x01010003, 0x010100d0, 0x010100d1
- };
- public static final int Fragment_id = 1;
- public static final int Fragment_name = 0;
- public static final int Fragment_tag = 2;
- }
-
- // ------------------------------------------------------------------------
- // HOOKS INTO ACTIVITY
- // ------------------------------------------------------------------------
-
- /**
- * Dispatch incoming result to the correct fragment.
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- int index = requestCode>>16;
- if (index != 0) {
- index--;
- if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {
- Log.w(TAG, "Activity result fragment index out of range: 0x"
- + Integer.toHexString(requestCode));
- return;
- }
- Fragment frag = mFragments.mActive.get(index);
- if (frag == null) {
- Log.w(TAG, "Activity result no fragment exists for index: 0x"
- + Integer.toHexString(requestCode));
- }
- frag.onActivityResult(requestCode&0xffff, resultCode, data);
- return;
- }
-
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- /**
- * Take care of popping the fragment back stack or finishing the activity
- * as appropriate.
- */
- public void onBackPressed() {
- if (!mFragments.popBackStackImmediate()) {
- finish();
- }
- }
-
- /**
- * Dispatch configuration change to all fragments.
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mFragments.dispatchConfigurationChanged(newConfig);
- }
-
- /**
- * Perform initialization of all fragments and loaders.
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- mFragments.attachActivity(this);
- // Old versions of the platform didn't do this!
- if (getLayoutInflater().getFactory() == null) {
- getLayoutInflater().setFactory(this);
- }
-
- super.onCreate(savedInstanceState);
-
- NonConfigurationInstances nc = (NonConfigurationInstances)
- getLastNonConfigurationInstance();
- if (nc != null) {
- mAllLoaderManagers = nc.loaders;
- }
- if (savedInstanceState != null) {
- Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
- mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
- }
- mFragments.dispatchCreate();
- }
-
- /**
- * Dispatch to Fragment.onCreateOptionsMenu().
- */
- @Override
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- boolean show = super.onCreatePanelMenu(featureId, menu);
- show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
- //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- return show;
- }
- // Prior to Honeycomb, the framework can't invalidate the options
- // menu, so we must always say we have one in case the app later
- // invalidates it and needs to have it shown.
- return true;
- }
- return super.onCreatePanelMenu(featureId, menu);
- }
-
- /**
- * Add support for inflating the <fragment> tag.
- */
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- if (!"fragment".equals(name)) {
- return super.onCreateView(name, context, attrs);
- }
-
- String fname = attrs.getAttributeValue(null, "class");
- TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
- if (fname == null) {
- fname = a.getString(FragmentTag.Fragment_name);
- }
- int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
- String tag = a.getString(FragmentTag.Fragment_tag);
- a.recycle();
-
- View parent = null; // NOTE: no way to get parent pre-Honeycomb.
- int containerId = parent != null ? parent.getId() : 0;
- if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
- throw new IllegalArgumentException(attrs.getPositionDescription()
- + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
- }
-
- // If we restored from a previous state, we may already have
- // instantiated this fragment from the state and should use
- // that instance instead of making a new one.
- Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
- if (fragment == null && tag != null) {
- fragment = mFragments.findFragmentByTag(tag);
- }
- if (fragment == null && containerId != View.NO_ID) {
- fragment = mFragments.findFragmentById(containerId);
- }
-
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
- + Integer.toHexString(id) + " fname=" + fname
- + " existing=" + fragment);
- if (fragment == null) {
- fragment = Fragment.instantiate(this, fname);
- fragment.mFromLayout = true;
- fragment.mFragmentId = id != 0 ? id : containerId;
- fragment.mContainerId = containerId;
- fragment.mTag = tag;
- fragment.mInLayout = true;
- fragment.mImmediateActivity = this;
- fragment.mFragmentManager = mFragments;
- fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
- mFragments.addFragment(fragment, true);
-
- } else if (fragment.mInLayout) {
- // A fragment already exists and it is not one we restored from
- // previous state.
- throw new IllegalArgumentException(attrs.getPositionDescription()
- + ": Duplicate id 0x" + Integer.toHexString(id)
- + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
- + " with another fragment for " + fname);
- } else {
- // This fragment was retained from a previous instance; get it
- // going now.
- fragment.mInLayout = true;
- fragment.mImmediateActivity = this;
- // If this fragment is newly instantiated (either right now, or
- // from last saved state), then give it the attributes to
- // initialize itself.
- if (!fragment.mRetaining) {
- fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
- }
- mFragments.moveToState(fragment);
- }
-
- if (fragment.mView == null) {
- throw new IllegalStateException("Fragment " + fname
- + " did not create a view.");
- }
- if (id != 0) {
- fragment.mView.setId(id);
- }
- if (fragment.mView.getTag() == null) {
- fragment.mView.setTag(tag);
- }
- return fragment.mView;
- }
-
- /**
- * Destroy all fragments and loaders.
- */
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- doReallyStop(false);
-
- mFragments.dispatchDestroy();
- if (mLoaderManager != null) {
- mLoaderManager.doDestroy();
- }
- }
-
- /**
- * Take care of calling onBackPressed() for pre-Eclair platforms.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- //v4 if (android.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */
- if (android.support.v2.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */
- && keyCode == KeyEvent.KEYCODE_BACK
- && event.getRepeatCount() == 0) {
- // Take care of calling this method on earlier versions of
- // the platform where it doesn't exist.
- onBackPressed();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Dispatch onLowMemory() to all fragments.
- */
- @Override
- public void onLowMemory() {
- super.onLowMemory();
- mFragments.dispatchLowMemory();
- }
-
- /**
- * Dispatch context and options menu to fragments.
- */
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- if (super.onMenuItemSelected(featureId, item)) {
- return true;
- }
-
- switch (featureId) {
- case Window.FEATURE_OPTIONS_PANEL:
- return mFragments.dispatchOptionsItemSelected(item);
-
- case Window.FEATURE_CONTEXT_MENU:
- return mFragments.dispatchContextItemSelected(item);
-
- default:
- return false;
- }
- }
-
- /**
- * Call onOptionsMenuClosed() on fragments.
- */
- @Override
- public void onPanelClosed(int featureId, Menu menu) {
- switch (featureId) {
- case Window.FEATURE_OPTIONS_PANEL:
- mFragments.dispatchOptionsMenuClosed(menu);
- break;
- }
- super.onPanelClosed(featureId, menu);
- }
-
- /**
- * Dispatch onPause() to fragments.
- */
- @Override
- protected void onPause() {
- super.onPause();
- mResumed = false;
- mFragments.dispatchPause();
- }
-
- /**
- * Dispatch onActivityCreated() on fragments.
- */
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- mFragments.dispatchActivityCreated();
- }
-
- /**
- * Dispatch onResume() to fragments.
- */
- @Override
- protected void onPostResume() {
- super.onPostResume();
- mFragments.dispatchResume();
- mFragments.execPendingActions();
- }
-
- /**
- * Dispatch onPrepareOptionsMenu() to fragments.
- */
- @Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
- if (mOptionsMenuInvalidated) {
- mOptionsMenuInvalidated = false;
- menu.clear();
- onCreatePanelMenu(featureId, menu);
- }
- boolean goforit = super.onPreparePanel(featureId, view, menu);
- goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
- return goforit && menu.hasVisibleItems();
- }
- return super.onPreparePanel(featureId, view, menu);
- }
-
- /**
- * Ensure any outstanding fragment transactions have been committed.
- */
- @Override
- protected void onResume() {
- super.onResume();
- mResumed = true;
- mFragments.execPendingActions();
- }
-
- /**
- * Retain all appropriate fragment and loader state. You can NOT
- * override this yourself!
- */
- @Override
- public final Object onRetainNonConfigurationInstance() {
- if (mStopped) {
- doReallyStop(true);
- }
-
- ArrayList<Fragment> fragments = mFragments.retainNonConfig();
- boolean retainLoaders = false;
- if (mAllLoaderManagers != null) {
- // prune out any loader managers that were already stopped and so
- // have nothing useful to retain.
- for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
- LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i);
- if (lm.mRetaining) {
- retainLoaders = true;
- } else {
- lm.doDestroy();
- mAllLoaderManagers.removeAt(i);
- }
- }
- }
- if (fragments == null && !retainLoaders) {
- return null;
- }
-
- NonConfigurationInstances nci = new NonConfigurationInstances();
- nci.activity = null;
- nci.children = null;
- nci.fragments = fragments;
- nci.loaders = mAllLoaderManagers;
- return nci;
- }
-
- /**
- * Save all appropriate fragment state.
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- Parcelable p = mFragments.saveAllState();
- if (p != null) {
- outState.putParcelable(FRAGMENTS_TAG, p);
- }
- }
-
- /**
- * Dispatch onStart() to all fragments. Ensure any created loaders are
- * now started.
- */
- @Override
- protected void onStart() {
- super.onStart();
-
- mStopped = false;
- mHandler.removeMessages(MSG_REALLY_STOPPED);
-
- mFragments.noteStateNotSaved();
- mFragments.execPendingActions();
-
-
- if (!mLoadersStarted) {
- mLoadersStarted = true;
- if (mLoaderManager != null) {
- mLoaderManager.doStart();
- } else if (!mCheckedForLoaderManager) {
- mLoaderManager = getLoaderManager(-1, mLoadersStarted, false);
- }
- mCheckedForLoaderManager = true;
- }
- // NOTE: HC onStart goes here.
-
- mFragments.dispatchStart();
- if (mAllLoaderManagers != null) {
- for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
- mAllLoaderManagers.valueAt(i).finishRetain();
- }
- }
- }
-
- /**
- * Dispatch onStop() to all fragments. Ensure all loaders are stopped.
- */
- @Override
- protected void onStop() {
- super.onStop();
-
- mStopped = true;
- mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
-
- mFragments.dispatchStop();
- }
-
- // ------------------------------------------------------------------------
- // NEW METHODS
- // ------------------------------------------------------------------------
-
- void supportInvalidateOptionsMenu() {
- //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- // If we are running on HC or greater, we can use the framework
- // API to invalidate the options menu.
- //ActivityCompatHoneycomb.invalidateOptionsMenu(this);
- return;
- }
-
- // Whoops, older platform... we'll use a hack, to manually rebuild
- // the options menu the next time it is prepared.
- mOptionsMenuInvalidated = true;
- }
-
- /**
- * Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
- *
- * @param prefix Desired prefix to prepend at each line of output.
- * @param fd The raw file descriptor that the dump is being sent to.
- * @param writer The PrintWriter to which you should dump your state. This will be
- * closed for you after you return.
- * @param args additional arguments to the dump request.
- */
- public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- //v4 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- if (android.support.v2.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
- // XXX This can only work if we can call the super-class impl. :/
- //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args);
- }
- writer.print(prefix); writer.print("Local FragmentActivity ");
- writer.print(Integer.toHexString(System.identityHashCode(this)));
- writer.println(" State:");
- String innerPrefix = prefix + " ";
- writer.print(innerPrefix); writer.print("mResumed=");
- writer.print(mResumed); writer.print(" mStopped=");
- writer.print(mStopped); writer.print(" mReallyStopped=");
- writer.println(mReallyStopped);
- writer.print(innerPrefix); writer.print("mLoadersStarted=");
- writer.println(mLoadersStarted);
- if (mLoaderManager != null) {
- writer.print(prefix); writer.print("Loader Manager ");
- writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager)));
- writer.println(":");
- mLoaderManager.dump(prefix + " ", fd, writer, args);
- }
- mFragments.dump(prefix, fd, writer, args);
- }
-
- void doReallyStop(boolean retaining) {
- if (!mReallyStopped) {
- mReallyStopped = true;
- mHandler.removeMessages(MSG_REALLY_STOPPED);
- onReallyStop(retaining);
- }
- }
-
- /**
- * Pre-HC, we didn't have a way to determine whether an activity was
- * being stopped for a config change or not until we saw
- * onRetainNonConfigurationInstance() called after onStop(). However
- * we need to know this, to know whether to retain fragments. This will
- * tell us what we need to know.
- */
- void onReallyStop(boolean retaining) {
- if (mLoadersStarted) {
- mLoadersStarted = false;
- if (mLoaderManager != null) {
- if (!retaining) {
- mLoaderManager.doStop();
- } else {
- mLoaderManager.doRetain();
- }
- }
- }
-
- mFragments.dispatchReallyStop(retaining);
- }
-
- // ------------------------------------------------------------------------
- // FRAGMENT SUPPORT
- // ------------------------------------------------------------------------
-
- /**
- * Called when a fragment is attached to the activity.
- */
- public void onAttachFragment(Fragment fragment) {
- }
-
- /**
- * Return the FragmentManager for interacting with fragments associated
- * with this activity.
- */
- public FragmentManager getSupportFragmentManager() {
- return mFragments;
- }
-
- /**
- * Modifies the standard behavior to allow results to be delivered to fragments.
- * This imposes a restriction that requestCode be <= 0xffff.
- */
- @Override
- public void startActivityForResult(Intent intent, int requestCode) {
- if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
- throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
- }
- super.startActivityForResult(intent, requestCode);
- }
-
- /**
- * Called by Fragment.startActivityForResult() to implement its behavior.
- */
- public void startActivityFromFragment(Fragment fragment, Intent intent,
- int requestCode) {
- if (requestCode == -1) {
- super.startActivityForResult(intent, -1);
- return;
- }
- if ((requestCode&0xffff0000) != 0) {
- throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
- }
- super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
- }
-
- void invalidateSupportFragmentIndex(int index) {
- //Log.v(TAG, "invalidateFragmentIndex: index=" + index);
- if (mAllLoaderManagers != null) {
- LoaderManagerImpl lm = mAllLoaderManagers.get(index);
- if (lm != null) {
- lm.doDestroy();
- }
- mAllLoaderManagers.remove(index);
- }
- }
-
- // ------------------------------------------------------------------------
- // LOADER SUPPORT
- // ------------------------------------------------------------------------
-
- /**
- * Return the LoaderManager for this fragment, creating it if needed.
- */
- public LoaderManager getSupportLoaderManager() {
- if (mLoaderManager != null) {
- return mLoaderManager;
- }
- mCheckedForLoaderManager = true;
- mLoaderManager = getLoaderManager(-1, mLoadersStarted, true);
- return mLoaderManager;
- }
-
- LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) {
- if (mAllLoaderManagers == null) {
- mAllLoaderManagers = new HCSparseArray<LoaderManagerImpl>();
- }
- LoaderManagerImpl lm = mAllLoaderManagers.get(index);
- if (lm == null) {
- if (create) {
- lm = new LoaderManagerImpl(this, started);
- mAllLoaderManagers.put(index, lm);
- }
- } else {
- lm.updateActivity(this);
- }
- return lm;
- }
-}