Cleanup for item tracking

This CL abstracts the Item tracking for RecyclerView. This makes
it much easier to test (better encapsulation) + cleaner code.
And as an added bonus, I merged some maps to speedup lookups.

There is also a small (unreleased) API change for the new
ItemAnimation callbacks. It was wrong in the first one but didn't
realize because SimpleItemAnimator was taking care of it.

I've also added some support annotations & fixed some warnings.

Bug: 24665726
Change-Id: Idba09e749f1c0f269d81dbdee4056f6b52ba8484
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index 17ada3a..1f91036 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -380,7 +380,7 @@
     ctor public RecyclerView.ItemAnimator();
     method public abstract boolean animateAppearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public abstract boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
-    method public abstract boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+    method public abstract boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public abstract boolean animatePersistence(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public boolean canReuseUpdatedViewHolder(android.support.v7.widget.RecyclerView.ViewHolder);
     method public final void dispatchAnimationFinished(android.support.v7.widget.RecyclerView.ViewHolder);
@@ -679,7 +679,6 @@
     method public int getTargetScrollPosition();
     method public boolean hasTargetScrollPosition();
     method public boolean isPreLayout();
-    method public void onViewIgnored(android.support.v7.widget.RecyclerView.ViewHolder);
     method public void put(int, java.lang.Object);
     method public void remove(int);
     method public boolean willRunPredictiveAnimations();
@@ -714,7 +713,7 @@
     method public boolean animateAppearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public abstract boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
-    method public boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+    method public boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public abstract boolean animateMove(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
     method public boolean animatePersistence(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
     method public abstract boolean animateRemove(android.support.v7.widget.RecyclerView.ViewHolder);
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
new file mode 100644
index 0000000..559bc6b
--- /dev/null
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2015 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.v7.widget;
+
+import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.Pair;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_POST;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
+import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+
+@SuppressWarnings("ConstantConditions")
+@RunWith(JUnit4.class)
+public class ViewInfoStoreTest extends TestCase {
+    ViewInfoStore mStore;
+    LoggingProcessCallback mCallback;
+    @Before
+    public void prepare() {
+        mStore = new ViewInfoStore();
+        mCallback = new LoggingProcessCallback();
+    }
+
+    @Test
+    public void addToPreLayout() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPreLayout(vh, info);
+        assertSame(info, find(vh, FLAG_PRE));
+        assertTrue(mStore.isInPreLayout(vh));
+        mStore.removeViewHolder(vh);
+        assertFalse(mStore.isInPreLayout(vh));
+    }
+
+    @Test
+    public void addToPostLayout() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPostLayout(vh, info);
+        assertSame(info, find(vh, FLAG_POST));
+        mStore.removeViewHolder(vh);
+        assertNull(find(vh, FLAG_POST));
+    }
+
+    @Test
+    public void popFromPreLayout() {
+        assertEquals(0, sizeOf(FLAG_PRE));
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPreLayout(vh, info);
+        assertSame(info, mStore.popFromPreLayout(vh));
+        assertNull(mStore.popFromPreLayout(vh));
+    }
+
+    @Test
+    public void addToOldChangeHolders() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        mStore.addToOldChangeHolders(1, vh);
+        assertSame(vh, mStore.getFromOldChangeHolders(1));
+        mStore.removeViewHolder(vh);
+        assertNull(mStore.getFromOldChangeHolders(1));
+    }
+
+    @Test
+    public void appearListTests() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        RecyclerView.ItemAnimator.ItemHolderInfo info = new MockInfo();
+        mStore.addToAppearedInPreLayoutHolders(vh, info);
+        assertEquals(1, sizeOf(FLAG_APPEAR));
+        RecyclerView.ViewHolder vh2 = new MockViewHolder();
+        mStore.addToAppearedInPreLayoutHolders(vh2, info);
+        assertEquals(2, sizeOf(FLAG_APPEAR));
+        mStore.removeViewHolder(vh2);
+        assertEquals(1, sizeOf(FLAG_APPEAR));
+    }
+
+    @Test
+    public void disappearListTest() {
+        RecyclerView.ViewHolder vh = new MockViewHolder();
+        mStore.addToDisappearedInLayout(vh);
+        assertEquals(1, sizeOf(FLAG_DISAPPEARED));
+        mStore.addToDisappearedInLayout(vh);
+        assertEquals(1, sizeOf(FLAG_DISAPPEARED));
+        RecyclerView.ViewHolder vh2 = new MockViewHolder();
+        mStore.addToDisappearedInLayout(vh2);
+        assertEquals(2, sizeOf(FLAG_DISAPPEARED));
+        mStore.removeViewHolder(vh2);
+        assertEquals(1, sizeOf(FLAG_DISAPPEARED));
+        mStore.removeFromDisappearedInLayout(vh);
+        assertEquals(0, sizeOf(FLAG_DISAPPEARED));
+    }
+
+    @Test
+    public void processAppear() {
+        ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPostLayout(vh, info);
+        mStore.process(mCallback);
+        assertEquals(new Pair<>(null, info), mCallback.appeared.get(vh));
+        assertTrue(mCallback.disappeared.isEmpty());
+        assertTrue(mCallback.unused.isEmpty());
+        assertTrue(mCallback.persistent.isEmpty());
+    }
+
+    @Test
+    public void processDisappearNormal() {
+        ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPreLayout(vh, info);
+        mStore.process(mCallback);
+        assertEquals(new Pair<>(info, null), mCallback.disappeared.get(vh));
+        assertTrue(mCallback.appeared.isEmpty());
+        assertTrue(mCallback.unused.isEmpty());
+        assertTrue(mCallback.persistent.isEmpty());
+    }
+
+    @Test
+    public void processDisappearMissingLayout() {
+        ViewHolder vh = new MockViewHolder();
+        MockInfo info = new MockInfo();
+        mStore.addToPreLayout(vh, info);
+        mStore.addToDisappearedInLayout(vh);
+        mStore.process(mCallback);
+        assertEquals(new Pair<>(info, null), mCallback.disappeared.get(vh));
+        assertTrue(mCallback.appeared.isEmpty());
+        assertTrue(mCallback.unused.isEmpty());
+        assertTrue(mCallback.persistent.isEmpty());
+    }
+
+    @Test
+    public void processDisappearMoveOut() {
+        ViewHolder vh = new MockViewHolder();
+        MockInfo pre = new MockInfo();
+        MockInfo post = new MockInfo();
+        mStore.addToPreLayout(vh, pre);
+        mStore.addToDisappearedInLayout(vh);
+        mStore.addToPostLayout(vh, post);
+        mStore.process(mCallback);
+        assertEquals(new Pair<>(pre, post), mCallback.disappeared.get(vh));
+        assertTrue(mCallback.appeared.isEmpty());
+        assertTrue(mCallback.unused.isEmpty());
+        assertTrue(mCallback.persistent.isEmpty());
+    }
+
+    @Test
+    public void processDisappearAppear() {
+        ViewHolder vh = new MockViewHolder();
+        MockInfo pre = new MockInfo();
+        MockInfo post = new MockInfo();
+        mStore.addToPreLayout(vh, pre);
+        mStore.addToDisappearedInLayout(vh);
+        mStore.addToPostLayout(vh, post);
+        mStore.removeFromDisappearedInLayout(vh);
+        mStore.process(mCallback);
+        assertTrue(mCallback.disappeared.isEmpty());
+        assertTrue(mCallback.appeared.isEmpty());
+        assertTrue(mCallback.unused.isEmpty());
+        assertEquals(mCallback.persistent.get(vh), new Pair<>(pre, post));
+    }
+
+    static class MockViewHolder extends RecyclerView.ViewHolder {
+        public MockViewHolder() {
+            super(new View(null));
+        }
+    }
+
+    static class MockInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
+
+    }
+
+    private int sizeOf(int flags) {
+        int cnt = 0;
+        final int size = mStore.mLayoutHolderMap.size();
+        for (int i = 0; i < size; i ++) {
+            ViewInfoStore.InfoRecord record = mStore.mLayoutHolderMap.valueAt(i);
+            if ((record.flags & flags) != 0) {
+                cnt ++;
+            }
+        }
+        return cnt;
+    }
+
+    private RecyclerView.ItemAnimator.ItemHolderInfo find(RecyclerView.ViewHolder viewHolder,
+            int flags) {
+        final int size = mStore.mLayoutHolderMap.size();
+        for (int i = 0; i < size; i ++) {
+            ViewInfoStore.InfoRecord record = mStore.mLayoutHolderMap.valueAt(i);
+            RecyclerView.ViewHolder holder = mStore.mLayoutHolderMap.keyAt(i);
+            if ((record.flags & flags) != 0 && holder == viewHolder) {
+                if (flags == FLAG_PRE || flags == FLAG_APPEAR) {
+                    return record.preInfo;
+                } else if (flags == FLAG_POST) {
+                    return record.postInfo;
+                }
+                throw new UnsupportedOperationException("don't know this flag");
+            }
+        }
+        return null;
+    }
+
+    private static class LoggingProcessCallback implements ViewInfoStore.ProcessCallback {
+        final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> disappeared = new HashMap<>();
+        final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> appeared = new HashMap<>();
+        final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> persistent = new HashMap<>();
+        final List<ViewHolder> unused = new ArrayList<>();
+        @Override
+        public void processDisappeared(ViewHolder viewHolder,
+                ItemHolderInfo preInfo,
+                @Nullable ItemHolderInfo postInfo) {
+            assertNotNull(preInfo);
+            assertFalse(disappeared.containsKey(viewHolder));
+            disappeared.put(viewHolder, new Pair<>(preInfo, postInfo));
+        }
+
+        @Override
+        public void processAppeared(ViewHolder viewHolder,
+                @Nullable ItemHolderInfo preInfo, @NonNull ItemHolderInfo info) {
+            assertNotNull(info);
+            assertFalse(appeared.containsKey(viewHolder));
+            appeared.put(viewHolder, new Pair<>(preInfo, info));
+        }
+
+        @Override
+        public void processPersistent(ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+            assertFalse(persistent.containsKey(viewHolder));
+            assertNotNull(preInfo);
+            assertNotNull(postInfo);
+            persistent.put(viewHolder, new Pair<>(preInfo, postInfo));
+        }
+
+        @Override
+        public void unused(ViewHolder holder) {
+            unused.add(holder);
+        }
+    }
+
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 9bb16e7..0809efe 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -35,20 +35,19 @@
 public class DefaultItemAnimator extends SimpleItemAnimator {
     private static final boolean DEBUG = false;
 
-    private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
-    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
-    private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
-    private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();
+    private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
+    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
+    private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
+    private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
 
-    private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
-            new ArrayList<ArrayList<ViewHolder>>();
-    private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
-    private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();
+    private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
+    private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
+    private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
 
-    private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
-    private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
-    private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
-    private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();
+    private ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
+    private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
+    private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
+    private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
 
     private static class MoveInfo {
         public ViewHolder holder;
@@ -110,7 +109,7 @@
         mPendingRemovals.clear();
         // Next, move stuff
         if (movesPending) {
-            final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
+            final ArrayList<MoveInfo> moves = new ArrayList<>();
             moves.addAll(mPendingMoves);
             mMovesList.add(moves);
             mPendingMoves.clear();
@@ -134,7 +133,7 @@
         }
         // Next, change stuff, to run in parallel with move animations
         if (changesPending) {
-            final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
+            final ArrayList<ChangeInfo> changes = new ArrayList<>();
             changes.addAll(mPendingChanges);
             mChangesList.add(changes);
             mPendingChanges.clear();
@@ -157,7 +156,7 @@
         }
         // Next, add stuff
         if (additionsPending) {
-            final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
+            final ArrayList<ViewHolder> additions = new ArrayList<>();
             additions.addAll(mPendingAdditions);
             mAdditionsList.add(additions);
             mPendingAdditions.clear();
@@ -325,7 +324,7 @@
         ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
         ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
         ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
-        if (newHolder != null && newHolder.itemView != null) {
+        if (newHolder != null) {
             // carry over translation values
             resetAnimation(newHolder);
             ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
@@ -484,21 +483,25 @@
         }
 
         // animations should be ended by the cancel above.
+        //noinspection PointlessBooleanExpression,ConstantConditions
         if (mRemoveAnimations.remove(item) && DEBUG) {
             throw new IllegalStateException("after animation is cancelled, item should not be in "
                     + "mRemoveAnimations list");
         }
 
+        //noinspection PointlessBooleanExpression,ConstantConditions
         if (mAddAnimations.remove(item) && DEBUG) {
             throw new IllegalStateException("after animation is cancelled, item should not be in "
                     + "mAddAnimations list");
         }
 
+        //noinspection PointlessBooleanExpression,ConstantConditions
         if (mChangeAnimations.remove(item) && DEBUG) {
             throw new IllegalStateException("after animation is cancelled, item should not be in "
                     + "mChangeAnimations list");
         }
 
+        //noinspection PointlessBooleanExpression,ConstantConditions
         if (mMoveAnimations.remove(item) && DEBUG) {
             throw new IllegalStateException("after animation is cancelled, item should not be in "
                     + "mMoveAnimations list");
@@ -638,5 +641,5 @@
 
         @Override
         public void onAnimationCancel(View view) {}
-    };
+    }
 }
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index f0b0afe..1e32420 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -16,7 +16,6 @@
 
 
 package android.support.v7.widget;
-
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.database.Observable;
@@ -30,9 +29,9 @@
 import android.os.SystemClock;
 import android.support.annotation.CallSuper;
 import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.os.TraceCompat;
-import android.support.v4.util.ArrayMap;
 import android.support.v4.view.InputDeviceCompat;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.NestedScrollingChild;
@@ -72,7 +71,6 @@
 import java.util.List;
 
 import static android.support.v7.widget.AdapterHelper.Callback;
-import static android.support.v7.widget.AdapterHelper.POSITION_TYPE_INVISIBLE;
 import static android.support.v7.widget.AdapterHelper.UpdateOp;
 import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
 
@@ -244,11 +242,22 @@
 
     private SavedState mPendingSavedState;
 
+    /**
+     * Handles adapter updates
+     */
     AdapterHelper mAdapterHelper;
 
+    /**
+     * Handles abstraction between LayoutManager children and RecyclerView children
+     */
     ChildHelper mChildHelper;
 
     /**
+     * Keeps data about views to be used for animations
+     */
+    final ViewInfoStore mViewInfoStore = new ViewInfoStore();
+
+    /**
      * Prior to L, there is no way to query this variable which is why we override the setter and
      * track it here.
      */
@@ -395,6 +404,44 @@
         }
     };
 
