Merge "Rework background manager and add TestCases"
diff --git a/api/current.txt b/api/current.txt
index 1ce5aca..1cdcbc0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1227,16 +1227,19 @@
   public final class BackgroundManager {
     method public void attach(android.view.Window);
     method public void attachToView(android.view.View);
+    method public void clearDrawable();
     method public final int getColor();
-    method public android.graphics.drawable.Drawable getDefaultDimLayer();
-    method public android.graphics.drawable.Drawable getDimLayer();
+    method public deprecated android.graphics.drawable.Drawable getDefaultDimLayer();
+    method public deprecated android.graphics.drawable.Drawable getDimLayer();
     method public android.graphics.drawable.Drawable getDrawable();
     method public static android.support.v17.leanback.app.BackgroundManager getInstance(android.app.Activity);
     method public boolean isAttached();
+    method public boolean isAutoReleaseOnStop();
     method public void release();
+    method public void setAutoReleaseOnStop(boolean);
     method public void setBitmap(android.graphics.Bitmap);
     method public void setColor(int);
-    method public void setDimLayer(android.graphics.drawable.Drawable);
+    method public deprecated void setDimLayer(android.graphics.drawable.Drawable);
     method public void setDrawable(android.graphics.drawable.Drawable);
     method public void setThemeDrawableResourceId(int);
   }
@@ -1329,6 +1332,7 @@
     method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
     method public int getSelectedPosition();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
     method public final boolean isHeadersTransitionOnBackEnabled();
     method public boolean isInHeadersTransition();
     method public boolean isShowingHeaders();
