Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d173fa3b authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Possible fix to issue #3213749: NPE at...

...android.app.Fragment.startActivityForResult(Fragment.java)

Make sure to remove all pending messages when AbsListView is detached
from its window.

But...  that's not enough.

It turns out that when a fragment's views are animating away, they of
course don't get detached until after the animation is done.  However
the fragment itself is immediately destroyed, leaving its live views
still going after that.

Here's a possible solution: when fragment manager initiates an animation
on a fragment whose views are being removed, it makes note of that so
it can hold off on destroying the fragment until the animation is over.

There are a lot of interesting race conditions here, if further operations
happen on the fragment while it is being animated.  I think the code here
does something sensible, and it does seem to work for the situations I
have tested, but it is hard to know all of the edge cases that may happen.

Change-Id: I4490ce8862a9bb714c7ea54baca3072c62126388
parent 7d4b0062
Loading
Loading
Loading
Loading
+14 −0
Original line number Original line Diff line number Diff line
@@ -323,6 +323,15 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
    
    
    int mState = INITIALIZING;
    int mState = INITIALIZING;
    
    
    // Non-null if the fragment's view hierarchy is currently animating away,
    // meaning we need to wait a bit on completely destroying it.  This is the
    // animation that is running.
    Animator mAnimatingAway;

    // If mAnimatingAway != null, this is the state we should move to once the
    // animation is done.
    int mStateAfterAnimating;

    // When instantiated from saved state, this is the saved state.
    // When instantiated from saved state, this is the saved state.
    Bundle mSavedFragmentState;
    Bundle mSavedFragmentState;
    SparseArray<Parcelable> mSavedViewState;
    SparseArray<Parcelable> mSavedViewState;