+    /**
+     * The callback to convert view info diffs into animations.
+     */
+    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
+            new ViewInfoStore.ProcessCallback() {
+        @Override
+        public void processDisappeared(ViewHolder viewHolder, ItemHolderInfo info,
+                ItemHolderInfo postInfo) {
+            mRecycler.unscrapView(viewHolder);
+            animateDisappearance(viewHolder, info, postInfo);
+        }
+        @Override
+        public void processAppeared(ViewHolder viewHolder,
+                ItemHolderInfo preInfo, ItemHolderInfo info) {
+            animateAppearance(viewHolder, preInfo, info);
+        }
+
+        @Override
+        public void processPersistent(ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+            viewHolder.setIsRecyclable(false);
+            if (mDataSetHasChangedAfterLayout) {
+                // since it was rebound, use change instead as we'll be mapping them from
+                // stable ids. If stable ids were false, we would not be running any
+                // animations
+                if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) {
+                    postAnimationRunner();
+                }
+            } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
+                postAnimationRunner();
+            }
+        }
+        @Override
+        public void unused(ViewHolder viewHolder) {
+            mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
+        }
+    };
+
     public RecyclerView(Context context) {
         this(context, null);
     }
@@ -2027,6 +2074,7 @@
             mLayout.dispatchDetachedFromWindow(this, mRecycler);
         }
         removeCallbacks(mItemAnimatorRunner);