@@ -1402,6 +1406,7 @@
 
   public static class BrowseFragment.MainFragmentRowsAdapter<T extends android.app.Fragment> {
     ctor public BrowseFragment.MainFragmentRowsAdapter(T);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
     method public final T getFragment();
     method public int getSelectedPosition();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
@@ -1431,6 +1436,7 @@
     method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
     method public int getSelectedPosition();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
     method public final boolean isHeadersTransitionOnBackEnabled();
     method public boolean isInHeadersTransition();
     method public boolean isShowingHeaders();
@@ -1504,6 +1510,7 @@
 
   public static class BrowseSupportFragment.MainFragmentRowsAdapter<T extends android.support.v4.app.Fragment> {
     ctor public BrowseSupportFragment.MainFragmentRowsAdapter(T);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
     method public final T getFragment();
     method public int getSelectedPosition();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
@@ -2084,6 +2091,7 @@
     ctor public RowsFragment();
     method public deprecated void enableRowScaling(boolean);
     method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
     method public android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
     method public android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
     method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
@@ -2109,6 +2117,7 @@
     ctor public RowsSupportFragment();
     method public deprecated void enableRowScaling(boolean);
     method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
     method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
     method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
     method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
@@ -3656,6 +3665,8 @@
     method public final android.support.v17.leanback.widget.Row getRow();
     method public final java.lang.Object getRowObject();
     method public final float getSelectLevel();
+    method public java.lang.Object getSelectedItem();
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder getSelectedItemViewHolder();
     method public final boolean isExpanded();
     method public final boolean isSelected();
     method public final void setActivated(boolean);
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BackgroundHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BackgroundHelper.java
index 54e851d..43506cd 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BackgroundHelper.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BackgroundHelper.java
@@ -25,7 +25,13 @@
 import android.support.v17.leanback.app.BackgroundManager;
 import android.support.v4.content.ContextCompat;
 import android.util.Log;
+import android.view.View;
 
+/**
+ * App uses BackgroundHelper for each Activity, it wraps BackgroundManager and provides:
+ * 1. AsyncTask to load bitmap in background thread.
+ * 2. Using a BitmapCache to cache loaded bitmaps.
+ */
 public class BackgroundHelper {
 
     private static final String TAG = "BackgroundHelper";
@@ -36,37 +42,73 @@
     // in case multiple backgrounds are set in quick succession.
     private static final int SET_BACKGROUND_DELAY_MS = 100;
 
+    /**
+     * An very simple example of BitmapCache.
+     */
+    public static class BitmapCache {
+        Bitmap mLastBitmap;
+        Object mLastToken;
+
+        // Singleton BitmapCache shared by multiple activities/backgroundHelper.
+        static BitmapCache sInstance = new BitmapCache();
+
+        private BitmapCache() {
+        }
+
+        /**
+         * Get cached bitmap by token, returns null if missing cache.
+         */
+        public Bitmap getCache(Object token) {
+            if (token == null ? mLastToken == null : token.equals(mLastToken)) {
+                if (DEBUG) Log.v(TAG, "hitCache token:" + token + " " + mLastBitmap);
+                return mLastBitmap;
+            }
+            return null;
+        }
+
+        /**
+         * Add cached bitmap.
+         */
+        public void putCache(Object token, Bitmap bitmap) {
+            if (DEBUG) Log.v(TAG, "putCache token:" + token + " " + bitmap);
+            mLastToken = token;
+            mLastBitmap = bitmap;
+        }
+
+        /**
+         * Add singleton of BitmapCache shared across activities.
+         */
+        public static BitmapCache getInstance() {
+            return sInstance;
+        }
+    }
+
+
     static class Request {
         Object mImageToken;
-        Activity mActivity;
         Bitmap mResult;
 
-        Request(Activity activity, Object imageToken) {
-            mActivity = activity;
+        Request(Object imageToken) {
             mImageToken = imageToken;
         }
     }
 
-    public BackgroundHelper() {
+    public BackgroundHelper(Activity activity) {
         if (DEBUG && !ENABLED) Log.v(TAG, "BackgroundHelper: disabled");
+        mActivity = activity;
     }
 
     class LoadBackgroundRunnable implements Runnable {
         Request mRequest;
 
-        LoadBackgroundRunnable(Activity activity, Object imageToken) {
-            mRequest = new Request(activity, imageToken);
+        LoadBackgroundRunnable(Object imageToken) {
+            mRequest = new Request(imageToken);
         }
 
         @Override
         public void run() {
-            if (mTask != null) {
-                if (DEBUG) Log.v(TAG, "Cancelling task");
-                mTask.cancel(true);
-            }
             if (DEBUG) Log.v(TAG, "Executing task");
-            mTask = new LoadBitmapTask();
-            mTask.execute(mRequest);
+            new LoadBitmapTask().execute(mRequest);
             mRunnable = null;
         }
     }
@@ -78,7 +120,7 @@
             if (DEBUG) Log.v(TAG, "doInBackground cancelled " + cancelled);
             Request request = params[0];
             if (!cancelled) {
-                request.mResult = loadBitmap(request.mActivity, request.mImageToken);
+                request.mResult = loadBitmap(request.mImageToken);
             }
             return request;
         }
@@ -86,10 +128,8 @@
         @Override
         protected void onPostExecute(Request request) {
             if (DEBUG) Log.v(TAG, "onPostExecute");
-            applyBackground(request.mActivity, request.mResult);
-            if (mTask == this) {
-                mTask = null;
-            }
+            BitmapCache.getInstance().putCache(request.mImageToken, request.mResult);
+            mBackgroundManager.setBitmap(request.mResult);
         }
 
         @Override
@@ -97,11 +137,11 @@
             if (DEBUG) Log.v(TAG, "onCancelled");
         }
 
-        private Bitmap loadBitmap(Activity activity, Object imageToken) {
+        private Bitmap loadBitmap(Object imageToken) {
             if (imageToken instanceof Integer) {
                 final int resourceId = (Integer) imageToken;
                 if (DEBUG) Log.v(TAG, "load resourceId " + resourceId);
-                Drawable drawable = ContextCompat.getDrawable(activity, resourceId);
+                Drawable drawable = ContextCompat.getDrawable(mActivity, resourceId);
                 if (drawable instanceof BitmapDrawable) {
                     return ((BitmapDrawable) drawable).getBitmap();
                 }
@@ -109,54 +149,93 @@
             return null;
         }
 
-        private void applyBackground(Activity activity, Bitmap bitmap) {
-            BackgroundManager backgroundManager = BackgroundManager.getInstance(activity);
-            if (backgroundManager == null || !backgroundManager.isAttached()) {
-                return;
-            }
-            backgroundManager.setBitmap(bitmap);
-        }
     }
 
-    private LoadBackgroundRunnable mRunnable;
-    private LoadBitmapTask mTask;
+    final Activity mActivity;
+    BackgroundManager mBackgroundManager;
+    LoadBackgroundRunnable mRunnable;
 
     // Allocate a dedicated handler because there may be no view available
     // when setBackground is invoked.
-    private Handler mHandler = new Handler();
+    static Handler sHandler = new Handler();
 
-    public void setBackground(Activity activity, Object imageToken) {
+    void createBackgroundManagerIfNeeded() {
+        if (mBackgroundManager == null) {
+            mBackgroundManager = BackgroundManager.getInstance(mActivity);
+        }
+    }
+
+    /**
+     * Attach BackgroundManager to activity window.
+     */
+    public void attachToWindow() {
         if (!ENABLED) {
             return;
         }
+        if (DEBUG) Log.v(TAG, "attachToWindow " + mActivity);
+        createBackgroundManagerIfNeeded();
+        mBackgroundManager.attach(mActivity.getWindow());
+    }
+
+    /**
+     * Attach BackgroundManager to a view inside activity.
+     */
+    public void attachToView(View backgroundView) {
+        if (!ENABLED) {
+            return;
+        }
+        if (DEBUG) Log.v(TAG, "attachToView " + mActivity + " " + backgroundView);
+        createBackgroundManagerIfNeeded();
+        mBackgroundManager.attachToView(backgroundView);
+    }
+
+    /**
+     * Sets a background bitmap. It will look up the cache first if missing, an AsyncTask will
+     * will be launched to load the bitmap.
+     */
+    public void setBackground(Object imageToken) {
+        if (!ENABLED) {
+            return;
+        }
+        if (DEBUG) Log.v(TAG, "set imageToken " + imageToken + " to " + mActivity);
+        createBackgroundManagerIfNeeded();
+        if (imageToken == null) {
+            mBackgroundManager.setDrawable(null);
+            return;
+        }
+        Bitmap cachedBitmap = BitmapCache.getInstance().getCache(imageToken);
+        if (cachedBitmap != null) {
+            mBackgroundManager.setBitmap(cachedBitmap);
+            return;
+        }
         if (mRunnable != null) {
-            mHandler.removeCallbacks(mRunnable);
+            sHandler.removeCallbacks(mRunnable);
         }
-        mRunnable = new LoadBackgroundRunnable(activity, imageToken);
-        mHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS);
+        mRunnable = new LoadBackgroundRunnable(imageToken);
+        sHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS);
     }
 
-    public void setDrawable(Activity activity, Drawable drawable) {
-        BackgroundManager backgroundManager = BackgroundManager.getInstance(activity);
-        if (!backgroundManager.isAttached()) {
-            backgroundManager.attachToView(activity.findViewById(R.id.details_background_view));
-        }
-        backgroundManager.setDrawable(drawable);
-    }
-
-    static public void attach(Activity activity) {
+    /**
+     * Clear Drawable.
+     */
+    public void clearDrawable() {
         if (!ENABLED) {
             return;
         }
-        if (DEBUG) Log.v(TAG, "attach to activity " + activity);
-        BackgroundManager.getInstance(activity).attach(activity.getWindow());
+        if (DEBUG) Log.v(TAG, "clearDrawable to " + mActivity);
+        createBackgroundManagerIfNeeded();
+        mBackgroundManager.clearDrawable();
     }
 
-    static public void release(Activity activity) {
+    /**
+     * Directly sets a Drawable as background.
+     */
+    public void setDrawable(Drawable drawable) {
         if (!ENABLED) {
             return;
         }
-        if (DEBUG) Log.v(TAG, "release from activity " + activity);
-        BackgroundManager.getInstance(activity).release();
+        if (DEBUG) Log.v(TAG, "setDrawable " + drawable + " to " + mActivity);
+        createBackgroundManagerIfNeeded();
+        mBackgroundManager.setDrawable(drawable);
     }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseActivity.java
index 38f2fb8..d91aa17 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseActivity.java
@@ -24,15 +24,4 @@
         setContentView(R.layout.browse);
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        BackgroundHelper.attach(this);
-    }
-
-    @Override
-    public void onStop() {
-        BackgroundHelper.release(this);
-        super.onStop();
-    }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorActivity.java
index 9b978f9..12b1177 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorActivity.java
@@ -36,18 +36,6 @@
         testError();
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        BackgroundHelper.attach(this);
-    }
-
-    @Override
-    public void onStop() {
-        BackgroundHelper.release(this);
-        super.onStop();
-    }
-
     private void testError() {
         Handler handler = new Handler();
         handler.postDelayed(new Runnable() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorSupportActivity.java
index 963325e..79ef723 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseErrorSupportActivity.java
@@ -39,18 +39,6 @@
         testError();
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        BackgroundHelper.attach(this);
-    }
-
-    @Override
-    public void onStop() {
-        BackgroundHelper.release(this);
-        super.onStop();
-    }
-
     private void testError() {
         Handler handler = new Handler();
         handler.postDelayed(new Runnable() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseFragment.java
index 378b7b5..6c1ce85 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseFragment.java
@@ -14,6 +14,7 @@
 package com.example.android.leanback;
 
 import android.app.Fragment;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
@@ -49,7 +50,7 @@
     private static final long HEADER_ID3 = 1003;
 
     private ArrayObjectAdapter mRowsAdapter;
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
 
     // For good performance, it's important to use a single instance of
     // a card presenter for all rows using that presenter.
@@ -65,6 +66,9 @@
         Log.i(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+        mBackgroundHelper.attachToWindow();
+
         setBadgeDrawable(ResourcesCompat.getDrawable(getActivity().getResources(),
                 R.drawable.ic_title, getActivity().getTheme()));
         setTitle("Leanback Sample App");
@@ -84,12 +88,13 @@
                     RowPresenter.ViewHolder rowViewHolder, Row row) {
                 Log.i(TAG, "onItemSelected: " + item + " row " + row);
 
-                if (isShowingHeaders()) {
-                    mBackgroundHelper.setBackground(getActivity(), null);
-                } else if (item instanceof PhotoItem) {
-                    mBackgroundHelper.setBackground(
-                            getActivity(), ((PhotoItem) item).getImageResourceId());
-                }
+                updateBackgroundToSelection();
+            }
+        });
+        setBrowseTransitionListener(new BrowseTransitionListener() {
+            @Override
+            public void onHeadersTransitionStop(boolean withHeaders) {
+                updateBackgroundToSelection();
             }
         });
         if (TEST_ENTRANCE_TRANSITION) {
@@ -110,6 +115,26 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        updateBackgroundToSelection();
+    }
+
+    void updateBackgroundToSelection() {
+        if (!isShowingHeaders()) {
+            RowPresenter.ViewHolder rowViewHolder = getSelectedRowViewHolder();
+            Object item = rowViewHolder == null ? null : rowViewHolder.getSelectedItem();
+            if (item != null) {
+                mBackgroundHelper.setBackground(((PhotoItem) item).getImageResourceId());
+            } else {
+                mBackgroundHelper.clearDrawable();
+            }
+        } else {
+            mBackgroundHelper.clearDrawable();
+        }
+    }
+
+    @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         return super.onCreateView(inflater, container, savedInstanceState);
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportActivity.java
index e96c220..83cb9e2 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportActivity.java
@@ -27,15 +27,4 @@
         setContentView(R.layout.browse_support);
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        BackgroundHelper.attach(this);
-    }
-
-    @Override
-    public void onStop() {
-        BackgroundHelper.release(this);
-        super.onStop();
-    }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
index 0773d47..1a8ab1f 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
@@ -52,7 +52,7 @@
     private static final long HEADER_ID3 = 1003;
 
     private ArrayObjectAdapter mRowsAdapter;
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
 
     // For good performance, it's important to use a single instance of
     // a card presenter for all rows using that presenter.
@@ -68,6 +68,9 @@
         Log.i(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+        mBackgroundHelper.attachToWindow();
+
         setBadgeDrawable(ResourcesCompat.getDrawable(getActivity().getResources(),
                 R.drawable.ic_title, getActivity().getTheme()));
         setTitle("Leanback Sample App");
@@ -87,12 +90,13 @@
                     RowPresenter.ViewHolder rowViewHolder, Row row) {
                 Log.i(TAG, "onItemSelected: " + item + " row " + row);
 
-                if (isShowingHeaders()) {
-                    mBackgroundHelper.setBackground(getActivity(), null);
-                } else if (item instanceof PhotoItem) {
-                    mBackgroundHelper.setBackground(
-                            getActivity(), ((PhotoItem) item).getImageResourceId());
-                }
+                updateBackgroundToSelection();
+            }
+        });
+        setBrowseTransitionListener(new BrowseTransitionListener() {
+            @Override
+            public void onHeadersTransitionStop(boolean withHeaders) {
+                updateBackgroundToSelection();
             }
         });
         if (TEST_ENTRANCE_TRANSITION) {
@@ -113,6 +117,26 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        updateBackgroundToSelection();
+    }
+
+    void updateBackgroundToSelection() {
+        if (!isShowingHeaders()) {
+            RowPresenter.ViewHolder rowViewHolder = getSelectedRowViewHolder();
+            Object item = rowViewHolder == null ? null : rowViewHolder.getSelectedItem();
+            if (item != null) {
+                mBackgroundHelper.setBackground(((PhotoItem) item).getImageResourceId());
+            } else {
+                mBackgroundHelper.clearDrawable();
+            }
+        } else {
+            mBackgroundHelper.clearDrawable();
+        }
+    }
+
+    @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         return super.onCreateView(inflater, container, savedInstanceState);
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
index cae115f..09d9526 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
@@ -47,7 +47,7 @@
     private ArrayObjectAdapter mRowsAdapter;
     private PhotoItem mPhotoItem;
     final CardPresenter cardPresenter = new CardPresenter();
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
 
     private static final int ACTION_PLAY = 1;
     private static final int ACTION_RENT = 2;
@@ -68,6 +68,9 @@
         Log.i(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+        mBackgroundHelper.attachToWindow();
+
         Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(),
                 R.drawable.ic_title, context.getTheme()));
@@ -216,8 +219,7 @@
     public void onStart() {
         super.onStart();
         if (mPhotoItem != null) {
-            mBackgroundHelper.setBackground(
-                    getActivity(), mPhotoItem.getImageResourceId());
+            mBackgroundHelper.setBackground(mPhotoItem.getImageResourceId());
         }
     }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
index eb1e201..28a61d9 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
@@ -50,7 +50,7 @@
     private ArrayObjectAdapter mRowsAdapter;
     private PhotoItem mPhotoItem;
     final CardPresenter cardPresenter = new CardPresenter();
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
 
     private static final int ACTION_PLAY = 1;
     private static final int ACTION_RENT = 2;
@@ -71,6 +71,9 @@
         Log.i(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+        mBackgroundHelper.attachToWindow();
+
         Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(),
                 R.drawable.ic_title, context.getTheme()));
@@ -219,8 +222,7 @@
     public void onStart() {
         super.onStart();
         if (mPhotoItem != null) {
-            mBackgroundHelper.setBackground(
-                    getActivity(), mPhotoItem.getImageResourceId());
+            mBackgroundHelper.setBackground(mPhotoItem.getImageResourceId());
         }
     }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
index 7a77766..fae9b2b 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
@@ -43,7 +43,9 @@
 import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.Toast;
 
 public class NewDetailsFragment extends android.support.v17.leanback.app.DetailsFragment {
@@ -73,7 +75,7 @@
     private FullWidthDetailsOverviewSharedElementHelper mHelper;
     private DetailsBackgroundParallaxHelper mParallaxHelper;
     private DetailsFragmentVideoHelper mVideoHelper;
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
     private int mBitmapMinVerticalOffset = -100;
     private MediaPlayerGlue mMediaPlayerGlue;
     private VideoFragment mVideoFragment;
@@ -92,6 +94,8 @@
         super.onCreate(savedInstanceState);
         initializeTest();
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+
         final Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
                 context.getTheme()));
