Fix memory leak when fast scrolling rows.

If a presenter starts a view property animation, then the parent row view
will have transient state which may cause a row to fail to be recycled
during a fast scroll, because RecyclerView checks transient state and
refuses to recycle a view if it or its children has running view property
animation.

This can cause a memory leak because ObjectAdapters have references to
ItemBridgeAdapters via the registered observer mechanism.

Apps should clear any view property animations in Presenter
onViewDetachedFromWindow, but in case they don't we'll do it for them in
the base class.

b/17013302

Change-Id: Ibdf5998e81dd130128f88f85d88243ec27a70dd5
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
index fb131e0..aa9e3f8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -234,4 +234,11 @@
             mTitleView.setMaxLines(1);
         }
     }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mImageView.animate().cancel();
+        mImageView.setAlpha(1f);
+        super.onDetachedFromWindow();
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index c13f48a..8940352 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -412,14 +412,15 @@
         super.onBindRowViewHolder(holder, item);
         ViewHolder vh = (ViewHolder) holder;
         ListRow rowItem = (ListRow) item;
-        vh.mItemBridgeAdapter.clear();
         vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
         vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
     }
 
     @Override
     protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
-        ((ViewHolder) holder).mGridView.setAdapter(null);
+        ViewHolder vh = (ViewHolder) holder;
+        vh.mGridView.setAdapter(null);
+        vh.mItemBridgeAdapter.clear();
         super.onUnbindRowViewHolder(holder);
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
index ed7714d..9a2b0bf 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -103,9 +103,29 @@
      * the consumer of an presenter's views may choose to cache views offscreen while they
      * are not visible, attaching an detaching them as appropriate.</p>
      *
+     * Any view property animations should be cancelled here or the view may fail
+     * to be recycled.
+     *
      * @param holder Holder of the view being detached
      */
     public void onViewDetachedFromWindow(ViewHolder holder) {
+        // If there are view property animations running then RecyclerView won't recycle.
+        cancelAnimationsRecursive(holder.view);
+    }
+
+    /**
+     * Utility method for removing all running animations on a view.
+     */
+    protected static void cancelAnimationsRecursive(View view) {
+        if (view.hasTransientState()) {
+            view.animate().cancel();
+            if (view instanceof ViewGroup) {
+                final int count = ((ViewGroup) view).getChildCount();
+                for (int i = 0; view.hasTransientState() && i < count; i++) {
+                    cancelAnimationsRecursive(((ViewGroup) view).getChildAt(i));
+                }
+            }
+        }
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index 27f1975..1c7ed3d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -415,6 +415,7 @@
         if (vh.mHeaderViewHolder != null) {
             mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
         }
+        cancelAnimationsRecursive(vh.view);
     }
 
     /**