+        mViewInfoStore.onDetach();
     }
 
     /**
@@ -2754,7 +2802,7 @@
      * PERSISTENT views are animated via
      * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
      * DISAPPEARING views are animated via
-     * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo)}
+     * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
      * APPEARING views are animated via
      * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
      * and changed views are animated via
@@ -2769,25 +2817,19 @@
             Log.e(TAG, "No layout manager attached; skipping layout");
             return;
         }
-        mState.mDisappearingViewsInLayoutPass.clear();
+        mViewInfoStore.clear();
         eatRequestLayout();
         onEnterLayoutOrScroll();
 
         processAdapterUpdatesAndSetAnimationFlags();
         mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
-        if (mState.mTrackOldChangeHolders && mState.mOldChangedHolders == null) {
-            mState.mOldChangedHolders = new ArrayMap<>();
-        }
         mItemsAddedOrRemoved = mItemsChanged = false;
-        ArrayMap<View, ItemHolderInfo> appearingViewInfo = null;
         mState.mInPreLayout = mState.mRunPredictiveAnimations;
         mState.mItemCount = mAdapter.getItemCount();
         findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
 
         if (mState.mRunSimpleAnimations) {
             // Step 0: Find out where all non-removed items are, pre-layout
-            mState.mPreLayoutHolderMap.clear();
-            mState.mPostLayoutHolderMap.clear();
             int count = mChildHelper.getChildCount();
             for (int i = 0; i < count; ++i) {
                 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
@@ -2798,7 +2840,7 @@
                         .recordPreLayoutInformation(mState, holder,
                                 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                 holder.getUnmodifiedPayloads());
-                mState.mPreLayoutHolderMap.put(holder, animationInfo);
+                mViewInfoStore.addToPreLayout(holder, animationInfo);
                 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                         && !holder.shouldIgnore() && !holder.isInvalid()) {
                     long key = getChangedHolderKey(holder);
@@ -2809,7 +2851,7 @@
                     //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                     // When this case is detected, RV will un-hide that view and add to the old
                     // change holders list.
-                    mState.mOldChangedHolders.put(key, holder);
+                    mViewInfoStore.addToOldChangeHolders(key, holder);
                 }
             }
         }
@@ -2827,22 +2869,13 @@
             mLayout.onLayoutChildren(mRecycler, mState);
             mState.mStructureChanged = didStructureChange;
 
-            appearingViewInfo = new ArrayMap<View, ItemHolderInfo>();
             for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
-                boolean found = false;
                 final View child = mChildHelper.getChildAt(i);
                 final ViewHolder viewHolder = getChildViewHolderInt(child);
                 if (viewHolder.shouldIgnore()) {
                     continue;
                 }
-                for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) {
-                    ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j);
-                    if (holder.itemView == child) {
-                        found = true;
-                        break;
-                    }
-                }
-                if (!found) {
+                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                     int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                     boolean wasHidden = viewHolder
                             .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
@@ -2854,7 +2887,7 @@
                     if (wasHidden) {
                         recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                     } else {
-                        appearingViewInfo.put(child, animationInfo);
+                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                     }
                 }
             }
@@ -2888,84 +2921,33 @@
                 long key = getChangedHolderKey(holder);
                 final ItemHolderInfo animationInfo = mItemAnimator
                         .recordPostLayoutInformation(mState, holder);
-                ViewHolder oldChangeViewHolder = mState.mTrackOldChangeHolders ?
-                        mState.mOldChangedHolders.get(key) : null;
+                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                 if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                     // run a change animation
-                    final ItemHolderInfo preInfo = mState.mPreLayoutHolderMap
-                            .get(oldChangeViewHolder);
-                    mState.mPreLayoutHolderMap.remove(oldChangeViewHolder);
+                    final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
+                            oldChangeViewHolder);
                     animateChange(oldChangeViewHolder, holder, preInfo, animationInfo);
                 } else {
-                    mState.mPostLayoutHolderMap.put(holder, animationInfo);
+                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                 }
             }
 
-            processDisappearingList(appearingViewInfo);
-            // Step 4: Animate DISAPPEARING and REMOVED items
-            int preLayoutCount = mState.mPreLayoutHolderMap.size();
-            for (int i = preLayoutCount - 1; i >= 0; i--) {
-                ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
-                if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
-                    ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
-                    mState.mPreLayoutHolderMap.removeAt(i);
-                    mRecycler.unscrapView(itemHolder);
-                    animateDisappearance(itemHolder, disappearingItem);
-                }
-            }
-            // Step 5: Animate APPEARING and ADDED items
-            int postLayoutCount = mState.mPostLayoutHolderMap.size();
-            if (postLayoutCount > 0) {
-                for (int i = postLayoutCount - 1; i >= 0; i--) {
-                    ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i);
-                    ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i);
-                    if (!mState.mPreLayoutHolderMap.containsKey(itemHolder)) {
-                        mState.mPostLayoutHolderMap.removeAt(i);
-                        ItemHolderInfo preInfo = appearingViewInfo == null ? null :
-                                appearingViewInfo.remove(itemHolder.itemView);
-                        animateAppearance(itemHolder, preInfo, info);
-                    }
-                }
-            }
-            // Step 6: Animate PERSISTENT items
-            count = mState.mPostLayoutHolderMap.size();
-            for (int i = 0; i < count; ++i) {
-                final ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i);
-                final ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i);
-                final ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder);
-                if (preInfo != null && postInfo != null) {
-                    postHolder.setIsRecyclable(false);
-                    if (mDataSetHasChangedAfterLayout) {
-                        // since it was rebound, use change instead as we'll be mapping them from
-                        // stable ids. If stable ids were false, we would not be running any
-                        // animations
-                        if (mItemAnimator
-                                .animateChange(postHolder, postHolder, preInfo, postInfo)) {
-                            postAnimationRunner();
-                        }
-                    } else if (mItemAnimator.animatePersistence(postHolder, preInfo, postInfo)) {
-                        postAnimationRunner();
-                    }
-                }
-            }
+            // Step 4: Process view info lists and trigger animations
+            mViewInfoStore.process(mViewInfoProcessCallback);
         }
         resumeRequestLayout(false);
         mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
         mState.mRunSimpleAnimations = false;
-        mState.mPreLayoutHolderMap.clear();
-        mState.mPostLayoutHolderMap.clear();
+
         mState.mRunPredictiveAnimations = false;
         onExitLayoutOrScroll();
         mLayout.mRequestedSimpleAnimations = false;
         if (mRecycler.mChangedScrap != null) {
             mRecycler.mChangedScrap.clear();
         }
-        if (mState.mOldChangedHolders != null) {
-            mState.mOldChangedHolders.clear();
-        }
-
+        mViewInfoStore.clear();
         if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
             dispatchOnScrolled(0, 0);
         }
@@ -2982,9 +2964,9 @@
         if (mState.mTrackOldChangeHolders && viewHolder.isUpdated()
                 && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) {
             long key = getChangedHolderKey(viewHolder);
-            mState.mOldChangedHolders.put(key, viewHolder);
+            mViewInfoStore.addToOldChangeHolders(key, viewHolder);
         }
-        mState.mPreLayoutHolderMap.put(viewHolder, animationInfo);
+        mViewInfoStore.addToPreLayout(viewHolder, animationInfo);
     }
 
     private void findMinMaxChildLayoutPositions(int[] into) {
@@ -3054,58 +3036,33 @@
         return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
     }
 
-    /**
-     * A LayoutManager may want to layout a view just to animate disappearance.
-     * This method handles those views and triggers remove animation on them.
-     *
-     * @param appearingViews The map of views that appeared in this layout
-     */
-    private void processDisappearingList(ArrayMap<View, ItemHolderInfo> appearingViews) {
-        final List<View> disappearingList = mState.mDisappearingViewsInLayoutPass;
-        for (int i = disappearingList.size() - 1; i >= 0; i --) {
-            View view = disappearingList.get(i);
-            ViewHolder vh = getChildViewHolderInt(view);
-            final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh);
-            if (!mState.isPreLayout()) {
-                mState.mPostLayoutHolderMap.remove(vh);
-            }
-            if (appearingViews.remove(view) != null) {
-                mLayout.removeAndRecycleView(view, mRecycler);
-                continue;
-            }
-            animateDisappearance(vh, info);
-        }
-        disappearingList.clear();
-    }
-
-    private void animateAppearance(ViewHolder itemHolder, ItemHolderInfo preLayoutInfo,
-            ItemHolderInfo postLayoutInfo) {
+    private void animateAppearance(@NonNull ViewHolder itemHolder,
+            @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
         itemHolder.setIsRecyclable(false);
         if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
             postAnimationRunner();
         }
     }
 
-    private void animateDisappearance(ViewHolder holder, ItemHolderInfo preLayoutInfo) {
+    private void animateDisappearance(@NonNull ViewHolder holder,
+            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
         addAnimatingView(holder);
         holder.setIsRecyclable(false);
-        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo)) {
+        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
             postAnimationRunner();
         }
     }
 
-    private void animateChange(ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preInfo,
-            ItemHolderInfo postInfo) {
+    private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
         oldHolder.setIsRecyclable(false);
         if (oldHolder != newHolder) {
             oldHolder.mShadowedHolder = newHolder;
             // old holder should disappear after animation ends
             addAnimatingView(oldHolder);
             mRecycler.unscrapView(oldHolder);
-            if (newHolder != null) {
-                newHolder.setIsRecyclable(false);
-                newHolder.mShadowingHolder = oldHolder;
-            }
+            newHolder.setIsRecyclable(false);
+            newHolder.mShadowingHolder = oldHolder;
         }
         if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
             postAnimationRunner();
@@ -4715,7 +4672,7 @@
             }
             // even if the holder is not removed, we still call this method so that it is removed
             // from view holder lists.
-            mState.onViewRecycled(holder);
+            mViewInfoStore.removeViewHolder(holder);
             if (!cached && !recycled && transientStatePreventsRecycling) {
                 holder.mOwnerRecyclerView = null;
             }
@@ -4960,7 +4917,7 @@
                 mAdapter.onViewRecycled(holder);
             }
             if (mState != null) {
-                mState.onViewRecycled(holder);
+                mViewInfoStore.removeViewHolder(holder);
             }
             if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
         }
@@ -6259,14 +6216,14 @@
             final ViewHolder holder = getChildViewHolderInt(child);
             if (disappearing || holder.isRemoved()) {
                 // these views will be hidden at the end of the layout pass.
-                mRecyclerView.mState.addToDisappearingList(child);
+                mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
             } else {
                 // This may look like unnecessary but may happen if layout manager supports
                 // predictive layouts and adapter removed then re-added the same item.
                 // In this case, added version will be visible in the post layout (because add is
                 // deferred) but RV will still bind it to the same View.
                 // So if a View re-appears in post layout pass, remove it from disappearing list.
-                mRecyclerView.mState.removeFromDisappearingList(child);
+                mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
             }
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             if (holder.wasReturnedFromScrap() || holder.isScrap()) {
@@ -6468,9 +6425,9 @@
         public void attachView(View child, int index, LayoutParams lp) {
             ViewHolder vh = getChildViewHolderInt(child);
             if (vh.isRemoved()) {
-                mRecyclerView.mState.addToDisappearingList(child);
+                mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh);
             } else {
-                mRecyclerView.mState.removeFromDisappearingList(child);
+                mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh);
             }
             mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
             if (DISPATCH_TEMP_DETACH)  {
@@ -6768,7 +6725,7 @@
             }
             final ViewHolder vh = getChildViewHolderInt(view);
             vh.addFlags(ViewHolder.FLAG_IGNORE);
-            mRecyclerView.mState.onViewIgnored(vh);
+            mRecyclerView.mViewInfoStore.removeViewHolder(vh);
         }
 
         /**
@@ -9386,15 +9343,6 @@
     public static class State {
 
         private int mTargetPosition = RecyclerView.NO_POSITION;
-        ArrayMap<ViewHolder, ItemHolderInfo> mPreLayoutHolderMap =
-                new ArrayMap<ViewHolder, ItemHolderInfo>();
-        ArrayMap<ViewHolder, ItemHolderInfo> mPostLayoutHolderMap =
-                new ArrayMap<ViewHolder, ItemHolderInfo>();
-        // nullable
-        ArrayMap<Long, ViewHolder> mOldChangedHolders = new ArrayMap<Long, ViewHolder>();
-
-        // we use this like a set
-        final List<View> mDisappearingViewsInLayoutPass = new ArrayList<View>();
 
         private SparseArray<Object> mData;
 
@@ -9560,45 +9508,10 @@
                     mItemCount;
         }
 
-        void onViewRecycled(ViewHolder holder) {
-            mPreLayoutHolderMap.remove(holder);
-            mPostLayoutHolderMap.remove(holder);
-            if (mOldChangedHolders != null) {
-                removeFrom(mOldChangedHolders, holder);
-            }
-            mDisappearingViewsInLayoutPass.remove(holder.itemView);
-            // holder cannot be in new list.
-        }
-
-        public void onViewIgnored(ViewHolder holder) {
-            onViewRecycled(holder);
-        }
-
-        private void removeFrom(ArrayMap<Long, ViewHolder> holderMap, ViewHolder holder) {
-            for (int i = holderMap.size() - 1; i >= 0; i --) {
-                if (holder == holderMap.valueAt(i)) {
-                    holderMap.removeAt(i);
-                    return;
-                }
-            }
-        }
-
-        void removeFromDisappearingList(View child) {
-            mDisappearingViewsInLayoutPass.remove(child);
-        }
-
-        void addToDisappearingList(View child) {
-            if (!mDisappearingViewsInLayoutPass.contains(child)) {
-                mDisappearingViewsInLayoutPass.add(child);
-            }
-        }
-
         @Override
         public String toString() {
             return "State{" +
                     "mTargetPosition=" + mTargetPosition +
-                    ", mPreLayoutHolderMap=" + mPreLayoutHolderMap +
-                    ", mPostLayoutHolderMap=" + mPostLayoutHolderMap +
                     ", mData=" + mData +
                     ", mItemCount=" + mItemCount +
                     ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount +
@@ -9651,7 +9564,8 @@
      * animateChange()}
      * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()},
      * and
-     * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()} call.
+     * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+     * animateDisappearance()} call.
      *
      * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p>
      *
@@ -9840,8 +9754,9 @@
          * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          */
-        public ItemHolderInfo recordPreLayoutInformation(State state,
-                ViewHolder viewHolder, @AdapterChanges int changeFlags, List<Object> payloads) {
+        public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
+                @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
+                @NonNull List<Object> payloads) {
             return obtainHolderInfo().setFrom(viewHolder);
         }
 
@@ -9865,11 +9780,12 @@
          *
          * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
          * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
-         * @see #animateDisappearance(ViewHolder, ItemHolderInfo)
+         * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          */
-        public ItemHolderInfo recordPostLayoutInformation(State state, ViewHolder viewHolder) {
+        public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
+                @NonNull ViewHolder viewHolder) {
             return obtainHolderInfo().setFrom(viewHolder);
         }
 
@@ -9877,11 +9793,16 @@
          * Called by the RecyclerView when a ViewHolder has disappeared from the layout.
          * <p>
          * This means that the View was a child of the LayoutManager when layout started but has
-         * not been re-laid-out by the LayoutManager. It might be removed from the adapter or simply
+         * been removed by the LayoutManager. It might have been removed from the adapter or simply
          * become invisible due to other factors. You can distinguish these two cases by checking
          * the change flags that were passed to
          * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
          * <p>
+         * If LayoutManager supports predictive animations, it might provide a target disappear
+         * location for the View by laying it out in that location. When that happens,
+         * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the
+         * response of that call will be passed to this method as the <code>postLayoutInfo</code>.
+         * <p>
          * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
          * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
          * decides not to animate the view).
@@ -9889,12 +9810,15 @@
          * @param viewHolder    The ViewHolder which should be animated
          * @param preLayoutInfo The information that was returned from
          *                      {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         * @param postLayoutInfo The information that was returned from
+         *                       {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be
+         *                       null if the LayoutManager did not layout the item.
          *
          * @return true if a later call to {@link #runPendingAnimations()} is requested,
          * false otherwise.
          */
-        public abstract boolean animateDisappearance(ViewHolder viewHolder,
-                ItemHolderInfo preLayoutInfo);
+        public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
 
         /**
          * Called by the RecyclerView when a ViewHolder is added to the layout.
@@ -9919,8 +9843,8 @@
          * @return true if a later call to {@link #runPendingAnimations()} is requested,
          * false otherwise.
          */
-        public abstract boolean animateAppearance(ViewHolder viewHolder,
-                ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);
+        public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
+                @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
 
         /**
          * Called by the RecyclerView when a ViewHolder is present in both before and after the
@@ -9949,8 +9873,8 @@
          * @return true if a later call to {@link #runPendingAnimations()} is requested,
          * false otherwise.
          */
-        public abstract boolean animatePersistence(ViewHolder viewHolder,
-                ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);
+        public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
 
         /**
          * Called by the RecyclerView when an adapter item is present both before and after the
@@ -10004,8 +9928,9 @@
          * @return true if a later call to {@link #runPendingAnimations()} is requested,
          * false otherwise.
          */
-        public abstract boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
-                ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);
+        public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
+                @NonNull ViewHolder newHolder,
+                @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
 
         @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
             int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
@@ -10031,10 +9956,10 @@
          * animateChange()}
          * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * animatePersistence()}, and
-         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()},
-         * which inform the RecyclerView that the ItemAnimator wants to be called later to start
-         * the associated animations. runPendingAnimations() will be scheduled to be run
-         * on the next frame.
+         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be
+         * called later to start the associated animations. runPendingAnimations() will be scheduled
+         * to be run on the next frame.
          */
         abstract public void runPendingAnimations();
 
@@ -10079,7 +10004,8 @@
          * animateAppearance()},
          * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * animatePersistence()}, or
-         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()}, there
+         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateDisappearance()}, there
          * should
          * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass.
          * <p>
@@ -10116,9 +10042,9 @@
          * animateAppearance()},
          * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * animatePersistence()}, or
-         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()}, there
-         * should
-         * be a matching {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass.
+         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateDisappearance()}, there should be a matching
+         * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass.
          * <p>
          * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
          * animateChange()}, sublcass should call this method for both the <code>oldHolder</code>
diff --git a/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
index a1c27b5..1266236 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
@@ -1,21 +1,25 @@
 package android.support.v7.widget;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
 import android.util.Log;
 import android.view.View;
 
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.RecyclerView.Adapter;
-import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
-
 /**
  * A wrapper class for ItemAnimator that records View bounds and decides whether it should run
- * move, change, add or remove animations. This class also replicates the original ItemAnimator API.
+ * move, change, add or remove animations. This class also replicates the original ItemAnimator
+ * API.
  * <p>
- * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like to
+ * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like
+ * to
  * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
  * class that extends {@link ItemHolderInfo}.
  */
-abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimator {
+abstract public class
+        SimpleItemAnimator extends RecyclerView.ItemAnimator {
 
     private static final boolean DEBUG = false;
 
@@ -41,13 +45,14 @@
      * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
      * The value of this property is true by default.
      *
+     * @param supportsChangeAnimations true if change animations are supported by
+     *                                 this ItemAnimator, false otherwise. If the property is false,
+     *                                 the ItemAnimator
+     *                                 will not receive a call to
+     *                                 {@link #animateChange(ViewHolder, ViewHolder, int, int, int,
+     *                                 int)} when changes occur.
      * @see Adapter#notifyItemChanged(int)
      * @see Adapter#notifyItemRangeChanged(int, int)
-     *
-     * @param supportsChangeAnimations true if change animations are supported by
-     * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator
-     * will not receive a call to
-     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur.
      */
     public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
         mSupportsChangeAnimations = supportsChangeAnimations;
@@ -59,12 +64,13 @@
     }
 
     @Override
-    public boolean animateDisappearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo) {
+    public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
         int oldLeft = preLayoutInfo.left;
         int oldTop = preLayoutInfo.top;
         View disappearingItemView = viewHolder.itemView;
-        int newLeft = disappearingItemView.getLeft();
-        int newTop = disappearingItemView.getTop();
+        int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
+        int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
         if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
             disappearingItemView.layout(newLeft, newTop,
                     newLeft + disappearingItemView.getWidth(),
@@ -82,9 +88,9 @@
     }
 
     @Override
-    public boolean animateAppearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo,
-            ItemHolderInfo postLayoutInfo) {
-        if (preLayoutInfo != null &&  (preLayoutInfo.left != postLayoutInfo.left
+    public boolean animateAppearance(@NonNull ViewHolder viewHolder,
+            @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
+        if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
                 || preLayoutInfo.top != postLayoutInfo.top)) {
             // slide items in if before/after locations differ
             if (DEBUG) {
@@ -101,8 +107,8 @@
     }
 
     @Override
-    public boolean animatePersistence(ViewHolder viewHolder, ItemHolderInfo preInfo,
-            ItemHolderInfo postInfo) {
+    public boolean animatePersistence(@NonNull ViewHolder viewHolder,
+            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
         if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
             if (DEBUG) {
                 Log.d(TAG, "PERSISTENT: " + viewHolder +
@@ -116,15 +122,15 @@
     }
 
     @Override
-    public boolean animateChange(ViewHolder oldHolder,
-            ViewHolder newHolder, ItemHolderInfo preInfo, ItemHolderInfo postInfo) {
+    public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
         if (DEBUG) {
             Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
         }
         final int fromLeft = preInfo.left;
         final int fromTop = preInfo.top;
         final int toLeft, toTop;
-        if (newHolder == null || newHolder.shouldIgnore()) {
+        if (newHolder.shouldIgnore()) {
             toLeft = preInfo.left;
             toTop = preInfo.top;
         } else {
@@ -238,8 +244,8 @@
      * Method to be called by subclasses when a remove animation is done.
      *
      * @param item The item which has been removed
-     *
-     * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo)
+     * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+     * ItemHolderInfo)
      */
     public final void dispatchRemoveFinished(ViewHolder item) {
         onRemoveFinished(item);
@@ -250,7 +256,8 @@
      * Method to be called by subclasses when a move animation is done.
      *
      * @param item The item which has been moved
-     * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo)
+     * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+     * ItemHolderInfo)
      * @see RecyclerView.ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
      * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
      */
@@ -272,12 +279,12 @@
     /**
      * Method to be called by subclasses when a change animation is done.
      *
-     * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
-     * @param item The item which has been changed (this method must be called for
-     * each non-null ViewHolder passed into
-     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+     * @param item    The item which has been changed (this method must be called for
+     *                each non-null ViewHolder passed into
+     *                {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
      * @param oldItem true if this is the old item that was changed, false if
-     * it is the new item that replaced the old item.
+     *                it is the new item that replaced the old item.
+     * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
      */
     public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
         onChangeFinished(item, oldItem);
@@ -314,11 +321,11 @@
     /**
      * Method to be called by subclasses when a change animation is being started.
      *
-     * @param item The item which has been changed (this method must be called for
-     * each non-null ViewHolder passed into
-     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+     * @param item    The item which has been changed (this method must be called for
+     *                each non-null ViewHolder passed into
+     *                {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
      * @param oldItem true if this is the old item that was changed, false if
-     * it is the new item that replaced the old item.
+     *                it is the new item that replaced the old item.
      */
     public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
         onChangeStarting(item, oldItem);
@@ -333,7 +340,8 @@
      * @param item The ViewHolder being animated.
      */
     @SuppressWarnings("UnusedParameters")
-    public void onRemoveStarting(ViewHolder item) {}
+    public void onRemoveStarting(ViewHolder item) {
+    }
 
     /**
      * Called when a remove animation has ended on the given ViewHolder.
@@ -343,7 +351,8 @@
      *
      * @param item The ViewHolder being animated.
      */
-    public void onRemoveFinished(ViewHolder item) {}
+    public void onRemoveFinished(ViewHolder item) {
+    }
 
     /**
      * Called when an add animation is being started on the given ViewHolder.
@@ -354,7 +363,8 @@
      * @param item The ViewHolder being animated.
      */
     @SuppressWarnings("UnusedParameters")
-    public void onAddStarting(ViewHolder item) {}
+    public void onAddStarting(ViewHolder item) {
+    }
 
     /**
      * Called when an add animation has ended on the given ViewHolder.
@@ -364,7 +374,8 @@
      *
      * @param item The ViewHolder being animated.
      */
-    public void onAddFinished(ViewHolder item) {}
+    public void onAddFinished(ViewHolder item) {
+    }
 
     /**
      * Called when a move animation is being started on the given ViewHolder.
@@ -375,7 +386,8 @@
      * @param item The ViewHolder being animated.
      */
     @SuppressWarnings("UnusedParameters")
-    public void onMoveStarting(ViewHolder item) {}
+    public void onMoveStarting(ViewHolder item) {
+    }
 
     /**
      * Called when a move animation has ended on the given ViewHolder.
@@ -385,7 +397,8 @@
      *
      * @param item The ViewHolder being animated.
      */
-    public void onMoveFinished(ViewHolder item) {}
+    public void onMoveFinished(ViewHolder item) {
+    }
 
     /**
      * Called when a change animation is being started on the given ViewHolder.
@@ -393,12 +406,13 @@
      * this method to handle any ViewHolder-specific operations linked to animation
      * lifecycles.
      *
-     * @param item The ViewHolder being animated.
+     * @param item    The ViewHolder being animated.
      * @param oldItem true if this is the old item that was changed, false if
-     * it is the new item that replaced the old item.
+     *                it is the new item that replaced the old item.
      */
     @SuppressWarnings("UnusedParameters")
-    public void onChangeStarting(ViewHolder item, boolean oldItem) {}
+    public void onChangeStarting(ViewHolder item, boolean oldItem) {
+    }
 
     /**
      * Called when a change animation has ended on the given ViewHolder.
@@ -406,9 +420,10 @@
      * this method to handle any ViewHolder-specific operations linked to animation
      * lifecycles.
      *
-     * @param item The ViewHolder being animated.
+     * @param item    The ViewHolder being animated.
      * @param oldItem true if this is the old item that was changed, false if
-     * it is the new item that replaced the old item.
+     *                it is the new item that replaced the old item.
      */
-    public void onChangeFinished(ViewHolder item, boolean oldItem) {}
+    public void onChangeFinished(ViewHolder item, boolean oldItem) {
+    }
 }
diff --git a/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
new file mode 100644
index 0000000..0af8dfb
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2015 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.v7.widget;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.LongSparseArray;
+import android.support.v4.util.Pools;
+
+import static android.support.v7.widget.RecyclerView.ViewHolder;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
+import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_POST;
+/**
+ * This class abstracts all tracking for Views to run animations
+ *
+ * @hide
+ */
+class ViewInfoStore {
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * View data records for pre-layout
+     */
+    @VisibleForTesting
+    final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
+
+    @VisibleForTesting
+    final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
+
+    /**
+     * Clears the state and all existing tracking data
+     */
+    void clear() {
+        mLayoutHolderMap.clear();
+        mOldChangedHolders.clear();
+    }
+
+    /**
+     * Adds the item information to the prelayout tracking
+     * @param holder The ViewHolder whose information is being saved
+     * @param info The information to save
+     */
+    void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
+        InfoRecord record = mLayoutHolderMap.get(holder);
+        if (record == null) {
+            record = InfoRecord.obtain();
+            mLayoutHolderMap.put(holder, record);
+        }
+        record.preInfo = info;
+        record.flags |= FLAG_PRE;
+    }
+
+    /**
+     * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
+     * @param vh The ViewHolder whose information is being queried
+     * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+     */
+    @Nullable
+    ItemHolderInfo popFromPreLayout(ViewHolder vh) {
+        int index = mLayoutHolderMap.indexOfKey(vh);
+        if (index < 0) {
+            return null;
+        }
+        final InfoRecord record = mLayoutHolderMap.valueAt(index);
+        if (record != null && (record.flags & FLAG_PRE) != 0) {
+            record.flags &= ~FLAG_PRE;
+            final ItemHolderInfo info = record.preInfo;
+            if (record.flags == 0) {
+                mLayoutHolderMap.removeAt(index);
+                InfoRecord.recycle(record);
+            }
+            return info;
+        }
+        return null;
+    }
+
+    /**
+     * Adds the given ViewHolder to the oldChangeHolders list
+     * @param key The key to identify the ViewHolder.
+     * @param holder The ViewHolder to store
+     */
+    void addToOldChangeHolders(long key, ViewHolder holder) {
+        mOldChangedHolders.put(key, holder);
+    }
+
+    /**
+     * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
+     * LayoutManager during a pre-layout pass. We distinguish them from other views that were
+     * already in the pre-layout so that ItemAnimator can choose to run a different animation for
+     * them.
+     *
+     * @param holder The ViewHolder to store
+     * @param info The information to save
+     */
+    void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
+        InfoRecord record = mLayoutHolderMap.get(holder);
+        if (record == null) {
+            record = InfoRecord.obtain();
+            mLayoutHolderMap.put(holder, record);
+        }
+        record.flags |= FLAG_APPEAR;
+        record.preInfo = info;
+    }
+
+    /**
+     * Checks whether the given ViewHolder is in preLayout list
+     * @param viewHolder The ViewHolder to query
+     *
+     * @return True if the ViewHolder is present in preLayout, false otherwise
+     */
+    boolean isInPreLayout(ViewHolder viewHolder) {
+        final InfoRecord record = mLayoutHolderMap.get(viewHolder);
+        return record != null && (record.flags & FLAG_PRE) != 0;
+    }
+
+    /**
+     * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
+     * null.
+     * @param key The key to be used to find the ViewHolder.
+     *
+     * @return A ViewHolder if exists or null if it does not exist.
+     */
+    ViewHolder getFromOldChangeHolders(long key) {
+        return mOldChangedHolders.get(key);
+    }
+
+    /**
+     * Adds the item information to the post layout list
+     * @param holder The ViewHolder whose information is being saved
+     * @param info The information to save
+     */
+    void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
+        InfoRecord record = mLayoutHolderMap.get(holder);
+        if (record == null) {
+            record = InfoRecord.obtain();
+            mLayoutHolderMap.put(holder, record);
+        }
+        record.postInfo = info;
+        record.flags |= FLAG_POST;
+    }
+
+    /**
+     * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
+     * This list holds such items so that we can animate / recycle these ViewHolders properly.
+     *
+     * @param holder The ViewHolder which disappeared during a layout.
+     */
+    void addToDisappearedInLayout(ViewHolder holder) {
+        InfoRecord record = mLayoutHolderMap.get(holder);
+        if (record == null) {
+            record = InfoRecord.obtain();
+            mLayoutHolderMap.put(holder, record);
+        }
+        record.flags |= FLAG_DISAPPEARED;
+    }
+
+    /**
+     * Removes a ViewHolder from disappearing list.
+     * @param holder The ViewHolder to be removed from the disappearing list.
+     */
+    void removeFromDisappearedInLayout(ViewHolder holder) {
+        InfoRecord record = mLayoutHolderMap.get(holder);
+        if (record == null) {
+            return;
+        }
+        record.flags &= ~FLAG_DISAPPEARED;
+    }
+
+    void process(ProcessCallback callback) {
+        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) {
+            final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
+            final InfoRecord record = mLayoutHolderMap.removeAt(index);
+            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
+                // Appeared then disappeared. Not useful for animations.
+                callback.unused(viewHolder);
+            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
+                // Set as "disappeared" by the LayoutManager (addDisappearingView)
+                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
+            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
+                // Appeared in the layout but not in the adapter (e.g. entered the viewport)
+                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
+                // Persistent in both passes. Animate persistence
+                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
+            } else if ((record.flags & FLAG_PRE) != 0) {
+                // Was in pre-layout, never been added to post layout
+                callback.processDisappeared(viewHolder, record.preInfo, null);
+            } else if ((record.flags & FLAG_POST) != 0) {
+                // Was not in pre-layout, been added to post layout
+                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+            } else if ((record.flags & FLAG_APPEAR) != 0) {
+                // Scrap view. RecyclerView will handle removing/recycling this.
+            } else if (DEBUG) {
+                throw new IllegalStateException("record without any reasonable flag combination:/");
+            }
+            InfoRecord.recycle(record);
+        }
+    }
+
+    /**
+     * Removes the ViewHolder from all list
+     * @param holder The ViewHolder which we should stop tracking
+     */
+    void removeViewHolder(ViewHolder holder) {
+        for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
+            if (holder == mOldChangedHolders.valueAt(i)) {
+                mOldChangedHolders.removeAt(i);
+                break;
+            }
+        }
+        final InfoRecord info = mLayoutHolderMap.remove(holder);
+        if (info != null) {
+            InfoRecord.recycle(info);
+        }
+    }
+
+    void onDetach() {
+        InfoRecord.drainCache();
+    }
+
+    interface ProcessCallback {
+        void processDisappeared(ViewHolder viewHolder, ItemHolderInfo preInfo,
+                @Nullable ItemHolderInfo postInfo);
+        void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
+                ItemHolderInfo postInfo);
+        void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
+                @NonNull ItemHolderInfo postInfo);
+        void unused(ViewHolder holder);
+    }
+
+    static class InfoRecord {
+        // disappearing list
+        static final int FLAG_DISAPPEARED = 1;
+        // appear in pre layout list
+        static final int FLAG_APPEAR = 1 << 1;
+        // pre layout, this is necessary to distinguish null item info
+        static final int FLAG_PRE = 1 << 2;
+        // post layout, this is necessary to distinguish null item info
+        static final int FLAG_POST = 1 << 3;
+        static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
+        static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
+        static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
+        int flags;
+        @Nullable ItemHolderInfo preInfo;
+        @Nullable ItemHolderInfo postInfo;
+        static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
+
+        private InfoRecord() {
+        }
+
+        static InfoRecord obtain() {
+            InfoRecord record = sPool.acquire();
+            return record == null ? new InfoRecord() : record;
+        }
+
+        static void recycle(InfoRecord record) {
+            record.flags = 0;
+            record.preInfo = null;
+            record.postInfo = null;
+            sPool.release(record);
+        }
+
+        static void drainCache() {
+            //noinspection StatementWithEmptyBody
+            while (sPool.acquire() != null);
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
index e690f22..4764c00 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -84,6 +84,7 @@
         recyclerView.waitForDraw(1);
         mLayoutManager.mOnLayoutCallbacks.reset();
         getInstrumentation().waitForIdleSync();
+        checkForMainThreadException();
         assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
         assertEquals("all expected children should be laid out", firstLayoutItemCount,
                 mLayoutManager.getChildCount());
@@ -650,7 +651,7 @@
         }
     }
 
-    static class AnimateChange extends AnimatePersistence {
+    static class AnimateChange extends AnimateLogBase {
 
         final RecyclerView.ViewHolder newHolder;
 
@@ -661,7 +662,7 @@
         }
     }
 
-    static class AnimatePersistence extends AnimateAppearance {
+    static class AnimatePersistence extends AnimateLogBase {
 
         public AnimatePersistence(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
                 LoggingInfo post) {
@@ -669,30 +670,60 @@
         }
     }
 
-    static class AnimateAppearance extends AnimateDisappearance {
-
-        final LoggingInfo postInfo;
-
+    static class AnimateAppearance extends AnimateLogBase {
         public AnimateAppearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
                 LoggingInfo post) {
-            super(viewHolder, pre);
-            this.postInfo = post;
+            super(viewHolder, pre, post);
         }
     }
 
     static class AnimateDisappearance extends AnimateLogBase {
-        public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre) {
-            super(viewHolder, pre);
+        public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
+                LoggingInfo post) {
+            super(viewHolder, pre, post);
         }
     }
     static class AnimateLogBase {
 
         final RecyclerView.ViewHolder viewHolder;
         final LoggingInfo preInfo;
+        final LoggingInfo postInfo;
 
-        public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre) {
+        public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
+                LoggingInfo postInfo) {
             this.viewHolder = viewHolder;
             this.preInfo = pre;
+            this.postInfo = postInfo;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            AnimateLogBase that = (AnimateLogBase) o;
+
+            if (viewHolder != null ? !viewHolder.equals(that.viewHolder)
+                    : that.viewHolder != null) {
+                return false;
+            }
+            if (preInfo != null ? !preInfo.equals(that.preInfo) : that.preInfo != null) {
+                return false;
+            }
+            return !(postInfo != null ? !postInfo.equals(that.postInfo) : that.postInfo != null);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = viewHolder != null ? viewHolder.hashCode() : 0;
+            result = 31 * result + (preInfo != null ? preInfo.hashCode() : 0);
+            result = 31 * result + (postInfo != null ? postInfo.hashCode() : 0);
+            return result;
         }
     }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/InfoStoreTrojan.java b/v7/recyclerview/tests/src/android/support/v7/widget/InfoStoreTrojan.java
new file mode 100644
index 0000000..b3fbae4
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/InfoStoreTrojan.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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.v7.widget;
+
+/**
+ * Helper class for tests to check internals of ViewInfoStore
+ */
+public class InfoStoreTrojan {
+    static int sizeOfPreLayout(ViewInfoStore store) {
+        return sizeOf(store, ViewInfoStore.InfoRecord.FLAG_PRE);
+    }
+    static int sizeOfPostLayout(ViewInfoStore store) {
+        return sizeOf(store, ViewInfoStore.InfoRecord.FLAG_POST);
+    }
+    static int sizeOf(ViewInfoStore store, int flags) {
+        int cnt = 0;
+        final int size = store.mLayoutHolderMap.size();
+        for (int i = 0; i < size; i ++) {
+            ViewInfoStore.InfoRecord record = store.mLayoutHolderMap.valueAt(i);
+            if ((record.flags & flags) != 0) {
+                cnt ++;
+            }
+        }
+        return cnt;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
index 0e4bca7..3ef0b3f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
@@ -15,6 +15,9 @@
  */
 package android.support.v7.widget;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -29,12 +32,31 @@
  * Includes tests for the new RecyclerView animations API (v2).
  */
 public class ItemAnimatorV2ApiTest extends BaseRecyclerViewAnimationsTest {
-
     @Override
     protected RecyclerView.ItemAnimator createItemAnimator() {
         return mAnimator;
     }
 
+    public void testChangeMovedOutside() throws Throwable {
+        setupBasic(10);
+        final RecyclerView.ViewHolder target = mRecyclerView.findViewHolderForAdapterPosition(9);
+        mLayoutManager.expectLayouts(2);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
+        mTestAdapter.changeAndNotify(9, 1);
+        mLayoutManager.waitForLayout(2);
+        // changed item shold not be laid out and should just receive disappear
+        LoggingInfo pre = mAnimator.preLayoutInfoMap.get(target);
+        assertNotNull("test sanity", pre);
+        assertNull("test sanity", mAnimator.postLayoutInfoMap.get(target));
+        assertTrue(mAnimator.animateChangeList.isEmpty());
+        assertEquals(1, mAnimator.animateDisappearanceList.size());
+        assertEquals(new AnimateDisappearance(target, pre, null),
+                mAnimator.animateDisappearanceList.get(0));
+        // This is kind of problematic because layout manager will never layout the updated
+        // version of this view since it went out of bounds and it won't show up in scrap.
+        // I don't think we can do much better since other option is to bind a fresh view
+    }
+
     public void testSimpleAdd() throws Throwable {
         setupBasic(10);
         mLayoutManager.expectLayouts(2);
@@ -49,11 +71,11 @@
         // the first two should not receive anything
         for (int i = 0; i < 2; i++) {
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         for (int i = 3; i < mTestAdapter.getItemCount(); i++) {
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -68,16 +90,16 @@
         assertEquals(1, mAnimator.animateDisappearanceList.size());
         AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0);
         assertSame(vh, log.viewHolder);
-        assertFalse(mAnimator.postLayoutInfo.containsKey(vh));
+        assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh));
         assertEquals(FLAG_REMOVED, log.preInfo.changeFlags);
         // the first two should not receive anything
         for (int i = 0; i < 2; i++) {
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         for (int i = 3; i < mTestAdapter.getItemCount(); i++) {
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -92,8 +114,8 @@
         AnimateChange log = mAnimator.animateChangeList.get(0);
         assertSame(vh, log.viewHolder);
         assertSame(vh, log.newHolder);
-        assertTrue(mAnimator.preLayoutInfo.containsKey(vh));
-        assertTrue(mAnimator.postLayoutInfo.containsKey(vh));
+        assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh));
+        assertTrue(mAnimator.postLayoutInfoMap.containsKey(vh));
         assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
         assertEquals(0, log.postInfo.changeFlags);
         //others should not receive anything
@@ -102,7 +124,7 @@
                 continue;
             }
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -127,9 +149,9 @@
         assertSame(vh, log.viewHolder);
         assertSame(newVh, log.newHolder);
         assertNull(vh.itemView.getParent());
-        assertTrue(mAnimator.preLayoutInfo.containsKey(vh));
-        assertFalse(mAnimator.postLayoutInfo.containsKey(vh));
-        assertTrue(mAnimator.postLayoutInfo.containsKey(newVh));
+        assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh));
+        assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh));
+        assertTrue(mAnimator.postLayoutInfoMap.containsKey(newVh));
         assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
         assertEquals(0, log.postInfo.changeFlags);
         //others should not receive anything
@@ -138,7 +160,7 @@
                 continue;
             }
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -183,12 +205,12 @@
         assertSame(reused, logReused.viewHolder);
         assertSame(reused, logReused.newHolder);
 
-        assertTrue(mAnimator.preLayoutInfo.containsKey(replaced));
-        assertTrue(mAnimator.preLayoutInfo.containsKey(reused));
+        assertTrue(mAnimator.preLayoutInfoMap.containsKey(replaced));
+        assertTrue(mAnimator.preLayoutInfoMap.containsKey(reused));
 
-        assertTrue(mAnimator.postLayoutInfo.containsKey(newVh));
-        assertTrue(mAnimator.postLayoutInfo.containsKey(reused));
-        assertFalse(mAnimator.postLayoutInfo.containsKey(replaced));
+        assertTrue(mAnimator.postLayoutInfoMap.containsKey(newVh));
+        assertTrue(mAnimator.postLayoutInfoMap.containsKey(reused));
+        assertFalse(mAnimator.postLayoutInfoMap.containsKey(replaced));
 
         assertEquals(FLAG_CHANGED, logReplaced.preInfo.changeFlags);
         assertEquals(FLAG_CHANGED, logReused.preInfo.changeFlags);
@@ -201,7 +223,7 @@
                 continue;
             }
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -216,7 +238,7 @@
         assertEquals(1, mAnimator.animateDisappearanceList.size());
         AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0);
         assertSame(vh, log.viewHolder);
-        assertFalse(mAnimator.postLayoutInfo.containsKey(vh));
+        assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh));
         assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
         assertEquals(0, mAnimator.animateChangeList.size());
         assertEquals(0, mAnimator.animateAppearanceList.size());
@@ -235,8 +257,8 @@
         AnimateChange log = mAnimator.animateChangeList.get(0);
         assertSame(vh, log.viewHolder);
         assertSame(vh, log.newHolder);
-        assertTrue(mAnimator.preLayoutInfo.containsKey(vh));
-        assertTrue(mAnimator.postLayoutInfo.containsKey(vh));
+        assertTrue(mAnimator.preLayoutInfoMap.containsKey(vh));
+        assertTrue(mAnimator.postLayoutInfoMap.containsKey(vh));
         assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
         assertEquals(0, log.postInfo.changeFlags);
         assertNotNull(log.preInfo.payloads);
@@ -247,7 +269,7 @@
                 continue;
             }
             RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
-            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+            assertEquals(0, mAnimator.preLayoutInfoMap.get(other).changeFlags);
         }
         checkForMainThreadException();
     }
@@ -407,8 +429,8 @@
                 return true;
             }
         };
-        Map<RecyclerView.ViewHolder, LoggingInfo> preLayoutInfo = new HashMap<>();
-        Map<RecyclerView.ViewHolder, LoggingInfo> postLayoutInfo = new HashMap<>();
+        Map<RecyclerView.ViewHolder, LoggingInfo> preLayoutInfoMap = new HashMap<>();
+        Map<RecyclerView.ViewHolder, LoggingInfo> postLayoutInfoMap = new HashMap<>();
 
         List<AnimateAppearance> animateAppearanceList = new ArrayList<>();
         List<AnimateDisappearance> animateDisappearanceList = new ArrayList<>();
@@ -420,69 +442,73 @@
             return canReUseCallback.canReUse(viewHolder);
         }
 
+        @NonNull
         @Override
-        public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
-                RecyclerView.ViewHolder viewHolder,
-                @AdapterChanges int changeFlags, List<Object> payloads) {
+        public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state,
+                @NonNull RecyclerView.ViewHolder viewHolder,
+                @AdapterChanges int changeFlags, @NonNull List<Object> payloads) {
             LoggingInfo loggingInfo = new LoggingInfo(viewHolder, changeFlags, payloads);
-            preLayoutInfo.put(viewHolder, loggingInfo);
+            preLayoutInfoMap.put(viewHolder, loggingInfo);
             return loggingInfo;
         }
 
+        @NonNull
         @Override
-        public ItemHolderInfo recordPostLayoutInformation(RecyclerView.State state,
-                RecyclerView.ViewHolder viewHolder) {
+        public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
+                @NonNull RecyclerView.ViewHolder viewHolder) {
             LoggingInfo loggingInfo = new LoggingInfo(viewHolder, 0, null);
-            postLayoutInfo.put(viewHolder, loggingInfo);
+            postLayoutInfoMap.put(viewHolder, loggingInfo);
             return loggingInfo;
         }
 
         @Override
-        public boolean animateDisappearance(RecyclerView.ViewHolder viewHolder,
-                ItemHolderInfo preInfo) {
-            animateDisappearanceList.add(new AnimateDisappearance(viewHolder, (LoggingInfo) preInfo));
-            assertSame(preLayoutInfo.get(viewHolder), preInfo);
+        public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preLayoutInfo,
+                @Nullable ItemHolderInfo postLayoutInfo) {
+            animateDisappearanceList.add(new AnimateDisappearance(viewHolder,
+                    (LoggingInfo) preLayoutInfo, (LoggingInfo) postLayoutInfo));
+            assertSame(preLayoutInfoMap.get(viewHolder), preLayoutInfo);
+            assertSame(postLayoutInfoMap.get(viewHolder), postLayoutInfo);
             dispatchAnimationFinished(viewHolder);
 
             return false;
         }
 
         @Override
-        public boolean animateAppearance(RecyclerView.ViewHolder viewHolder, ItemHolderInfo preInfo,
-                ItemHolderInfo postInfo) {
+        public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder,
+                ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
             animateAppearanceList.add(
                     new AnimateAppearance(viewHolder, (LoggingInfo) preInfo, (LoggingInfo) postInfo));
-            assertSame(preLayoutInfo.get(viewHolder), preInfo);
-            assertSame(postLayoutInfo.get(viewHolder), postInfo);
+            assertSame(preLayoutInfoMap.get(viewHolder), preInfo);
+            assertSame(postLayoutInfoMap.get(viewHolder), postInfo);
             dispatchAnimationFinished(viewHolder);
             return false;
         }
 
         @Override
-        public boolean animatePersistence(RecyclerView.ViewHolder viewHolder,
-                ItemHolderInfo preInfo,
-                ItemHolderInfo postInfo) {
+        public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder,
+                @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
             animatePersistenceList.add(new AnimatePersistence(viewHolder, (LoggingInfo) preInfo,
                     (LoggingInfo) postInfo));
             dispatchAnimationFinished(viewHolder);
-            assertSame(preLayoutInfo.get(viewHolder), preInfo);
-            assertSame(postLayoutInfo.get(viewHolder), postInfo);
+            assertSame(preLayoutInfoMap.get(viewHolder), preInfo);
+            assertSame(postLayoutInfoMap.get(viewHolder), postInfo);
             return false;
         }
 
         @Override
-        public boolean animateChange(RecyclerView.ViewHolder oldHolder,
-                RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
-                ItemHolderInfo postInfo) {
+        public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder,
+                @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo,
+                @NonNull ItemHolderInfo postInfo) {
             animateChangeList.add(new AnimateChange(oldHolder, newHolder, (LoggingInfo) preInfo,
                     (LoggingInfo) postInfo));
             if (oldHolder != null) {
                 dispatchAnimationFinished(oldHolder);
-                assertSame(preLayoutInfo.get(oldHolder), preInfo);
+                assertSame(preLayoutInfoMap.get(oldHolder), preInfo);
             }
             if (newHolder != null) {
                 dispatchAnimationFinished(newHolder);
-                assertSame(postLayoutInfo.get(newHolder), postInfo);
+                assertSame(postLayoutInfoMap.get(newHolder), postInfo);
             }
 
             return false;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
index e41286e..9bbdd4b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
@@ -13,10 +13,16 @@
 
 package android.support.v7.widget;
 
+import android.support.annotation.NonNull;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import android.support.v7.widget.BaseRecyclerViewAnimationsTest.AnimateDisappearance;
+import android.support.v7.widget.BaseRecyclerViewAnimationsTest.AnimateChange;
+import android.support.v7.widget.BaseRecyclerViewAnimationsTest.AnimatePersistence;
+import android.support.v7.widget.BaseRecyclerViewAnimationsTest.AnimateAppearance;
 
 public class LoggingItemAnimator extends DefaultItemAnimator {
 
@@ -30,10 +36,10 @@
 
     final ArrayList<RecyclerView.ViewHolder> mChangeNewVHs = new ArrayList<RecyclerView.ViewHolder>();
 
-    List<BaseRecyclerViewAnimationsTest.AnimateAppearance> mAnimateAppearanceList = new ArrayList<>();
-    List<BaseRecyclerViewAnimationsTest.AnimateDisappearance> mAnimateDisappearanceList = new ArrayList<>();
-    List<BaseRecyclerViewAnimationsTest.AnimatePersistence> mAnimatePersistenceList = new ArrayList<>();
-    List<BaseRecyclerViewAnimationsTest.AnimateChange> mAnimateChangeList = new ArrayList<>();
+    List<AnimateAppearance> mAnimateAppearanceList = new ArrayList<>();
+    List<AnimateDisappearance> mAnimateDisappearanceList = new ArrayList<>();
+    List<AnimatePersistence> mAnimatePersistenceList = new ArrayList<>();
+    List<AnimateChange> mAnimateChangeList = new ArrayList<>();
 
     CountDownLatch mWaitForPendingAnimations;
 
@@ -48,38 +54,37 @@
     }
 
     @Override
-    public boolean animateDisappearance(RecyclerView.ViewHolder viewHolder,
-            ItemHolderInfo preLayoutInfo) {
+    public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
+            @NonNull ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
         mAnimateDisappearanceList
-                .add(new BaseRecyclerViewAnimationsTest.AnimateDisappearance(viewHolder, null));
-        return super.animateDisappearance(viewHolder, preLayoutInfo);
+                .add(new AnimateDisappearance(viewHolder, null, null));
+        return super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo);
     }
 
     @Override
-    public boolean animateAppearance(RecyclerView.ViewHolder viewHolder,
+    public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder,
             ItemHolderInfo preLayoutInfo,
-            ItemHolderInfo postLayoutInfo) {
+            @NonNull ItemHolderInfo postLayoutInfo) {
         mAnimateAppearanceList
-                .add(new BaseRecyclerViewAnimationsTest.AnimateAppearance(viewHolder, null, null));
+                .add(new AnimateAppearance(viewHolder, null, null));
         return super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo);
     }
 
     @Override
-    public boolean animatePersistence(RecyclerView.ViewHolder viewHolder,
-            ItemHolderInfo preInfo,
-            ItemHolderInfo postInfo) {
+    public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder,
+            @NonNull ItemHolderInfo preInfo,
+            @NonNull ItemHolderInfo postInfo) {
         mAnimatePersistenceList
-                .add(new BaseRecyclerViewAnimationsTest.AnimatePersistence(viewHolder, null, null));
+                .add(new AnimatePersistence(viewHolder, null, null));
         return super.animatePersistence(viewHolder, preInfo, postInfo);
     }
 
     @Override
-    public boolean animateChange(RecyclerView.ViewHolder oldHolder,
-            RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
-            ItemHolderInfo postInfo) {
+    public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder,
+            @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo,
+            @NonNull ItemHolderInfo postInfo) {
         mAnimateChangeList
-                .add(new BaseRecyclerViewAnimationsTest.AnimateChange(oldHolder, newHolder, null,
-                        null));
+                .add(new AnimateChange(oldHolder, newHolder, null, null));
         return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
     }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 9887d99..0b9ef15 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -1028,13 +1028,17 @@
         // try to trigger race conditions
         int targetItemCount = mTestAdapter.getItemCount();
         for (int i = 0; i < 100; i++) {
+            checkForMainThreadException();
             mTestAdapter.addAndNotify(0, 1);
+            checkForMainThreadException();
             mTestAdapter.addAndNotify(7, 1);
             targetItemCount += 2;
         }
+        checkForMainThreadException();
         // wait until main thread runnables are consumed
         while (targetItemCount != mTestAdapter.getItemCount()) {
             Thread.sleep(100);
+            checkForMainThreadException();
         }
         mLayoutManager.waitForLayout(2);
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 049adc7..6fb9592 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -1213,6 +1213,7 @@
         TestLayoutManager lm = new TestLayoutManager() {
             @Override
             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                ViewInfoStore infoStore = mRecyclerView.mViewInfoStore;
                 if (test.get()) {
                     try {
                         detachAndScrapAttachedViews(recycler);
@@ -1224,25 +1225,26 @@
                                         recycler);
                             }
                         }
-                        if (state.mOldChangedHolders != null) {
-                            for (int i = state.mOldChangedHolders.size() - 1; i >= 0; i--) {
+                        if (infoStore.mOldChangedHolders != null) {
+                            for (int i = infoStore.mOldChangedHolders.size() - 1; i >= 0; i--) {
                                 if (useRecycler) {
                                     recycler.recycleView(
-                                            state.mOldChangedHolders.valueAt(i).itemView);
+                                            infoStore.mOldChangedHolders.valueAt(i).itemView);
                                 } else {
                                     removeAndRecycleView(
-                                            state.mOldChangedHolders.valueAt(i).itemView, recycler);
+                                            infoStore.mOldChangedHolders.valueAt(i).itemView,
+                                            recycler);
                                 }
                             }
                         }
                         assertEquals("no scrap should be left over", 0, recycler.getScrapCount());
                         assertEquals("pre layout map should be empty", 0,
-                                state.mPreLayoutHolderMap.size());
+                                InfoStoreTrojan.sizeOfPreLayout(infoStore));
                         assertEquals("post layout map should be empty", 0,
-                                state.mPostLayoutHolderMap.size());
-                        if (state.mOldChangedHolders != null) {
+                                InfoStoreTrojan.sizeOfPostLayout(infoStore));
+                        if (infoStore.mOldChangedHolders != null) {
                             assertEquals("post old change map should be empty", 0,
-                                    state.mOldChangedHolders.size());
+                                    infoStore.mOldChangedHolders.size());
                         }
                     } catch (Throwable t) {
                         postExceptionToInstrumentation(t);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
new file mode 100644
index 0000000..4c78e3d
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 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.v7.widget;
+
+public class ViewInfoStoreTest {
+
+}