@@ -200,6 +204,14 @@
 
     }
 
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        mBackgroundHelper.attachToView(view.findViewById(R.id.details_background_view));
+        return view;
+    }
+
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
 
@@ -281,7 +293,7 @@
         mMediaPlayerGlue.setTitle("Diving with Sharks");
         mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
 
-        mBackgroundHelper.setDrawable(getActivity(), mParallaxHelper.getDrawable());
+        mBackgroundHelper.setDrawable(mParallaxHelper.getDrawable());
         mParallaxHelper.setCoverImageBitmap(bitmap);
     }
 
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
index 79ccb75..d34e17a 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
@@ -46,7 +46,9 @@
 import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.Toast;
 
 public class NewDetailsSupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
@@ -76,7 +78,7 @@
     private FullWidthDetailsOverviewSharedElementHelper mHelper;
     private DetailsBackgroundParallaxHelper mParallaxHelper;
     private DetailsFragmentVideoHelper mVideoHelper;
-    private BackgroundHelper mBackgroundHelper = new BackgroundHelper();
+    private BackgroundHelper mBackgroundHelper;
     private int mBitmapMinVerticalOffset = -100;
     private MediaPlayerGlue mMediaPlayerGlue;
     private VideoSupportFragment mVideoSupportFragment;
@@ -95,6 +97,8 @@
         super.onCreate(savedInstanceState);
         initializeTest();
 
+        mBackgroundHelper = new BackgroundHelper(getActivity());
+
         final Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
                 context.getTheme()));
@@ -203,6 +207,14 @@
 
     }
 
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        mBackgroundHelper.attachToView(view.findViewById(R.id.details_background_view));
+        return view;
+    }
+
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
 
@@ -284,7 +296,7 @@
         mMediaPlayerGlue.setTitle("Diving with Sharks");
         mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
 
-        mBackgroundHelper.setDrawable(getActivity(), mParallaxHelper.getDrawable());
+        mBackgroundHelper.setDrawable(mParallaxHelper.getDrawable());
         mParallaxHelper.setCoverImageBitmap(bitmap);
     }
 
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
index a3af52f..dde7bd2 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
@@ -1,3 +1,4 @@
+// CHECKSTYLE:OFF Generated code
 /* This file is auto-generated from OnboardingDemoFragment.java.  DO NOT MODIFY. */
 
 /*
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoSupportActivity.java
index 64759d4..751df64 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/VideoSupportActivity.java
@@ -1,3 +1,4 @@
+// CHECKSTYLE:OFF Generated code
 /* This file is auto-generated from OnboardingDemoFragment.java.  DO NOT MODIFY. */
 
 /*
diff --git a/v17/leanback/res/drawable/lb_background.xml b/v17/leanback/res/drawable/lb_background.xml
index 4732bc1..305a228 100644
--- a/v17/leanback/res/drawable/lb_background.xml
+++ b/v17/leanback/res/drawable/lb_background.xml
@@ -15,11 +15,8 @@
      limitations under the License.
 -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:drawable="@color/lb_grey" android:id="@+id/background_theme"/>
-    <item android:drawable="@color/lb_grey" android:id="@+id/background_color"/>
     <!-- Replaced at runtime with image to fade out -->
     <item android:drawable="@color/lb_grey" android:id="@+id/background_imageout"/>
     <!-- Replaced at runtime with image to fade in -->
     <item android:drawable="@color/lb_grey" android:id="@+id/background_imagein"/>
-    <item android:drawable="@color/lb_background_protection" android:id="@+id/background_dim" />
 </layer-list>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
index d3547f3..6b442c1 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
@@ -13,18 +13,17 @@
  */
 package android.support.v17.leanback.app;
 
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
 import android.app.Fragment;
 import android.support.annotation.RestrictTo;
 
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
 /**
  * Fragment used by the background manager.
  * @hide
  */
 @RestrictTo(GROUP_ID)
-public final class BackgroundFragment extends Fragment implements
-        BackgroundManager.FragmentStateQueriable {
+public final class BackgroundFragment extends Fragment {
     private BackgroundManager mBackgroundManager;
 
     void setBackgroundManager(BackgroundManager backgroundManager) {
@@ -58,6 +57,14 @@
     }
 
     @Override
+    public void onStop() {
+        if (mBackgroundManager != null) {
+            mBackgroundManager.onStop();
+        }
+        super.onStop();
+    }
+
+    @Override
     public void onDestroy() {
         super.onDestroy();
         // mBackgroundManager might be null:
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index 8809ef4..428d702 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -26,16 +26,17 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
 import android.os.Handler;
 import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.BackgroundHelper;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.view.animation.FastOutLinearInInterpolator;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -83,15 +84,10 @@
 // support continuity between fragments of different applications if desired.
 public final class BackgroundManager {
 
-    interface FragmentStateQueriable {
-        boolean isResumed();
-    }
-
     static final String TAG = "BackgroundManager";
     static final boolean DEBUG = false;
 
     static final int FULL_ALPHA = 255;
-    private static final int DIM_ALPHA_ON_SOLID = (int) (0.8f * FULL_ALPHA);
     private static final int CHANGE_BG_DELAY_MS = 500;
     private static final int FADE_DURATION = 500;
 
@@ -107,35 +103,52 @@
 
     Context mContext;
     Handler mHandler;
-    private Window mWindow;
     private WindowManager mWindowManager;
     private View mBgView;
     private BackgroundContinuityService mService;
     private int mThemeDrawableResourceId;
-    private FragmentStateQueriable mFragmentState;
+    private BackgroundFragment mFragmentState;
+    private boolean mAutoReleaseOnStop = true;
 
     private int mHeightPx;
     private int mWidthPx;
+    int mBackgroundColor;
     Drawable mBackgroundDrawable;
-    private int mBackgroundColor;
     private boolean mAttached;
     private long mLastSetTime;
 
     private final Interpolator mAccelerateInterpolator;
     private final Interpolator mDecelerateInterpolator;
-    private final ValueAnimator mAnimator;
-    private final ValueAnimator mDimAnimator;
+    final ValueAnimator mAnimator;
 
-    private static class BitmapDrawable extends Drawable {
+    static class BitmapDrawable extends Drawable {
 
-        static class ConstantState extends Drawable.ConstantState {
-            Bitmap mBitmap;
-            Matrix mMatrix;
-            Paint mPaint;
+        static final class ConstantState extends Drawable.ConstantState {
+            final Bitmap mBitmap;
+            final Matrix mMatrix;
+            final Paint mPaint = new Paint();
+
+            ConstantState(Bitmap bitmap, Matrix matrix) {
+                mBitmap = bitmap;
+                mMatrix = matrix != null ? matrix : new Matrix();
+                mPaint.setFilterBitmap(true);
+            }
+
+            ConstantState(ConstantState copyFrom) {
+                mBitmap = copyFrom.mBitmap;
+                mMatrix = copyFrom.mMatrix != null ? new Matrix(copyFrom.mMatrix) : new Matrix();
+                if (copyFrom.mPaint.getAlpha() != FULL_ALPHA) {
+                    mPaint.setAlpha(copyFrom.mPaint.getAlpha());
+                }
+                if (copyFrom.mPaint.getColorFilter() != null) {
+                    mPaint.setColorFilter(copyFrom.mPaint.getColorFilter());
+                }
+                mPaint.setFilterBitmap(true);
+            }
 
             @Override
             public Drawable newDrawable() {
-                return new BitmapDrawable(null, mBitmap, mMatrix);
+                return new BitmapDrawable(this);
             }
 
             @Override
@@ -144,17 +157,19 @@
             }
         }
 
-        private ConstantState mState = new ConstantState();
+        ConstantState mState;
+        boolean mMutated;
 
         BitmapDrawable(Resources resources, Bitmap bitmap) {
             this(resources, bitmap, null);
         }
 
         BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
-            mState.mBitmap = bitmap;
-            mState.mMatrix = matrix != null ? matrix : new Matrix();
-            mState.mPaint = new Paint();
-            mState.mPaint.setFilterBitmap(true);
+            mState = new ConstantState(bitmap, matrix);
+        }
+
+        BitmapDrawable(ConstantState state) {
+            mState = state;
         }
 
         Bitmap getBitmap() {
@@ -179,6 +194,7 @@
 
         @Override
         public void setAlpha(int alpha) {
+            mutate();
             if (mState.mPaint.getAlpha() != alpha) {
                 mState.mPaint.setAlpha(alpha);
                 invalidateSelf();
@@ -191,7 +207,9 @@
          */
         @Override
         public void setColorFilter(ColorFilter cf) {
+            mutate();
             mState.mPaint.setColorFilter(cf);
+            invalidateSelf();
         }
 
         public ColorFilter getColorFilter() {
@@ -202,59 +220,43 @@
         public ConstantState getConstantState() {
             return mState;
         }
+
+        @NonNull
+        @Override
+        public Drawable mutate() {
+            if (!mMutated) {
+                mMutated = true;
+                mState = new ConstantState(mState);
+            }
+            return this;
+        }
     }
 
-    static class DrawableWrapper {
-        private int mAlpha = FULL_ALPHA;
-        private Drawable mDrawable;
-        private ColorFilter mColorFilter;
+    static final class DrawableWrapper {
+        int mAlpha = FULL_ALPHA;
+        final Drawable mDrawable;
 
         public DrawableWrapper(Drawable drawable) {
             mDrawable = drawable;
-            updateAlpha();
-            updateColorFilter();
         }
         public DrawableWrapper(DrawableWrapper wrapper, Drawable drawable) {
             mDrawable = drawable;
-            mAlpha = wrapper.getAlpha();
-            updateAlpha();
-            mColorFilter = wrapper.getColorFilter();
-            updateColorFilter();
+            mAlpha = wrapper.mAlpha;
         }
 
         public Drawable getDrawable() {
             return mDrawable;
         }
-        public void setAlpha(int alpha) {
-            mAlpha = alpha;
-            updateAlpha();
-        }
-        public int getAlpha() {
-            return mAlpha;
-        }
-        private void updateAlpha() {
-            mDrawable.setAlpha(mAlpha);
-        }
-
-        public ColorFilter getColorFilter() {
-            return mColorFilter;
-        }
-        public void setColorFilter(ColorFilter colorFilter) {
-            mColorFilter = colorFilter;
-            updateColorFilter();
-        }
-        private void updateColorFilter() {
-            mDrawable.setColorFilter(mColorFilter);
-        }
 
         public void setColor(int color) {
             ((ColorDrawable) mDrawable).setColor(color);
         }
     }
 
-    static class TranslucentLayerDrawable extends LayerDrawable {
-        private DrawableWrapper[] mWrapper;
-        private Paint mPaint = new Paint();
+    static final class TranslucentLayerDrawable extends LayerDrawable {
+        DrawableWrapper[] mWrapper;
+        int mAlpha = FULL_ALPHA;
+        boolean mSuspendInvalidation;
 
         public TranslucentLayerDrawable(Drawable[] drawables) {
             super(drawables);
@@ -267,20 +269,20 @@
 
         @Override
         public void setAlpha(int alpha) {
-            if (mPaint.getAlpha() != alpha) {
-                int previousAlpha = mPaint.getAlpha();
-                mPaint.setAlpha(alpha);
+            mAlpha = alpha;
+            invalidateSelf();
+        }
+
+        void setWrapperAlpha(int wrapperIndex, int alpha) {
+            if (mWrapper[wrapperIndex] != null) {
+                mWrapper[wrapperIndex].mAlpha = alpha;
                 invalidateSelf();
-                onAlphaChanged(previousAlpha, alpha);
             }
         }
 
         // Queried by system transitions
         public int getAlpha() {
-            return mPaint.getAlpha();
-        }
-
-        protected void onAlphaChanged(int oldAlpha, int newAlpha) {
+            return mAlpha;
         }
 
         @Override
@@ -292,7 +294,6 @@
                     mWrapper[i] = new DrawableWrapper(mWrapper[i], getDrawable(i));
                 }
             }
-            invalidateSelf();
             return drawable;
         }
 
@@ -323,115 +324,80 @@
             for (int i = 0; i < getNumberOfLayers(); i++) {
                 if (getId(i) == id) {
                     mWrapper[i] = null;
-                    super.setDrawableByLayerId(id, createEmptyDrawable(context));
+                    if (!(getDrawable(i) instanceof EmptyDrawable)) {
+                        super.setDrawableByLayerId(id, createEmptyDrawable(context));
+                    }
                     break;
                 }
             }
         }
 
-        public DrawableWrapper findWrapperById(int id) {
+        public int findWrapperIndexById(int id) {
             for (int i = 0; i < getNumberOfLayers(); i++) {
                 if (getId(i) == id) {
-                    return mWrapper[i];
+                    return i;
                 }
             }
-            return null;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            if (mPaint.getAlpha() < FULL_ALPHA) {
-                canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(),
-                        mPaint, Canvas.ALL_SAVE_FLAG);
-            }
-            super.draw(canvas);
-            if (mPaint.getAlpha() < FULL_ALPHA) {
-                canvas.restore();
-            }
-        }
-    }
-
-    /**
-     * Optimizes drawing when the dim drawable is an alpha-only color and imagein is opaque.
-     * When the layer drawable is translucent (activity transition) then we can avoid the slow
-     * saveLayer/restore draw path.
-     */
-    private class OptimizedTranslucentLayerDrawable extends TranslucentLayerDrawable {
-        private PorterDuffColorFilter mColorFilter;
-        private boolean mUpdatingColorFilter;
-
-        public OptimizedTranslucentLayerDrawable(Drawable[] drawables) {
-            super(drawables);
-        }
-
-        @Override
-        protected void onAlphaChanged(int oldAlpha, int newAlpha) {
-            if (newAlpha == FULL_ALPHA && oldAlpha < FULL_ALPHA) {
-                if (DEBUG) Log.v(TAG, "transition complete");
-                postChangeRunnable();
-            }
-        }
-
-        @Override
-        public void invalidateSelf() {
-            super.invalidateSelf();
-            updateColorFilter();
+            return -1;
         }
 
         @Override
         public void invalidateDrawable(Drawable who) {
-            if (!mUpdatingColorFilter) {
-                invalidateSelf();
+            // Prevent invalidate when temporarily change child drawable's alpha in draw()
+            if (!mSuspendInvalidation) {
+                super.invalidateDrawable(who);
             }
         }
 
-        private void updateColorFilter() {
-            DrawableWrapper dimWrapper = findWrapperById(R.id.background_dim);
-            DrawableWrapper imageInWrapper = findWrapperById(R.id.background_imagein);
-            DrawableWrapper imageOutWrapper = findWrapperById(R.id.background_imageout);
-
-            mColorFilter = null;
-            if (imageInWrapper != null && imageInWrapper.getAlpha() == FULL_ALPHA
-                    && dimWrapper.getDrawable() instanceof ColorDrawable) {
-                int dimColor = ((ColorDrawable) dimWrapper.getDrawable()).getColor();
-                if (Color.red(dimColor) == 0
-                        && Color.green(dimColor) == 0
-                        && Color.blue(dimColor) == 0) {
-                    int dimAlpha = 255 - Color.alpha(dimColor);
-                    int color = Color.argb(getAlpha(), dimAlpha, dimAlpha, dimAlpha);
-                    mColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);
-                }
-            }
-            mUpdatingColorFilter = true;
-            if (imageInWrapper != null) {
-                imageInWrapper.setColorFilter(mColorFilter);
-            }
-            if (imageOutWrapper != null) {
-                imageOutWrapper.setColorFilter(null);
-            }
-            mUpdatingColorFilter = false;
-        }
-
         @Override
         public void draw(Canvas canvas) {
-            DrawableWrapper imageInWrapper = findWrapperById(R.id.background_imagein);
-            if (imageInWrapper != null && imageInWrapper.getDrawable() != null
-                    && imageInWrapper.getColorFilter() != null) {
-                imageInWrapper.getDrawable().draw(canvas);
-            } else {
-                super.draw(canvas);
+            for (int i = 0; i < mWrapper.length; i++) {
+                final Drawable d;
+                // For each child drawable, we multiple Wrapper's alpha and LayerDrawable's alpha
+                // temporarily using mSuspendInvalidation to suppress invalidate event.
+                if (mWrapper[i] != null && (d = mWrapper[i].getDrawable()) != null) {
+                    int alpha = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                            ? DrawableCompat.getAlpha(d) : FULL_ALPHA;
+                    final int savedAlpha = alpha;
+                    int multiple = 0;
+                    if (mAlpha < FULL_ALPHA) {
+                        alpha = alpha * mAlpha;
+                        multiple++;
+                    }
+                    if (mWrapper[i].mAlpha < FULL_ALPHA) {
+                        alpha = alpha * mWrapper[i].mAlpha;
+                        multiple++;
+                    }
+                    if (multiple == 0) {
+                        d.draw(canvas);
+                    } else {
+                        if (multiple == 1) {
+                            alpha = alpha / FULL_ALPHA;
+                        } else if (multiple == 2) {
+                            alpha = alpha / (FULL_ALPHA * FULL_ALPHA);
+                        }
+                        try {
+                            mSuspendInvalidation = true;
+                            d.setAlpha(alpha);
+                            d.draw(canvas);
+                            d.setAlpha(savedAlpha);
+                        } finally {
+                            mSuspendInvalidation = false;
+                        }
+                    }
+                }
             }
         }
     }
 
-    private TranslucentLayerDrawable createOptimizedTranslucentLayerDrawable(
+    TranslucentLayerDrawable createTranslucentLayerDrawable(
             LayerDrawable layerDrawable) {
         int numChildren = layerDrawable.getNumberOfLayers();
         Drawable[] drawables = new Drawable[numChildren];
         for (int i = 0; i < numChildren; i++) {
             drawables[i] = layerDrawable.getDrawable(i);
         }
-        TranslucentLayerDrawable result = new OptimizedTranslucentLayerDrawable(drawables);
+        TranslucentLayerDrawable result = new TranslucentLayerDrawable(drawables);
         for (int i = 0; i < numChildren; i++) {
             result.setId(i, layerDrawable.getId(i));
         }
@@ -439,7 +405,8 @@
     }
 
     TranslucentLayerDrawable mLayerDrawable;
-    private Drawable mDimDrawable;
+    int mImageInWrapperIndex;
+    int mImageOutWrapperIndex;
     ChangeBackgroundRunnable mChangeRunnable;
     private boolean mChangeRunnablePending;
 
@@ -474,25 +441,8 @@
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             int fadeInAlpha = (Integer) animation.getAnimatedValue();
-            DrawableWrapper imageInWrapper = getImageInWrapper();
-            if (imageInWrapper != null) {
-                imageInWrapper.setAlpha(fadeInAlpha);
-            } else {
-                DrawableWrapper imageOutWrapper = getImageOutWrapper();
-                if (imageOutWrapper != null) {
-                    imageOutWrapper.setAlpha(255 - fadeInAlpha);
-                }
-            }
-        }
-    };
-
-    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            DrawableWrapper dimWrapper = getDimWrapper();
-            if (dimWrapper != null) {
-                dimWrapper.setAlpha((Integer) animation.getAnimatedValue());
+            if (mImageInWrapperIndex != -1) {
+                mLayerDrawable.setWrapperAlpha(mImageInWrapperIndex, fadeInAlpha);
             }
         }
     };
@@ -501,7 +451,7 @@
      * Shared memory continuity service.
      */
     private static class BackgroundContinuityService {
-        private static final String TAG = "BackgroundContinuityService";
+        private static final String TAG = "BackgroundContinuity";
         private static boolean DEBUG = BackgroundManager.DEBUG;
 
         private static BackgroundContinuityService sService = new BackgroundContinuityService();
@@ -544,6 +494,7 @@
         }
         public void setColor(int color) {
             mColor = color;
+            mDrawable = null;
         }
         public void setDrawable(Drawable drawable) {
             mDrawable = drawable;
@@ -569,6 +520,14 @@
         }
     }
 
+    Drawable getDefaultDrawable() {
+        if (mBackgroundColor != Color.TRANSPARENT) {
+            return new ColorDrawable(mBackgroundColor);
+        } else {
+            return getThemeDrawable();
+        }
+    }
+
     private Drawable getThemeDrawable() {
         Drawable drawable = null;
         if (mThemeDrawableResourceId != -1) {
@@ -619,9 +578,6 @@
         mAnimator.addUpdateListener(mAnimationUpdateListener);
         mAnimator.setInterpolator(defaultInterpolator);
 
-        mDimAnimator = new ValueAnimator();
-        mDimAnimator.addUpdateListener(mDimUpdateListener);
-
         TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
                 android.R.attr.windowBackground });
         mThemeDrawableResourceId = ta.getResourceId(0, -1);
@@ -652,22 +608,12 @@
 
     DrawableWrapper getImageInWrapper() {
         return mLayerDrawable == null
-                ? null : mLayerDrawable.findWrapperById(R.id.background_imagein);
+                ? null : mLayerDrawable.mWrapper[mImageInWrapperIndex];
     }
 
     DrawableWrapper getImageOutWrapper() {
         return mLayerDrawable == null
-                ? null : mLayerDrawable.findWrapperById(R.id.background_imageout);
-    }
-
-    DrawableWrapper getDimWrapper() {
-        return mLayerDrawable == null
-                ? null : mLayerDrawable.findWrapperById(R.id.background_dim);
-    }
-
-    private DrawableWrapper getColorWrapper() {
-        return mLayerDrawable == null
-                ? null : mLayerDrawable.findWrapperById(R.id.background_color);
+                ? null : mLayerDrawable.mWrapper[mImageOutWrapperIndex];
     }
 
     /**
@@ -675,21 +621,12 @@
      * At that point the view becomes visible.
      */
     void onActivityStart() {
-        if (mService == null) {
-            return;
-        }
-        if (mLayerDrawable == null) {
-            if (DEBUG) {
-                Log.v(TAG, "onActivityStart " + this + " released state, syncing with service");
-            }
-            syncWithService();
-        } else {
-            if (DEBUG) {
-                Log.v(TAG, "onActivityStart " + this + " updating service color "
-                        + mBackgroundColor + " drawable " + mBackgroundDrawable);
-            }
-            mService.setColor(mBackgroundColor);
-            mService.setDrawable(mBackgroundDrawable);
+        updateImmediate();
+    }
+
+    void onStop() {
+        if (isAutoReleaseOnStop()) {
+            release();
         }
     }
 
@@ -712,33 +649,6 @@
         updateImmediate();
     }
 
-    private void lazyInit() {
-        if (mLayerDrawable != null) {
-            return;
-        }
-
-        LayerDrawable layerDrawable = (LayerDrawable)
-                ContextCompat.getDrawable(mContext, R.drawable.lb_background).mutate();
-        mLayerDrawable = createOptimizedTranslucentLayerDrawable(layerDrawable);
-        BackgroundHelper.setBackgroundPreservingAlpha(mBgView, mLayerDrawable);
-
-        mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
-        mLayerDrawable.updateDrawable(R.id.background_theme, getThemeDrawable());
-
-        updateDimWrapper();
-    }
-
-    private void updateDimWrapper() {
-        if (mDimDrawable == null) {
-            mDimDrawable = getDefaultDimLayer();
-        }
-        Drawable dimDrawable = mDimDrawable.getConstantState().newDrawable(
-                mContext.getResources()).mutate();
-        if (mLayerDrawable != null) {
-            mLayerDrawable.updateDrawable(R.id.background_dim, dimDrawable);
-        }
-    }
-
     /**
      * Makes the background visible on the given Window. The background manager must be attached
      * when the background is set.
@@ -762,7 +672,6 @@
 
     private void attachBehindWindow(Window window) {
         if (DEBUG) Log.v(TAG, "attachBehindWindow " + window);
-        mWindow = window;
         mWindowManager = window.getWindowManager();
 
         WindowManager.LayoutParams params = new WindowManager.LayoutParams(
@@ -786,6 +695,9 @@
      * Adds the composite drawable to the given view.
      */
     public void attachToView(View sceneRoot) {
+        if (mAttached) {
+            throw new IllegalStateException("Already attached to " + mBgView);
+        }
         mBgView = sceneRoot;
         mAttached = true;
         syncWithService();
@@ -811,7 +723,6 @@
         }
 
         mWindowManager = null;
-        mWindow = null;
         mBgView = null;
         mAttached = false;
 
@@ -822,56 +733,48 @@
     }
 
     /**
-     * Release references to Drawables. Typically called to reduce memory
-     * overhead when not visible.
-     * <p>
-     * When an Activity is started, if the BackgroundManager has not been
-     * released, the continuity service is updated from the BackgroundManager
-     * state. If the BackgroundManager was released, the BackgroundManager
-     * inherits the current state from the continuity service.
+     * Release references to Drawable/Bitmap. Typically called in Activity onStop() to reduce memory
+     * overhead when not visible. It's app's responsibility to restore the drawable/bitmap in
+     * Activity onStart(). The method is automatically called in onStop() when
+     * {@link #isAutoReleaseOnStop()} is true.
+     * @see #setAutoReleaseOnStop(boolean)
      */
     public void release() {
         if (DEBUG) Log.v(TAG, "release " + this);
         if (mLayerDrawable != null) {
             mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
             mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
-            mLayerDrawable.clearDrawable(R.id.background_theme, mContext);
             mLayerDrawable = null;
         }
         if (mChangeRunnable != null) {
             mHandler.removeCallbacks(mChangeRunnable);
             mChangeRunnable = null;
         }
-        releaseBackgroundBitmap();
-    }
-
-    void releaseBackgroundBitmap() {
         mBackgroundDrawable = null;
     }
 
-    void setBackgroundDrawable(Drawable drawable) {
-        mBackgroundDrawable = drawable;
-        mService.setDrawable(mBackgroundDrawable);
-    }
-
     /**
      * Sets the drawable used as a dim layer.
+     * @deprecated No longer support dim layer.
      */
+    @Deprecated
     public void setDimLayer(Drawable drawable) {
-        mDimDrawable = drawable;
-        updateDimWrapper();
     }
 
     /**
      * Returns the drawable used as a dim layer.
+     * @deprecated No longer support dim layer.
      */
+    @Deprecated
     public Drawable getDimLayer() {
-        return mDimDrawable;
+        return null;
     }
 
     /**
      * Returns the default drawable used as a dim layer.
+     * @deprecated No longer support dim layer.
      */
+    @Deprecated
     public Drawable getDefaultDimLayer() {
         return ContextCompat.getDrawable(mContext, R.color.lb_background_protection);
     }
@@ -901,29 +804,33 @@
         }
     }
 
+    private void lazyInit() {
+        if (mLayerDrawable != null) {
+            return;
+        }
+
+        LayerDrawable layerDrawable = (LayerDrawable)
+                ContextCompat.getDrawable(mContext, R.drawable.lb_background).mutate();
+        mLayerDrawable = createTranslucentLayerDrawable(layerDrawable);
+        mImageInWrapperIndex = mLayerDrawable.findWrapperIndexById(R.id.background_imagein);
+        mImageOutWrapperIndex = mLayerDrawable.findWrapperIndexById(R.id.background_imageout);
+        BackgroundHelper.setBackgroundPreservingAlpha(mBgView, mLayerDrawable);
+    }
+
     private void updateImmediate() {
+        if (!mAttached) {
+            return;
+        }
         lazyInit();
 
-        DrawableWrapper colorWrapper = getColorWrapper();
-        if (colorWrapper != null) {
-            colorWrapper.setColor(mBackgroundColor);
-        }
-        DrawableWrapper dimWrapper = getDimWrapper();
-        if (dimWrapper != null) {
-            dimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID);
-        }
-        showWallpaper(mBackgroundColor == Color.TRANSPARENT);
-
         if (mBackgroundDrawable == null) {
-            mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
+            if (DEBUG) Log.v(TAG, "Use defefault background");
+            mLayerDrawable.updateDrawable(R.id.background_imagein, getDefaultDrawable());
         } else {
-            if (DEBUG) Log.v(TAG, "Background drawable is available");
-            mLayerDrawable.updateDrawable(
-                    R.id.background_imagein, mBackgroundDrawable);
-            if (dimWrapper != null) {
-                dimWrapper.setAlpha(FULL_ALPHA);
-            }
+            if (DEBUG) Log.v(TAG, "Background drawable is available " + mBackgroundDrawable);
+            mLayerDrawable.updateDrawable(R.id.background_imagein, mBackgroundDrawable);
         }
+        mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
     }
 
     /**
@@ -933,13 +840,13 @@
     public void setColor(@ColorInt int color) {
         if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));
 
+        mService.setColor(color);
         mBackgroundColor = color;
-        mService.setColor(mBackgroundColor);
-
-        DrawableWrapper colorWrapper = getColorWrapper();
-        if (colorWrapper != null) {
-            colorWrapper.setColor(mBackgroundColor);
+        mBackgroundDrawable = null;
+        if (mLayerDrawable == null) {
+            return;
         }
+        setDrawableInternal(getDefaultDrawable());
     }
 
     /**
@@ -950,7 +857,26 @@
      */
     public void setDrawable(Drawable drawable) {
         if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
-        setDrawableInternal(drawable);
+
+        mService.setDrawable(drawable);
+        mBackgroundDrawable = drawable;
+        if (mLayerDrawable == null) {
+            return;
+        }
+        if (drawable == null) {
+            setDrawableInternal(getDefaultDrawable());
+        } else {
+            setDrawableInternal(drawable);
+        }
+    }
+
+    /**
+     * Clears the Drawable set by {@link #setDrawable(Drawable)} or {@link #setBitmap(Bitmap)}.
+     * BackgroundManager will show a solid color set by {@link #setColor(int)} or theme drawable
+     * if color is not provided.
+     */
+    public void clearDrawable() {
+        setDrawable(null);
     }
 
     private void setDrawableInternal(Drawable drawable) {
@@ -967,17 +893,6 @@
             mChangeRunnable = null;
         }
 
-        // If layer drawable is null then the activity hasn't started yet.
-        // If the layer drawable alpha is zero then the activity transition hasn't started yet.
-        // In these cases we can update the background immediately and let activity transition
-        // fade it in.
-        if (mLayerDrawable == null || mLayerDrawable.getAlpha() == 0) {
-            if (DEBUG) Log.v(TAG, "setDrawableInternal null or alpha is zero");
-            setBackgroundDrawable(drawable);
-            updateImmediate();
-            return;
-        }
-
         mChangeRunnable = new ChangeBackgroundRunnable(drawable);
         mChangeRunnablePending = true;
 
@@ -1000,7 +915,7 @@
         }
 
         if (bitmap == null) {
-            setDrawableInternal(null);
+            setDrawable(null);
             return;
         }
 
@@ -1040,45 +955,22 @@
 
         BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);
 
-        setDrawableInternal(bitmapDrawable);
+        setDrawable(bitmapDrawable);
     }
 
-    void applyBackgroundChanges() {
-        if (!mAttached) {
-            return;
-        }
+    /**
+     * Enable or disable call release() in Activity onStop(). Default is true.
+     * @param autoReleaseOnStop True to call release() in Activity onStop(), false otherwise.
+     */
+    public void setAutoReleaseOnStop(boolean autoReleaseOnStop) {
+        mAutoReleaseOnStop = autoReleaseOnStop;
+    }
 
-        if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);
-
-        int dimAlpha = -1;
-
-        if (getImageOutWrapper() != null) {
-            dimAlpha = mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID;
-        }
-
-        DrawableWrapper imageInWrapper = getImageInWrapper();
-        if (imageInWrapper == null && mBackgroundDrawable != null) {
-            if (DEBUG) Log.v(TAG, "creating new imagein drawable");
-            imageInWrapper = mLayerDrawable.updateDrawable(
-                    R.id.background_imagein, mBackgroundDrawable);
-            if (DEBUG) Log.v(TAG, "imageInWrapper animation starting");
-            imageInWrapper.setAlpha(0);
-            dimAlpha = FULL_ALPHA;
-        }
-
-        mAnimator.setDuration(FADE_DURATION);
-        mAnimator.start();
-
-        DrawableWrapper dimWrapper = getDimWrapper();
-        if (dimWrapper != null && dimAlpha >= 0) {
-            if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
-            mDimAnimator.cancel();
-            mDimAnimator.setIntValues(dimWrapper.getAlpha(), dimAlpha);
-            mDimAnimator.setDuration(FADE_DURATION);
-            mDimAnimator.setInterpolator(
-                    dimAlpha == FULL_ALPHA ? mDecelerateInterpolator : mAccelerateInterpolator);
-            mDimAnimator.start();
-        }
+    /**
+     * @return True if release() in Activity.onStop(), false otherwise.
+     */
+    public boolean isAutoReleaseOnStop() {
+        return mAutoReleaseOnStop;
     }
 
     /**
@@ -1108,14 +1000,19 @@
                 return true;
             }
         }
+        if (first instanceof ColorDrawable && second instanceof ColorDrawable) {
+            if (((ColorDrawable) first).getColor() == ((ColorDrawable) second).getColor()) {
+                return true;
+            }
+        }
         return false;
     }
 
     /**
      * Task which changes the background.
      */
-    class ChangeBackgroundRunnable implements Runnable {
-        Drawable mDrawable;
+    final class ChangeBackgroundRunnable implements Runnable {
+        final Drawable mDrawable;
 
         ChangeBackgroundRunnable(Drawable drawable) {
             mDrawable = drawable;
@@ -1133,15 +1030,13 @@
                 return;
             }
 
-            if (sameDrawable(mDrawable, mBackgroundDrawable)) {
-                if (DEBUG) Log.v(TAG, "new drawable same as current");
-                return;
-            }
-
-            releaseBackgroundBitmap();
-
             DrawableWrapper imageInWrapper = getImageInWrapper();
             if (imageInWrapper != null) {
+                if (sameDrawable(mDrawable, imageInWrapper.getDrawable())) {
+                    if (DEBUG) Log.v(TAG, "new drawable same as current");
+                    return;
+                }
+
                 if (DEBUG) Log.v(TAG, "moving image in to image out");
                 // Order is important! Setting a drawable "removes" the
                 // previous one from the view
@@ -1150,37 +1045,40 @@
                         imageInWrapper.getDrawable());
             }
 
-            setBackgroundDrawable(mDrawable);
             applyBackgroundChanges();
         }
+
+        void applyBackgroundChanges() {
+            if (!mAttached) {
+                return;
+            }
+
+            if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mDrawable);
+
+            DrawableWrapper imageInWrapper = getImageInWrapper();
+            if (imageInWrapper == null && mDrawable != null) {
+                if (DEBUG) Log.v(TAG, "creating new imagein drawable");
+                imageInWrapper = mLayerDrawable.updateDrawable(
+                        R.id.background_imagein, mDrawable);
+                if (DEBUG) Log.v(TAG, "imageInWrapper animation starting");
+                mLayerDrawable.setWrapperAlpha(mImageInWrapperIndex, 0);
+            }
+
+            mAnimator.setDuration(FADE_DURATION);
+            mAnimator.start();
+
+        }
+
+    }
+
+    static class EmptyDrawable extends BitmapDrawable {
+        EmptyDrawable(Resources res) {
+            super(res, (Bitmap) null);
+        }
     }
 
     static Drawable createEmptyDrawable(Context context) {
-        Bitmap bitmap = null;
-        return new BitmapDrawable(context.getResources(), bitmap);
+        return new EmptyDrawable(context.getResources());
     }
 
-    private void showWallpaper(boolean show) {
-        if (DEBUG) Log.v(TAG, "showWallpaper called with " + show);
-        if (mWindow == null) {
-            return;
-        }
-
-        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
-        if (show) {
-            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
-                return;
-            }
-            if (DEBUG) Log.v(TAG, "showing wallpaper");
-            layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-        } else {
-            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) {
-                return;
-            }
-            if (DEBUG) Log.v(TAG, "hiding wallpaper");
-            layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-        }
-
-        mWindow.setAttributes(layoutParams);
-    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 4630c79..a94b7f3 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -481,11 +481,19 @@
         }
 
         /**
-         * Returns the selected position.
+         * @return The position of selected row.
          */
         public int getSelectedPosition() {
             return 0;
         }
+
+        /**
+         * @param position Position of Row.
+         * @return Row ViewHolder.
+         */
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return null;
+        }
     }
 
     private boolean createMainFragment(ObjectAdapter adapter, int position) {
@@ -1485,6 +1493,17 @@
     }
 
     /**
+     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
+     */
+    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
+        if (mMainFragmentRowsAdapter != null) {
+            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
+            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
+        }
+        return null;
+    }
+
+    /**
      * Sets the selected row position.
      */
     public void setSelectedPosition(int position, boolean smooth) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index 4979a24..a08ce67 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -484,11 +484,19 @@
         }
 
         /**
-         * Returns the selected position.
+         * @return The position of selected row.
          */
         public int getSelectedPosition() {
             return 0;
         }
+
+        /**
+         * @param position Position of Row.
+         * @return Row ViewHolder.
+         */
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return null;
+        }
     }
 
     private boolean createMainFragment(ObjectAdapter adapter, int position) {
@@ -1488,6 +1496,17 @@
     }
 
     /**
+     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
+     */
+    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
+        if (mMainFragmentRowsAdapter != null) {
+            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
+            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
+        }
+        return null;
+    }
+
+    /**
      * Sets the selected row position.
      */
     public void setSelectedPosition(int position, boolean smooth) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index 6e1c008..ca486ed 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -556,6 +556,19 @@
         }
     }
 
+    /**
+     * Find row ViewHolder by position in adapter.
+     * @param position Position of row.
+     * @return ViewHolder of Row.
+     */
+    public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+        if (mVerticalGridView == null) {
+            return null;
+        }
+        return getRowViewHolder((ItemBridgeAdapter.ViewHolder) mVerticalGridView
+                .findViewHolderForAdapterPosition(position));
+    }
+
     public static class MainFragmentAdapter extends BrowseFragment.MainFragmentAdapter<RowsFragment> {
 
         public MainFragmentAdapter(RowsFragment fragment) {
@@ -641,5 +654,10 @@
         public int getSelectedPosition() {
             return getFragment().getSelectedPosition();
         }
+
+        @Override
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return getFragment().findRowViewHolderByPosition(position);
+        }
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index a5acb41..d6c8d3f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -559,6 +559,19 @@
         }
     }
 
+    /**
+     * Find row ViewHolder by position in adapter.
+     * @param position Position of row.
+     * @return ViewHolder of Row.
+     */
+    public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+        if (mVerticalGridView == null) {
+            return null;
+        }
+        return getRowViewHolder((ItemBridgeAdapter.ViewHolder) mVerticalGridView
+                .findViewHolderForAdapterPosition(position));
+    }
+
     public static class MainFragmentAdapter extends BrowseSupportFragment.MainFragmentAdapter<RowsSupportFragment> {
 
         public MainFragmentAdapter(RowsSupportFragment fragment) {
@@ -644,5 +657,10 @@
         public int getSelectedPosition() {
             return getFragment().getSelectedPosition();
         }
+
+        @Override
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return getFragment().findRowViewHolderByPosition(position);
+        }
     }
 }
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 790cee3..c4658e7 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -122,6 +122,21 @@
             }
             return ibvh.getViewHolder();
         }
+
+        @Override
+        public Presenter.ViewHolder getSelectedItemViewHolder() {
+            return getItemViewHolder(getSelectedPosition());
+        }
+
+        @Override
+        public Object getSelectedItem() {
+            ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView
+                    .findViewHolderForAdapterPosition(getSelectedPosition());
+            if (ibvh == null) {
+                return null;
+            }
+            return ibvh.getItem();
+        }
     }
 
     /**
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 8267145..034ecc3 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -290,6 +290,21 @@
         public final BaseOnItemViewClickedListener getOnItemViewClickedListener() {
             return mOnItemViewClickedListener;
         }
+        /**
+         * Return {@link ViewHolder} of currently selected item inside a row ViewHolder.
+         * @return The selected item's ViewHolder.
+         */
+        public Presenter.ViewHolder getSelectedItemViewHolder() {
+            return null;
+        }
+
+        /**
+         * Return currently selected item inside a row ViewHolder.
+         * @return The selected item.
+         */
+        public Object getSelectedItem() {
+            return null;
+        }
     }
 
     private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
diff --git a/v17/leanback/tests/AndroidManifest.xml b/v17/leanback/tests/AndroidManifest.xml
index e8b81b5..16b78cb 100644
--- a/v17/leanback/tests/AndroidManifest.xml
+++ b/v17/leanback/tests/AndroidManifest.xml
@@ -75,6 +75,9 @@
             android:theme="@style/Theme.Leanback"
             android:exported="true" />
 
