All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.support.v4.app.FragmentManager Maven / Gradle / Ivy

Go to download

The Support Package includes static "support libraries" that you can add to your Android application in order to use APIs that are either not available for older platform versions or that offer "utility" APIs that aren't a part of the framework APIs.

The newest version!
/*
 * 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.v4.app;

import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.util.DebugUtils;
import android.support.v4.util.LogWriter;
import android.util.Log;
import android.util.SparseArray;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.Animation.AnimationListener;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * Static library support version of the framework's {@link android.app.FragmentManager}.
 * Used to write apps that run on platforms prior to Android 3.0.  When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation.  See the framework SDK
 * documentation for a class overview.
 * 
 * 

Your activity must derive from {@link FragmentActivity} to use this. */ public abstract class FragmentManager { /** * Representation of an entry on the fragment back stack, as created * with {@link FragmentTransaction#addToBackStack(String) * FragmentTransaction.addToBackStack()}. Entries can later be * retrieved with {@link FragmentManager#getBackStackEntryAt(int) * FragmentManager.getBackStackEntry()}. * *

Note that you should never hold on to a BackStackEntry object; * the identifier as returned by {@link #getId} is the only thing that * will be persisted across activity instances. */ public interface BackStackEntry { /** * Return the unique identifier for the entry. This is the only * representation of the entry that will persist across activity * instances. */ public int getId(); /** * Get the name that was supplied to * {@link FragmentTransaction#addToBackStack(String) * FragmentTransaction.addToBackStack(String)} when creating this entry. */ public String getName(); /** * Return the full bread crumb title resource identifier for the entry, * or 0 if it does not have one. */ public int getBreadCrumbTitleRes(); /** * Return the short bread crumb title resource identifier for the entry, * or 0 if it does not have one. */ public int getBreadCrumbShortTitleRes(); /** * Return the full bread crumb title for the entry, or null if it * does not have one. */ public CharSequence getBreadCrumbTitle(); /** * Return the short bread crumb title for the entry, or null if it * does not have one. */ public CharSequence getBreadCrumbShortTitle(); } /** * Interface to watch for changes to the back stack. */ public interface OnBackStackChangedListener { /** * Called whenever the contents of the back stack change. */ public void onBackStackChanged(); } /** * Start a series of edit operations on the Fragments associated with * this FragmentManager. * *

Note: A fragment transaction can only be created/committed prior * to an activity saving its state. If you try to commit a transaction * after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()} * (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart} * or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error. * This is because the framework takes care of saving your current fragments * in the state, and if changes are made after the state is saved then they * will be lost.

*/ public abstract FragmentTransaction beginTransaction(); /** @hide -- remove once prebuilts are in. */ @Deprecated public FragmentTransaction openTransaction() { return beginTransaction(); } /** * After a {@link FragmentTransaction} is committed with * {@link FragmentTransaction#commit FragmentTransaction.commit()}, it * is scheduled to be executed asynchronously on the process's main thread. * If you want to immediately executing any such pending operations, you * can call this function (only from the main thread) to do so. Note that * all callbacks and other related behavior will be done from within this * call, so be careful about where this is called from. * * @return Returns true if there were any pending transactions to be * executed. */ public abstract boolean executePendingTransactions(); /** * Finds a fragment that was identified by the given id either when inflated * from XML or as the container ID when added in a transaction. This first * searches through fragments that are currently added to the manager's * activity; if no such fragment is found, then all fragments currently * on the back stack associated with this ID are searched. * @return The fragment if found or null otherwise. */ public abstract Fragment findFragmentById(int id); /** * Finds a fragment that was identified by the given tag either when inflated * from XML or as supplied when added in a transaction. This first * searches through fragments that are currently added to the manager's * activity; if no such fragment is found, then all fragments currently * on the back stack are searched. * @return The fragment if found or null otherwise. */ public abstract Fragment findFragmentByTag(String tag); /** * Flag for {@link #popBackStack(String, int)} * and {@link #popBackStack(int, int)}: If set, and the name or ID of * a back stack entry has been supplied, then all matching entries will * be consumed until one that doesn't match is found or the bottom of * the stack is reached. Otherwise, all entries up to but not including that entry * will be removed. */ public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; /** * Pop the top state off the back stack. Returns true if there was one * to pop, else false. This function is asynchronous -- it enqueues the * request to pop, but the action will not be performed until the application * returns to its event loop. */ public abstract void popBackStack(); /** * Like {@link #popBackStack()}, but performs the operation immediately * inside of the call. This is like calling {@link #executePendingTransactions()} * afterwards. * @return Returns true if there was something popped, else false. */ public abstract boolean popBackStackImmediate(); /** * Pop the last fragment transition from the manager's fragment * back stack. If there is nothing to pop, false is returned. * This function is asynchronous -- it enqueues the * request to pop, but the action will not be performed until the application * returns to its event loop. * * @param name If non-null, this is the name of a previous back state * to look for; if found, all states up to that state will be popped. The * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether * the named state itself is popped. If null, only the top state is popped. * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. */ public abstract void popBackStack(String name, int flags); /** * Like {@link #popBackStack(String, int)}, but performs the operation immediately * inside of the call. This is like calling {@link #executePendingTransactions()} * afterwards. * @return Returns true if there was something popped, else false. */ public abstract boolean popBackStackImmediate(String name, int flags); /** * Pop all back stack states up to the one with the given identifier. * This function is asynchronous -- it enqueues the * request to pop, but the action will not be performed until the application * returns to its event loop. * * @param id Identifier of the stated to be popped. If no identifier exists, * false is returned. * The identifier is the number returned by * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether * the named state itself is popped. * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. */ public abstract void popBackStack(int id, int flags); /** * Like {@link #popBackStack(int, int)}, but performs the operation immediately * inside of the call. This is like calling {@link #executePendingTransactions()} * afterwards. * @return Returns true if there was something popped, else false. */ public abstract boolean popBackStackImmediate(int id, int flags); /** * Return the number of entries currently in the back stack. */ public abstract int getBackStackEntryCount(); /** * Return the BackStackEntry at index index in the back stack; * entries start index 0 being the bottom of the stack. */ public abstract BackStackEntry getBackStackEntryAt(int index); /** * Add a new listener for changes to the fragment back stack. */ public abstract void addOnBackStackChangedListener(OnBackStackChangedListener listener); /** * Remove a listener that was previously added with * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}. */ public abstract void removeOnBackStackChangedListener(OnBackStackChangedListener listener); /** * Put a reference to a fragment in a Bundle. This Bundle can be * persisted as saved state, and when later restoring * {@link #getFragment(Bundle, String)} will return the current * instance of the same fragment. * * @param bundle The bundle in which to put the fragment reference. * @param key The name of the entry in the bundle. * @param fragment The Fragment whose reference is to be stored. */ public abstract void putFragment(Bundle bundle, String key, Fragment fragment); /** * Retrieve the current Fragment instance for a reference previously * placed with {@link #putFragment(Bundle, String, Fragment)}. * * @param bundle The bundle from which to retrieve the fragment reference. * @param key The name of the entry in the bundle. * @return Returns the current Fragment instance that is associated with * the given reference. */ public abstract Fragment getFragment(Bundle bundle, String key); /** * Save the current instance state of the given Fragment. This can be * used later when creating a new instance of the Fragment and adding * it to the fragment manager, to have it create itself to match the * current state returned here. Note that there are limits on how * this can be used: * *
    *
  • The Fragment must currently be attached to the FragmentManager. *
  • A new Fragment created using this saved state must be the same class * type as the Fragment it was created from. *
  • The saved state can not contain dependencies on other fragments -- * that is it can't use {@link #putFragment(Bundle, String, Fragment)} to * store a fragment reference because that reference may not be valid when * this saved state is later used. Likewise the Fragment's target and * result code are not included in this state. *
* * @param f The Fragment whose state is to be saved. * @return The generated state. This will be null if there was no * interesting state created by the fragment. */ public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); /** * Print the FragmentManager's state into the given stream. * * @param prefix Text to print at the front of each line. * @param fd The raw file descriptor that the dump is being sent to. * @param writer A PrintWriter to which the dump is to be set. * @param args Additional arguments to the dump request. */ public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args); /** * Control whether the framework's internal fragment manager debugging * logs are turned on. If enabled, you will see output in logcat as * the framework performs fragment operations. */ public static void enableDebugLogging(boolean enabled) { FragmentManagerImpl.DEBUG = enabled; } } final class FragmentManagerState implements Parcelable { FragmentState[] mActive; int[] mAdded; BackStackState[] mBackStack; public FragmentManagerState() { } public FragmentManagerState(Parcel in) { mActive = in.createTypedArray(FragmentState.CREATOR); mAdded = in.createIntArray(); mBackStack = in.createTypedArray(BackStackState.CREATOR); } public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeTypedArray(mActive, flags); dest.writeIntArray(mAdded); dest.writeTypedArray(mBackStack, flags); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public FragmentManagerState createFromParcel(Parcel in) { return new FragmentManagerState(in); } public FragmentManagerState[] newArray(int size) { return new FragmentManagerState[size]; } }; } /** * Container for fragments associated with an activity. */ final class FragmentManagerImpl extends FragmentManager { static boolean DEBUG = false; static final String TAG = "FragmentManager"; static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11; static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"; static final String TARGET_STATE_TAG = "android:target_state"; static final String VIEW_STATE_TAG = "android:view_state"; static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint"; ArrayList mPendingActions; Runnable[] mTmpActions; boolean mExecutingActions; ArrayList mActive; ArrayList mAdded; ArrayList mAvailIndices; ArrayList mBackStack; ArrayList mCreatedMenus; // Must be accessed while locked. ArrayList mBackStackIndices; ArrayList mAvailBackStackIndices; ArrayList mBackStackChangeListeners; int mCurState = Fragment.INITIALIZING; FragmentActivity mActivity; boolean mNeedMenuInvalidate; boolean mStateSaved; boolean mDestroyed; String mNoTransactionsBecause; boolean mHavePendingDeferredStart; // Temporary vars for state save and restore. Bundle mStateBundle = null; SparseArray mStateArray = null; Runnable mExecCommit = new Runnable() { @Override public void run() { execPendingActions(); } }; @Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } @Override public boolean executePendingTransactions() { return execPendingActions(); } @Override public void popBackStack() { enqueueAction(new Runnable() { @Override public void run() { popBackStackState(mActivity.mHandler, null, -1, 0); } }, false); } @Override public boolean popBackStackImmediate() { checkStateLoss(); executePendingTransactions(); return popBackStackState(mActivity.mHandler, null, -1, 0); } @Override public void popBackStack(final String name, final int flags) { enqueueAction(new Runnable() { @Override public void run() { popBackStackState(mActivity.mHandler, name, -1, flags); } }, false); } @Override public boolean popBackStackImmediate(String name, int flags) { checkStateLoss(); executePendingTransactions(); return popBackStackState(mActivity.mHandler, name, -1, flags); } @Override public void popBackStack(final int id, final int flags) { if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } enqueueAction(new Runnable() { @Override public void run() { popBackStackState(mActivity.mHandler, null, id, flags); } }, false); } @Override public boolean popBackStackImmediate(int id, int flags) { checkStateLoss(); executePendingTransactions(); if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } return popBackStackState(mActivity.mHandler, null, id, flags); } @Override public int getBackStackEntryCount() { return mBackStack != null ? mBackStack.size() : 0; } @Override public BackStackEntry getBackStackEntryAt(int index) { return mBackStack.get(index); } @Override public void addOnBackStackChangedListener(OnBackStackChangedListener listener) { if (mBackStackChangeListeners == null) { mBackStackChangeListeners = new ArrayList(); } mBackStackChangeListeners.add(listener); } @Override public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) { if (mBackStackChangeListeners != null) { mBackStackChangeListeners.remove(listener); } } @Override public void putFragment(Bundle bundle, String key, Fragment fragment) { if (fragment.mIndex < 0) { throw new IllegalStateException("Fragment " + fragment + " is not currently in the FragmentManager"); } bundle.putInt(key, fragment.mIndex); } @Override public Fragment getFragment(Bundle bundle, String key) { int index = bundle.getInt(key, -1); if (index == -1) { return null; } if (index >= mActive.size()) { throw new IllegalStateException("Fragement no longer exists for key " + key + ": index " + index); } Fragment f = mActive.get(index); if (f == null) { throw new IllegalStateException("Fragement no longer exists for key " + key + ": index " + index); } return f; } @Override public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { if (fragment.mIndex < 0) { throw new IllegalStateException("Fragment " + fragment + " is not currently in the FragmentManager"); } if (fragment.mState > Fragment.INITIALIZING) { Bundle result = saveFragmentBasicState(fragment); return result != null ? new Fragment.SavedState(result) : null; } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("FragmentManager{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" in "); DebugUtils.buildShortClassTag(mActivity, sb); sb.append("}}"); return sb.toString(); } @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { String innerPrefix = prefix + " "; int N; if (mActive != null) { N = mActive.size(); if (N > 0) { writer.print(prefix); writer.print("Active Fragments in "); writer.print(Integer.toHexString(System.identityHashCode(this))); writer.println(":"); for (int i=0; i 0) { writer.print(prefix); writer.println("Added Fragments:"); for (int i=0; i 0) { writer.print(prefix); writer.println("Fragments Created Menus:"); for (int i=0; i 0) { writer.print(prefix); writer.println("Back Stack:"); for (int i=0; i 0) { writer.print(prefix); writer.println("Back Stack Indices:"); for (int i=0; i 0) { writer.print(prefix); writer.print("mAvailBackStackIndices: "); writer.println(Arrays.toString(mAvailBackStackIndices.toArray())); } } if (mPendingActions != null) { N = mPendingActions.size(); if (N > 0) { writer.print(prefix); writer.println("Pending Actions:"); for (int i=0; i Fragment.CREATED) { newState = Fragment.CREATED; } if (f.mRemoving && newState > f.mState) { // While removing a fragment, we can't change it to a higher state. newState = f.mState; } // Defer start if requested; don't allow it to move to STARTED or higher // if it's not already started. if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } if (f.mState < newState) { // For fragments that are created from a layout, when restoring from // state we don't want to allow them to be created until they are // being reloaded from the layout. if (f.mFromLayout && !f.mInLayout) { return; } if (f.mAnimatingAway != null) { // The fragment is currently being animated... but! Now we // want to move our state back up. Give up on waiting for the // animation, move to whatever the final state should be once // the animation is done, and then we can proceed from there. f.mAnimatingAway = null; moveToState(f, f.mStateAfterAnimating, 0, 0); } switch (f.mState) { case Fragment.INITIALIZING: if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); if (f.mSavedFragmentState != null) { f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mTarget = getFragment(f.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG); if (f.mTarget != null) { f.mTargetRequestCode = f.mSavedFragmentState.getInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); } f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); if (!f.mUserVisibleHint) { f.mDeferStart = true; if (newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } } } f.mActivity = mActivity; f.mFragmentManager = mActivity.mFragments; f.mCalled = false; f.onAttach(mActivity); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } mActivity.onAttachFragment(f); if (!f.mRetaining) { f.mCalled = false; f.onCreate(f.mSavedFragmentState); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onCreate()"); } } f.mRetaining = false; if (f.mFromLayout) { // For fragments that are part of the content view // layout, we need to instantiate the view immediately // and the inflater will take care of adding it. f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState); if (f.mView != null) { f.mInnerView = f.mView; f.mView = NoSaveStateFrameLayout.wrap(f.mView); if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); } else { f.mInnerView = null; } } case Fragment.CREATED: if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { container = (ViewGroup)mActivity.findViewById(f.mContainerId); if (container == null && !f.mRestored) { throw new IllegalArgumentException("No view found for id 0x" + Integer.toHexString(f.mContainerId) + " for fragment " + f); } } f.mContainer = container; f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mInnerView = f.mView; f.mView = NoSaveStateFrameLayout.wrap(f.mView); if (container != null) { Animation anim = loadAnimation(f, transit, true, transitionStyle); if (anim != null) { f.mView.startAnimation(anim); } container.addView(f.mView); } if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); } else { f.mInnerView = null; } } f.mCalled = false; f.onActivityCreated(f.mSavedFragmentState); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onActivityCreated()"); } if (f.mView != null) { f.restoreViewState(); } f.mSavedFragmentState = null; } case Fragment.ACTIVITY_CREATED: case Fragment.STOPPED: if (newState > Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "moveto STARTED: " + f); f.mCalled = false; f.performStart(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onStart()"); } } case Fragment.STARTED: if (newState > Fragment.STARTED) { if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f); f.mCalled = false; f.mResumed = true; f.onResume(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onResume()"); } f.mSavedFragmentState = null; f.mSavedViewState = null; } } } else if (f.mState > newState) { switch (f.mState) { case Fragment.RESUMED: if (newState < Fragment.RESUMED) { if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); f.mCalled = false; f.onPause(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onPause()"); } f.mResumed = false; } case Fragment.STARTED: if (newState < Fragment.STARTED) { if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); f.mCalled = false; f.performStop(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onStop()"); } } case Fragment.STOPPED: if (newState < Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f); f.performReallyStop(); } case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f); if (f.mView != null) { // Need to save the current view state if not // done already. if (!mActivity.isFinishing() && f.mSavedViewState == null) { saveFragmentViewState(f); } } f.mCalled = false; f.performDestroyView(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onDestroyView()"); } if (f.mView != null && f.mContainer != null) { Animation anim = null; if (mCurState > Fragment.INITIALIZING && !mDestroyed) { anim = loadAnimation(f, transit, false, transitionStyle); } if (anim != null) { final Fragment fragment = f; f.mAnimatingAway = f.mView; f.mStateAfterAnimating = newState; anim.setAnimationListener(new AnimationListener() { @Override public void onAnimationEnd(Animation animation) { if (fragment.mAnimatingAway != null) { fragment.mAnimatingAway = null; moveToState(fragment, fragment.mStateAfterAnimating, 0, 0); } } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationStart(Animation animation) { } }); f.mView.startAnimation(anim); } f.mContainer.removeView(f.mView); } f.mContainer = null; f.mView = null; f.mInnerView = null; } case Fragment.CREATED: if (newState < Fragment.CREATED) { if (mDestroyed) { if (f.mAnimatingAway != null) { // The fragment's containing activity is // being destroyed, but this fragment is // currently animating away. Stop the // animation right now -- it is not needed, // and we can't wait any more on destroying // the fragment. View v = f.mAnimatingAway; f.mAnimatingAway = null; v.clearAnimation(); } } if (f.mAnimatingAway != null) { // We are waiting for the fragment's view to finish // animating away. Just make a note of the state // the fragment now should move to once the animation // is done. f.mStateAfterAnimating = newState; newState = Fragment.CREATED; } else { if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); if (!f.mRetaining) { f.mCalled = false; f.onDestroy(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onDestroy()"); } } f.mCalled = false; f.onDetach(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onDetach()"); } if (!f.mRetaining) { makeInactive(f); } else { f.mActivity = null; f.mFragmentManager = null; } } } } } f.mState = newState; } void moveToState(Fragment f) { moveToState(f, mCurState, 0, 0); } void moveToState(int newState, boolean always) { moveToState(newState, 0, 0, always); } void moveToState(int newState, int transit, int transitStyle, boolean always) { if (mActivity == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No activity"); } if (!always && mCurState == newState) { return; } mCurState = newState; if (mActive != null) { boolean loadersRunning = false; for (int i=0; i= 0) { return; } if (mAvailIndices == null || mAvailIndices.size() <= 0) { if (mActive == null) { mActive = new ArrayList(); } f.setIndex(mActive.size()); mActive.add(f); } else { f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1)); mActive.set(f.mIndex, f); } } void makeInactive(Fragment f) { if (f.mIndex < 0) { return; } if (DEBUG) Log.v(TAG, "Freeing fragment index " + f.mIndex); mActive.set(f.mIndex, null); if (mAvailIndices == null) { mAvailIndices = new ArrayList(); } mAvailIndices.add(f.mIndex); mActivity.invalidateSupportFragmentIndex(f.mIndex); f.initState(); } public void addFragment(Fragment fragment, boolean moveToStateNow) { if (mAdded == null) { mAdded = new ArrayList(); } if (DEBUG) Log.v(TAG, "add: " + fragment); makeActive(fragment); if (!fragment.mDetached) { mAdded.add(fragment); fragment.mAdded = true; fragment.mRemoving = false; if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } if (moveToStateNow) { moveToState(fragment); } } } public void removeFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { mAdded.remove(fragment); if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; fragment.mRemoving = true; moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, transition, transitionStyle); } } public void hideFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "hide: " + fragment); if (!fragment.mHidden) { fragment.mHidden = true; if (fragment.mView != null) { Animation anim = loadAnimation(fragment, transition, true, transitionStyle); if (anim != null) { fragment.mView.startAnimation(anim); } fragment.mView.setVisibility(View.GONE); } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(true); } } public void showFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "show: " + fragment); if (fragment.mHidden) { fragment.mHidden = false; if (fragment.mView != null) { Animation anim = loadAnimation(fragment, transition, true, transitionStyle); if (anim != null) { fragment.mView.startAnimation(anim); } fragment.mView.setVisibility(View.VISIBLE); } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(false); } } public void detachFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "detach: " + fragment); if (!fragment.mDetached) { fragment.mDetached = true; if (fragment.mAdded) { // We are not already in back stack, so need to remove the fragment. mAdded.remove(fragment); if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; moveToState(fragment, Fragment.CREATED, transition, transitionStyle); } } } public void attachFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "attach: " + fragment); if (fragment.mDetached) { fragment.mDetached = false; if (!fragment.mAdded) { mAdded.add(fragment); fragment.mAdded = true; if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } moveToState(fragment, mCurState, transition, transitionStyle); } } } public Fragment findFragmentById(int id) { if (mActive != null) { // First look through added fragments. for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f != null && f.mFragmentId == id) { return f; } } // Now for any known fragment. for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f != null && f.mFragmentId == id) { return f; } } } return null; } public Fragment findFragmentByTag(String tag) { if (mActive != null && tag != null) { // First look through added fragments. for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f != null && tag.equals(f.mTag)) { return f; } } // Now for any known fragment. for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f != null && tag.equals(f.mTag)) { return f; } } } return null; } public Fragment findFragmentByWho(String who) { if (mActive != null && who != null) { for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f != null && who.equals(f.mWho)) { return f; } } } return null; } private void checkStateLoss() { if (mStateSaved) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } if (mNoTransactionsBecause != null) { throw new IllegalStateException( "Can not perform this action inside of " + mNoTransactionsBecause); } } public void enqueueAction(Runnable action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mActivity == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList(); } mPendingActions.add(action); if (mPendingActions.size() == 1) { mActivity.mHandler.removeCallbacks(mExecCommit); mActivity.mHandler.post(mExecCommit); } } } public int allocBackStackIndex(BackStackRecord bse) { synchronized (this) { if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) { if (mBackStackIndices == null) { mBackStackIndices = new ArrayList(); } int index = mBackStackIndices.size(); if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); mBackStackIndices.add(bse); return index; } else { int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1); if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); mBackStackIndices.set(index, bse); return index; } } } public void setBackStackIndex(int index, BackStackRecord bse) { synchronized (this) { if (mBackStackIndices == null) { mBackStackIndices = new ArrayList(); } int N = mBackStackIndices.size(); if (index < N) { if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); mBackStackIndices.set(index, bse); } else { while (N < index) { mBackStackIndices.add(null); if (mAvailBackStackIndices == null) { mAvailBackStackIndices = new ArrayList(); } if (DEBUG) Log.v(TAG, "Adding available back stack index " + N); mAvailBackStackIndices.add(N); N++; } if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); mBackStackIndices.add(bse); } } } public void freeBackStackIndex(int index) { synchronized (this) { mBackStackIndices.set(index, null); if (mAvailBackStackIndices == null) { mAvailBackStackIndices = new ArrayList(); } if (DEBUG) Log.v(TAG, "Freeing back stack index " + index); mAvailBackStackIndices.add(index); } } /** * Only call from main thread! */ public boolean execPendingActions() { if (mExecutingActions) { throw new IllegalStateException("Recursive entry to executePendingTransactions"); } if (Looper.myLooper() != mActivity.mHandler.getLooper()) { throw new IllegalStateException("Must be called from main thread of process"); } boolean didSomething = false; while (true) { int numActions; synchronized (this) { if (mPendingActions == null || mPendingActions.size() == 0) { break; } numActions = mPendingActions.size(); if (mTmpActions == null || mTmpActions.length < numActions) { mTmpActions = new Runnable[numActions]; } mPendingActions.toArray(mTmpActions); mPendingActions.clear(); mActivity.mHandler.removeCallbacks(mExecCommit); } mExecutingActions = true; for (int i=0; i(); } mBackStack.add(state); reportBackStackChanged(); } boolean popBackStackState(Handler handler, String name, int id, int flags) { if (mBackStack == null) { return false; } if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) { int last = mBackStack.size()-1; if (last < 0) { return false; } final BackStackRecord bss = mBackStack.remove(last); bss.popFromBackStack(true); reportBackStackChanged(); } else { int index = -1; if (name != null || id >= 0) { // If a name or ID is specified, look for that place in // the stack. index = mBackStack.size()-1; while (index >= 0) { BackStackRecord bss = mBackStack.get(index); if (name != null && name.equals(bss.getName())) { break; } if (id >= 0 && id == bss.mIndex) { break; } index--; } if (index < 0) { return false; } if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) { index--; // Consume all following entries that match. while (index >= 0) { BackStackRecord bss = mBackStack.get(index); if ((name != null && name.equals(bss.getName())) || (id >= 0 && id == bss.mIndex)) { index--; continue; } break; } } } if (index == mBackStack.size()-1) { return false; } final ArrayList states = new ArrayList(); for (int i=mBackStack.size()-1; i>index; i--) { states.add(mBackStack.remove(i)); } final int LAST = states.size()-1; for (int i=0; i<=LAST; i++) { if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); states.get(i).popFromBackStack(i == LAST); } reportBackStackChanged(); } return true; } ArrayList retainNonConfig() { ArrayList fragments = null; if (mActive != null) { for (int i=0; i(); } fragments.add(f); f.mRetaining = true; f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; } } } return fragments; } void saveFragmentViewState(Fragment f) { if (f.mInnerView == null) { return; } if (mStateArray == null) { mStateArray = new SparseArray(); } else { mStateArray.clear(); } f.mInnerView.saveHierarchyState(mStateArray); if (mStateArray.size() > 0) { f.mSavedViewState = mStateArray; mStateArray = null; } } Bundle saveFragmentBasicState(Fragment f) { Bundle result = null; if (mStateBundle == null) { mStateBundle = new Bundle(); } f.onSaveInstanceState(mStateBundle); if (!mStateBundle.isEmpty()) { result = mStateBundle; mStateBundle = null; } if (f.mView != null) { saveFragmentViewState(f); } if (f.mSavedViewState != null) { if (result == null) { result = new Bundle(); } result.putSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); } if (!f.mUserVisibleHint) { // Only add this if it's not the default value result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint); } return result; } Parcelable saveAllState() { // Make sure all pending operations have now been executed to get // our state update-to-date. execPendingActions(); if (HONEYCOMB) { // As of Honeycomb, we save state after pausing. Prior to that // it is before pausing. With fragments this is an issue, since // there are many things you may do after pausing but before // stopping that change the fragment state. For those older // devices, we will not at this point say that we have saved // the state, so we will allow them to continue doing fragment // transactions. This retains the same semantics as Honeycomb, // though you do have the risk of losing the very most recent state // if the process is killed... we'll live with that. mStateSaved = true; } if (mActive == null || mActive.size() <= 0) { return null; } // First collect all active fragments. int N = mActive.size(); FragmentState[] active = new FragmentState[N]; boolean haveFragments = false; for (int i=0; i Fragment.INITIALIZING && fs.mSavedFragmentState == null) { fs.mSavedFragmentState = saveFragmentBasicState(f); if (f.mTarget != null) { if (f.mTarget.mIndex < 0) { String msg = "Failure saving state: " + f + " has target not in fragment manager: " + f.mTarget; Log.e(TAG, msg); dump(" ", null, new PrintWriter(new LogWriter(TAG)), new String[] { }); throw new IllegalStateException(msg); } if (fs.mSavedFragmentState == null) { fs.mSavedFragmentState = new Bundle(); } putFragment(fs.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget); if (f.mTargetRequestCode != 0) { fs.mSavedFragmentState.putInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, f.mTargetRequestCode); } } } else { fs.mSavedFragmentState = f.mSavedFragmentState; } if (DEBUG) Log.v(TAG, "Saved state of " + f + ": " + fs.mSavedFragmentState); } } if (!haveFragments) { if (DEBUG) Log.v(TAG, "saveAllState: no fragments!"); return null; } int[] added = null; BackStackState[] backStack = null; // Build list of currently added fragments. if (mAdded != null) { N = mAdded.size(); if (N > 0) { added = new int[N]; for (int i=0; i 0) { backStack = new BackStackState[N]; for (int i=0; i nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; if (fms.mActive == null) return; // First re-attach any non-config instances we are retaining back // to their saved state, so we don't try to instantiate them again. if (nonConfig != null) { for (int i=0; i(fms.mActive.length); if (mAvailIndices != null) { mAvailIndices.clear(); } for (int i=0; i(); } if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i); mAvailIndices.add(i); } } // Update the target of all retained fragments. if (nonConfig != null) { for (int i=0; i= 0) { if (f.mTargetIndex < mActive.size()) { f.mTarget = mActive.get(f.mTargetIndex); } else { Log.w(TAG, "Re-attaching retained fragment " + f + " target no longer exists: " + f.mTargetIndex); f.mTarget = null; } } } } // Build the list of currently added fragments. if (fms.mAdded != null) { mAdded = new ArrayList(fms.mAdded.length); for (int i=0; i(fms.mBackStack.length); for (int i=0; i= 0) { setBackStackIndex(bse.mIndex, bse); } } } else { mBackStack = null; } } public void attachActivity(FragmentActivity activity) { if (mActivity != null) throw new IllegalStateException(); mActivity = activity; } public void noteStateNotSaved() { mStateSaved = false; } public void dispatchCreate() { mStateSaved = false; moveToState(Fragment.CREATED, false); } public void dispatchActivityCreated() { mStateSaved = false; moveToState(Fragment.ACTIVITY_CREATED, false); } public void dispatchStart() { mStateSaved = false; moveToState(Fragment.STARTED, false); } public void dispatchResume() { mStateSaved = false; moveToState(Fragment.RESUMED, false); } public void dispatchPause() { moveToState(Fragment.STARTED, false); } public void dispatchStop() { // See saveAllState() for the explanation of this. We do this for // all platform versions, to keep our behavior more consistent between // them. mStateSaved = true; moveToState(Fragment.STOPPED, false); } public void dispatchReallyStop() { moveToState(Fragment.ACTIVITY_CREATED, false); } public void dispatchDestroy() { mDestroyed = true; execPendingActions(); moveToState(Fragment.INITIALIZING, false); mActivity = null; } public void dispatchConfigurationChanged(Configuration newConfig) { if (mActive != null) { for (int i=0; i newMenus = null; if (mActive != null) { for (int i=0; i(); } newMenus.add(f); } } } if (mCreatedMenus != null) { for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy