RecyclerView Animation API V2

This CL introduces a new Animation for RecyclerView that
gives more responsibility and power to the ItemAnimator.

It is backward incompatible but there is a new SimpleItemAnimator
that mimics the old API for easy transition.

The main goal for this API change is to enable better change
animations. This change will enable ItemAnimator to easily animate
contents of ViewHolders.

Bug: 22507896
Change-Id: I4d9a6b92ff5c27691b277a777da77116476b1cd3
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 93a454e..73e5b40 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -23,6 +23,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.support.v7.widget.SimpleItemAnimator;
 
 /**
  * An abstract base class for vertically and horizontally scrolling lists. The items come
@@ -201,7 +202,7 @@
         // Disable change animation by default on leanback.
         // Change animation will create a new view and cause undesired
         // focus animation between the old view and new view.
-        getItemAnimator().setSupportsChangeAnimations(false);
+        ((SimpleItemAnimator)getItemAnimator()).setSupportsChangeAnimations(false);
         super.setRecyclerListener(new RecyclerView.RecyclerListener() {
             @Override
             public void onViewRecycled(RecyclerView.ViewHolder holder) {
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index e37956e..17ada3a 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -110,7 +110,7 @@
 
 package android.support.v7.widget {
 
-  public class DefaultItemAnimator extends android.support.v7.widget.RecyclerView.ItemAnimator {
+  public class DefaultItemAnimator extends android.support.v7.widget.SimpleItemAnimator {
     ctor public DefaultItemAnimator();
     method public boolean animateAdd(android.support.v7.widget.RecyclerView.ViewHolder);
     method public boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
@@ -378,48 +378,57 @@
 
   public static abstract class RecyclerView.ItemAnimator {
     ctor public RecyclerView.ItemAnimator();
-    method public abstract boolean animateAdd(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public abstract boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
-    method public abstract boolean animateMove(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
-    method public abstract boolean animateRemove(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void dispatchAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void dispatchAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    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 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);
+    method public final void dispatchAnimationStarted(android.support.v7.widget.RecyclerView.ViewHolder);
     method public final void dispatchAnimationsFinished();
-    method public final void dispatchChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
-    method public final void dispatchChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
-    method public final void dispatchMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void dispatchMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void dispatchRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void dispatchRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
     method public abstract void endAnimation(android.support.v7.widget.RecyclerView.ViewHolder);
     method public abstract void endAnimations();
     method public long getAddDuration();
     method public long getChangeDuration();
     method public long getMoveDuration();
     method public long getRemoveDuration();
-    method public boolean getSupportsChangeAnimations();
     method public abstract boolean isRunning();
     method public final boolean isRunning(android.support.v7.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener);
-    method public void onAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void onAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void onChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
-    method public void onChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
-    method public void onMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void onMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void onRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void onRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo obtainHolderInfo();
+    method public void onAnimationFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onAnimationStarted(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.ViewHolder);
+    method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.ViewHolder, int, java.util.List<java.lang.Object>);
     method public abstract void runPendingAnimations();
     method public void setAddDuration(long);
     method public void setChangeDuration(long);
     method public void setMoveDuration(long);
     method public void setRemoveDuration(long);
-    method public void setSupportsChangeAnimations(boolean);
+    field public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096; // 0x1000
+    field public static final int FLAG_CHANGED = 2; // 0x2
+    field public static final int FLAG_INVALIDATED = 4; // 0x4
+    field public static final int FLAG_MOVED = 2048; // 0x800
+    field public static final int FLAG_REMOVED = 8; // 0x8
+  }
+
+  public static abstract class RecyclerView.ItemAnimator.AdapterChanges implements java.lang.annotation.Annotation {
   }
 
   public static abstract interface RecyclerView.ItemAnimator.ItemAnimatorFinishedListener {
     method public abstract void onAnimationsFinished();
   }
 
+  public static class RecyclerView.ItemAnimator.ItemHolderInfo {
+    ctor public RecyclerView.ItemAnimator.ItemHolderInfo();
+    method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(android.support.v7.widget.RecyclerView.ViewHolder, int);
+    field public int bottom;
+    field public int changeFlags;
+    field public int left;
+    field public int right;
+    field public int top;
+  }
+
   public static abstract class RecyclerView.ItemDecoration {
     ctor public RecyclerView.ItemDecoration();
     method public deprecated void getItemOffsets(android.graphics.Rect, int, android.support.v7.widget.RecyclerView);
@@ -699,6 +708,36 @@
     ctor public RecyclerViewAccessibilityDelegate(android.support.v7.widget.RecyclerView);
   }
 
+  public abstract class SimpleItemAnimator extends android.support.v7.widget.RecyclerView.ItemAnimator {
+    ctor public SimpleItemAnimator();
+    method public abstract boolean animateAdd(android.support.v7.widget.RecyclerView.ViewHolder);
+    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 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);
+    method public final void dispatchAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void dispatchAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void dispatchChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+    method public final void dispatchChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+    method public final void dispatchMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void dispatchMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void dispatchRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void dispatchRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public boolean getSupportsChangeAnimations();
+    method public void onAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+    method public void onChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+    method public void onMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void onRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void setSupportsChangeAnimations(boolean);
+  }
+
   public class StaggeredGridLayoutManager extends android.support.v7.widget.RecyclerView.LayoutManager {
     ctor public StaggeredGridLayoutManager(android.content.Context, android.util.AttributeSet, int, int);
     ctor public StaggeredGridLayoutManager(int, int);
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index e9feab8..9220c5e 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -67,6 +67,8 @@
 
     final OpReorderer mOpReorderer;
 
+    private int mExistingUpdateTypes = 0;
+
     AdapterHelper(Callback callback) {
         this(callback, false);
     }
@@ -85,6 +87,7 @@
     void reset() {
         recycleUpdateOpsAndClearList(mPendingUpdates);
         recycleUpdateOpsAndClearList(mPostponedList);
+        mExistingUpdateTypes = 0;
     }
 
     void preProcess() {
@@ -119,6 +122,7 @@
             mCallback.onDispatchSecondPass(mPostponedList.get(i));
         }
         recycleUpdateOpsAndClearList(mPostponedList);
+        mExistingUpdateTypes = 0;
     }
 
     private void applyMove(UpdateOp op) {
@@ -457,6 +461,10 @@
         return mPendingUpdates.size() > 0;
     }
 
+    boolean hasAnyUpdateTypes(int updateTypes) {
+        return (mExistingUpdateTypes & updateTypes) != 0;
+    }
+
     int findPositionOffset(int position) {
         return findPositionOffset(position, 0);
     }
@@ -495,6 +503,7 @@
      */
     boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
+        mExistingUpdateTypes |= UpdateOp.UPDATE;
         return mPendingUpdates.size() == 1;
     }
 
@@ -503,6 +512,7 @@
      */
     boolean onItemRangeInserted(int positionStart, int itemCount) {
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
+        mExistingUpdateTypes |= UpdateOp.ADD;
         return mPendingUpdates.size() == 1;
     }
 
@@ -511,6 +521,7 @@
      */
     boolean onItemRangeRemoved(int positionStart, int itemCount) {
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
+        mExistingUpdateTypes |= UpdateOp.REMOVE;
         return mPendingUpdates.size() == 1;
     }
 
@@ -525,6 +536,7 @@
             throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
         }
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
+        mExistingUpdateTypes |= UpdateOp.MOVE;
         return mPendingUpdates.size() == 1;
     }
 
@@ -561,6 +573,7 @@
             }
         }
         recycleUpdateOpsAndClearList(mPendingUpdates);
+        mExistingUpdateTypes = 0;
     }
 
     public int applyPendingUpdatesToPosition(int position) {
@@ -604,13 +617,13 @@
      */
     static class UpdateOp {
 
-        static final int ADD = 0;
+        static final int ADD = 1;
 
-        static final int REMOVE = 1;
+        static final int REMOVE = 1 << 1;
 
-        static final int UPDATE = 2;
+        static final int UPDATE = 1 << 2;
 
-        static final int MOVE = 3;
+        static final int MOVE = 1 << 3;
 
         static final int POOL_SIZE = 30;
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index fa45d84..9bb16e7 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -32,7 +32,7 @@
  *
  * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
  */
-public class DefaultItemAnimator extends RecyclerView.ItemAnimator {
+public class DefaultItemAnimator extends SimpleItemAnimator {
     private static final boolean DEBUG = false;
 
     private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
@@ -310,6 +310,11 @@
     @Override
     public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
             int fromX, int fromY, int toX, int toY) {
+        if (oldHolder == newHolder) {
+            // Don't know how to run change animations when the same view holder is re-used.
+            // run a move animation to handle position changes.
+            return animateMove(oldHolder, fromX, fromY, toX, toY);
+        }
         final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
         final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
         final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d21f046..f75797d 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -27,8 +27,9 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.support.annotation.CallSuper;
 import android.os.SystemClock;
+import android.support.annotation.CallSuper;
+import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.support.v4.os.TraceCompat;
 import android.support.v4.util.ArrayMap;
@@ -62,6 +63,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
@@ -70,6 +73,7 @@
 
 import static android.support.v7.widget.AdapterHelper.Callback;
 import static android.support.v7.widget.AdapterHelper.UpdateOp;
+import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
 
 /**
  * A flexible view for providing a limited window into a large data set.
@@ -257,26 +261,15 @@
      */
     private final Runnable mUpdateChildViewsRunnable = new Runnable() {
         public void run() {
-            if (!mFirstLayoutComplete) {
+            if (!mFirstLayoutComplete || isLayoutRequested()) {
                 // a layout request will happen, we should not do layout here.
                 return;
             }
-            if (mDataSetHasChangedAfterLayout) {
-                TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
-                dispatchLayout();
-                TraceCompat.endSection();
-            } else if (mAdapterHelper.hasPendingUpdates()) {
-                TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
-                eatRequestLayout();
-                mAdapterHelper.preProcess();
-                if (!mLayoutRequestEaten) {
-                    // We run this after pre-processing is complete so that ViewHolders have their
-                    // final adapter positions. No need to run it if a layout is already requested.
-                    rebindUpdatedViewHolders();
-                }
-                resumeRequestLayout(true);
-                TraceCompat.endSection();
+            if (mLayoutFrozen) {
+                mLayoutRequestEaten = true;
+                return; //we'll process updates when ice age ends.
             }
+            consumePendingUpdateOperations();
         }
     };
 
@@ -1368,7 +1361,59 @@
      * This method consumes all deferred changes to avoid that case.
      */
     private void consumePendingUpdateOperations() {
-        mUpdateChildViewsRunnable.run();
+        if (!mFirstLayoutComplete) {
+            // a layout request will happen, we should not do layout here.
+            return;
+        }
+        if (mDataSetHasChangedAfterLayout) {
+            TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+            dispatchLayout();
+            TraceCompat.endSection();
+            return;
+        }
+        if (!mAdapterHelper.hasPendingUpdates()) {
+            return;
+        }
+
+        // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
+        // of the visible items is affected and if not, just ignore the change.
+        if (mAdapterHelper.hasAnyUpdateTypes(UpdateOp.UPDATE) && !mAdapterHelper
+                .hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) {
+            TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
+            eatRequestLayout();
+            mAdapterHelper.preProcess();
+            if (!mLayoutRequestEaten) {
+                if (hasUpdatedView()) {
+                    dispatchLayout();
+                } else {
+                    // no need to layout, clean state
+                    mAdapterHelper.consumePostponedUpdates();
+                }
+            }
+            resumeRequestLayout(true);
+            TraceCompat.endSection();
+        } else if (mAdapterHelper.hasPendingUpdates()) {
+            TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+            dispatchLayout();
+            TraceCompat.endSection();
+        }
+    }
+
+    /**
+     * @return True if an existing view holder needs to be updated
+     */
+    private boolean hasUpdatedView() {
+        final int childCount = mChildHelper.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+            if (holder == null || holder.shouldIgnore()) {
+                continue;
+            }
+            if (holder.isUpdated()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -1400,27 +1445,7 @@
                 unconsumedY = y - consumedY;
             }
             TraceCompat.endSection();
-            if (supportsChangeAnimations()) {
-                // Fix up shadow views used by changing animations
-                int count = mChildHelper.getChildCount();
-                for (int i = 0; i < count; i++) {
-                    View view = mChildHelper.getChildAt(i);
-                    ViewHolder holder = getChildViewHolder(view);
-                    if (holder != null && holder.mShadowingHolder != null) {
-                        ViewHolder shadowingHolder = holder.mShadowingHolder;
-                        View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null;
-                        if (shadowingView != null) {
-                            int left = view.getLeft();
-                            int top = view.getTop();
-                            if (left != shadowingView.getLeft() || top != shadowingView.getTop()) {
-                                shadowingView.layout(left, top,
-                                        left + shadowingView.getWidth(),
-                                        top + shadowingView.getHeight());
-                            }
-                        }
-                    }
-                }
-            }
+            repositionShadowingViews();
             onExitLayoutOrScroll();
             resumeRequestLayout(false);
         }
@@ -2662,10 +2687,6 @@
         return mItemAnimator;
     }
 
-    private boolean supportsChangeAnimations() {
-        return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations();
-    }
-
     /**
      * Post a runnable to the next frame to run pending item animations. Only the first such
      * request will be posted, governed by the mPostedAnimatorRunner flag.
@@ -2703,8 +2724,7 @@
         } else {
             mAdapterHelper.consumeUpdatesInOnePass();
         }
-        boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) ||
-                (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations()));
+        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
         mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null &&
                 (mDataSetHasChangedAfterLayout || animationTypeSupported ||
                         mLayout.mRequestedSimpleAnimations) &&
@@ -2730,11 +2750,14 @@
      * The overall approach figures out what items exist before/after layout and
      * infers one of the five above states for each of the items. Then the animations
      * are set up accordingly:
-     * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)})
-     * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)})
-     * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)})
-     * DISAPPEARING views are moved off screen
-     * APPEARING views are moved on screen
+     * PERSISTENT views are animated via
+     * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+     * DISAPPEARING views are animated via
+     * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo)}
+     * APPEARING views are animated via
+     * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+     * and changed views are animated via
+     * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
      */
     void dispatchLayout() {
         if (mAdapter == null) {
@@ -2750,11 +2773,12 @@
         onEnterLayoutOrScroll();
 
         processAdapterUpdatesAndSetAnimationFlags();
-
-        mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged
-                && supportsChangeAnimations() ? new ArrayMap<Long, ViewHolder>() : null;
+        final boolean trackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
+        if (trackOldChangeHolders && mState.mOldChangedHolders == null) {
+            mState.mOldChangedHolders = new ArrayMap<>();
+        }
         mItemsAddedOrRemoved = mItemsChanged = false;
-        ArrayMap<View, Rect> appearingViewInitialBounds = null;
+        ArrayMap<View, ItemHolderInfo> appearingViewInfo = null;
         mState.mInPreLayout = mState.mRunPredictiveAnimations;
         mState.mItemCount = mAdapter.getItemCount();
         findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
@@ -2769,9 +2793,16 @@
                 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                     continue;
                 }
-                final View view = holder.itemView;
-                mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
-                        view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+                final ItemHolderInfo animationInfo = mItemAnimator
+                        .recordPreLayoutInformation(mState, holder,
+                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
+                                holder.getUnmodifiedPayloads());
+                mState.mPreLayoutHolderMap.put(holder, animationInfo);
+                if (trackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
+                        && !holder.shouldIgnore() && !holder.isInvalid()) {
+                    long key = getChangedHolderKey(holder);
+                    mState.mOldChangedHolders.put(key, holder);
+                }
             }
         }
         if (mState.mRunPredictiveAnimations) {
@@ -2782,30 +2813,18 @@
 
             // Save old positions so that LayoutManager can run its mapping logic.
             saveOldPositions();
-            // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations.
-            if (mState.mOldChangedHolders != null) {
-                int count = mChildHelper.getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
-                    if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) {
-                        long key = getChangedHolderKey(holder);
-                        mState.mOldChangedHolders.put(key, holder);
-                        mState.mPreLayoutHolderMap.remove(holder);
-                    }
-                }
-            }
-
             final boolean didStructureChange = mState.mStructureChanged;
             mState.mStructureChanged = false;
             // temporarily disable flag because we are asking for previous layout
             mLayout.onLayoutChildren(mRecycler, mState);
             mState.mStructureChanged = didStructureChange;
 
-            appearingViewInitialBounds = new ArrayMap<View, Rect>();
+            appearingViewInfo = new ArrayMap<View, ItemHolderInfo>();
             for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                 boolean found = false;
-                View child = mChildHelper.getChildAt(i);
-                if (getChildViewHolderInt(child).shouldIgnore()) {
+                final View child = mChildHelper.getChildAt(i);
+                final ViewHolder viewHolder = getChildViewHolderInt(child);
+                if (viewHolder.shouldIgnore()) {
                     continue;
                 }
                 for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) {
@@ -2816,8 +2835,11 @@
                     }
                 }
                 if (!found) {
-                    appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(),
-                            child.getRight(), child.getBottom()));
+                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
+                    flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
+                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
+                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
+                    appearingViewInfo.put(child, animationInfo);
                 }
             }
             // we don't process disappearing list because they may re-appear in post layout pass.
@@ -2825,19 +2847,6 @@
             mAdapterHelper.consumePostponedUpdates();
         } else {
             clearOldPositions();
-            // in case pre layout did run but we decided not to run predictive animations.
-            mAdapterHelper.consumeUpdatesInOnePass();
-            if (mState.mOldChangedHolders != null) {
-                int count = mChildHelper.getChildCount();
-                for (int i = 0; i < count; ++i) {
-                    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
-                    if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) {
-                        long key = getChangedHolderKey(holder);
-                        mState.mOldChangedHolders.put(key, holder);
-                        mState.mPreLayoutHolderMap.remove(holder);
-                    }
-                }
-            }
         }
         mState.mItemCount = mAdapter.getItemCount();
         mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
@@ -2853,25 +2862,30 @@
         mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
 
         if (mState.mRunSimpleAnimations) {
-            // Step 3: Find out where things are now, post-layout
-            ArrayMap<Long, ViewHolder> newChangedHolders = mState.mOldChangedHolders != null ?
-                    new ArrayMap<Long, ViewHolder>() : null;
+            // Step 3: Find out where things are now, and process change animations.
             int count = mChildHelper.getChildCount();
             for (int i = 0; i < count; ++i) {
                 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                 if (holder.shouldIgnore()) {
                     continue;
                 }
-                final View view = holder.itemView;
                 long key = getChangedHolderKey(holder);
-                if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) {
-                    newChangedHolders.put(key, holder);
+                final ItemHolderInfo animationInfo = mItemAnimator
+                        .recordPostLayoutInformation(mState, holder);
+                ViewHolder oldChangeViewHolder = trackOldChangeHolders ?
+                        mState.mOldChangedHolders.get(key) : null;
+                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
+                    // run a change animation
+                    final ItemHolderInfo preInfo = mState.mPreLayoutHolderMap
+                            .get(oldChangeViewHolder);
+                    mState.mPreLayoutHolderMap.remove(oldChangeViewHolder);
+                    animateChange(oldChangeViewHolder, holder, preInfo, animationInfo);
                 } else {
-                    mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
-                            view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+                    mState.mPostLayoutHolderMap.put(holder, animationInfo);
                 }
             }
-            processDisappearingList(appearingViewInitialBounds);
+
+            processDisappearingList(appearingViewInfo);
             // Step 4: Animate DISAPPEARING and REMOVED items
             int preLayoutCount = mState.mPreLayoutHolderMap.size();
             for (int i = preLayoutCount - 1; i >= 0; i--) {
@@ -2879,10 +2893,8 @@
                 if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
                     ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
                     mState.mPreLayoutHolderMap.removeAt(i);
-
-                    View disappearingItemView = disappearingItem.holder.itemView;
-                    mRecycler.unscrapView(disappearingItem.holder);
-                    animateDisappearance(disappearingItem);
+                    mRecycler.unscrapView(itemHolder);
+                    animateDisappearance(itemHolder, disappearingItem);
                 }
             }
             // Step 5: Animate APPEARING and ADDED items
@@ -2891,55 +2903,35 @@
                 for (int i = postLayoutCount - 1; i >= 0; i--) {
                     ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i);
                     ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i);
-                    if ((mState.mPreLayoutHolderMap.isEmpty() ||
-                            !mState.mPreLayoutHolderMap.containsKey(itemHolder))) {
+                    if (!mState.mPreLayoutHolderMap.containsKey(itemHolder)) {
                         mState.mPostLayoutHolderMap.removeAt(i);
-                        Rect initialBounds = (appearingViewInitialBounds != null) ?
-                                appearingViewInitialBounds.get(itemHolder.itemView) : null;
-                        animateAppearance(itemHolder, initialBounds,
-                                info.left, info.top);
+                        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) {
-                ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i);
-                ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i);
-                ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder);
+                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) {
-                    if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
-                        postHolder.setIsRecyclable(false);
-                        if (DEBUG) {
-                            Log.d(TAG, "PERSISTENT: " + postHolder +
-                                    " with view " + postHolder.itemView);
-                        }
-                        if (mItemAnimator.animateMove(postHolder,
-                                preInfo.left, preInfo.top, postInfo.left, postInfo.top)) {
+                    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 7: Animate CHANGING items
-            count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0;
-            // traverse reverse in case view gets recycled while we are traversing the list.
-            for (int i = count - 1; i >= 0; i--) {
-                long key = mState.mOldChangedHolders.keyAt(i);
-                ViewHolder oldHolder = mState.mOldChangedHolders.get(key);
-                View oldView = oldHolder.itemView;
-                if (oldHolder.shouldIgnore()) {
-                    continue;
-                }
-                // We probably don't need this check anymore since these views are removed from
-                // the list if they are recycled.
-                if (mRecycler.mChangedScrap != null &&
-                        mRecycler.mChangedScrap.contains(oldHolder)) {
-                    animateChange(oldHolder, newChangedHolders.get(key));
-                } else if (DEBUG) {
-                    Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder);
-                }
-            }
         }
         resumeRequestLayout(false);
         mLayout.removeAndRecycleScrapInt(mRecycler);
@@ -2952,7 +2944,9 @@
         if (mRecycler.mChangedScrap != null) {
             mRecycler.mChangedScrap.clear();
         }
-        mState.mOldChangedHolders = null;
+        if (mState.mOldChangedHolders != null) {
+            mState.mOldChangedHolders.clear();
+        }
 
         if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
             dispatchOnScrolled(0, 0);
@@ -3029,8 +3023,10 @@
     /**
      * 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, Rect> appearingViews) {
+    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);
@@ -3043,97 +3039,41 @@
                 mLayout.removeAndRecycleView(view, mRecycler);
                 continue;
             }
-            if (info != null) {
-                animateDisappearance(info);
-            } else {
-                // let it disappear from the position it becomes visible
-                animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(),
-                        view.getRight(), view.getBottom()));
-            }
+            animateDisappearance(vh, info);
         }
         disappearingList.clear();
     }
 
-    private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft,
-            int afterTop) {
-        View newItemView = itemHolder.itemView;
-        if (beforeBounds != null &&
-                (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) {
-            // slide items in if before/after locations differ
-            itemHolder.setIsRecyclable(false);
-            if (DEBUG) {
-                Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView);
-            }
-            if (mItemAnimator.animateMove(itemHolder,
-                    beforeBounds.left, beforeBounds.top,
-                    afterLeft, afterTop)) {
-                postAnimationRunner();
-            }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView);
-            }
-            itemHolder.setIsRecyclable(false);
-            if (mItemAnimator.animateAdd(itemHolder)) {
-                postAnimationRunner();
-            }
+    private void animateAppearance(ViewHolder itemHolder, ItemHolderInfo preLayoutInfo,
+            ItemHolderInfo postLayoutInfo) {
+        itemHolder.setIsRecyclable(false);
+        if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
+            postAnimationRunner();
         }
     }
 
-    private void animateDisappearance(ItemHolderInfo disappearingItem) {
-        View disappearingItemView = disappearingItem.holder.itemView;
-        addAnimatingView(disappearingItem.holder);
-        int oldLeft = disappearingItem.left;
-        int oldTop = disappearingItem.top;
-        int newLeft = disappearingItemView.getLeft();
-        int newTop = disappearingItemView.getTop();
-        if (!disappearingItem.holder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
-            disappearingItem.holder.setIsRecyclable(false);
-            disappearingItemView.layout(newLeft, newTop,
-                    newLeft + disappearingItemView.getWidth(),
-                    newTop + disappearingItemView.getHeight());
-            if (DEBUG) {
-                Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder +
-                        " with view " + disappearingItemView);
-            }
-            if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop,
-                    newLeft, newTop)) {
-                postAnimationRunner();
-            }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "REMOVED: " + disappearingItem.holder +
-                        " with view " + disappearingItemView);
-            }
-            disappearingItem.holder.setIsRecyclable(false);
-            if (mItemAnimator.animateRemove(disappearingItem.holder)) {
-                postAnimationRunner();
-            }
+    private void animateDisappearance(ViewHolder holder, ItemHolderInfo preLayoutInfo) {
+        addAnimatingView(holder);
+        holder.setIsRecyclable(false);
+        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo)) {
+            postAnimationRunner();
         }
     }
 
-    private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) {
+    private void animateChange(ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preInfo,
+            ItemHolderInfo postInfo) {
         oldHolder.setIsRecyclable(false);
-        addAnimatingView(oldHolder);
-        oldHolder.mShadowedHolder = newHolder;
-        mRecycler.unscrapView(oldHolder);
-        if (DEBUG) {
-            Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
+        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;
+            }
         }
-        final int fromLeft = oldHolder.itemView.getLeft();
-        final int fromTop = oldHolder.itemView.getTop();
-        final int toLeft, toTop;
-        if (newHolder == null || newHolder.shouldIgnore()) {
-            toLeft = fromLeft;
-            toTop = fromTop;
-        } else {
-            toLeft = newHolder.itemView.getLeft();
-            toTop = newHolder.itemView.getTop();
-            newHolder.setIsRecyclable(false);
-            newHolder.mShadowingHolder = oldHolder;
-        }
-        if(mItemAnimator.animateChange(oldHolder, newHolder,
-                fromLeft, fromTop, toLeft, toTop)) {
+        if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
             postAnimationRunner();
         }
     }
@@ -3405,9 +3345,6 @@
                 // ViewHolders have their final positions assigned.
                 holder.addFlags(ViewHolder.FLAG_UPDATE);
                 holder.addChangePayload(payload);
-                if (supportsChangeAnimations()) {
-                    holder.addFlags(ViewHolder.FLAG_CHANGED);
-                }
                 // lp cannot be null since we get ViewHolder from it.
                 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
             }
@@ -3415,36 +3352,8 @@
         mRecycler.viewRangeUpdate(positionStart, itemCount);
     }
 
-    void rebindUpdatedViewHolders() {
-        final int childCount = mChildHelper.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
-            // validate type is correct
-            if (holder == null || holder.shouldIgnore()) {
-                continue;
-            }
-            if (holder.isRemoved() || holder.isInvalid()) {
-                requestLayout();
-            } else if (holder.needsUpdate()) {
-                final int type = mAdapter.getItemViewType(holder.mPosition);
-                if (holder.getItemViewType() == type) {
-                    // Binding an attached view will request a layout if needed.
-                    if (!holder.isChanged() || !supportsChangeAnimations()) {
-                        mAdapter.bindViewHolder(holder, holder.mPosition);
-                    } else {
-                        // Don't rebind changed holders if change animations are enabled.
-                        // We want the old contents for the animation and will get a new
-                        // holder for the new contents.
-                        requestLayout();
-                    }
-                } else {
-                    // binding to a new view will need re-layout anyways. We can as well trigger
-                    // it here so that it happens during layout
-                    requestLayout();
-                    break;
-                }
-            }
-        }
+    private boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
+        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder);
     }
 
     private void setDataSetChangedAfterLayout() {
@@ -3913,25 +3822,8 @@
                         overscrollY = dy - vresult;
                     }
                     TraceCompat.endSection();
-                    if (supportsChangeAnimations()) {
-                        // Fix up shadow views used by changing animations
-                        int count = mChildHelper.getChildCount();
-                        for (int i = 0; i < count; i++) {
-                            View view = mChildHelper.getChildAt(i);
-                            ViewHolder holder = getChildViewHolder(view);
-                            if (holder != null && holder.mShadowingHolder != null) {
-                                View shadowingView = holder.mShadowingHolder.itemView;
-                                int left = view.getLeft();
-                                int top = view.getTop();
-                                if (left != shadowingView.getLeft() ||
-                                        top != shadowingView.getTop()) {
-                                    shadowingView.layout(left, top,
-                                            left + shadowingView.getWidth(),
-                                            top + shadowingView.getHeight());
-                                }
-                            }
-                        }
-                    }
+                    repositionShadowingViews();
+
                     onExitLayoutOrScroll();
                     resumeRequestLayout(false);
 
@@ -4097,6 +3989,26 @@
 
     }
 
+    private void repositionShadowingViews() {
+        // Fix up shadow views used by change animations
+        int count = mChildHelper.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View view = mChildHelper.getChildAt(i);
+            ViewHolder holder = getChildViewHolder(view);
+            if (holder != null && holder.mShadowingHolder != null) {
+                View shadowingView = holder.mShadowingHolder.itemView;
+                int left = view.getLeft();
+                int top = view.getTop();
+                if (left != shadowingView.getLeft() ||
+                        top != shadowingView.getTop()) {
+                    shadowingView.layout(left, top,
+                            left + shadowingView.getWidth(),
+                            top + shadowingView.getHeight());
+                }
+            }
+        }
+    }
+
     private class RecyclerViewDataObserver extends AdapterDataObserver {
         @Override
         public void onChanged() {
@@ -4730,8 +4642,8 @@
                         holder);
             }
             if (forceRecycle || holder.isRecyclable()) {
-                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED |
-                        ViewHolder.FLAG_CHANGED | ViewHolder.FLAG_UPDATE)) {
+                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
+                        | ViewHolder.FLAG_UPDATE)) {
                     // Retire oldest cached view
                     final int cachedViewSize = mCachedViews.size();
                     if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
@@ -4773,6 +4685,7 @@
         void quickRecycleScrapView(View view) {
             final ViewHolder holder = getChildViewHolderInt(view);
             holder.mScrapContainer = null;
+            holder.mInChangeScrap = false;
             holder.clearReturnedFromScrapFlag();
             recycleViewHolderInternal(holder);
         }
@@ -4788,18 +4701,19 @@
          */
         void scrapView(View view) {
             final ViewHolder holder = getChildViewHolderInt(view);
-            holder.setScrapContainer(this);
-            if (!holder.isChanged() || !supportsChangeAnimations()) {
+            if (!holder.isUpdated() || holder.isInvalid() || canReuseUpdatedViewHolder(holder)) {
                 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                     throw new IllegalArgumentException("Called scrap view with an invalid view."
                             + " Invalid views cannot be reused from scrap, they should rebound from"
                             + " recycler pool.");
                 }
+                holder.setScrapContainer(this, false);
                 mAttachedScrap.add(holder);
             } else {
                 if (mChangedScrap == null) {
                     mChangedScrap = new ArrayList<ViewHolder>();
                 }
+                holder.setScrapContainer(this, true);
                 mChangedScrap.add(holder);
             }
         }
@@ -4811,12 +4725,13 @@
          * until it is explicitly removed and recycled.</p>
          */
         void unscrapView(ViewHolder holder) {
-            if (!holder.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) {
-                mAttachedScrap.remove(holder);
-            } else {
+            if (holder.mInChangeScrap) {
                 mChangedScrap.remove(holder);
+            } else {
+                mAttachedScrap.remove(holder);
             }
             holder.mScrapContainer = null;
+            holder.mInChangeScrap = false;
             holder.clearReturnedFromScrapFlag();
         }
 
@@ -4830,6 +4745,9 @@
 
         void clearScrap() {
             mAttachedScrap.clear();
+            if (mChangedScrap != null) {
+                mChangedScrap.clear();
+            }
         }
 
         ViewHolder getChangedScrapViewForPosition(int position) {
@@ -4943,6 +4861,8 @@
                         }
                         return holder;
                     } else if (!dryRun) {
+                        // if we are running animations, it is actually better to keep it in scrap
+                        // but this would force layout manager to lay it out which would be bad.
                         // Recycle this scrap. Type mismatch.
                         mAttachedScrap.remove(i);
                         removeDetachedView(holder.itemView, false);
@@ -6827,7 +6747,7 @@
                 }
                 return;
             }
-            if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() &&
+            if (viewHolder.isInvalid() && !viewHolder.isRemoved() &&
                     !mRecyclerView.mAdapter.hasStableIds()) {
                 removeViewAt(index);
                 recycler.recycleViewHolderInternal(viewHolder);
@@ -8236,12 +8156,6 @@
         static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
 
         /**
-         * This ViewHolder's contents have changed. This flag is used as an indication that
-         * change animations may be used, if supported by the ItemAnimator.
-         */
-        static final int FLAG_CHANGED = 1 << 6;
-
-        /**
          * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
          * it unless LayoutManager is replaced.
          * It is still fully visible to the LayoutManager.
@@ -8268,6 +8182,16 @@
          */
         static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;
 
+        /**
+         * Used by ItemAnimator when a ViewHolder's position changes
+         */
+        static final int FLAG_MOVED = 1 << 11;
+
+        /**
+         * Used by ItemAnimator when a ViewHolder appears in pre-layout
+         */
+        static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;
+
         private int mFlags;
 
         private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST;
@@ -8280,6 +8204,8 @@
         // If non-null, view is currently considered scrap and may be reused for other data by the
         // scrap container.
         private Recycler mScrapContainer = null;
+        // Keeps whether this ViewHolder lives in Change scrap or Attached scrap
+        private boolean mInChangeScrap = false;
 
         // Saves isImportantForAccessibility value for the view item while it's in hidden state and
         // marked as unimportant for accessibility.
@@ -8460,8 +8386,9 @@
             mFlags = mFlags & ~FLAG_IGNORE;
         }
 
-        void setScrapContainer(Recycler recycler) {
+        void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
             mScrapContainer = recycler;
+            mInChangeScrap = isChangeScrap;
         }
 
         boolean isInvalid() {
@@ -8472,10 +8399,6 @@
             return (mFlags & FLAG_UPDATE) != 0;
         }
 
-        boolean isChanged() {
-            return (mFlags & FLAG_CHANGED) != 0;
-        }
-
         boolean isBound() {
             return (mFlags & FLAG_BOUND) != 0;
         }
@@ -8579,16 +8502,18 @@
             final StringBuilder sb = new StringBuilder("ViewHolder{" +
                     Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId +
                     ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
-            if (isScrap()) sb.append(" scrap");
+            if (isScrap()) {
+                sb.append(" scrap ")
+                        .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
+            }
             if (isInvalid()) sb.append(" invalid");
             if (!isBound()) sb.append(" unbound");
             if (needsUpdate()) sb.append(" update");
             if (isRemoved()) sb.append(" removed");
             if (shouldIgnore()) sb.append(" ignored");
-            if (isChanged()) sb.append(" changed");
             if (isTmpDetached()) sb.append(" tmpDetached");
             if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
-            if (isAdapterPositionUnknown()) sb.append("undefined adapter position");
+            if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");
 
             if (itemView.getParent() == null) sb.append(" no parent");
             sb.append("}");
@@ -8651,6 +8576,10 @@
         private boolean doesTransientStatePreventRecycling() {
             return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView);
         }
+
+        boolean isUpdated() {
+            return (mFlags & FLAG_UPDATE) != 0;
+        }
     }
 
     private int getAdapterPositionFor(ViewHolder viewHolder) {
@@ -8785,7 +8714,7 @@
          * @return true if the item the view corresponds to was changed in the data set
          */
         public boolean isItemChanged() {
-            return mViewHolder.isChanged();
+            return mViewHolder.isUpdated();
         }
 
         /**
@@ -9608,71 +9537,21 @@
     private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
 
         @Override
-        public void onRemoveFinished(ViewHolder item) {
+        public void onAnimationFinished(ViewHolder item) {
             item.setIsRecyclable(true);
-            if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
-                removeDetachedView(item.itemView, false);
-            }
-        }
-
-        @Override
-        public void onAddFinished(ViewHolder item) {
-            item.setIsRecyclable(true);
-            if (!item.shouldBeKeptAsChild()) {
-                removeAnimatingView(item.itemView);
-            }
-        }
-
-        @Override
-        public void onMoveFinished(ViewHolder item) {
-            item.setIsRecyclable(true);
-            if (!item.shouldBeKeptAsChild()) {
-                removeAnimatingView(item.itemView);
-            }
-        }
-
-        @Override
-        public void onChangeFinished(ViewHolder item) {
-            item.setIsRecyclable(true);
-            /**
-             * We check both shadowed and shadowing because a ViewHolder may get both roles at the
-             * same time.
-             *
-             * Assume this flow:
-             * item X is represented by VH_1. Then itemX changes, so we create VH_2 .
-             * RV sets the following and calls item animator:
-             * VH_1.shadowed = VH_2;
-             * VH_1.mChanged = true;
-             * VH_2.shadowing =VH_1;
-             *
-             * Then, before the first change finishes, item changes again so we create VH_3.
-             * RV sets the following and calls item animator:
-             * VH_2.shadowed = VH_3
-             * VH_2.mChanged = true
-             * VH_3.shadowing = VH_2
-             *
-             * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has
-             * both shadowing and shadowed fields set. Shadowing information is obsolete now
-             * because the first animation where VH_2 is newViewHolder is not valid anymore.
-             * We ended up in this case because VH_2 played both roles. On the other hand,
-             * we DO NOT want to clear its changed flag.
-             *
-             * If second change was simply reverting first change, we would find VH_1 in
-             * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before
-             * re-using
-             */
             if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
                 item.mShadowedHolder = null;
-                item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags);
             }
             // always null this because an OldViewHolder can never become NewViewHolder w/o being
             // recycled.
             item.mShadowingHolder = null;
             if (!item.shouldBeKeptAsChild()) {
-                removeAnimatingView(item.itemView);
+                if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
+                    removeDetachedView(item.itemView, false);
+                }
             }
         }
-    };
+    }
 
     /**
      * This class defines the animations that take place on items as changes are made
@@ -9680,22 +9559,77 @@
      *
      * Subclasses of ItemAnimator can be used to implement custom animations for actions on
      * ViewHolder items. The RecyclerView will manage retaining these items while they
-     * are being animated, but implementors must call the appropriate "Starting"
-     * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)},
-     * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or
-     * {@link #dispatchAddStarting(ViewHolder)})
-     * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)},
-     * {@link #dispatchMoveFinished(ViewHolder)},
-     * {@link #dispatchChangeFinished(ViewHolder, boolean)},
-     * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is
-     * being started and ended.
+     * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)}
+     * when a ViewHolder's animation is finished. In other words, there must be a matching
+     * {@link #dispatchAnimationFinished(ViewHolder)} call for each
+     * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()},
+     * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+     * animateChange()}
+     * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()},
+     * and
+     * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()} call.
      *
-     * <p>By default, RecyclerView uses {@link DefaultItemAnimator}</p>
+     * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p>
      *
      * @see #setItemAnimator(ItemAnimator)
      */
+    @SuppressWarnings("UnusedParameters")
     public static abstract class ItemAnimator {
 
+        /**
+         * The Item represented by this ViewHolder is updated.
+         * <p>
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         */
+        public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE;
+
+        /**
+         * The Item represented by this ViewHolder is removed from the adapter.
+         * <p>
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         */
+        public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED;
+
+        /**
+         * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content
+         * represented by this ViewHolder is invalid.
+         * <p>
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         */
+        public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID;
+
+        /**
+         * The position of the Item represented by this ViewHolder has been changed. This flag is
+         * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to
+         * any adapter change that may have a side effect on this item. (e.g. The item before this
+         * one has been removed from the Adapter).
+         * <p>
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         */
+        public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED;
+
+        /**
+         * This ViewHolder was not laid out but has been added to the layout in pre-layout state
+         * by the {@link LayoutManager}. This means that the item was already in the Adapter but
+         * invisible and it may become visible in the post layout phase. LayoutManagers may prefer
+         * to add new items in pre-layout to specify their virtual location when they are invisible
+         * (e.g. to specify the item should <i>animate in</i> from below the visible area).
+         * <p>
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         */
+        public static final int FLAG_APPEARED_IN_PRE_LAYOUT
+                = ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT;
+
+        /**
+         * The set of flags that might be passed to
+         * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         */
+        @IntDef(flag=true, value={
+                FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED,
+                FLAG_APPEARED_IN_PRE_LAYOUT
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface AdapterChanges {}
         private ItemAnimatorListener mListener = null;
         private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners =
                 new ArrayList<ItemAnimatorFinishedListener>();
@@ -9705,8 +9639,6 @@
         private long mMoveDuration = 250;
         private long mChangeDuration = 250;
 
-        private boolean mSupportsChangeAnimations = true;
-
         /**
          * Gets the current duration for which all move animations will run.
          *
@@ -9780,35 +9712,6 @@
         }
 
         /**
-         * Returns whether this ItemAnimator supports animations of change events.
-         *
-         * @return true if change animations are supported, false otherwise
-         */
-        public boolean getSupportsChangeAnimations() {
-            return mSupportsChangeAnimations;
-        }
-
-        /**
-         * Sets whether this ItemAnimator supports animations of item change events.
-         * If you set this property to false, actions on the data set which change the
-         * contents of items will not be animated. What those animations are is left
-         * up to the discretion of the ItemAnimator subclass, in its
-         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
-         * The value of this property is true by default.
-         *
-         * @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;
-        }
-
-        /**
          * Internal only:
          * Sets the listener that must be called when the animator is finished
          * animating the item (or immediately if no animation happens). This is set
@@ -9821,219 +9724,244 @@
         }
 
         /**
+         * Called by the RecyclerView before the layout begins. Item animator should record
+         * necessary information about the View before it is potentially rebound, moved or removed.
+         * <p>
+         * The data returned from this method will be passed to the related <code>animate**</code>
+         * methods.
+         * <p>
+         * Note that this method may be called after pre-layout phase if LayoutManager adds new
+         * Views to the layout in pre-layout pass.
+         * <p>
+         * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+         * the View and the adapter change flags.
+         *
+         * @param state       The current State of RecyclerView which includes some useful data
+         *                    about the layout that will be calculated.
+         * @param viewHolder  The ViewHolder whose information should be recorded.
+         * @param changeFlags Additional information about what changes happened in the Adapter
+         *                    about the Item represented by this ViewHolder. For instance, if
+         *                    item is deleted from the adapter, {@link #FLAG_REMOVED} will be set.
+         * @param payloads    The payload list that was previously passed to
+         *                    {@link Adapter#notifyItemChanged(int, Object)} or
+         *                    {@link Adapter#notifyItemRangeChanged(int, int, Object)}.
+         *
+         * @return An ItemHolderInfo instance that preserves necessary information about the
+         * ViewHolder. This object will be passed back to related <code>animate**</code> methods
+         * after layout is complete.
+         *
+         * @see #recordPostLayoutInformation(State, ViewHolder)
+         * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * @see #animateDisappearance(ViewHolder, ItemHolderInfo)
+         * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         */
+        public ItemHolderInfo recordPreLayoutInformation(State state,
+                ViewHolder viewHolder, @AdapterChanges int changeFlags, List<Object> payloads) {
+            return obtainHolderInfo().setFrom(viewHolder);
+        }
+
+        /**
+         * Called by the RecyclerView after the layout is complete. Item animator should record
+         * necessary information about the View's final state.
+         * <p>
+         * The data returned from this method will be passed to the related <code>animate**</code>
+         * methods.
+         * <p>
+         * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+         * the View.
+         *
+         * @param state      The current State of RecyclerView which includes some useful data about
+         *                   the layout that will be calculated.
+         * @param viewHolder The ViewHolder whose information should be recorded.
+         *
+         * @return An ItemHolderInfo that preserves necessary information about the ViewHolder.
+         * This object will be passed back to related <code>animate**</code> methods when
+         * RecyclerView decides how items should be animated.
+         *
+         * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+         * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * @see #animateDisappearance(ViewHolder, ItemHolderInfo)
+         * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         */
+        public ItemHolderInfo recordPostLayoutInformation(State state, ViewHolder viewHolder) {
+            return obtainHolderInfo().setFrom(viewHolder);
+        }
+
+        /**
+         * 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
+         * 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>
+         * 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).
+         *
+         * @param viewHolder    The ViewHolder which should be animated
+         * @param preLayoutInfo The information that was returned from
+         *                      {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         *
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        public abstract boolean animateDisappearance(ViewHolder viewHolder,
+                ItemHolderInfo preLayoutInfo);
+
+        /**
+         * Called by the RecyclerView when a ViewHolder is added to the layout.
+         * <p>
+         * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started
+         * but has  been added by the LayoutManager. It might be newly added to the adapter or
+         * simply become visible due to other factors.
+         * <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).
+         *
+         * @param viewHolder     The ViewHolder which should be animated
+         * @param preLayoutInfo  The information that was returned from
+         *                       {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         *                       Might be null if Item was just added to the adapter or
+         *                       LayoutManager does not support predictive animations or it could
+         *                       not predict that this ViewHolder will become visible.
+         * @param postLayoutInfo The information that was returned from {@link
+         *                       #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         *
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        public abstract boolean animateAppearance(ViewHolder viewHolder,
+                ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);
+
+        /**
+         * Called by the RecyclerView when a ViewHolder is present in both before and after the
+         * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call
+         * for it or a {@link Adapter#notifyDataSetChanged()} call.
+         * <p>
+         * This ViewHolder still represents the same data that it was representing when the layout
+         * started but its position / size may be changed by the LayoutManager.
+         * <p>
+         * If the Item's layout position didn't change, RecyclerView still calls this method because
+         * it does not track this information (or does not necessarily know that an animation is
+         * not required). Your ItemAnimator should handle this case and if there is nothing to
+         * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return
+         * <code>false</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).
+         *
+         * @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
+         *                       #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         *
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        public abstract boolean animatePersistence(ViewHolder viewHolder,
+                ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo);
+
+        /**
+         * Called by the RecyclerView when an adapter item is present both before and after the
+         * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call
+         * for it. This method may also be called when
+         * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that
+         * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when
+         * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called,
+         * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be
+         * called for the new ViewHolder and the old one will be recycled.
+         * <p>
+         * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is
+         * a good possibility that item contents didn't really change but it is rebound from the
+         * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the
+         * screen didn't change and your animator should handle this case as well and avoid creating
+         * unnecessary animations.
+         * <p>
+         * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the
+         * previous presentation of the item as-is and supply a new ViewHolder for the updated
+         * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder)}.
+         * This is useful if you don't know the contents of the Item and would like
+         * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique).
+         * <p>
+         * When you are writing a custom item animator for your layout, it might be more performant
+         * and elegant to re-use the same ViewHolder and animate the content changes manually.
+         * <p>
+         * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change.
+         * If the Item's view type has changed or ItemAnimator returned <code>false</code> for
+         * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder)} was called, the
+         * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances
+         * which represent the same Item. In that case, only the new ViewHolder is visible
+         * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations.
+         * <p>
+         * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct
+         * ViewHolder when their animation is complete
+         * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to
+         * animate the view).
+         * <p>
+         *  If oldHolder and newHolder are the same instance, you should call
+         * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>.
+         *
+         * @param oldHolder     The ViewHolder before the layout is started, might be the same
+         *                      instance with newHolder.
+         * @param newHolder     The ViewHolder after the layout is finished, might be the same
+         *                      instance with oldHolder.
+         * @param preLayoutInfo  The information that was returned from
+         *                       {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         * @param postLayoutInfo The information that was returned from {@link
+         *                       #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+         *
+         * @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);
+
+        @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
+            int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
+            if (viewHolder.isInvalid()) {
+                return FLAG_INVALIDATED;
+            }
+            if ((flags & FLAG_INVALIDATED) == 0) {
+                final int oldPos = viewHolder.getOldPosition();
+                final int pos = viewHolder.getAdapterPosition();
+                if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos){
+                    flags |= FLAG_MOVED;
+                }
+            }
+            return flags;
+        }
+
+        /**
          * Called when there are pending animations waiting to be started. This state
-         * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()},
-         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and
-         * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the
-         * RecyclerView that the ItemAnimator wants to be called later to start the
-         * associated animations. runPendingAnimations() will be scheduled to be run
+         * is governed by the return values from
+         * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateAppearance()},
+         * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * 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.
          */
         abstract public void runPendingAnimations();
 
         /**
-         * Called when an item is removed from the RecyclerView. Implementors can choose
-         * whether and how to animate that change, but must always call
-         * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
-         * immediately (if no animation will occur) or after the animation actually finishes.
-         * The return value indicates whether an animation has been set up and whether the
-         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
-         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
-         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
-         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
-         * {@link #animateRemove(ViewHolder) animateRemove()}, and
-         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
-         * then start the animations together in the later call to {@link #runPendingAnimations()}.
-         *
-         * <p>This method may also be called for disappearing items which continue to exist in the
-         * RecyclerView, but for which the system does not have enough information to animate
-         * them out of view. In that case, the default animation for removing items is run
-         * on those items as well.</p>
-         *
-         * @param holder The item that is being removed.
-         * @return true if a later call to {@link #runPendingAnimations()} is requested,
-         * false otherwise.
-         */
-        abstract public boolean animateRemove(ViewHolder holder);
-
-        /**
-         * Called when an item is added to the RecyclerView. Implementors can choose
-         * whether and how to animate that change, but must always call
-         * {@link #dispatchAddFinished(ViewHolder)} when done, either
-         * immediately (if no animation will occur) or after the animation actually finishes.
-         * The return value indicates whether an animation has been set up and whether the
-         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
-         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
-         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
-         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
-         * {@link #animateRemove(ViewHolder) animateRemove()}, and
-         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
-         * then start the animations together in the later call to {@link #runPendingAnimations()}.
-         *
-         * <p>This method may also be called for appearing items which were already in the
-         * RecyclerView, but for which the system does not have enough information to animate
-         * them into view. In that case, the default animation for adding items is run
-         * on those items as well.</p>
-         *
-         * @param holder The item that is being added.
-         * @return true if a later call to {@link #runPendingAnimations()} is requested,
-         * false otherwise.
-         */
-        abstract public boolean animateAdd(ViewHolder holder);
-
-        /**
-         * Called when an item is moved in the RecyclerView. Implementors can choose
-         * whether and how to animate that change, but must always call
-         * {@link #dispatchMoveFinished(ViewHolder)} when done, either
-         * immediately (if no animation will occur) or after the animation actually finishes.
-         * The return value indicates whether an animation has been set up and whether the
-         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
-         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
-         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
-         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
-         * {@link #animateRemove(ViewHolder) animateRemove()}, and
-         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
-         * then start the animations together in the later call to {@link #runPendingAnimations()}.
-         *
-         * @param holder The item that is being moved.
-         * @return true if a later call to {@link #runPendingAnimations()} is requested,
-         * false otherwise.
-         */
-        abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY,
-                int toX, int toY);
-
-        /**
-         * Called when an item is changed in the RecyclerView, as indicated by a call to
-         * {@link Adapter#notifyItemChanged(int)} or
-         * {@link Adapter#notifyItemRangeChanged(int, int)}.
-         * <p>
-         * Implementers can choose whether and how to animate changes, but must always call
-         * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder,
-         * either immediately (if no animation will occur) or after the animation actually finishes.
-         * The return value indicates whether an animation has been set up and whether the
-         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
-         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
-         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
-         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
-         * {@link #animateRemove(ViewHolder) animateRemove()}, and
-         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
-         * then start the animations together in the later call to {@link #runPendingAnimations()}.
-         *
-         * @param oldHolder The original item that changed.
-         * @param newHolder The new item that was created with the changed content. Might be null
-         * @param fromLeft  Left of the old view holder
-         * @param fromTop   Top of the old view holder
-         * @param toLeft    Left of the new view holder
-         * @param toTop     Top of the new view holder
-         * @return true if a later call to {@link #runPendingAnimations()} is requested,
-         * false otherwise.
-         */
-        abstract public boolean animateChange(ViewHolder oldHolder,
-                ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
-
-
-        /**
-         * Method to be called by subclasses when a remove animation is done.
-         *
-         * @param item The item which has been removed
-         */
-        public final void dispatchRemoveFinished(ViewHolder item) {
-            onRemoveFinished(item);
-            if (mListener != null) {
-                mListener.onRemoveFinished(item);
-            }
-        }
-
-        /**
-         * Method to be called by subclasses when a move animation is done.
-         *
-         * @param item The item which has been moved
-         */
-        public final void dispatchMoveFinished(ViewHolder item) {
-            onMoveFinished(item);
-            if (mListener != null) {
-                mListener.onMoveFinished(item);
-            }
-        }
-
-        /**
-         * Method to be called by subclasses when an add animation is done.
-         *
-         * @param item The item which has been added
-         */
-        public final void dispatchAddFinished(ViewHolder item) {
-            onAddFinished(item);
-            if (mListener != null) {
-                mListener.onAddFinished(item);
-            }
-        }
-
-        /**
-         * 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 oldItem true if this is the old item that was changed, false if
-         * it is the new item that replaced the old item.
-         */
-        public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
-            onChangeFinished(item, oldItem);
-            if (mListener != null) {
-                mListener.onChangeFinished(item);
-            }
-        }
-
-        /**
-         * Method to be called by subclasses when a remove animation is being started.
-         *
-         * @param item The item being removed
-         */
-        public final void dispatchRemoveStarting(ViewHolder item) {
-            onRemoveStarting(item);
-        }
-
-        /**
-         * Method to be called by subclasses when a move animation is being started.
-         *
-         * @param item The item being moved
-         */
-        public final void dispatchMoveStarting(ViewHolder item) {
-            onMoveStarting(item);
-        }
-
-        /**
-         * Method to be called by subclasses when an add animation is being started.
-         *
-         * @param item The item being added
-         */
-        public final void dispatchAddStarting(ViewHolder item) {
-            onAddStarting(item);
-        }
-
-        /**
-         * 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 oldItem true if this is the old item that was changed, false if
-         * it is the new item that replaced the old item.
-         */
-        public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
-            onChangeStarting(item, oldItem);
-        }
-
-        /**
          * Method called when an animation on a view should be ended immediately.
          * This could happen when other events, like scrolling, occur, so that
          * animating views can be quickly put into their proper end locations.
          * Implementations should ensure that any animations running on the item
          * are canceled and affected properties are set to their end values.
-         * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)}
-         * should be called since the animations are effectively done when this
-         * method is called.
+         * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+         * animation since the animations are effectively done when this method is called.
          *
          * @param item The item for which an animation should be stopped.
          */
@@ -10045,9 +9973,8 @@
          * animating views can be quickly put into their proper end locations.
          * Implementations should ensure that any animations running on any items
          * are canceled and affected properties are set to their end values.
-         * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)}
-         * should be called since the animations are effectively done when this
-         * method is called.
+         * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+         * animation since the animations are effectively done when this method is called.
          */
         abstract public void endAnimations();
 
@@ -10061,6 +9988,81 @@
         abstract public boolean isRunning();
 
         /**
+         * Method to be called by subclasses when an animation is finished.
+         * <p>
+         * For each call RecyclerView makes to
+         * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateAppearance()},
+         * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animatePersistence()}, or
+         * {@link #animateDisappearance(ViewHolder, ItemHolderInfo) animateDisappearance()}, there
+         * should
+         * be a matching {@link #dispatchAnimationFinished(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>
+         * and <code>newHolder</code>  (if they are not the same instance).
+         *
+         * @param viewHolder The ViewHolder whose animation is finished.
+         * @see #onAnimationFinished(ViewHolder)
+         */
+        public final void dispatchAnimationFinished(ViewHolder viewHolder) {
+            onAnimationFinished(viewHolder);
+            if (mListener != null) {
+                mListener.onAnimationFinished(viewHolder);
+            }
+        }
+
+        /**
+         * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the
+         * ItemAniamtor.
+         *
+         * @param viewHolder The ViewHolder whose animation is finished. There might still be other
+         *                   animations running on this ViewHolder.
+         * @see #dispatchAnimationFinished(ViewHolder)
+         */
+        public void onAnimationFinished(ViewHolder viewHolder) {
+        }
+
+        /**
+         * Method to be called by subclasses when an animation is started.
+         * <p>
+         * For each call RecyclerView makes to
+         * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * 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.
+         * <p>
+         * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+         * animateChange()}, sublcass should call this method for both the <code>oldHolder</code>
+         * and <code>newHolder</code> (if they are not the same instance).
+         * <p>
+         * If your ItemAnimator decides not to animate a ViewHolder, it should call
+         * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling
+         * {@link #dispatchAnimationStarted(ViewHolder)}.
+         *
+         * @param viewHolder The ViewHolder whose animation is starting.
+         * @see #onAnimationStarted(ViewHolder)
+         */
+        public final void dispatchAnimationStarted(ViewHolder viewHolder) {
+            onAnimationStarted(viewHolder);
+        }
+
+        /**
+         * Called when a new animation is started on the given ViewHolder.
+         *
+         * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder
+         *                   might already be animating and this might be another animation.
+         * @see #dispatchAnimationStarted(ViewHolder)
+         */
+        public void onAnimationStarted(ViewHolder viewHolder) {
+
+        }
+
+        /**
          * Like {@link #isRunning()}, this method returns whether there are any item
          * animations currently running. Addtionally, the listener passed in will be called
          * when there are no item animations running, either immediately (before the method
@@ -10089,15 +10091,23 @@
         }
 
         /**
-         * The interface to be implemented by listeners to animation events from this
-         * ItemAnimator. This is used internally and is not intended for developers to
-         * create directly.
+         * When an item is changed, ItemAnimator can decide whether it wants to re-use
+         * the same ViewHolder for animations or RecyclerView should create a copy of the
+         * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+         * <p>
+         * Note that this method will only be called if the {@link ViewHolder} still has the same
+         * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+         * both {@link ViewHolder}s in the
+         * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+         *
+         * @param viewHolder The ViewHolder which represents the changed item's old content.
+         *
+         * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+         *         RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+         *         ItemAnimator to animate. Default implementation returns <code>true</code>.
          */
-        interface ItemAnimatorListener {
-            void onRemoveFinished(ViewHolder item);
-            void onAddFinished(ViewHolder item);
-            void onMoveFinished(ViewHolder item);
-            void onChangeFinished(ViewHolder item);
+        public boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
+            return true;
         }
 
         /**
@@ -10113,6 +10123,28 @@
         }
 
         /**
+         * Returns a new {@link ItemHolderInfo} which will be used to store information about the
+         * ViewHolder. This information will later be passed into <code>animate**</code> methods.
+         * <p>
+         * You can override this method if you want to extend {@link ItemHolderInfo} and provide
+         * your own instances.
+         *
+         * @return A new {@link ItemHolderInfo}.
+         */
+        public ItemHolderInfo obtainHolderInfo() {
+            return new ItemHolderInfo();
+        }
+
+        /**
+         * The interface to be implemented by listeners to animation events from this
+         * ItemAnimator. This is used internally and is not intended for developers to
+         * create directly.
+         */
+        interface ItemAnimatorListener {
+            void onAnimationFinished(ViewHolder item);
+        }
+
+        /**
          * This interface is used to inform listeners when all pending or running animations
          * in an ItemAnimator are finished. This can be used, for example, to delay an action
          * in a data set until currently-running animations are complete.
@@ -10124,105 +10156,79 @@
         }
 
         /**
-         * Called when a remove animation is being started on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
+         * A simple data structure that holds information about an item's bounds.
+         * This information is used in calculating item animations. Default implementation of
+         * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and
+         * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data
+         * structure. You can extend this class if you would like to keep more information about
+         * the Views.
+         * <p>
+         * If you want to provide your own implementation butstill use `super` methods to record
+         * basic information, you can override {@link #obtainHolderInfo()} to provide your own
+         * instances.
          */
-        public void onRemoveStarting(ViewHolder item) {}
+        public static class ItemHolderInfo {
 
-        /**
-         * Called when a remove animation has ended on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
-         */
-        public void onRemoveFinished(ViewHolder item) {}
+            /**
+             * The left edge of the View (excluding decorations)
+             */
+            public int left;
 
-        /**
-         * Called when an add animation is being started on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
-         */
-        public void onAddStarting(ViewHolder item) {}
+            /**
+             * The top edge of the View (excluding decorations)
+             */
+            public int top;
 
-        /**
-         * Called when an add animation has ended on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
-         */
-        public void onAddFinished(ViewHolder item) {}
+            /**
+             * The right edge of the View (excluding decorations)
+             */
+            public int right;
 
-        /**
-         * Called when a move animation is being started on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
-         */
-        public void onMoveStarting(ViewHolder item) {}
+            /**
+             * The bottom edge of the View (excluding decorations)
+             */
+            public int bottom;
 
-        /**
-         * Called when a move animation has ended on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @param item The ViewHolder being animated.
-         */
-        public void onMoveFinished(ViewHolder item) {}
+            /**
+             * The change flags that were passed to
+             * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}.
+             */
+            @AdapterChanges
+            public int changeFlags;
 
-        /**
-         * Called when a change animation is being started on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @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.
-         */
-        public void onChangeStarting(ViewHolder item, boolean oldItem) {}
+            public ItemHolderInfo() {
+            }
 
-        /**
-         * Called when a change animation has ended on the given ViewHolder.
-         * The default implementation does nothing. Subclasses may wish to override
-         * this method to handle any ViewHolder-specific operations linked to animation
-         * lifecycles.
-         *
-         * @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.
-         */
-        public void onChangeFinished(ViewHolder item, boolean oldItem) {}
+            /**
+             * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+             * the given ViewHolder. Clears all {@link #changeFlags}.
+             *
+             * @param holder The ViewHolder whose bounds should be copied.
+             * @return This {@link ItemHolderInfo}
+             */
+            public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) {
+                return setFrom(holder, 0);
+            }
 
-    }
-
-    /**
-     * Internal data structure that holds information about an item's bounds.
-     * This information is used in calculating item animations.
-     */
-    private static class ItemHolderInfo {
-        ViewHolder holder;
-        int left, top, right, bottom;
-
-        ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) {
-            this.holder = holder;
-            this.left = left;
-            this.top = top;
-            this.right = right;
-            this.bottom = bottom;
+            /**
+             * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+             * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter.
+             *
+             * @param holder The ViewHolder whose bounds should be copied.
+             * @param flags  The adapter change flags that were passed into
+             *               {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int,
+             *               List)}.
+             * @return This {@link ItemHolderInfo}
+             */
+            public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
+                    @AdapterChanges int flags) {
+                final View view = holder.itemView;
+                this.left = view.getLeft();
+                this.top = view.getTop();
+                this.right = view.getRight();
+                this.bottom = view.getBottom();
+                return this;
+            }
         }
     }
 
@@ -10243,7 +10249,7 @@
      * order of two views will not have any effect if their elevation values are different since
      * elevation overrides the result of this callback.
      */
-    public static interface ChildDrawingOrderCallback {
+    public interface ChildDrawingOrderCallback {
         /**
          * Returns the index of the child to draw for this iteration. Override this
          * if you want to change the drawing order of children. By default, it
@@ -10254,6 +10260,6 @@
          *
          * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
          */
-        public int onGetChildDrawingOrder(int childCount, int i);
+        int onGetChildDrawingOrder(int childCount, int i);
     }
 }
diff --git a/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
new file mode 100644
index 0000000..a1c27b5
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
@@ -0,0 +1,414 @@
+package android.support.v7.widget;
+
+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.
+ * <p>
+ * 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 {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "SimpleItemAnimator";
+
+    boolean mSupportsChangeAnimations = true;
+
+    /**
+     * Returns whether this ItemAnimator supports animations of change events.
+     *
+     * @return true if change animations are supported, false otherwise
+     */
+    @SuppressWarnings("unused")
+    public boolean getSupportsChangeAnimations() {
+        return mSupportsChangeAnimations;
+    }
+
+    /**
+     * Sets whether this ItemAnimator supports animations of item change events.
+     * If you set this property to false, actions on the data set which change the
+     * contents of items will not be animated. What those animations do is left
+     * up to the discretion of the ItemAnimator subclass, in its
+     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
+     * The value of this property is true by default.
+     *
+     * @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;
+    }
+
+    @Override
+    public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
+        return !mSupportsChangeAnimations || viewHolder.isInvalid();
+    }
+
+    @Override
+    public boolean animateDisappearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo) {
+        int oldLeft = preLayoutInfo.left;
+        int oldTop = preLayoutInfo.top;
+        View disappearingItemView = viewHolder.itemView;
+        int newLeft = disappearingItemView.getLeft();
+        int newTop = disappearingItemView.getTop();
+        if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
+            disappearingItemView.layout(newLeft, newTop,
+                    newLeft + disappearingItemView.getWidth(),
+                    newTop + disappearingItemView.getHeight());
+            if (DEBUG) {
+                Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
+            }
+            return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
+            }
+            return animateRemove(viewHolder);
+        }
+    }
+
+    @Override
+    public boolean animateAppearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo,
+            ItemHolderInfo postLayoutInfo) {
+        if (preLayoutInfo != null &&  (preLayoutInfo.left != postLayoutInfo.left
+                || preLayoutInfo.top != postLayoutInfo.top)) {
+            // slide items in if before/after locations differ
+            if (DEBUG) {
+                Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
+            }
+            return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
+                    postLayoutInfo.left, postLayoutInfo.top);
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
+            }
+            return animateAdd(viewHolder);
+        }
+    }
+
+    @Override
+    public boolean animatePersistence(ViewHolder viewHolder, ItemHolderInfo preInfo,
+            ItemHolderInfo postInfo) {
+        if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
+            if (DEBUG) {
+                Log.d(TAG, "PERSISTENT: " + viewHolder +
+                        " with view " + viewHolder.itemView);
+            }
+            return animateMove(viewHolder,
+                    preInfo.left, preInfo.top, postInfo.left, postInfo.top);
+        }
+        dispatchMoveFinished(viewHolder);
+        return false;
+    }
+
+    @Override
+    public boolean animateChange(ViewHolder oldHolder,
+            ViewHolder newHolder, ItemHolderInfo preInfo, 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()) {
+            toLeft = preInfo.left;
+            toTop = preInfo.top;
+        } else {
+            toLeft = postInfo.left;
+            toTop = postInfo.top;
+        }
+        return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
+    }
+
+    /**
+     * Called when an item is removed from the RecyclerView. Implementors can choose
+     * whether and how to animate that change, but must always call
+     * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
+     * immediately (if no animation will occur) or after the animation actually finishes.
+     * The return value indicates whether an animation has been set up and whether the
+     * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+     * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+     * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+     * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+     * {@link #animateRemove(ViewHolder) animateRemove()}, and
+     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+     * then start the animations together in the later call to {@link #runPendingAnimations()}.
+     *
+     * <p>This method may also be called for disappearing items which continue to exist in the
+     * RecyclerView, but for which the system does not have enough information to animate
+     * them out of view. In that case, the default animation for removing items is run
+     * on those items as well.</p>
+     *
+     * @param holder The item that is being removed.
+     * @return true if a later call to {@link #runPendingAnimations()} is requested,
+     * false otherwise.
+     */
+    abstract public boolean animateRemove(ViewHolder holder);
+
+    /**
+     * Called when an item is added to the RecyclerView. Implementors can choose
+     * whether and how to animate that change, but must always call
+     * {@link #dispatchAddFinished(ViewHolder)} when done, either
+     * immediately (if no animation will occur) or after the animation actually finishes.
+     * The return value indicates whether an animation has been set up and whether the
+     * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+     * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+     * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+     * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+     * {@link #animateRemove(ViewHolder) animateRemove()}, and
+     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+     * then start the animations together in the later call to {@link #runPendingAnimations()}.
+     *
+     * <p>This method may also be called for appearing items which were already in the
+     * RecyclerView, but for which the system does not have enough information to animate
+     * them into view. In that case, the default animation for adding items is run
+     * on those items as well.</p>
+     *
+     * @param holder The item that is being added.
+     * @return true if a later call to {@link #runPendingAnimations()} is requested,
+     * false otherwise.
+     */
+    abstract public boolean animateAdd(ViewHolder holder);
+
+    /**
+     * Called when an item is moved in the RecyclerView. Implementors can choose
+     * whether and how to animate that change, but must always call
+     * {@link #dispatchMoveFinished(ViewHolder)} when done, either
+     * immediately (if no animation will occur) or after the animation actually finishes.
+     * The return value indicates whether an animation has been set up and whether the
+     * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+     * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+     * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+     * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+     * {@link #animateRemove(ViewHolder) animateRemove()}, and
+     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+     * then start the animations together in the later call to {@link #runPendingAnimations()}.
+     *
+     * @param holder The item that is being moved.
+     * @return true if a later call to {@link #runPendingAnimations()} is requested,
+     * false otherwise.
+     */
+    abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY,
+            int toX, int toY);
+
+    /**
+     * Called when an item is changed in the RecyclerView, as indicated by a call to
+     * {@link Adapter#notifyItemChanged(int)} or
+     * {@link Adapter#notifyItemRangeChanged(int, int)}.
+     * <p>
+     * Implementers can choose whether and how to animate changes, but must always call
+     * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder,
+     * either immediately (if no animation will occur) or after the animation actually finishes.
+     * The return value indicates whether an animation has been set up and whether the
+     * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+     * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+     * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+     * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+     * {@link #animateRemove(ViewHolder) animateRemove()}, and
+     * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+     * then start the animations together in the later call to {@link #runPendingAnimations()}.
+     *
+     * @param oldHolder The original item that changed.
+     * @param newHolder The new item that was created with the changed content. Might be null
+     * @param fromLeft  Left of the old view holder
+     * @param fromTop   Top of the old view holder
+     * @param toLeft    Left of the new view holder
+     * @param toTop     Top of the new view holder
+     * @return true if a later call to {@link #runPendingAnimations()} is requested,
+     * false otherwise.
+     */
+    abstract public boolean animateChange(ViewHolder oldHolder,
+            ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
+
+    /**
+     * 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)
+     */
+    public final void dispatchRemoveFinished(ViewHolder item) {
+        onRemoveFinished(item);
+        dispatchAnimationFinished(item);
+    }
+
+    /**
+     * 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#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+     * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+     */
+    public final void dispatchMoveFinished(ViewHolder item) {
+        onMoveFinished(item);
+        dispatchAnimationFinished(item);
+    }
+
+    /**
+     * Method to be called by subclasses when an add animation is done.
+     *
+     * @param item The item which has been added
+     */
+    public final void dispatchAddFinished(ViewHolder item) {
+        onAddFinished(item);
+        dispatchAnimationFinished(item);
+    }
+
+    /**
+     * 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 oldItem true if this is the old item that was changed, false if
+     * it is the new item that replaced the old item.
+     */
+    public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
+        onChangeFinished(item, oldItem);
+        dispatchAnimationFinished(item);
+    }
+
+    /**
+     * Method to be called by subclasses when a remove animation is being started.
+     *
+     * @param item The item being removed
+     */
+    public final void dispatchRemoveStarting(ViewHolder item) {
+        onRemoveStarting(item);
+    }
+
+    /**
+     * Method to be called by subclasses when a move animation is being started.
+     *
+     * @param item The item being moved
+     */
+    public final void dispatchMoveStarting(ViewHolder item) {
+        onMoveStarting(item);
+    }
+
+    /**
+     * Method to be called by subclasses when an add animation is being started.
+     *
+     * @param item The item being added
+     */
+    public final void dispatchAddStarting(ViewHolder item) {
+        onAddStarting(item);
+    }
+
+    /**
+     * 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 oldItem true if this is the old item that was changed, false if
+     * it is the new item that replaced the old item.
+     */
+    public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
+        onChangeStarting(item, oldItem);
+    }
+
+    /**
+     * Called when a remove animation is being started on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public void onRemoveStarting(ViewHolder item) {}
+
+    /**
+     * Called when a remove animation has ended on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    public void onRemoveFinished(ViewHolder item) {}
+
+    /**
+     * Called when an add animation is being started on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public void onAddStarting(ViewHolder item) {}
+
+    /**
+     * Called when an add animation has ended on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    public void onAddFinished(ViewHolder item) {}
+
+    /**
+     * Called when a move animation is being started on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public void onMoveStarting(ViewHolder item) {}
+
+    /**
+     * Called when a move animation has ended on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @param item The ViewHolder being animated.
+     */
+    public void onMoveFinished(ViewHolder item) {}
+
+    /**
+     * Called when a change animation is being started on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @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.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public void onChangeStarting(ViewHolder item, boolean oldItem) {}
+
+    /**
+     * Called when a change animation has ended on the given ViewHolder.
+     * The default implementation does nothing. Subclasses may wish to override
+     * this method to handle any ViewHolder-specific operations linked to animation
+     * lifecycles.
+     *
+     * @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.
+     */
+    public void onChangeFinished(ViewHolder item, boolean oldItem) {}
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 322fe34..89e30b8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -1040,7 +1040,7 @@
         final int mode = View.MeasureSpec.getMode(spec);
         if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
             return View.MeasureSpec.makeMeasureSpec(
-                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
+                    Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode);
         }
         return spec;
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
new file mode 100644
index 0000000..1d095b4
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -0,0 +1,633 @@
+/*
+ * 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.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for animation related tests.
+ */
+public class BaseRecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
+
+    protected static final boolean DEBUG = false;
+
+    protected static final String TAG = "RecyclerViewAnimationsTest";
+
+    AnimationLayoutManager mLayoutManager;
+
+    TestAdapter mTestAdapter;
+
+    public BaseRecyclerViewAnimationsTest() {
+        super(DEBUG);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    RecyclerView setupBasic(int itemCount) throws Throwable {
+        return setupBasic(itemCount, 0, itemCount);
+    }
+
+    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
+            throws Throwable {
+        return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
+    }
+
+    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
+            TestAdapter testAdapter)
+            throws Throwable {
+        final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
+        recyclerView.setHasFixedSize(true);
+        if (testAdapter == null) {
+            mTestAdapter = new TestAdapter(itemCount);
+        } else {
+            mTestAdapter = testAdapter;
+        }
+        recyclerView.setAdapter(mTestAdapter);
+        recyclerView.setItemAnimator(createItemAnimator());
+        mLayoutManager = new AnimationLayoutManager();
+        recyclerView.setLayoutManager(mLayoutManager);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
+
+        mLayoutManager.expectLayouts(1);
+        recyclerView.expectDraw(1);
+        setRecyclerView(recyclerView);
+        mLayoutManager.waitForLayout(2);
+        recyclerView.waitForDraw(1);
+        mLayoutManager.mOnLayoutCallbacks.reset();
+        getInstrumentation().waitForIdleSync();
+        assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
+        assertEquals("all expected children should be laid out", firstLayoutItemCount,
+                mLayoutManager.getChildCount());
+        return recyclerView;
+    }
+
+    protected RecyclerView.ItemAnimator createItemAnimator() {
+        return new DefaultItemAnimator();
+    }
+
+    public TestRecyclerView getTestRecyclerView() {
+        return (TestRecyclerView) mRecyclerView;
+    }
+
+    class AnimationLayoutManager extends TestLayoutManager {
+
+        protected int mTotalLayoutCount = 0;
+        private String log;
+
+        OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
+        };
+
+
+
+        @Override
+        public boolean supportsPredictiveItemAnimations() {
+            return true;
+        }
+
+        public String getLog() {
+            return log;
+        }
+
+        private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
+            builder.append("\nViewHolders:\n");
+            for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
+                builder.append(vh).append("\n");
+            }
+            builder.append("scrap:\n");
+            for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
+                builder.append(vh).append("\n");
+            }
+
+            if (state.isPreLayout() && !done) {
+                log = "\n" + builder.toString();
+            } else {
+                log += "\n" + builder.toString();
+            }
+            return log;
+        }
+
+        @Override
+        public void expectLayouts(int count) {
+            super.expectLayouts(count);
+            mOnLayoutCallbacks.mLayoutCount = 0;
+        }
+
+        public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
+            mOnLayoutCallbacks = onLayoutCallbacks;
+        }
+
+        @Override
+        public final void onLayoutChildren(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            try {
+                mTotalLayoutCount++;
+                prepareLog(recycler, state, false);
+                if (state.isPreLayout()) {
+                    validateOldPositions(recycler, state);
+                } else {
+                    validateClearedOldPositions(recycler, state);
+                }
+                mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
+                prepareLog(recycler, state, true);
+            } finally {
+                layoutLatch.countDown();
+            }
+        }
+
+        private void validateClearedOldPositions(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (getTestRecyclerView() == null) {
+                return;
+            }
+            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
+                assertEquals("there should NOT be an old position in post layout",
+                        RecyclerView.NO_POSITION, viewHolder.mOldPosition);
+                assertEquals("there should NOT be a pre layout position in post layout",
+                        RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
+            }
+        }
+
+        private void validateOldPositions(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (getTestRecyclerView() == null) {
+                return;
+            }
+            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
+                if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
+                    assertTrue("there should be an old position in pre-layout",
+                            viewHolder.mOldPosition != RecyclerView.NO_POSITION);
+                }
+            }
+        }
+
+        public int getTotalLayoutCount() {
+            return mTotalLayoutCount;
+        }
+
+        @Override
+        public boolean canScrollVertically() {
+            return true;
+        }
+
+        @Override
+        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            mOnLayoutCallbacks.onScroll(dy, recycler, state);
+            return super.scrollVerticallyBy(dy, recycler, state);
+        }
+
+        public void onPostDispatchLayout() {
+            mOnLayoutCallbacks.postDispatchLayout();
+        }
+
+        @Override
+        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
+            super.waitForLayout(timeout, timeUnit);
+            checkForMainThreadException();
+        }
+    }
+
+    abstract class OnLayoutCallbacks {
+
+        int mLayoutMin = Integer.MIN_VALUE;
+
+        int mLayoutItemCount = Integer.MAX_VALUE;
+
+        int expectedPreLayoutItemCount = -1;
+
+        int expectedPostLayoutItemCount = -1;
+
+        int mDeletedViewCount;
+
+        int mLayoutCount = 0;
+
+        void setExpectedItemCounts(int preLayout, int postLayout) {
+            expectedPreLayoutItemCount = preLayout;
+            expectedPostLayoutItemCount = postLayout;
+        }
+
+        void reset() {
+            mLayoutMin = Integer.MIN_VALUE;
+            mLayoutItemCount = Integer.MAX_VALUE;
+            expectedPreLayoutItemCount = -1;
+            expectedPostLayoutItemCount = -1;
+            mLayoutCount = 0;
+        }
+
+        void beforePreLayout(RecyclerView.Recycler recycler,
+                AnimationLayoutManager lm, RecyclerView.State state) {
+            mDeletedViewCount = 0;
+            for (int i = 0; i < lm.getChildCount(); i++) {
+                View v = lm.getChildAt(i);
+                if (lm.getLp(v).isItemRemoved()) {
+                    mDeletedViewCount++;
+                }
+            }
+        }
+
+        void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                RecyclerView.State state) {
+            if (DEBUG) {
+                Log.d(TAG, "item count " + state.getItemCount());
+            }
+            lm.detachAndScrapAttachedViews(recycler);
+            final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
+            final int count = mLayoutItemCount
+                    == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
+            lm.layoutRange(recycler, start, start + count);
+            assertEquals("correct # of children should be laid out",
+                    count, lm.getChildCount());
+            lm.assertVisibleItemPositions();
+        }
+
+        private void assertNoPreLayoutPosition(RecyclerView.Recycler recycler) {
+            for (RecyclerView.ViewHolder vh : recycler.mAttachedScrap) {
+                assertPreLayoutPosition(vh);
+            }
+        }
+
+        private void assertNoPreLayoutPosition(RecyclerView.LayoutManager lm) {
+            for (int i = 0; i < lm.getChildCount(); i ++) {
+                final RecyclerView.ViewHolder vh = mRecyclerView
+                        .getChildViewHolder(lm.getChildAt(i));
+                assertPreLayoutPosition(vh);
+            }
+        }
+
+        private void assertPreLayoutPosition(RecyclerView.ViewHolder vh) {
+            assertEquals("in post layout, there should not be a view holder w/ a pre "
+                    + "layout position", RecyclerView.NO_POSITION, vh.mPreLayoutPosition);
+            assertEquals("in post layout, there should not be a view holder w/ an old "
+                    + "layout position", RecyclerView.NO_POSITION, vh.mOldPosition);
+        }
+
+        void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                RecyclerView.State state) {
+            if (state.isPreLayout()) {
+                if (expectedPreLayoutItemCount != -1) {
+                    assertEquals("on pre layout, state should return abstracted adapter size",
+                            expectedPreLayoutItemCount, state.getItemCount());
+                }
+                beforePreLayout(recycler, lm, state);
+            } else {
+                if (expectedPostLayoutItemCount != -1) {
+                    assertEquals("on post layout, state should return real adapter size",
+                            expectedPostLayoutItemCount, state.getItemCount());
+                }
+                beforePostLayout(recycler, lm, state);
+            }
+            if (!state.isPreLayout()) {
+                assertNoPreLayoutPosition(recycler);
+            }
+            doLayout(recycler, lm, state);
+            if (state.isPreLayout()) {
+                afterPreLayout(recycler, lm, state);
+            } else {
+                afterPostLayout(recycler, lm, state);
+                assertNoPreLayoutPosition(lm);
+            }
+            mLayoutCount++;
+        }
+
+        void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void postDispatchLayout() {
+        }
+
+        public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+
+        }
+    }
+
+    class TestRecyclerView extends RecyclerView {
+
+        CountDownLatch drawLatch;
+
+        public TestRecyclerView(Context context) {
+            super(context);
+        }
+
+        public TestRecyclerView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        void initAdapterManager() {
+            super.initAdapterManager();
+            mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
+                @Override
+                public void run() {
+                    validatePostUpdateOp();
+                }
+            };
+        }
+
+        @Override
+        boolean isAccessibilityEnabled() {
+            return true;
+        }
+
+        public void expectDraw(int count) {
+            drawLatch = new CountDownLatch(count);
+        }
+
+        public void waitForDraw(long timeout) throws Throwable {
+            drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
+            assertEquals("all expected draws should happen at the expected time frame",
+                    0, drawLatch.getCount());
+        }
+
+        List<ViewHolder> collectViewHolders() {
+            List<ViewHolder> holders = new ArrayList<ViewHolder>();
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+                if (holder != null) {
+                    holders.add(holder);
+                }
+            }
+            return holders;
+        }
+
+
+        private void validateViewHolderPositions() {
+            final Set<Integer> existingOffsets = new HashSet<Integer>();
+            int childCount = getChildCount();
+            StringBuilder log = new StringBuilder();
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
+                TestViewHolder tvh = (TestViewHolder) vh;
+                log.append(tvh.mBoundItem).append(vh)
+                        .append(" hidden:")
+                        .append(mChildHelper.mHiddenViews.contains(vh.itemView))
+                        .append("\n");
+            }
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
+                if (vh.isInvalid()) {
+                    continue;
+                }
+                if (vh.getLayoutPosition() < 0) {
+                    LayoutManager lm = getLayoutManager();
+                    for (int j = 0; j < lm.getChildCount(); j ++) {
+                        assertNotSame("removed view holder should not be in LM's child list",
+                                vh.itemView, lm.getChildAt(j));
+                    }
+                } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
+                    if (!existingOffsets.add(vh.getLayoutPosition())) {
+                        throw new IllegalStateException("view holder position conflict for "
+                                + "existing views " + vh + "\n" + log);
+                    }
+                }
+            }
+        }
+
+        void validatePostUpdateOp() {
+            try {
+                validateViewHolderPositions();
+                if (super.mState.isPreLayout()) {
+                    validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
+                }
+                validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+        }
+
+
+
+        private void validateAdapterPosition(AnimationLayoutManager lm) {
+            for (ViewHolder vh : collectViewHolders()) {
+                if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
+                    assertEquals("adapter position calculations should match view holder "
+                                    + "pre layout:" + mState.isPreLayout()
+                                    + " positions\n" + vh + "\n" + lm.getLog(),
+                            mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
+                }
+            }
+        }
+
+        // ensures pre layout positions are continuous block. This is not necessarily a case
+        // but valid in test RV
+        private void validatePreLayoutSequence(AnimationLayoutManager lm) {
+            Set<Integer> preLayoutPositions = new HashSet<Integer>();
+            for (ViewHolder vh : collectViewHolders()) {
+                assertTrue("pre layout positions should be distinct " + lm.getLog(),
+                        preLayoutPositions.add(vh.mPreLayoutPosition));
+            }
+            int minPos = Integer.MAX_VALUE;
+            for (Integer pos : preLayoutPositions) {
+                if (pos < minPos) {
+                    minPos = pos;
+                }
+            }
+            for (int i = 1; i < preLayoutPositions.size(); i++) {
+                assertNotNull("next position should exist " + lm.getLog(),
+                        preLayoutPositions.contains(minPos + i));
+            }
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            super.dispatchDraw(canvas);
+            if (drawLatch != null) {
+                drawLatch.countDown();
+            }
+        }
+
+        @Override
+        void dispatchLayout() {
+            try {
+                super.dispatchLayout();
+                if (getLayoutManager() instanceof AnimationLayoutManager) {
+                    ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
+                }
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+
+        }
+
+
+    }
+
+    abstract class AdapterOps {
+
+        final public void run(TestAdapter adapter) throws Throwable {
+            onRun(adapter);
+        }
+
+        abstract void onRun(TestAdapter testAdapter) throws Throwable;
+    }
+
+    static class CollectPositionResult {
+
+        // true if found in scrap
+        public RecyclerView.ViewHolder scrapResult;
+
+        public RecyclerView.ViewHolder adapterResult;
+
+        static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
+            CollectPositionResult cpr = new CollectPositionResult();
+            cpr.scrapResult = viewHolder;
+            return cpr;
+        }
+
+        static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
+            CollectPositionResult cpr = new CollectPositionResult();
+            cpr.adapterResult = viewHolder;
+            return cpr;
+        }
+
+        @Override
+        public String toString() {
+            return "CollectPositionResult{" +
+                    "scrapResult=" + scrapResult +
+                    ", adapterResult=" + adapterResult +
+                    '}';
+        }
+    }
+
+    static class PositionConstraint {
+
+        public static enum Type {
+            scrap,
+            adapter,
+            adapterScrap /*first pass adapter, second pass scrap*/
+        }
+
+        Type mType;
+
+        int mOldPos; // if VH
+
+        int mPreLayoutPos;
+
+        int mPostLayoutPos;
+
+        int mValidateCount = 0;
+
+        public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.scrap;
+            constraint.mOldPos = oldPos;
+            constraint.mPreLayoutPos = preLayoutPos;
+            constraint.mPostLayoutPos = postLayoutPos;
+            return constraint;
+        }
+
+        public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.adapterScrap;
+            constraint.mOldPos = RecyclerView.NO_POSITION;
+            constraint.mPreLayoutPos = preLayoutPos;
+            constraint.mPostLayoutPos = position;// adapter pos does not change
+            return constraint;
+        }
+
+        public static PositionConstraint adapter(int position) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.adapter;
+            constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
+            constraint.mOldPos = RecyclerView.NO_POSITION;
+            constraint.mPostLayoutPos = position;// adapter pos does not change
+            return constraint;
+        }
+
+        public void assertValidate() {
+            int expectedValidate = 0;
+            if (mPreLayoutPos >= 0) {
+                expectedValidate ++;
+            }
+            if (mPostLayoutPos >= 0) {
+                expectedValidate ++;
+            }
+            assertEquals("should run all validates", expectedValidate, mValidateCount);
+        }
+
+        @Override
+        public String toString() {
+            return "Cons{" +
+                    "t=" + mType.name() +
+                    ", old=" + mOldPos +
+                    ", pre=" + mPreLayoutPos +
+                    ", post=" + mPostLayoutPos +
+                    '}';
+        }
+
+        public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
+            mValidateCount ++;
+            assertNotNull(this + ": result should not be null\n" + log, result);
+            RecyclerView.ViewHolder viewHolder;
+            if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
+                assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
+                viewHolder = result.scrapResult;
+            } else {
+                assertNotNull(this + ": result should come from adapter\n"  + log,
+                        result.adapterResult);
+                assertEquals(this + ": old position should be none when it came from adapter\n" + log,
+                        RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
+                viewHolder = result.adapterResult;
+            }
+            if (state.isPreLayout()) {
+                assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
+                        viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
+                                viewHolder.mPreLayoutPosition);
+                assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
+                        viewHolder.getLayoutPosition());
+                if (mType == Type.scrap) {
+                    assertEquals(this + ": old position should match\n" + log, mOldPos,
+                            result.scrapResult.getOldPosition());
+                }
+            } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
+                    .isRemoved()) {
+                assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
+                        + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 2659ef2..53c3f81 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -164,7 +164,7 @@
         } catch (Exception e) {
             throw e;
         } catch (Throwable throwable) {
-            throw new Exception(throwable);
+            throw new Exception(Log.getStackTraceString(throwable));
         }
     }
 
@@ -713,6 +713,16 @@
             });
         }
 
+        public void changeAndNotifyWithPayload(final int start, final int count,
+                final Object payload) throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    notifyItemRangeChanged(start, count, payload);
+                }
+            });
+        }
+
         public void changePositionsAndNotify(final int... positions) throws Throwable {
             runTestOnUiThread(new Runnable() {
                 @Override
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
index 46833ca..5010be9 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -58,10 +58,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mAnimator = new DefaultItemAnimator();
-        mAdapter = new Adapter(20);
-        mDummyParent = getActivity().mContainer;
-        mAnimator.setListener(new RecyclerView.ItemAnimator.ItemAnimatorListener() {
+        mAnimator = new DefaultItemAnimator() {
             @Override
             public void onRemoveFinished(RecyclerView.ViewHolder item) {
                 try {
@@ -93,7 +90,7 @@
             }
 
             @Override
-            public void onChangeFinished(RecyclerView.ViewHolder item) {
+            public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
                 try {
                     assertTrue(mChangeFinished.add(item));
                     onFinished(item);
@@ -106,7 +103,9 @@
                 assertNotNull(mExpectedItems.remove(item));
                 mExpectedItemCount.release(1);
             }
-        });
+        };
+        mAdapter = new Adapter(20);
+        mDummyParent = getActivity().mContainer;
     }
 
     void checkForMainThreadException() throws Throwable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index ca7b0a4..3a39f3d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Debug;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.util.Log;
@@ -291,7 +290,7 @@
                 return 3;
             }
         });
-        rv.getItemAnimator().setSupportsChangeAnimations(true);
+        ((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(true);
         waitForFirstLayout(rv);
         View lastView = rv.getChildAt(rv.getChildCount() - 1);
         final int lastPos = rv.getChildAdapterPosition(lastView);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
new file mode 100644
index 0000000..c9192a7
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
@@ -0,0 +1,572 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_CHANGED;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_MOVED;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_REMOVED;
+
+/**
+ * Includes tests for the new RecyclerView animations API (v2).
+ */
+public class ItemAnimatorV2ApiTest extends BaseRecyclerViewAnimationsTest {
+
+    @Override
+    protected RecyclerView.ItemAnimator createItemAnimator() {
+        return mAnimator;
+    }
+
+    public void testSimpleAdd() throws Throwable {
+        setupBasic(10);
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.addAndNotify(2, 1);
+        mLayoutManager.waitForLayout(2);
+        RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        assertEquals(1, mAnimator.animateAppearanceList.size());
+        AnimateAppearance log = mAnimator.animateAppearanceList.get(0);
+        assertSame(vh, log.viewHolder);
+        assertNull(log.preInfo);
+        assertEquals(0, log.postInfo.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);
+        }
+        for (int i = 3; i < mTestAdapter.getItemCount(); i++) {
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testSimpleRemove() throws Throwable {
+        setupBasic(10);
+        RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.deleteAndNotify(2, 1);
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+        assertEquals(1, mAnimator.animateDisappearanceList.size());
+        AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0);
+        assertSame(vh, log.viewHolder);
+        assertFalse(mAnimator.postLayoutInfo.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);
+        }
+        for (int i = 3; i < mTestAdapter.getItemCount(); i++) {
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(FLAG_MOVED, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testSimpleUpdate() throws Throwable {
+        setupBasic(10);
+        RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.changeAndNotify(2, 1);
+        mLayoutManager.waitForLayout(2);
+        assertEquals(1, mAnimator.animateChangeList.size());
+        AnimateChange log = mAnimator.animateChangeList.get(0);
+        assertSame(vh, log.viewHolder);
+        assertSame(vh, log.newHolder);
+        assertTrue(mAnimator.preLayoutInfo.containsKey(vh));
+        assertTrue(mAnimator.postLayoutInfo.containsKey(vh));
+        assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
+        assertEquals(0, log.postInfo.changeFlags);
+        //others should not receive anything
+        for (int i = 0; i < mTestAdapter.getItemCount(); i++) {
+            if (i == 2) {
+                continue;
+            }
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testUpdateWithDuplicateViewHolder() throws Throwable {
+        setupBasic(10);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        mAnimator.canReUseCallback = new CanReUseCallback() {
+            @Override
+            public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+                assertSame(viewHolder, vh);
+                return false;
+            }
+        };
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.changeAndNotify(2, 1);
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        assertNotSame(vh, newVh);
+        assertEquals(1, mAnimator.animateChangeList.size());
+        AnimateChange log = mAnimator.animateChangeList.get(0);
+        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));
+        assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
+        assertEquals(0, log.postInfo.changeFlags);
+        //others should not receive anything
+        for (int i = 0; i < mTestAdapter.getItemCount(); i++) {
+            if (i == 2) {
+                continue;
+            }
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testUpdateWithOneDuplicateAndOneInPlace() throws Throwable {
+        setupBasic(10);
+        final RecyclerView.ViewHolder replaced = mRecyclerView.findViewHolderForAdapterPosition(2);
+        final RecyclerView.ViewHolder reused = mRecyclerView.findViewHolderForAdapterPosition(3);
+        mAnimator.canReUseCallback = new CanReUseCallback() {
+            @Override
+            public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+                if (viewHolder == replaced) {
+                    return false;
+                } else if (viewHolder == reused) {
+                    return true;
+                }
+                fail("unpexpected view");
+                return false;
+            }
+        };
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.changeAndNotify(2, 2);
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2);
+
+        assertNotSame(replaced, newVh);
+        assertSame(reused, mRecyclerView.findViewHolderForAdapterPosition(3));
+
+        assertEquals(2, mAnimator.animateChangeList.size());
+        AnimateChange logReplaced = null, logReused = null;
+        for (AnimateChange change : mAnimator.animateChangeList) {
+            if (change.newHolder == change.viewHolder) {
+                logReused = change;
+            } else {
+                logReplaced = change;
+            }
+        }
+        assertNotNull(logReplaced);
+        assertNotNull(logReused);
+        assertSame(replaced, logReplaced.viewHolder);
+        assertSame(newVh, logReplaced.newHolder);
+        assertSame(reused, logReused.viewHolder);
+        assertSame(reused, logReused.newHolder);
+
+        assertTrue(mAnimator.preLayoutInfo.containsKey(replaced));
+        assertTrue(mAnimator.preLayoutInfo.containsKey(reused));
+
+        assertTrue(mAnimator.postLayoutInfo.containsKey(newVh));
+        assertTrue(mAnimator.postLayoutInfo.containsKey(reused));
+        assertFalse(mAnimator.postLayoutInfo.containsKey(replaced));
+
+        assertEquals(FLAG_CHANGED, logReplaced.preInfo.changeFlags);
+        assertEquals(FLAG_CHANGED, logReused.preInfo.changeFlags);
+
+        assertEquals(0, logReplaced.postInfo.changeFlags);
+        assertEquals(0, logReused.postInfo.changeFlags);
+        //others should not receive anything
+        for (int i = 0; i < mTestAdapter.getItemCount(); i++) {
+            if (i == 2 || i == 3) {
+                continue;
+            }
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testChangeToDisappear() throws Throwable {
+        setupBasic(10);
+        RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(9);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.changeAndNotify(9, 1);
+        mLayoutManager.waitForLayout(2);
+        assertEquals(1, mAnimator.animateDisappearanceList.size());
+        AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0);
+        assertSame(vh, log.viewHolder);
+        assertFalse(mAnimator.postLayoutInfo.containsKey(vh));
+        assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
+        assertEquals(0, mAnimator.animateChangeList.size());
+        assertEquals(0, mAnimator.animateAppearanceList.size());
+        assertEquals(9, mAnimator.animatePersistenceList.size());
+        checkForMainThreadException();
+    }
+
+    public void testUpdatePayload() throws Throwable {
+        setupBasic(10);
+        RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        mLayoutManager.expectLayouts(2);
+        Object payload = new Object();
+        mTestAdapter.changeAndNotifyWithPayload(2, 1, payload);
+        mLayoutManager.waitForLayout(2);
+        assertEquals(1, mAnimator.animateChangeList.size());
+        AnimateChange log = mAnimator.animateChangeList.get(0);
+        assertSame(vh, log.viewHolder);
+        assertSame(vh, log.newHolder);
+        assertTrue(mAnimator.preLayoutInfo.containsKey(vh));
+        assertTrue(mAnimator.postLayoutInfo.containsKey(vh));
+        assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
+        assertEquals(0, log.postInfo.changeFlags);
+        assertNotNull(log.preInfo.payloads);
+        assertTrue(log.preInfo.payloads.contains(payload));
+        //others should not receive anything
+        for (int i = 0; i < mTestAdapter.getItemCount(); i++) {
+            if (i == 2) {
+                continue;
+            }
+            RecyclerView.ViewHolder other = mRecyclerView.findViewHolderForAdapterPosition(i);
+            assertEquals(0, mAnimator.preLayoutInfo.get(other).changeFlags);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testNotifyDataSetChanged() throws Throwable {
+        TestAdapter adapter = new TestAdapter(10);
+        adapter.setHasStableIds(true);
+        setupBasic(10, 0, 10, adapter);
+        mLayoutManager.expectLayouts(1);
+        mTestAdapter.dispatchDataSetChanged();
+        mLayoutManager.waitForLayout(2);
+        assertEquals(10, mAnimator.animateChangeList.size());
+        for (AnimateChange change : mAnimator.animateChangeList) {
+            assertNotNull(change.preInfo);
+            assertNotNull(change.postInfo);
+            assertSame(change.preInfo.viewHolder, change.postInfo.viewHolder);
+        }
+        assertEquals(0, mAnimator.animatePersistenceList.size());
+        assertEquals(0, mAnimator.animateAppearanceList.size());
+        assertEquals(0, mAnimator.animateDisappearanceList.size());
+    }
+
+    public void testNotifyDataSetChangedWithoutStableIds() throws Throwable {
+        TestAdapter adapter = new TestAdapter(10);
+        adapter.setHasStableIds(false);
+        setupBasic(10, 0, 10, adapter);
+        mLayoutManager.expectLayouts(1);
+        mTestAdapter.dispatchDataSetChanged();
+        mLayoutManager.waitForLayout(2);
+        assertEquals(0, mAnimator.animateChangeList.size());
+        assertEquals(0, mAnimator.animatePersistenceList.size());
+        assertEquals(0, mAnimator.animateAppearanceList.size());
+        assertEquals(0, mAnimator.animateDisappearanceList.size());
+    }
+
+    public void testNotifyDataSetChangedWithAppearing() throws Throwable {
+        notifyDataSetChangedWithAppearing(false);
+    }
+
+    public void testNotifyDataSetChangedWithAppearingNotifyBoth() throws Throwable {
+        notifyDataSetChangedWithAppearing(true);
+    }
+
+    public void notifyDataSetChangedWithAppearing(final boolean notifyBoth) throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        adapter.setHasStableIds(true);
+        setupBasic(10, 0, 10, adapter);
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (notifyBoth) {
+                        adapter.addAndNotify(2, 2);
+                    } else {
+                        adapter.mItems.add(2, new Item(2, "custom 1"));
+                        adapter.mItems.add(3, new Item(3, "custom 2"));
+                    }
+
+                    adapter.notifyDataSetChanged();
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        assertEquals(10, mAnimator.animateChangeList.size());
+        assertEquals(0, mAnimator.animatePersistenceList.size());
+        assertEquals(2, mAnimator.animateAppearanceList.size());
+        assertEquals(0, mAnimator.animateDisappearanceList.size());
+    }
+
+    public void testNotifyDataSetChangedWithDispappearing() throws Throwable {
+        notifyDataSetChangedWithDispappearing(false);
+    }
+
+    public void testNotifyDataSetChangedWithDispappearingNotifyBoth() throws Throwable {
+        notifyDataSetChangedWithDispappearing(true);
+    }
+
+    public void notifyDataSetChangedWithDispappearing(final boolean notifyBoth) throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        adapter.setHasStableIds(true);
+        setupBasic(10, 0, 10, adapter);
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (notifyBoth) {
+                        adapter.deleteAndNotify(2, 2);
+                    } else {
+                        adapter.mItems.remove(2);
+                        adapter.mItems.remove(2);
+                    }
+                    adapter.notifyDataSetChanged();
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        assertEquals(8, mAnimator.animateChangeList.size());
+        assertEquals(0, mAnimator.animatePersistenceList.size());
+        assertEquals(0, mAnimator.animateAppearanceList.size());
+        assertEquals(2, mAnimator.animateDisappearanceList.size());
+    }
+
+    public void testNotifyUpdateWithChangedAdapterType() throws Throwable {
+        final AtomicInteger itemType = new AtomicInteger(1);
+        final TestAdapter adapter = new TestAdapter(10) {
+            @Override
+            public int getItemViewType(int position) {
+                return position == 2 ? itemType.get() : 20;
+            }
+        };
+        adapter.setHasStableIds(true);
+        setupBasic(10, 0, 10, adapter);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+
+        mAnimator.canReUseCallback = new CanReUseCallback() {
+            @Override
+            public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+                return viewHolder != vh;
+            }
+        };
+
+        mLayoutManager.expectLayouts(1);
+        itemType.set(3);
+        adapter.dispatchDataSetChanged();
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(2);
+        // TODO we should be able to map old type to the new one but doing that change has some
+        // recycling side effects.
+        assertEquals(9, mAnimator.animateChangeList.size());
+        assertEquals(0, mAnimator.animatePersistenceList.size());
+        assertEquals(1, mAnimator.animateAppearanceList.size());
+        assertEquals(0, mAnimator.animateDisappearanceList.size());
+        assertNotSame(vh, newVh);
+        for (AnimateChange change : mAnimator.animateChangeList) {
+            if (change.viewHolder == vh) {
+                assertSame(change.newHolder, newVh);
+                assertSame(change.viewHolder, vh);
+            } else {
+                assertSame(change.newHolder, change.viewHolder);
+            }
+        }
+    }
+
+    LoggingV2Animator mAnimator = new LoggingV2Animator();
+
+    class LoggingV2Animator extends RecyclerView.ItemAnimator {
+
+        CanReUseCallback canReUseCallback = new CanReUseCallback() {
+            @Override
+            public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+                return true;
+            }
+        };
+        Map<RecyclerView.ViewHolder, LoggingInfo> preLayoutInfo = new HashMap<>();
+        Map<RecyclerView.ViewHolder, LoggingInfo> postLayoutInfo = new HashMap<>();
+
+        List<AnimateAppearance> animateAppearanceList = new ArrayList<>();
+        List<AnimateDisappearance> animateDisappearanceList = new ArrayList<>();
+        List<AnimatePersistence> animatePersistenceList = new ArrayList<>();
+        List<AnimateChange> animateChangeList = new ArrayList<>();
+
+        @Override
+        public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
+            return canReUseCallback.canReUse(viewHolder);
+        }
+
+        @Override
+        public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
+                RecyclerView.ViewHolder viewHolder,
+                @AdapterChanges int changeFlags, List<Object> payloads) {
+            LoggingInfo loggingInfo = new LoggingInfo(viewHolder, changeFlags, payloads);
+            preLayoutInfo.put(viewHolder, loggingInfo);
+            return loggingInfo;
+        }
+
+        @Override
+        public ItemHolderInfo recordPostLayoutInformation(RecyclerView.State state,
+                RecyclerView.ViewHolder viewHolder) {
+            LoggingInfo loggingInfo = new LoggingInfo(viewHolder, 0, null);
+            postLayoutInfo.put(viewHolder, loggingInfo);
+            return loggingInfo;
+        }
+
+        @Override
+        public boolean animateDisappearance(RecyclerView.ViewHolder viewHolder,
+                ItemHolderInfo preInfo) {
+            animateDisappearanceList.add(new AnimateDisappearance(viewHolder, preInfo));
+            assertSame(preLayoutInfo.get(viewHolder), preInfo);
+            dispatchAnimationFinished(viewHolder);
+
+            return false;
+        }
+
+        @Override
+        public boolean animateAppearance(RecyclerView.ViewHolder viewHolder, ItemHolderInfo preInfo,
+                ItemHolderInfo postInfo) {
+            animateAppearanceList.add(
+                    new AnimateAppearance(viewHolder, preInfo, postInfo));
+            assertSame(preLayoutInfo.get(viewHolder), preInfo);
+            assertSame(postLayoutInfo.get(viewHolder), postInfo);
+            dispatchAnimationFinished(viewHolder);
+            return false;
+        }
+
+        @Override
+        public boolean animatePersistence(RecyclerView.ViewHolder viewHolder,
+                ItemHolderInfo preInfo,
+                ItemHolderInfo postInfo) {
+            animatePersistenceList.add(new AnimatePersistence(viewHolder, preInfo, postInfo));
+            dispatchAnimationFinished(viewHolder);
+            assertSame(preLayoutInfo.get(viewHolder), preInfo);
+            assertSame(postLayoutInfo.get(viewHolder), postInfo);
+            return false;
+        }
+
+        @Override
+        public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+                RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
+                ItemHolderInfo postInfo) {
+            animateChangeList.add(new AnimateChange(oldHolder, newHolder, preInfo, postInfo));
+            if (oldHolder != null) {
+                dispatchAnimationFinished(oldHolder);
+                assertSame(preLayoutInfo.get(oldHolder), preInfo);
+            }
+            if (newHolder != null) {
+                dispatchAnimationFinished(newHolder);
+                assertSame(postLayoutInfo.get(newHolder), postInfo);
+            }
+
+            return false;
+        }
+
+        @Override
+        public void runPendingAnimations() {
+
+        }
+
+        @Override
+        public void endAnimation(RecyclerView.ViewHolder item) {
+        }
+
+        @Override
+        public void endAnimations() {
+
+        }
+
+        @Override
+        public boolean isRunning() {
+            return false;
+        }
+    }
+
+    static class LoggingInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
+
+        final RecyclerView.ViewHolder viewHolder;
+        @RecyclerView.ItemAnimator.AdapterChanges
+        final int changeFlags;
+        final List<Object> payloads;
+
+        LoggingInfo(RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
+            this.viewHolder = viewHolder;
+            this.changeFlags = changeFlags;
+            if (payloads != null) {
+                this.payloads = new ArrayList<>();
+                this.payloads.addAll(payloads);
+            } else {
+                this.payloads = null;
+            }
+            setFrom(viewHolder);
+        }
+    }
+
+    static class AnimateChange extends AnimatePersistence {
+
+        final RecyclerView.ViewHolder newHolder;
+
+        public AnimateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
+                Object pre, Object post) {
+            super(oldHolder, pre, post);
+            this.newHolder = newHolder;
+        }
+    }
+
+    static class AnimatePersistence extends AnimateAppearance {
+
+        public AnimatePersistence(RecyclerView.ViewHolder viewHolder, Object pre, Object post) {
+            super(viewHolder, pre, post);
+        }
+    }
+
+    static class AnimateAppearance extends AnimateDisappearance {
+
+        final LoggingInfo postInfo;
+
+        public AnimateAppearance(RecyclerView.ViewHolder viewHolder, Object pre, Object post) {
+            super(viewHolder, pre);
+            this.postInfo = (LoggingInfo) post;
+        }
+    }
+
+    static class AnimateDisappearance {
+
+        final RecyclerView.ViewHolder viewHolder;
+        final LoggingInfo preInfo;
+
+        public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, Object pre) {
+            this.viewHolder = viewHolder;
+            this.preInfo = (LoggingInfo) pre;
+        }
+    }
+
+    interface CanReUseCallback {
+
+        boolean canReUse(RecyclerView.ViewHolder viewHolder);
+    }
+}
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 119ad80..85b6a5f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -16,11 +16,8 @@
 
 package android.support.v7.widget;
 
