Merge "Primary navigation fragments"

This commit is contained in:
TreeHugger Robot
2017-01-31 18:40:40 +00:00
committed by Android (Google) Code Review
7 changed files with 203 additions and 58 deletions

View File

@@ -4687,6 +4687,7 @@ package android.app {
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
method public abstract void popBackStack();
@@ -4763,6 +4764,7 @@ package android.app {
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int);
method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment);
method public abstract android.app.FragmentTransaction setTransition(int);
method public abstract android.app.FragmentTransaction setTransitionStyle(int);
method public abstract android.app.FragmentTransaction show(android.app.Fragment);

View File

@@ -4847,6 +4847,7 @@ package android.app {
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
method public abstract void popBackStack();
@@ -4923,6 +4924,7 @@ package android.app {
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int);
method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment);
method public abstract android.app.FragmentTransaction setTransition(int);
method public abstract android.app.FragmentTransaction setTransitionStyle(int);
method public abstract android.app.FragmentTransaction show(android.app.Fragment);

View File

@@ -4697,6 +4697,7 @@ package android.app {
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
method public abstract void popBackStack();
@@ -4773,6 +4774,7 @@ package android.app {
method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int);
method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int);
method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment);
method public abstract android.app.FragmentTransaction setTransition(int);
method public abstract android.app.FragmentTransaction setTransitionStyle(int);
method public abstract android.app.FragmentTransaction show(android.app.Fragment);

View File

