Inflate fragments from layout into child FragmentManagers
Previously, if an app inflated a layout in a Fragment's onCreateView
that itself had fragments included, those fragments would be added to
the Activity-level FragmentManager and would not share the same
lifecycle with the fragment it was inflated for. This led to some
nasty management headaches.
If an app targets L or above, add the fragment to the child
FragmentManager of the current fragment when inflated using the
LayoutInflater passed to the parent fragment.
Bug 12763389
Change-Id: Iad4ed7d5df602aea9579bf1503e206fa894630c1
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d0d0289..83cbaa1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5139,78 +5139,7 @@
return onCreateView(name, context, attrs);
}
- String fname = attrs.getAttributeValue(null, "class");
- TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
- if (fname == null) {
- fname = a.getString(com.android.internal.R.styleable.Fragment_name);
- }
- int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
- String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
- a.recycle();
-
- 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.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;
- // 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;
+ return mFragments.onCreateView(parent, name, context, attrs);
}
/**
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6c0d379..2ff3d57 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1104,7 +1105,15 @@
* inflation. Maybe this should become a public API. Note sure.
*/
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
- return mActivity.getLayoutInflater();
+ // Newer platform versions use the child fragment manager's LayoutInflaterFactory.
+ if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity);
+ getChildFragmentManager(); // Init if needed; use raw implementation below.
+ result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory());
+ return result;
+ } else {
+ return mActivity.getLayoutInflater();
+ }
}
/**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b8f1962..a97fa650 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -27,11 +28,13 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.AttributeSet;
import android.util.DebugUtils;
import android.util.Log;
import android.util.LogWriter;
import android.util.SparseArray;
import android.util.SuperNotCalledException;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -397,7 +400,7 @@
/**
* Container for fragments associated with an activity.
*/
-final class FragmentManagerImpl extends FragmentManager {
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
static boolean DEBUG = false;
static final String TAG = "FragmentManager";
@@ -432,7 +435,7 @@
boolean mDestroyed;
String mNoTransactionsBecause;
boolean mHavePendingDeferredStart;
-
+
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
SparseArray<Parcelable> mStateArray = null;
@@ -2052,4 +2055,100 @@
}
return animAttr;
}
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ if (!"fragment".equals(name)) {
+ return null;
+ }
+
+ String fname = attrs.getAttributeValue(null, "class");
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
+ if (fname == null) {
+ fname = a.getString(com.android.internal.R.styleable.Fragment_name);
+ }
+ int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
+ String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
+ a.recycle();
+
+ 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 ? findFragmentById(id) : null;
+ if (fragment == null && tag != null) {
+ fragment = findFragmentByTag(tag);
+ }
+ if (fragment == null && containerId != View.NO_ID) {
+ fragment = findFragmentById(containerId);
+ }
+
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+ + Integer.toHexString(id) + " fname=" + fname
+ + " existing=" + fragment);
+ if (fragment == null) {
+ fragment = Fragment.instantiate(context, fname);
+ fragment.mFromLayout = true;
+ fragment.mFragmentId = id != 0 ? id : containerId;
+ fragment.mContainerId = containerId;
+ fragment.mTag = tag;
+ fragment.mInLayout = true;
+ fragment.mFragmentManager = this;
+ fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+ 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;
+ // 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(mActivity, attrs, fragment.mSavedFragmentState);
+ }
+ }
+
+ // If we haven't finished entering the CREATED state ourselves yet,
+ // push the inflated child fragment along.
+ if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
+ moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ } else {
+ 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;
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return null;
+ }
+
+ LayoutInflater.Factory2 getLayoutInflaterFactory() {
+ return this;
+ }
}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index b9ed801..577415e 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -311,7 +311,7 @@
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
- mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2);
+ mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
@@ -319,7 +319,11 @@
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
- mPrivateFactory = factory;
+ if (mPrivateFactory == null) {
+ mPrivateFactory = factory;
+ } else {
+ mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
+ }
}
/**