-import android.content.Context;
-import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.support.v4.view.ViewCompat;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,66 +28,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class RecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
-
-    private static final boolean DEBUG = false;
-
-    private static final String TAG = "RecyclerViewAnimationsTest";
-
-    AnimationLayoutManager mLayoutManager;
-
-    TestAdapter mTestAdapter;
-
-    public RecyclerViewAnimationsTest() {
-        super(DEBUG);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    RecyclerView setupBasic(int itemCount) throws Throwable {
-        return setupBasic(itemCount, 0, itemCount);
-    }
-
-    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
-            throws Throwable {
-        return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
-    }
-
-    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
-            TestAdapter testAdapter)
-            throws Throwable {
-        final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
-        recyclerView.setHasFixedSize(true);
-        if (testAdapter == null) {
-            mTestAdapter = new TestAdapter(itemCount);
-        } else {
-            mTestAdapter = testAdapter;
-        }
-        recyclerView.setAdapter(mTestAdapter);
-        mLayoutManager = new AnimationLayoutManager();
-        recyclerView.setLayoutManager(mLayoutManager);
-        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
-        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
-
-        mLayoutManager.expectLayouts(1);
-        recyclerView.expectDraw(1);
-        setRecyclerView(recyclerView);
-        mLayoutManager.waitForLayout(2);
-        recyclerView.waitForDraw(1);
-        mLayoutManager.mOnLayoutCallbacks.reset();
-        getInstrumentation().waitForIdleSync();
-        assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
-        assertEquals("all expected children should be laid out", firstLayoutItemCount,
-                mLayoutManager.getChildCount());
-        return recyclerView;
-    }
+/**
+ * Tests for {@link SimpleItemAnimator} API.
+ */
+public class RecyclerViewAnimationsTest extends BaseRecyclerViewAnimationsTest {
 
     public void testDetachBeforeAnimations() throws Throwable {
         setupBasic(10, 0, 5);
@@ -379,6 +322,10 @@
         assertTrue("added-removed view should be recycled", found);
     }
 
+    public void testTmpRemoveMe() throws Throwable {
+        changeAnimTest(false, false, true, false);
+    }
+
     public void testChangeAnimations()  throws Throwable {
         final boolean[] booleans = {true, false};
         for (boolean supportsChange : booleans) {
@@ -392,6 +339,7 @@
             }
         }
     }
+
     public void changeAnimTest(final boolean supportsChangeAnim, final boolean changeType,
             final boolean hasStableIds, final boolean deleteSomeItems)  throws Throwable {
         final int changedIndex = 3;
@@ -400,7 +348,7 @@
         final String logPrefix = "supportsChangeAnim:" + supportsChangeAnim +
                 ", change view type:" + changeType +
                 ", has stable ids:" + hasStableIds +
-                ", force predictive:" + deleteSomeItems;
+                ", delete some items:" + deleteSomeItems;
         TestAdapter testAdapter = new TestAdapter(10) {
             @Override
             public int getItemViewType(int position) {
@@ -428,7 +376,8 @@
         };
         testAdapter.setHasStableIds(hasStableIds);
         setupBasic(testAdapter.getItemCount(), 0, 10, testAdapter);
-        mRecyclerView.getItemAnimator().setSupportsChangeAnimations(supportsChangeAnim);
+        ((SimpleItemAnimator)mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(
+                supportsChangeAnim);
 
         final RecyclerView.ViewHolder toBeChangedVH =
                 mRecyclerView.findViewHolderForLayoutPosition(changedIndex);
@@ -439,13 +388,8 @@
                     RecyclerView.State state) {
                 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
                         changedIndex);
-                if (supportsChangeAnim) {
-                    assertTrue(logPrefix + " changed view holder should have correct flag"
-                            , vh.isChanged());
-                } else {
-                    assertFalse(logPrefix + " changed view holder should have correct flag"
-                            , vh.isChanged());
-                }
+                assertTrue(logPrefix + " changed view holder should have correct flag"
+                        , vh.isUpdated());
             }
 
             @Override
@@ -453,7 +397,6 @@
                     AnimationLayoutManager layoutManager, RecyclerView.State state) {
                 RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
                         changedIndex);
-                assertFalse(logPrefix + "VH should not be marked as changed", vh.isChanged());
                 if (supportsChangeAnim) {
                     assertNotSame(logPrefix + "a new VH should be given if change is supported",
                             toBeChangedVH, vh);
@@ -482,7 +425,7 @@
                 }
             });
         } else {
-            mTestAdapter.notifyItemChanged(3);
+            mTestAdapter.changeAndNotify(3, 1);
         }
 
         mLayoutManager.waitForLayout(2);
@@ -533,7 +476,8 @@
         };
         testAdapter.setHasStableIds(false);
         setupBasic(testAdapter.getItemCount(), 0, 10, testAdapter);
-        mRecyclerView.getItemAnimator().setSupportsChangeAnimations(supportsChangeAnim);
+        ((SimpleItemAnimator)mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(
+                supportsChangeAnim);
 
         int numTests = notifyPayloads.length;
         for (int i= 0; i < numTests; i++) {
@@ -990,10 +934,6 @@
                 8, mRecyclerView.getChildCount());
     }
 
-    public TestRecyclerView getTestRecyclerView() {
-        return (TestRecyclerView) mRecyclerView;
-    }
-
     public void testRemoveScrapInvalidate() throws Throwable {
         setupBasic(10);
         TestRecyclerView testRecyclerView = getTestRecyclerView();
@@ -1035,11 +975,14 @@
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
-                // [0,1,2,3,4]
-                // delete 1
-                mTestAdapter.notifyItemRangeRemoved(1, 1);
-                // delete 3
-                mTestAdapter.notifyItemRangeRemoved(2, 1);
+                try {
+                    // delete 1
+                    mTestAdapter.deleteAndNotify(1, 1);
+                    // delete 3
+                    mTestAdapter.deleteAndNotify(2, 1);
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
             }
         });
         mLayoutManager.waitForLayout(2);
@@ -1180,10 +1123,14 @@
                 }
                 Map<Integer, CollectPositionResult> positions
                         = collectPositions(lm.mRecyclerView, recycler, state, ids);
+                StringBuilder positionLog = new StringBuilder("\nPosition logs:\n");
+                for (Map.Entry<Integer, CollectPositionResult> entry : positions.entrySet()) {
+                    positionLog.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
+                }
                 for (PositionConstraint constraint : constraints) {
                     if (constraint.mPreLayoutPos != -1) {
                         constraint.validate(state, positions.get(constraint.mPreLayoutPos),
-                                lm.getLog());
+                                lm.getLog() + positionLog);
                     }
                 }
             }
@@ -1199,10 +1146,15 @@
                 }
                 Map<Integer, CollectPositionResult> positions
                         = collectPositions(lm.mRecyclerView, recycler, state, ids);
+                StringBuilder positionLog = new StringBuilder("\nPosition logs:\n");
+                for (Map.Entry<Integer, CollectPositionResult> entry : positions.entrySet()) {
+                    positionLog.append(entry.getKey()).append(":")
+                            .append(entry.getValue()).append("\n");
+                }
                 for (PositionConstraint constraint : constraints) {
                     if (constraint.mPostLayoutPos >= 0) {
                         constraint.validate(state, positions.get(constraint.mPostLayoutPos),
-                                lm.getLog());
+                                lm.getLog() + positionLog);
                     }
                 }
             }