+        <activity android:name="android.support.v17.leanback.app.TestActivity"
+            android:theme="@style/Theme.Leanback"
+            android:exported="true" />
     </application>
 
 </manifest>
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java
new file mode 100644
index 0000000..7e7873a
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2016 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.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+/**
+ * @hide from javadoc
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundManagerTest {
+
+    @Rule
+    public TestName mUnitTestName = new TestName();
+
+    @Rule
+    public TestActivity.TestActivityTestRule mRule;
+
+    String generateProviderName(String name) {
+        return mUnitTestName.getMethodName() + "_" + name;
+    }
+
+    void waitForActivityStop(final TestActivity activity) {
+        PollingCheck.waitFor(5000/* timeout */, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canPreProceed() {
+                return false;
+            }
+
+            @Override
+            public boolean canProceed() {
+                // two step: first ChangeRunnable gets run.
+                // then Animator is finished.
+                return !activity.isStarted();
+            }
+        });
+    }
+
+    void waitForBackgroundAnimationFinish(final BackgroundManager manager) {
+        PollingCheck.waitFor(5000/* timeout */, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canPreProceed() {
+                return false;
+            }
+
+            @Override
+            public boolean canProceed() {
+                // two step: first ChangeRunnable gets run.
+                // then Animator is finished.
+                return manager.mLayerDrawable != null && manager.mChangeRunnable == null
+                        && !manager.mAnimator.isRunning();
+            }
+        });
+    }
+
+    void setBitmapAndVerify(final BackgroundManager manager, final  Bitmap bitmap)
+            throws Throwable {
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setBitmap(bitmap);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsBitmapDrawable(manager, bitmap);
+    }
+
+    void setDrawableAndVerify(final BackgroundManager manager, final Drawable drawable)
+            throws Throwable {
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setDrawable(drawable);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsDrawable(manager, drawable);
+    }
+
+    void setBitmapNullAndVerifyColor(final BackgroundManager manager, final int color)
+            throws Throwable {
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setBitmap(null);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, color);
+    }
+
+    void setDrawableNullAndVerifyColor(final BackgroundManager manager, final int color)
+            throws Throwable {
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setDrawable(null);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, color);
+    }
+
+    void setColorAndVerify(final BackgroundManager manager, final  int color)
+            throws Throwable {
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setColor(color);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, color);
+    }
+
+    /**
+     * Launch TestActivity without using TestActivityRule.
+     */
+    TestActivity launchActivity(String name, final TestActivity.Provider provider2)
+            throws Throwable {
+        final String providerName2 = generateProviderName(name);
+        TestActivity.setProvider(providerName2, provider2);
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Intent intent = new Intent(mRule.getActivity(), TestActivity.class);
+                intent.putExtra(TestActivity.EXTRA_PROVIDER, providerName2);
+                mRule.getActivity().startActivity(intent);
+            }
+        });
+
+        PollingCheck.waitFor(5000/*timeout*/, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canPreProceed() {
+                return false;
+            }
+
+            @Override
+            public boolean canProceed() {
+                return provider2.getActivity() != null && provider2.getActivity().isStarted();
+            }
+        });
+        return provider2.getActivity();
+    }
+
+    void assertIsColorDrawable(BackgroundManager manager, int color) {
+        assertNull(manager.mLayerDrawable.mWrapper[0]);
+        assertTrue(manager.mLayerDrawable.getDrawable(0)
+                instanceof BackgroundManager.EmptyDrawable);
+        assertEquals(manager.mLayerDrawable.mWrapper[1].mAlpha, 255);
+        assertEquals(((ColorDrawable) manager.mLayerDrawable.getDrawable(1)).getColor(), color);
+        assertNull(manager.mBackgroundDrawable);
+    }
+
+    void assertIsBitmapDrawable(BackgroundManager manager, Bitmap bitmap) {
+        assertNull(manager.mLayerDrawable.mWrapper[0]);
+        assertTrue(manager.mLayerDrawable.getDrawable(0)
+                instanceof BackgroundManager.EmptyDrawable);
+        assertEquals(manager.mLayerDrawable.mWrapper[1].mAlpha, 255);
+        assertSame(((BackgroundManager.BitmapDrawable) manager.mLayerDrawable.getDrawable(1))
+                .mState.mBitmap, bitmap);
+        assertSame(((BackgroundManager.BitmapDrawable) manager.mBackgroundDrawable)
+                .mState.mBitmap, bitmap);
+    }
+
+    void assertIsDrawable(BackgroundManager manager, Drawable drawable) {
+        assertNull(manager.mLayerDrawable.mWrapper[0]);
+        assertTrue(manager.mLayerDrawable.getDrawable(0)
+                instanceof BackgroundManager.EmptyDrawable);
+        assertEquals(manager.mLayerDrawable.mWrapper[1].mAlpha, 255);
+        assertSame(manager.mLayerDrawable.getDrawable(1), drawable);
+        assertSame(manager.mBackgroundDrawable, drawable);
+    }
+
+    Bitmap createBitmap(int width, int height, int color) {
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(color);
+        canvas.drawRect(0, 0, width, height, paint);
+        return bitmap;
+    }
+
+    Drawable createDrawable(int width, int height, int color) {
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(color);
+        canvas.drawRect(0, 0, width, height, paint);
+        return new BitmapDrawable(mRule.getActivity().getResources(), bitmap);
+    }
+
+    void testSwitchBackgrounds(final BackgroundManager manager) throws Throwable {
+        setBitmapAndVerify(manager, createBitmap(200, 100, Color.GREEN));
+
+        // Drawable -> Drawable
+        setDrawableAndVerify(manager, createDrawable(200, 100, Color.MAGENTA));
+
+        setBitmapAndVerify(manager, createBitmap(200, 100, Color.GRAY));
+
+        // Drawable -> Color
+        setColorAndVerify(manager, Color.RED);
+
+        // Color -> Drawable
+        setBitmapAndVerify(manager, createBitmap(200, 100, Color.BLACK));
+
+        // Set Drawable to null -> show last Color
+        setBitmapNullAndVerifyColor(manager, Color.RED);
+
+        // Color -> Drawable
+        setBitmapAndVerify(manager, createBitmap(200, 100, Color.MAGENTA));
+    }
+
+    @Test
+    public void establishInOnAttachToWindow() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onAttachedToWindow(TestActivity activity) {
+                BackgroundManager.getInstance(activity).attach(activity.getWindow());
+            }
+
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager.getInstance(activity).setColor(Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        BackgroundManager manager = BackgroundManager.getInstance(activity1);
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, Color.BLUE);
+
+        testSwitchBackgrounds(manager);
+    }
+
+    @Test
+    public void multipleSetBitmaps() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onAttachedToWindow(TestActivity activity) {
+                BackgroundManager.getInstance(activity).attach(activity.getWindow());
+            }
+
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager.getInstance(activity).setColor(Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1,
+                generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        final BackgroundManager manager = BackgroundManager.getInstance(activity1);
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, Color.BLUE);
+
+        final Bitmap bitmap1 = createBitmap(200, 100, Color.RED);
+        final Bitmap bitmap2 = createBitmap(200, 100, Color.GRAY);
+        final Bitmap bitmap3 = createBitmap(200, 100, Color.GREEN);
+        final Bitmap bitmap4 = createBitmap(200, 100, Color.MAGENTA);
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setBitmap(bitmap1);
+                manager.setBitmap(bitmap2);
+                manager.setBitmap(bitmap3);
+                manager.setBitmap(bitmap4);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsBitmapDrawable(manager, bitmap4);
+    }
+
+    @Test
+    public void multipleSetBitmapsAndColor() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onAttachedToWindow(TestActivity activity) {
+                BackgroundManager.getInstance(activity).attach(activity.getWindow());
+            }
+
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager.getInstance(activity).setColor(Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        final BackgroundManager manager = BackgroundManager.getInstance(activity1);
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, Color.BLUE);
+
+        final Bitmap bitmap1 = createBitmap(200, 100, Color.RED);
+        final Bitmap bitmap2 = createBitmap(200, 100, Color.GRAY);
+        final Bitmap bitmap3 = createBitmap(200, 100, Color.GREEN);
+        final int color = Color.MAGENTA;
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setBitmap(bitmap1);
+                manager.setBitmap(bitmap2);
+            }
+        });
+        mRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                manager.setBitmap(bitmap3);
+                manager.setColor(color);
+            }
+        });
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, color);
+    }
+
+    @Test
+    public void establishInOnCreate() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onCreate(TestActivity activity, Bundle savedInstanceState) {
+                super.onCreate(activity, savedInstanceState);
+                BackgroundManager.getInstance(activity).attach(activity.getWindow());
+            }
+
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager.getInstance(activity).setColor(Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        BackgroundManager manager = BackgroundManager.getInstance(activity1);
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, Color.BLUE);
+
+        testSwitchBackgrounds(manager);
+    }
+
+    @Test
+    public void establishInOnStart() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                m.setColor(Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        BackgroundManager manager = BackgroundManager.getInstance(activity1);
+        waitForBackgroundAnimationFinish(manager);
+        assertIsColorDrawable(manager, Color.BLUE);
+
+        testSwitchBackgrounds(manager);
+    }
+
+    @Test
+    public void assignColorImmediately() throws Throwable {
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                // if we set color before attach, it will be assigned immediately
+                m.setColor(Color.BLUE);
+                m.attach(activity.getWindow());
+                assertIsColorDrawable(m, Color.BLUE);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        BackgroundManager manager = BackgroundManager.getInstance(activity1);
+
+        testSwitchBackgrounds(manager);
+    }
+
+    @Test
+    public void assignBitmapImmediately() throws Throwable {
+        final Bitmap bitmap = createBitmap(200, 100, Color.BLUE);
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                // if we set bitmap before attach, it will be assigned immediately
+                m.setBitmap(bitmap);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+
+        BackgroundManager manager = BackgroundManager.getInstance(activity1);
+
+        testSwitchBackgrounds(manager);
+    }
+
+
+    @Test
+    public void inheritBitmapByNewActivity() throws Throwable {
+        final Bitmap bitmap = createBitmap(200, 100, Color.BLUE);
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                // if we set bitmap before attach, it will be assigned immediately
+                m.setBitmap(bitmap);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+        activity1.finish();
+
+        TestActivity.Provider provider2 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+
+        TestActivity activity2 = launchActivity("activity2", provider2);
+        waitForActivityStop(activity1);
+
+        BackgroundManager manager2 = BackgroundManager.getInstance(activity2);
+        assertIsBitmapDrawable(manager2, bitmap);
+        activity2.finish();
+    }
+
+    @Test
+    public void inheritColorByNewActivity() throws Throwable {
+        final int color = Color.BLUE;
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                // if we set color before attach, it will be assigned immediately
+                m.setColor(color);
+                m.attach(activity.getWindow());
+                assertIsColorDrawable(m, color);
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+        activity1.finish();
+
+        TestActivity.Provider provider2 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                assertIsColorDrawable(m, color);
+            }
+        };
+        TestActivity activity2 = launchActivity("activity2", provider2);
+        waitForActivityStop(activity1);
+
+        BackgroundManager manager2 = BackgroundManager.getInstance(activity2);
+        assertIsColorDrawable(manager2, color);
+        activity2.finish();
+    }
+
+    @Test
+    public void returnFromNewActivity() throws Throwable {
+        final int color = Color.RED;
+        final Bitmap bitmap = createBitmap(200, 100, Color.BLUE);
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onCreate(TestActivity activity, Bundle savedInstanceState) {
+                super.onCreate(activity, savedInstanceState);
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                // if we set bitmap before attach, it will be assigned immediately
+                m.setColor(color);
+                m.setBitmap(bitmap);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+        final BackgroundManager manager1 = BackgroundManager.getInstance(activity1);
+
+        TestActivity.Provider provider2 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+        TestActivity activity2 = launchActivity("activity2", provider2);
+        waitForActivityStop(activity1);
+        final BackgroundManager manager2 = BackgroundManager.getInstance(activity2);
+
+        final Bitmap bitmap2 = createBitmap(200, 100, Color.GREEN);
+        setBitmapAndVerify(manager2, bitmap2);
+
+        // after activity2 is launched, activity will lose its bitmap and released LayerDrawable
+        assertNull(manager1.mBackgroundDrawable);
+        assertNull(manager1.mLayerDrawable);
+
+        activity2.finish();
+
+        // when return from the other app, last drawable is cleared.
+        waitForBackgroundAnimationFinish(manager1);
+        assertIsColorDrawable(manager1, color);
+    }
+
+    @Test
+    public void manuallyReleaseInOnStop() throws Throwable {
+        final int color = Color.RED;
+        final Bitmap bitmap = createBitmap(200, 100, Color.BLUE);
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onCreate(TestActivity activity, Bundle savedInstanceState) {
+                super.onCreate(activity, savedInstanceState);
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.setAutoReleaseOnStop(false);
+                // if we set bitmap before attach, it will be assigned immediately
+                m.setColor(color);
+                m.setBitmap(bitmap);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+
+            @Override
+            public void onStop(TestActivity activity) {
+                BackgroundManager.getInstance(activity).release();
+            }
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+        final BackgroundManager manager1 = BackgroundManager.getInstance(activity1);
+
+        TestActivity.Provider provider2 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+        TestActivity activity2 = launchActivity("activity2", provider2);
+        waitForActivityStop(activity1);
+        final BackgroundManager manager2 = BackgroundManager.getInstance(activity2);
+
+        final Bitmap bitmap2 = createBitmap(200, 100, Color.GREEN);
+        setBitmapAndVerify(manager2, bitmap2);
+
+        // after activity2 is launched, activity will lose its bitmap and released LayerDrawable
+        assertNull(manager1.mBackgroundDrawable);
+        assertNull(manager1.mLayerDrawable);
+
+        activity2.finish();
+
+        // when return from the other app, last drawable is cleared.
+        waitForBackgroundAnimationFinish(manager1);
+        assertIsColorDrawable(manager1, color);
+    }
+
+    @Test
+    public void disableAutoRelease() throws Throwable {
+        final int color = Color.RED;
+        final Bitmap bitmap = createBitmap(200, 100, Color.BLUE);
+        TestActivity.Provider provider1 = new TestActivity.Provider() {
+            @Override
+            public void onCreate(TestActivity activity, Bundle savedInstanceState) {
+                super.onCreate(activity, savedInstanceState);
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.setAutoReleaseOnStop(false);
+                // if we set bitmap before attach, it will be assigned immediately
+                m.setColor(color);
+                m.setBitmap(bitmap);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+
+        };
+        mRule = new TestActivity.TestActivityTestRule(provider1, generateProviderName("activity1"));
+        final TestActivity activity1 = mRule.launchActivity();
+        final BackgroundManager manager1 = BackgroundManager.getInstance(activity1);
+
+        TestActivity.Provider provider2 = new TestActivity.Provider() {
+            @Override
+            public void onStart(TestActivity activity) {
+                BackgroundManager m = BackgroundManager.getInstance(activity);
+                m.attach(activity.getWindow());
+                assertIsBitmapDrawable(m, bitmap);
+            }
+        };
+        TestActivity activity2 = launchActivity("activity2", provider2);
+        waitForActivityStop(activity1);
+        final BackgroundManager manager2 = BackgroundManager.getInstance(activity2);
+
+        final Bitmap bitmap2 = createBitmap(200, 100, Color.GREEN);
+        setBitmapAndVerify(manager2, bitmap2);
+
+        // after activity2 is launched, activity will keep its drawable because
+        // setAutoReleaseOnStop(false)
+        assertIsBitmapDrawable(manager1, bitmap);
+
+        activity2.finish();
+
+        // when return from the activity, it's still the same bitmap
+        waitForBackgroundAnimationFinish(manager1);
+        assertIsBitmapDrawable(manager1, bitmap);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java
new file mode 100644
index 0000000..7f9b408
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 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.v17.leanback.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.HashMap;
+
+/**
+ * A general Activity that allows test set a Provider to custom activity's behavior in life
+ * cycle events.
+ */
+public class TestActivity extends Activity {
+
+    public static class Provider {
+
+        TestActivity mActivity;
+
+        /**
+         * @return Currently attached activity.
+         */
+        public TestActivity getActivity() {
+            return mActivity;
+        }
+
+        @CallSuper
+        public void onCreate(TestActivity activity, Bundle savedInstanceState) {
+            mActivity = activity;
+        }
+
+        public void onAttachedToWindow(TestActivity activity) {
+        }
+
+        public void onStart(TestActivity activity) {
+        }
+
+        public void onStop(TestActivity activity) {
+        }
+
+        public void onPause(TestActivity activity) {
+        }
+
+        public void onResume(TestActivity activity) {
+        }
+
+        public void onDestroy(TestActivity activity) {
+        }
+    }
+
+    public static class TestActivityTestRule extends ActivityTestRule<TestActivity> {
+
+        String mProviderName;
+        public TestActivityTestRule(TestActivity.Provider provider, String providerName) {
+            super(TestActivity.class, false, false);
+            mProviderName = providerName;
+            provider.mActivity = null;
+            TestActivity.setProvider(mProviderName, provider);
+        }
+
+        public TestActivity launchActivity() {
+            Intent intent = new Intent();
+            intent.putExtra(TestActivity.EXTRA_PROVIDER, mProviderName);
+            return launchActivity(intent);
+        }
+    }
+
+    public static final String EXTRA_PROVIDER = "testActivityProvider";
+
+    static HashMap<String, Provider> sProviders = new HashMap();
+
+    String mProviderName;
+    Provider mProvider;
+    boolean mStarted;
+
+    public TestActivity() {
+    }
+
+    public static void setProvider(String name, Provider provider) {
+        sProviders.put(name, provider);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mProviderName = getIntent().getStringExtra(EXTRA_PROVIDER);
+        mProvider = sProviders.get(mProviderName);
+        if (mProvider != null) {
+            mProvider.onCreate(this, savedInstanceState);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mProvider != null) {
+            mProvider.onAttachedToWindow(this);
+        }
+    }
+
+    public boolean isStarted() {
+        return mStarted;
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mStarted = true;
+        if (mProvider != null) {
+            mProvider.onStart(this);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (mProvider != null) {
+            mProvider.onPause(this);
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mProvider != null) {
+            mProvider.onResume(this);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        mStarted = false;
+        if (mProvider != null) {
+            mProvider.onStop(this);
+        }
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mProvider != null) {
+            mProvider.onDestroy(this);
+            setProvider(mProviderName, null);
+        }
+        super.onDestroy();
+    }
+}