@@ -186,6 +186,8 @@ final class BackStackRecord extends FragmentTransaction implements
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;
static final int OP_SET_PRIMARY_NAV = 8;
static final int OP_UNSET_PRIMARY_NAV = 9;
static final class Op {
int cmd;
@@ -194,6 +196,14 @@ final class BackStackRecord extends FragmentTransaction implements
int exitAnim;
int popEnterAnim;
int popExitAnim;
Op() {
}
Op(int cmd, Fragment fragment) {
this.cmd = cmd;
this.fragment = fragment;
}
}
ArrayList<Op> mOps = new ArrayList<>();
@@ -318,6 +328,13 @@ final class BackStackRecord extends FragmentTransaction implements
case OP_ATTACH:
cmdStr = "ATTACH";
break;
case OP_SET_PRIMARY_NAV:
cmdStr="SET_PRIMARY_NAV";
break;
case OP_UNSET_PRIMARY_NAV:
cmdStr="UNSET_PRIMARY_NAV";
break;
default:
cmdStr = "cmd=" + op.cmd;
break;
@@ -438,10 +455,7 @@ final class BackStackRecord extends FragmentTransaction implements
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
addOp(new Op(opcmd, fragment));
}
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
@@ -458,46 +472,37 @@ final class BackStackRecord extends FragmentTransaction implements
}
public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op);
addOp(new Op(OP_REMOVE, fragment));
return this;
}
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
addOp(new Op(OP_HIDE, fragment));
return this;
}
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op);
addOp(new Op(OP_SHOW, fragment));
return this;
}
public FragmentTransaction detach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_DETACH;
op.fragment = fragment;
addOp(op);
addOp(new Op(OP_DETACH, fragment));
return this;
}
public FragmentTransaction attach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_ATTACH;
op.fragment = fragment;
addOp(op);
addOp(new Op(OP_ATTACH, fragment));
return this;
}
public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) {
addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
return this;
}
@@ -689,7 +694,8 @@ final class BackStackRecord extends FragmentTransaction implements
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
if (op.fragment.mContainerId == containerId) {
final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0;
if (fragContainer != 0 && fragContainer == containerId) {
return true;
}
}
@@ -704,7 +710,7 @@ final class BackStackRecord extends FragmentTransaction implements
int lastContainer = -1;
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final int container = op.fragment.mContainerId;
final int container = op.fragment != null ? op.fragment.mContainerId : 0;
if (container != 0 && container != lastContainer) {
lastContainer = container;
for (int i = startIndex; i < endIndex; i++) {
@@ -712,7 +718,9 @@ final class BackStackRecord extends FragmentTransaction implements
final int numThoseOps = record.mOps.size();
for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
final Op thatOp = record.mOps.get(thoseOpIndex);
if (thatOp.fragment.mContainerId == container) {
final int thatContainer = thatOp.fragment != null
? thatOp.fragment.mContainerId : 0;
if (thatContainer == container) {
return true;
}
}
@@ -731,7 +739,9 @@ final class BackStackRecord extends FragmentTransaction implements
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.fragment;
f.setNextTransition(mTransition, mTransitionStyle);
if (f != null) {
f.setNextTransition(mTransition, mTransitionStyle);
}
switch (op.cmd) {
case OP_ADD:
f.setNextAnim(op.enterAnim);
@@ -757,10 +767,16 @@ final class BackStackRecord extends FragmentTransaction implements
f.setNextAnim(op.enterAnim);
mManager.attachFragment(f);
break;
case OP_SET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(f);
break;
case OP_UNSET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(null);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
if (!mAllowOptimization && op.cmd != OP_ADD) {
if (!mAllowOptimization && op.cmd != OP_ADD && f != null) {
mManager.moveFragmentToExpectedState(f);
}
}
@@ -778,7 +794,10 @@ final class BackStackRecord extends FragmentTransaction implements
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
Fragment f = op.fragment;
f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
if (f != null) {
f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
switch (op.cmd) {
case OP_ADD:
f.setNextAnim(op.popExitAnim);
@@ -804,10 +823,16 @@ final class BackStackRecord extends FragmentTransaction implements
f.setNextAnim(op.popExitAnim);
mManager.detachFragment(f);
break;
case OP_SET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(null);
break;
case OP_UNSET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(f);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
if (!mAllowOptimization && op.cmd != OP_REMOVE) {
if (!mAllowOptimization && op.cmd != OP_REMOVE && f != null) {
mManager.moveFragmentToExpectedState(f);
}
}
@@ -817,15 +842,28 @@ final class BackStackRecord extends FragmentTransaction implements
}
/**
* Removes all OP_REPLACE ops and replaces them with the proper add and remove
* operations that are equivalent to the replace. This must be called prior to
* {@link #executeOps()} or any other call that operations on mOps.
* Expands all meta-ops into their more primitive equivalents. This must be called prior to
* {@link #executeOps()} or any other call that operations on mOps for forward navigation.
* It should not be called for pop/reverse navigation operations.
*
* <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
* operations that are equivalent to the replace.</p>
*
* <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
* ops so that we can restore the old primary nav fragment later. Since callers call this
* method in a loop before running ops from several transactions at once, the caller should
* pass the return value from this method as the oldPrimaryNav parameter for the next call.
* The first call in such a loop should pass the value of
* {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
*
* @param added Initialized to the fragments that are in the mManager.mAdded, this
* will be modified to contain the fragments that will be in mAdded
* after the execution ({@link #executeOps()}.
* @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
* this set of ops
* @return the new oldPrimaryNav fragment after this record's ops would be run
*/
void expandReplaceOps(ArrayList<Fragment> added) {
Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
switch (op.cmd) {
@@ -834,22 +872,33 @@ final class BackStackRecord extends FragmentTransaction implements
added.add(op.fragment);
break;
case OP_REMOVE:
case OP_DETACH:
case OP_DETACH: {
added.remove(op.fragment);
break;
if (op.fragment == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
opNum++;
oldPrimaryNav = null;
}
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
final Fragment f = op.fragment;
final int containerId = f.mContainerId;
boolean alreadyAdded = false;
for (int i = added.size() - 1; i >= 0; i--) {
Fragment old = added.get(i);
final Fragment old = added.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
alreadyAdded = true;
} else {
Op removeOp = new Op();
removeOp.cmd = OP_REMOVE;
removeOp.fragment = old;
// This is duplicated from above since we only make
// a single pass for expanding ops. Unset any outgoing primary nav.
if (old == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
opNum++;
oldPrimaryNav = null;
}
final Op removeOp = new Op(OP_REMOVE, old);
removeOp.enterAnim = op.enterAnim;
removeOp.popEnterAnim = op.popEnterAnim;
removeOp.exitAnim = op.exitAnim;
@@ -869,8 +918,18 @@ final class BackStackRecord extends FragmentTransaction implements
}
}
break;
case OP_SET_PRIMARY_NAV: {
// It's ok if this is null, that means we will restore to no active
// primary navigation fragment on a pop.
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
opNum++;
// Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
oldPrimaryNav = op.fragment;
}
break;
}
}
return oldPrimaryNav;
}
/**
@@ -917,8 +976,8 @@ final class BackStackRecord extends FragmentTransaction implements
private static boolean isFragmentPostponed(Op op) {
final Fragment fragment = op.fragment;
return (fragment.mAdded && fragment.mView != null && !fragment.mDetached &&
!fragment.mHidden && fragment.isPostponed());
return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
&& !fragment.mHidden && fragment.isPostponed();
}
public String getName() {

View File

@@ -359,6 +359,18 @@ public abstract class FragmentManager {
*/
public abstract void unregisterFragmentLifecycleCallbacks(FragmentLifecycleCallbacks cb);
/**
* Return the currently active primary navigation fragment for this FragmentManager.
*
* <p>The primary navigation fragment's
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
* to process delegated navigation actions such as {@link #popBackStack()} if no ID
* or transaction name is provided to pop to.</p>
*
* @return the fragment designated as the primary navigation fragment
*/
public abstract Fragment getPrimaryNavigationFragment();
/**
* Print the FragmentManager's state into the given stream.
*
@@ -524,6 +536,7 @@ final class FragmentManagerState implements Parcelable {
FragmentState[] mActive;
int[] mAdded;
BackStackState[] mBackStack;
int mPrimaryNavActiveIndex = -1;
public FragmentManagerState() {
}
@@ -532,6 +545,7 @@ final class FragmentManagerState implements Parcelable {
mActive = in.createTypedArray(FragmentState.CREATOR);
mAdded = in.createIntArray();
mBackStack = in.createTypedArray(BackStackState.CREATOR);
mPrimaryNavActiveIndex = in.readInt();
}
public int describeContents() {
@@ -542,6 +556,7 @@ final class FragmentManagerState implements Parcelable {
dest.writeTypedArray(mActive, flags);
dest.writeIntArray(mAdded);
dest.writeTypedArray(mBackStack, flags);
dest.writeInt(mPrimaryNavActiveIndex);
}
public static final Parcelable.Creator<FragmentManagerState> CREATOR
@@ -626,6 +641,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
FragmentHostCallback<?> mHost;
FragmentContainer mContainer;
Fragment mParent;
Fragment mPrimaryNav;
boolean mNeedMenuInvalidate;
boolean mStateSaved;
@@ -783,6 +799,16 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
execPendingActions();
ensureExecReady(true);
if (mPrimaryNav != null // We have a primary nav fragment
&& id < 0 // No valid id (since they're local)
&& name == null) { // no name to pop to (since they're local)
final FragmentManager childManager = mPrimaryNav.mChildFragmentManager;
if (childManager != null && childManager.popBackStackImmediate()) {
// We did something, just not to this specific FragmentManager. Return true.
return true;
}
}
boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
if (executePop) {
mExecutingActions = true;
@@ -2038,11 +2064,12 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
if (mAdded != null) {
mTmpAddedFragments.addAll(mAdded);
}
Fragment oldPrimaryNav = getPrimaryNavigationFragment();
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (!isPop) {
record.expandReplaceOps(mTmpAddedFragments);
oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
} else {
record.trackAddedFragmentsInPop(mTmpAddedFragments);
}
@@ -2318,20 +2345,20 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
*/
private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records,
ArrayList<Boolean> isPop) {
int numActions;
boolean didSomething = false;
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
return false;
}
numActions = mPendingActions.size();
final int numActions = mPendingActions.size();
for (int i = 0; i < numActions; i++) {
mPendingActions.get(i).generateOps(records, isPop);
didSomething |= mPendingActions.get(i).generateOps(records, isPop);
}
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}
return numActions > 0;
return didSomething;
}
void doPendingDeferredStart() {
@@ -2618,6 +2645,9 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
return fms;
}
@@ -2744,6 +2774,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
} else {
mBackStack = null;
}
if (fms.mPrimaryNavActiveIndex >= 0) {
mPrimaryNav = mActive.get(fms.mPrimaryNavActiveIndex);
}
}
public void attachController(FragmentHostCallback<?> host, FragmentContainer container,
@@ -2942,6 +2976,19 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
}
}
public void setPrimaryNavigationFragment(Fragment f) {
if (f != null && (f.getFragmentManager() != this || f.mIndex >= mActive.size()
|| mActive.get(f.mIndex) != f)) {
throw new IllegalArgumentException("Fragment " + f
+ " is not an active fragment of FragmentManager " + this);
}
mPrimaryNav = f;
}
public Fragment getPrimaryNavigationFragment() {
return mPrimaryNav;
}
public void registerFragmentLifecycleCallbacks(FragmentLifecycleCallbacks cb,
boolean recursive) {
if (mLifecycleCallbacks == null) {
@@ -3386,6 +3433,16 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
@Override
public boolean generateOps(ArrayList<BackStackRecord> records,
ArrayList<Boolean> isRecordPop) {
if (mPrimaryNav != null // We have a primary nav fragment
&& mId < 0 // No valid id (since they're local)
&& mName == null) { // no name to pop to (since they're local)
final FragmentManager childManager = mPrimaryNav.mChildFragmentManager;
if (childManager != null && childManager.popBackStackImmediate()) {
// We didn't add any operations for this FragmentManager even though
// a child did do work.
return false;
}
}
return popBackStackState(records, isRecordPop, mName, mId, mFlags);
}
}

View File

@@ -131,6 +131,24 @@ public abstract class FragmentTransaction {
*/
public abstract FragmentTransaction attach(Fragment fragment);
/**
* Set a currently active fragment in this FragmentManager as the primary navigation fragment.
*
* <p>The primary navigation fragment's
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
* to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
* if no ID or transaction name is provided to pop to. Navigation operations outside of the
* fragment system may choose to delegate those actions to the primary navigation fragment
* as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
*
* <p>The fragment provided must currently be added to the FragmentManager to be set as
* a primary navigation fragment, or previously added as part of this transaction.</p>
*
* @param fragment the fragment to set as the primary navigation fragment
* @return the same FragmentTransaction instance
*/
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);
/**
* @return <code>true</code> if this transaction contains no operations,
* <code>false</code> otherwise.

View File

@@ -43,14 +43,16 @@ class FragmentTransition {
* REPLACE operations have already been replaced by add/remove operations.
*/
private static final int[] INVERSE_OPS = {
BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
BackStackRecord.OP_REMOVE, // inverse of OP_ADD
BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
BackStackRecord.OP_ADD, // inverse of OP_REMOVE
BackStackRecord.OP_SHOW, // inverse of OP_HIDE
BackStackRecord.OP_HIDE, // inverse of OP_SHOW
BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
BackStackRecord.OP_REMOVE, // inverse of OP_ADD
BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
BackStackRecord.OP_ADD, // inverse of OP_REMOVE
BackStackRecord.OP_SHOW, // inverse of OP_HIDE
BackStackRecord.OP_HIDE, // inverse of OP_SHOW
BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV
BackStackRecord.OP_SET_PRIMARY_NAV, // inverse of OP_UNSET_PRIMARY_NAV
};
/**
@@ -1232,6 +1234,9 @@ class FragmentTransition {
SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
boolean isOptimizedTransaction) {
final Fragment fragment = op.fragment;
if (fragment == null) {
return; // no fragment, no transition
}
final int containerId = fragment.mContainerId;
if (containerId == 0) {
return; // no container, no transition