@@ -1264,530 +1216,4 @@
         assertTrue("since LM force recycled a view, animate disappearance should not be called",
                 animateRemoveList.isEmpty());
     }
-
-    class AnimationLayoutManager extends TestLayoutManager {
-
-        private int mTotalLayoutCount = 0;
-        private String log;
-
-        OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
-        };
-
-
-
-        @Override
-        public boolean supportsPredictiveItemAnimations() {
-            return true;
-        }
-
-        public String getLog() {
-            return log;
-        }
-
-        private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
-            StringBuilder builder = new StringBuilder();
-            builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
-            builder.append("\nViewHolders:\n");
-            for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
-                builder.append(vh).append("\n");
-            }
-            builder.append("scrap:\n");
-            for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
-                builder.append(vh).append("\n");
-            }
-
-            if (state.isPreLayout() && !done) {
-                log = "\n" + builder.toString();
-            } else {
-                log += "\n" + builder.toString();
-            }
-            return log;
-        }
-
-        @Override
-        public void expectLayouts(int count) {
-            super.expectLayouts(count);
-            mOnLayoutCallbacks.mLayoutCount = 0;
-        }
-
-        public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
-            mOnLayoutCallbacks = onLayoutCallbacks;
-        }
-
-        @Override
-        public final void onLayoutChildren(RecyclerView.Recycler recycler,
-                RecyclerView.State state) {
-            try {
-                mTotalLayoutCount++;
-                prepareLog(recycler, state, false);
-                if (state.isPreLayout()) {
-                    validateOldPositions(recycler, state);
-                } else {
-                    validateClearedOldPositions(recycler, state);
-                }
-                mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
-                prepareLog(recycler, state, true);
-            } finally {
-                layoutLatch.countDown();
-            }
-        }
-
-        private void validateClearedOldPositions(RecyclerView.Recycler recycler,
-                RecyclerView.State state) {
-            if (getTestRecyclerView() == null) {
-                return;
-            }
-            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
-                assertEquals("there should NOT be an old position in post layout",
-                        RecyclerView.NO_POSITION, viewHolder.mOldPosition);
-                assertEquals("there should NOT be a pre layout position in post layout",
-                        RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
-            }
-        }
-
-        private void validateOldPositions(RecyclerView.Recycler recycler,
-                RecyclerView.State state) {
-            if (getTestRecyclerView() == null) {
-                return;
-            }
-            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
-                if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
-                    assertTrue("there should be an old position in pre-layout",
-                            viewHolder.mOldPosition != RecyclerView.NO_POSITION);
-                }
-            }
-        }
-
-        public int getTotalLayoutCount() {
-            return mTotalLayoutCount;
-        }
-
-        @Override
-        public boolean canScrollVertically() {
-            return true;
-        }
-
-        @Override
-        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
-                RecyclerView.State state) {
-            mOnLayoutCallbacks.onScroll(dy, recycler, state);
-            return super.scrollVerticallyBy(dy, recycler, state);
-        }
-
-        public void onPostDispatchLayout() {
-            mOnLayoutCallbacks.postDispatchLayout();
-        }
-
-        @Override
-        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
-            super.waitForLayout(timeout, timeUnit);
-            checkForMainThreadException();
-        }
-    }
-
-    abstract class OnLayoutCallbacks {
-
-        int mLayoutMin = Integer.MIN_VALUE;
-
-        int mLayoutItemCount = Integer.MAX_VALUE;
-
-        int expectedPreLayoutItemCount = -1;
-
-        int expectedPostLayoutItemCount = -1;
-
-        int mDeletedViewCount;
-
-        int mLayoutCount = 0;
-
-        void setExpectedItemCounts(int preLayout, int postLayout) {
-            expectedPreLayoutItemCount = preLayout;
-            expectedPostLayoutItemCount = postLayout;
-        }
-
-        void reset() {
-            mLayoutMin = Integer.MIN_VALUE;
-            mLayoutItemCount = Integer.MAX_VALUE;
-            expectedPreLayoutItemCount = -1;
-            expectedPostLayoutItemCount = -1;
-            mLayoutCount = 0;
-        }
-
-        void beforePreLayout(RecyclerView.Recycler recycler,
-                AnimationLayoutManager lm, RecyclerView.State state) {
-            mDeletedViewCount = 0;
-            for (int i = 0; i < lm.getChildCount(); i++) {
-                View v = lm.getChildAt(i);
-                if (lm.getLp(v).isItemRemoved()) {
-                    mDeletedViewCount++;
-                }
-            }
-        }
-
-        void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
-                RecyclerView.State state) {
-            if (DEBUG) {
-                Log.d(TAG, "item count " + state.getItemCount());
-            }
-            lm.detachAndScrapAttachedViews(recycler);
-            final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
-            final int count = mLayoutItemCount
-                    == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
-            lm.layoutRange(recycler, start, start + count);
-            assertEquals("correct # of children should be laid out",
-                    count, lm.getChildCount());
-            lm.assertVisibleItemPositions();
-        }
-
-        private void assertNoPreLayoutPosition(RecyclerView.Recycler recycler) {
-            for (RecyclerView.ViewHolder vh : recycler.mAttachedScrap) {
-                assertPreLayoutPosition(vh);
-            }
-        }
-
-        private void assertNoPreLayoutPosition(RecyclerView.LayoutManager lm) {
-            for (int i = 0; i < lm.getChildCount(); i ++) {
-                final RecyclerView.ViewHolder vh = mRecyclerView
-                        .getChildViewHolder(lm.getChildAt(i));
-                assertPreLayoutPosition(vh);
-            }
-        }
-
-        private void assertPreLayoutPosition(RecyclerView.ViewHolder vh) {
-            assertEquals("in post layout, there should not be a view holder w/ a pre "
-                    + "layout position", RecyclerView.NO_POSITION, vh.mPreLayoutPosition);
-            assertEquals("in post layout, there should not be a view holder w/ an old "
-                    + "layout position", RecyclerView.NO_POSITION, vh.mOldPosition);
-        }
-
-        void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
-                RecyclerView.State state) {
-
-            if (state.isPreLayout()) {
-                if (expectedPreLayoutItemCount != -1) {
-                    assertEquals("on pre layout, state should return abstracted adapter size",
-                            expectedPreLayoutItemCount, state.getItemCount());
-                }
-                beforePreLayout(recycler, lm, state);
-            } else {
-                if (expectedPostLayoutItemCount != -1) {
-                    assertEquals("on post layout, state should return real adapter size",
-                            expectedPostLayoutItemCount, state.getItemCount());
-                }
-                beforePostLayout(recycler, lm, state);
-            }
-            if (!state.isPreLayout()) {
-                assertNoPreLayoutPosition(recycler);
-            }
-            doLayout(recycler, lm, state);
-            if (state.isPreLayout()) {
-                afterPreLayout(recycler, lm, state);
-            } else {
-                afterPostLayout(recycler, lm, state);
-                assertNoPreLayoutPosition(lm);
-            }
-            mLayoutCount++;
-        }
-
-        void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
-                RecyclerView.State state) {
-        }
-
-        void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
-                RecyclerView.State state) {
-        }
-
-        void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
-                RecyclerView.State state) {
-        }
-
-        void postDispatchLayout() {
-        }
-
-        public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
-
-        }
-    }
-
-    class TestRecyclerView extends RecyclerView {
-
-        CountDownLatch drawLatch;
-
-        public TestRecyclerView(Context context) {
-            super(context);
-        }
-
-        public TestRecyclerView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
-            super(context, attrs, defStyle);
-        }
-
-        @Override
-        void initAdapterManager() {
-            super.initAdapterManager();
-            mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
-                @Override
-                public void run() {
-                    validatePostUpdateOp();
-                }
-            };
-        }
-
-        @Override
-        boolean isAccessibilityEnabled() {
-            return true;
-        }
-
-        public void expectDraw(int count) {
-            drawLatch = new CountDownLatch(count);
-        }
-
-        public void waitForDraw(long timeout) throws Throwable {
-            drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
-            assertEquals("all expected draws should happen at the expected time frame",
-                    0, drawLatch.getCount());
-        }
-
-        List<ViewHolder> collectViewHolders() {
-            List<ViewHolder> holders = new ArrayList<ViewHolder>();
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                ViewHolder holder = getChildViewHolderInt(getChildAt(i));
-                if (holder != null) {
-                    holders.add(holder);
-                }
-            }
-            return holders;
-        }
-
-
-        private void validateViewHolderPositions() {
-            final Set<Integer> existingOffsets = new HashSet<Integer>();
-            int childCount = getChildCount();
-            StringBuilder log = new StringBuilder();
-            for (int i = 0; i < childCount; i++) {
-                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
-                TestViewHolder tvh = (TestViewHolder) vh;
-                log.append(tvh.mBoundItem).append(vh)
-                        .append(" hidden:")
-                        .append(mChildHelper.mHiddenViews.contains(vh.itemView))
-                        .append("\n");
-            }
-            for (int i = 0; i < childCount; i++) {
-                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
-                if (vh.isInvalid()) {
-                    continue;
-                }
-                if (vh.getLayoutPosition() < 0) {
-                    LayoutManager lm = getLayoutManager();
-                    for (int j = 0; j < lm.getChildCount(); j ++) {
-                        assertNotSame("removed view holder should not be in LM's child list",
-                                vh.itemView, lm.getChildAt(j));
-                    }
-                } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
-                    if (!existingOffsets.add(vh.getLayoutPosition())) {
-                        throw new IllegalStateException("view holder position conflict for "
-                                + "existing views " + vh + "\n" + log);
-                    }
-                }
-            }
-        }
-
-        void validatePostUpdateOp() {
-            try {
-                validateViewHolderPositions();
-                if (super.mState.isPreLayout()) {
-                    validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
-                }
-                validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
-            } catch (Throwable t) {
-                postExceptionToInstrumentation(t);
-            }
-        }
-
-
-
-        private void validateAdapterPosition(AnimationLayoutManager lm) {
-            for (ViewHolder vh : collectViewHolders()) {
-                if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
-                    assertEquals("adapter position calculations should match view holder "
-                            + "pre layout:" + mState.isPreLayout()
-                            + " positions\n" + vh + "\n" + lm.getLog(),
-                            mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
-                }
-            }
-        }
-
-        // ensures pre layout positions are continuous block. This is not necessarily a case
-        // but valid in test RV
-        private void validatePreLayoutSequence(AnimationLayoutManager lm) {
-            Set<Integer> preLayoutPositions = new HashSet<Integer>();
-            for (ViewHolder vh : collectViewHolders()) {
-                assertTrue("pre layout positions should be distinct " + lm.getLog(),
-                        preLayoutPositions.add(vh.mPreLayoutPosition));
-            }
-            int minPos = Integer.MAX_VALUE;
-            for (Integer pos : preLayoutPositions) {
-                if (pos < minPos) {
-                    minPos = pos;
-                }
-            }
-            for (int i = 1; i < preLayoutPositions.size(); i++) {
-                assertNotNull("next position should exist " + lm.getLog(),
-                        preLayoutPositions.contains(minPos + i));
-            }
-        }
-
-        @Override
-        protected void dispatchDraw(Canvas canvas) {
-            super.dispatchDraw(canvas);
-            if (drawLatch != null) {
-                drawLatch.countDown();
-            }
-        }
-
-        @Override
-        void dispatchLayout() {
-            try {
-                super.dispatchLayout();
-                if (getLayoutManager() instanceof AnimationLayoutManager) {
-                    ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
-                }
-            } catch (Throwable t) {
-                postExceptionToInstrumentation(t);
-            }
-
-        }
-
-
-    }
-
-    abstract class AdapterOps {
-
-        final public void run(TestAdapter adapter) throws Throwable {
-            onRun(adapter);
-        }
-
-        abstract void onRun(TestAdapter testAdapter) throws Throwable;
-    }
-
-    static class CollectPositionResult {
-
-        // true if found in scrap
-        public RecyclerView.ViewHolder scrapResult;
-
-        public RecyclerView.ViewHolder adapterResult;
-
-        static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
-            CollectPositionResult cpr = new CollectPositionResult();
-            cpr.scrapResult = viewHolder;
-            return cpr;
-        }
-
-        static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
-            CollectPositionResult cpr = new CollectPositionResult();
-            cpr.adapterResult = viewHolder;
-            return cpr;
-        }
-    }
-
-    static class PositionConstraint {
-
-        public static enum Type {
-            scrap,
-            adapter,
-            adapterScrap /*first pass adapter, second pass scrap*/
-        }
-
-        Type mType;
-
-        int mOldPos; // if VH
-
-        int mPreLayoutPos;
-
-        int mPostLayoutPos;
-
-        int mValidateCount = 0;
-
-        public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
-            PositionConstraint constraint = new PositionConstraint();
-            constraint.mType = Type.scrap;
-            constraint.mOldPos = oldPos;
-            constraint.mPreLayoutPos = preLayoutPos;
-            constraint.mPostLayoutPos = postLayoutPos;
-            return constraint;
-        }
-
-        public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
-            PositionConstraint constraint = new PositionConstraint();
-            constraint.mType = Type.adapterScrap;
-            constraint.mOldPos = RecyclerView.NO_POSITION;
-            constraint.mPreLayoutPos = preLayoutPos;
-            constraint.mPostLayoutPos = position;// adapter pos does not change
-            return constraint;
-        }
-
-        public static PositionConstraint adapter(int position) {
-            PositionConstraint constraint = new PositionConstraint();
-            constraint.mType = Type.adapter;
-            constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
-            constraint.mOldPos = RecyclerView.NO_POSITION;
-            constraint.mPostLayoutPos = position;// adapter pos does not change
-            return constraint;
-        }
-
-        public void assertValidate() {
-            int expectedValidate = 0;
-            if (mPreLayoutPos >= 0) {
-                expectedValidate ++;
-            }
-            if (mPostLayoutPos >= 0) {
-                expectedValidate ++;
-            }
-            assertEquals("should run all validates", expectedValidate, mValidateCount);
-        }
-
-        @Override
-        public String toString() {
-            return "Cons{" +
-                    "t=" + mType.name() +
-                    ", old=" + mOldPos +
-                    ", pre=" + mPreLayoutPos +
-                    ", post=" + mPostLayoutPos +
-                    '}';
-        }
-
-        public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
-            mValidateCount ++;
-            assertNotNull(this + ": result should not be null\n" + log, result);
-            RecyclerView.ViewHolder viewHolder;
-            if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
-                assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
-                viewHolder = result.scrapResult;
-            } else {
-                assertNotNull(this + ": result should come from adapter\n"  + log,
-                        result.adapterResult);
-                assertEquals(this + ": old position should be none when it came from adapter\n" + log,
-                        RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
-                viewHolder = result.adapterResult;
-            }
-            if (state.isPreLayout()) {
-                assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
-                        viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
-                        viewHolder.mPreLayoutPosition);
-                assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
-                        viewHolder.getLayoutPosition());
-                if (mType == Type.scrap) {
-                    assertEquals(this + ": old position should match\n" + log, mOldPos,
-                            result.scrapResult.getOldPosition());
-                }
-            } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
-                    .isRemoved()) {
-                assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
-                        + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
-            }
-        }
-    }
 }
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 61312d3..049adc7 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -28,7 +28,6 @@
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
 import android.test.TouchUtils;
 import android.util.Log;
 import android.view.Gravity;
@@ -196,8 +195,8 @@
             }
 
             @Override
-            public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
-                    RecyclerView.State state) {
+            public View onFocusSearchFailed(View focused, int direction,
+                    RecyclerView.Recycler recycler, RecyclerView.State state) {
                 focusSearchCalled.addAndGet(1);
                 focusLatch.countDown();
                 return null;
@@ -218,7 +217,6 @@
             }
         });
         assertTrue(c.hasFocus());
-
         freezeLayout(true);
         sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
         assertEquals("onFocusSearchFailed should not be called when layout is frozen",
@@ -1259,7 +1257,7 @@
         RecyclerView recyclerView = new RecyclerView(getActivity());
         recyclerView.setAdapter(testAdapter);
         recyclerView.setLayoutManager(lm);
-        recyclerView.getItemAnimator().setSupportsChangeAnimations(true);
+        ((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(true);
         lm.expectLayouts(1);
         setRecyclerView(recyclerView);
         lm.waitForLayout(2);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
index dfa2974..c2fac6e 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
@@ -17,7 +17,6 @@
 package android.support.v7.widget.helper;
 
 import android.app.Instrumentation;
-import android.os.Debug;
 import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.BaseRecyclerViewInstrumentationTest;