@@ -1240,6 +1249,11 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
        if (mView != null) {
        if (mView != null) {
            writer.print(prefix); writer.print("mView="); writer.println(mView);
            writer.print(prefix); writer.print("mView="); writer.println(mView);
        }
        }
        if (mAnimatingAway != null) {
            writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway);
            writer.print(prefix); writer.print("mStateAfterAnimating=");
                    writer.println(mStateAfterAnimating);
        }
        if (mLoaderManager != null) {
        if (mLoaderManager != null) {
            writer.print(prefix); writer.println("Loader Manager:");
            writer.print(prefix); writer.println("Loader Manager:");
            mLoaderManager.dump(prefix + "  ", fd, writer, args);
            mLoaderManager.dump(prefix + "  ", fd, writer, args);
+129 −30
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@ import android.view.ViewGroup;
import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;


/**
/**
 * Interface for interacting with {@link Fragment} objects inside of an
 * Interface for interacting with {@link Fragment} objects inside of an
@@ -331,6 +332,7 @@ final class FragmentManagerImpl extends FragmentManager {
    
    
    boolean mNeedMenuInvalidate;
    boolean mNeedMenuInvalidate;
    boolean mStateSaved;
    boolean mStateSaved;
    boolean mDestroyed;
    String mNoTransactionsBecause;
    String mNoTransactionsBecause;
    
    
    // Temporary vars for state save and restore.
    // Temporary vars for state save and restore.
@@ -473,25 +475,25 @@ final class FragmentManagerImpl extends FragmentManager {


    @Override
    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        if (mActive == null || mActive.size() <= 0) {
        String innerPrefix = prefix + "    ";
            return;
        }


        int N;
        if (mActive != null) {
            N = mActive.size();
            if (N > 0) {
                writer.print(prefix); writer.print("Active Fragments in ");
                writer.print(prefix); writer.print("Active Fragments in ");
                        writer.print(Integer.toHexString(System.identityHashCode(this)));
                        writer.print(Integer.toHexString(System.identityHashCode(this)));
                        writer.println(":");
                        writer.println(":");

        String innerPrefix = prefix + "    ";

        int N = mActive.size();
                for (int i=0; i<N; i++) {
                for (int i=0; i<N; i++) {
                    Fragment f = mActive.get(i);
                    Fragment f = mActive.get(i);
            if (f != null) {
                    writer.print(prefix); writer.print("  #"); writer.print(i);
                    writer.print(prefix); writer.print("  #"); writer.print(i);
                        writer.print(": "); writer.println(f.toString());
                            writer.print(": "); writer.println(f);
                    if (f != null) {
                        f.dump(innerPrefix, fd, writer, args);
                        f.dump(innerPrefix, fd, writer, args);
                    }
                    }
                }
                }
            }
        }


        if (mAdded != null) {
        if (mAdded != null) {
            N = mAdded.size();
            N = mAdded.size();
@@ -505,6 +507,18 @@ final class FragmentManagerImpl extends FragmentManager {
            }
            }
        }
        }


        if (mCreatedMenus != null) {
            N = mCreatedMenus.size();
            if (N > 0) {
                writer.print(prefix); writer.println("Fragments Created Menus:");
                for (int i=0; i<N; i++) {
                    Fragment f = mCreatedMenus.get(i);
                    writer.print(prefix); writer.print("  #"); writer.print(i);
                            writer.print(": "); writer.println(f.toString());
                }
            }
        }

        if (mBackStack != null) {
        if (mBackStack != null) {
            N = mBackStack.size();
            N = mBackStack.size();
            if (N > 0) {
            if (N > 0) {
@@ -517,6 +531,54 @@ final class FragmentManagerImpl extends FragmentManager {
                }
                }
            }
            }
        }
        }

        synchronized (this) {
            if (mBackStackIndices != null) {
                N = mBackStackIndices.size();
                if (N > 0) {
                    writer.print(prefix); writer.println("Back Stack Indices:");
                    for (int i=0; i<N; i++) {
                        BackStackRecord bs = mBackStackIndices.get(i);
                        writer.print(prefix); writer.print("  #"); writer.print(i);
                                writer.print(": "); writer.println(bs);
                    }
                }
            }

            if (mAvailBackStackIndices != null && mAvailBackStackIndices.size() > 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<N; i++) {
                    Runnable r = mPendingActions.get(i);
                    writer.print(prefix); writer.print("  #"); writer.print(i);
                            writer.print(": "); writer.println(r);
                }
            }
        }

        writer.print(prefix); writer.println("FragmentManager misc state:");
        writer.print(prefix); writer.print("  mCurState="); writer.print(mCurState);
                writer.print(" mStateSaved="); writer.print(mStateSaved);
                writer.print(" mDestroyed="); writer.println(mDestroyed);
        if (mNeedMenuInvalidate) {
            writer.print(prefix); writer.print("  mNeedMenuInvalidate=");
                    writer.println(mNeedMenuInvalidate);
        }
        if (mNoTransactionsBecause != null) {
            writer.print(prefix); writer.print("  mNoTransactionsBecause=");
                    writer.println(mNoTransactionsBecause);
        }
        if (mAvailIndices != null && mAvailIndices.size() > 0) {
            writer.print(prefix); writer.print("  mAvailIndices: ");
                    writer.println(Arrays.toString(mAvailIndices.toArray()));
        }
    }
    }


    Animator loadAnimator(Fragment fragment, int transit, boolean enter,
    Animator loadAnimator(Fragment fragment, int transit, boolean enter,
@@ -569,6 +631,14 @@ final class FragmentManagerImpl extends FragmentManager {
        }
        }
        
        
        if (f.mState < newState) {
        if (f.mState < newState) {
            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) {
            switch (f.mState) {
                case Fragment.INITIALIZING:
                case Fragment.INITIALIZING:
                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
@@ -716,18 +786,26 @@ final class FragmentManagerImpl extends FragmentManager {
                        }
                        }
                        if (f.mView != null && f.mContainer != null) {
                        if (f.mView != null && f.mContainer != null) {
                            Animator anim = null;
                            Animator anim = null;
                            if (mCurState > Fragment.INITIALIZING) {
                            if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
                                anim = loadAnimator(f, transit, false,
                                anim = loadAnimator(f, transit, false,
                                        transitionStyle);
                                        transitionStyle);
                            }
                            }
                            if (anim != null) {
                            if (anim != null) {
                                final ViewGroup container = f.mContainer;
                                final ViewGroup container = f.mContainer;
                                final View view = f.mView;
                                final View view = f.mView;
                                final Fragment fragment = f;
                                container.startViewTransition(view);
                                container.startViewTransition(view);
                                f.mAnimatingAway = anim;
                                f.mStateAfterAnimating = newState;
                                anim.addListener(new AnimatorListenerAdapter() {
                                anim.addListener(new AnimatorListenerAdapter() {
                                    @Override
                                    @Override
                                    public void onAnimationEnd(Animator anim) {
                                    public void onAnimationEnd(Animator anim) {
                                        container.endViewTransition(view);
                                        container.endViewTransition(view);
                                        if (fragment.mAnimatingAway != null) {
                                            fragment.mAnimatingAway = null;
                                            moveToState(fragment, fragment.mStateAfterAnimating,
                                                    0, 0);
                                        }
                                    }
                                    }
                                });
                                });
                                anim.setTarget(f.mView);
                                anim.setTarget(f.mView);
@@ -741,6 +819,25 @@ final class FragmentManagerImpl extends FragmentManager {
                    }
                    }
                case Fragment.CREATED:
                case Fragment.CREATED:
                    if (newState < 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.
                                f.mAnimatingAway = null;
                                f.mAnimatingAway.cancel();
                            }
                        }
                        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;
                        } else {
                            if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                            if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                            if (!f.mRetaining) {
                            if (!f.mRetaining) {
                                f.mCalled = false;
                                f.mCalled = false;
@@ -763,6 +860,7 @@ final class FragmentManagerImpl extends FragmentManager {
                        }
                        }
                    }
                    }
            }
            }
        }
        
        
        f.mState = newState;
        f.mState = newState;
    }
    }
@@ -1442,6 +1540,7 @@ final class FragmentManagerImpl extends FragmentManager {
    }
    }
    
    
    public void dispatchDestroy() {
    public void dispatchDestroy() {
        mDestroyed = true;
        moveToState(Fragment.INITIALIZING, false);
        moveToState(Fragment.INITIALIZING, false);
        mActivity = null;
        mActivity = null;
    }
    }
+34 −2
Original line number Original line Diff line number Diff line
@@ -511,6 +511,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
     */
     */
    private AbsListView.PerformClick mPerformClick;
    private AbsListView.PerformClick mPerformClick;


    /**
     * Delayed action for touch mode.
     */
    private Runnable mTouchModeReset;

    /**
    /**
     * This view is in transcript mode -- it shows the bottom of the list when the data
     * This view is in transcript mode -- it shows the bottom of the list when the data
     * changes
     * changes
@@ -2322,6 +2327,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
            mFlingStrictSpan.finish();
            mFlingStrictSpan.finish();
            mFlingStrictSpan = null;
            mFlingStrictSpan = null;
        }
        }

        if (mFlingRunnable != null) {
            removeCallbacks(mFlingRunnable);
        }

        if (mPositionScroller != null) {
            removeCallbacks(mPositionScroller);
        }

        if (mClearScrollingCache != null) {
            removeCallbacks(mClearScrollingCache);
        }

        if (mPerformClick != null) {
            removeCallbacks(mPerformClick);
        }

        if (mTouchModeReset != null) {
            removeCallbacks(mTouchModeReset);
            mTouchModeReset = null;
        }
    }
    }


    @Override
    @Override
@@ -3020,7 +3046,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                                    ((TransitionDrawable) d).resetTransition();
                                    ((TransitionDrawable) d).resetTransition();
                                }
                                }
                            }
                            }
                            postDelayed(new Runnable() {
                            if (mTouchModeReset != null) {
                                removeCallbacks(mTouchModeReset);
                            }
                            mTouchModeReset = new Runnable() {
                                @Override
                                public void run() {
                                public void run() {
                                    mTouchMode = TOUCH_MODE_REST;
                                    mTouchMode = TOUCH_MODE_REST;
                                    child.setPressed(false);
                                    child.setPressed(false);
@@ -3029,7 +3059,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                                        post(performClick);
                                        post(performClick);
                                    }
                                    }
                                }
                                }
                            }, ViewConfiguration.getPressedStateDuration());
                            };
                            postDelayed(mTouchModeReset,
                                    ViewConfiguration.getPressedStateDuration());
                        } else {
                        } else {
                            mTouchMode = TOUCH_MODE_REST;
                            mTouchMode = TOUCH_MODE_REST;
                            updateSelectorState();
                            updateSelectorState();