Remove overlay view for better performance

Overlay view does not help performance,  when animating
background color,  invalidate() will be called on overlay View.
That's equivalent to invalidate() on ShadowOverlayContainer.
Remove the extra view to save memory and override draw()
instead.  This turns out to save about 5~10% in UI thread and
RenderThread in a vertical scrolling of BrowseFragment
according to systrace.

Refactoring a ShadowOverlayHelper to include all options for ListRowPresenter:
hasOverlay, hasShadow, hasRoundedCorner, make decision whether to
create a wrapper.

Ultimately the extra layer ShadowOverlayContainer could be removed
and we can use setForeground() on any view for API>=23,  but
animating foreground causes uncessary rebuildOutline() right now.
So we force to always use a wrapper for overlay.

b/22794753

Change-Id: I4848f7fc6b832706445da8e6e1b807d0f1830968
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index dd03d6a..57b54e0 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -40,6 +40,16 @@
 
 # -----------------------------------------------------------------------
 
+#  A helper sub-library that makes direct use of API 23.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-api23
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api23)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 #  A helper sub-library that makes direct use of API 21.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v17-leanback-api21
@@ -79,6 +89,7 @@
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
+        android-support-v17-leanback-api23 \
         android-support-v17-leanback-api21 android-support-v17-leanback-common
 LOCAL_JAVA_LIBRARIES := \
         android-support-v4 \
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index 3acc9a2..74fc258 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -948,8 +948,8 @@
     method public void setContentText(java.lang.CharSequence);
     method public void setInfoAreaBackground(android.graphics.drawable.Drawable);
     method public void setInfoAreaBackgroundColor(int);
-    method public void setMainImage(android.graphics.drawable.Drawable, boolean);
     method public void setMainImage(android.graphics.drawable.Drawable);
+    method public void setMainImage(android.graphics.drawable.Drawable, boolean);
     method public void setMainImageAdjustViewBounds(boolean);
     method public void setMainImageDimensions(int, int);
     method public void setMainImageScaleType(android.widget.ImageView.ScaleType);
@@ -1469,10 +1469,12 @@
     ctor public ShadowOverlayContainer(android.content.Context);
     ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
     ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet, int);
+    ctor public ShadowOverlayContainer(android.content.Context, int, boolean, boolean);
     method public int getShadowType();
     method public android.view.View getWrappedView();
     method public deprecated void initialize(boolean, boolean);
-    method public void initialize(boolean, boolean, boolean);
+    method public deprecated void initialize(boolean, boolean, boolean);
+    method public void initialize(int, boolean, boolean);
     method protected void onLayout(boolean, int, int, int, int);
     method public static void prepareParentForShadow(android.view.ViewGroup);
     method public void setOverlayColor(int);
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
index ab4d179..cd4ff3e 100644
--- a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
@@ -19,14 +19,14 @@
 import android.graphics.Outline;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.view.ViewGroup;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 
 class ShadowHelperApi21 {
 
     static class ShadowImpl {
-        ViewGroup mShadowContainer;
+        View mShadowContainer;
         float mNormalZ;
         float mFocusedZ;
     }
@@ -41,7 +41,7 @@
 
     /* add shadows and return a implementation detail object */
     public static Object addDynamicShadow(
-            ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
+            View shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
         if (roundedCorners) {
             RoundedRectHelperApi21.setClipToRoundedOutline(shadowContainer, true);
         } else {
@@ -52,7 +52,9 @@
         impl.mNormalZ = unfocusedZ;
         impl.mFocusedZ = focusedZ;
         shadowContainer.setZ(impl.mNormalZ);
-        shadowContainer.setTransitionGroup(true);
+        if (shadowContainer instanceof ViewGroup) {
+            ((ViewGroup) shadowContainer).setTransitionGroup(true);
+        }
         return impl;
     }
 
diff --git a/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java b/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java
new file mode 100644
index 0000000..c4760d4
--- /dev/null
+++ b/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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.widget;
+
+import android.support.v17.leanback.R;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+class ForegroundHelperApi23 {
+
+    public static Drawable getForeground(View view) {
+        return view.getForeground();
+    }
+
+    public static void setForeground(View view, Drawable drawable) {
+        view.setForeground(drawable);
+    }
+}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 8dc79e8..401a5c4 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -19,8 +19,8 @@
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
-        main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
-        main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+        main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'api23', 'src']
+        main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'api23', 'src']
         main.res.srcDirs = ['res']
 
         androidTest.setRoot('tests')
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_card_color_overlay.xml
deleted file mode 100644
index 45a40e1..0000000
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 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.
--->
-
-<View
-      xmlns:android="http://schemas.android.com/apk/res/android"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent" />
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
index 06a5d2e..d4ba288 100644
--- a/v17/leanback/res/values/ids.xml
+++ b/v17/leanback/res/values/ids.xml
@@ -16,6 +16,7 @@
 -->
  <resources>
      <item type="id" name="lb_focus_animator" />
+     <item type="id" name="lb_shadow_impl" />
      <item type="id" name="lb_slide_transition_value" />
 
      <item type="id" name="lb_control_play_pause" />
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index 48829eb..f6ecd9d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -97,12 +97,10 @@
             float scale = 1f + mScaleDiff * level;
             mView.setScaleX(scale);
             mView.setScaleY(scale);
-            if (mWrapper != null) {
-                mWrapper.setShadowFocusLevel(level);
-                if (mDimmer != null) {
-                    mDimmer.setActiveLevel(level);
-                    mWrapper.setOverlayColor(mDimmer.getPaint().getColor());
-                }
+            ShadowOverlayHelper.setShadowFocusLevel(mView, level);
+            if (mWrapper != null && mDimmer != null) {
+                mDimmer.setActiveLevel(level);
+                mWrapper.setOverlayColor(mDimmer.getPaint().getColor());
             }
         }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
new file mode 100644
index 0000000..c9bed58
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
@@ -0,0 +1,78 @@
+package android.support.v17.leanback.widget;
+
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+final class ForegroundHelper {
+
+    final static ForegroundHelper sInstance = new ForegroundHelper();
+    ForegroundHelperVersionImpl mImpl;
+
+    /**
+     * Interface implemented by classes that support Shadow.
+     */
+    static interface ForegroundHelperVersionImpl {
+
+        public void setForeground(View view, Drawable drawable);
+
+        public Drawable getForeground(View view);
+    }
+
+    /**
+     * Implementation used on api 23 (and above).
+     */
+    private static final class ForegroundHelperApi23Impl implements ForegroundHelperVersionImpl {
+        @Override
+        public void setForeground(View view, Drawable drawable) {
+            ForegroundHelperApi23.setForeground(view, drawable);
+        }
+
+        @Override
+        public Drawable getForeground(View view) {
+            return ForegroundHelperApi23.getForeground(view);
+        }
+    }
+
+    /**
+     * Stub implementation
+     */
+    private static final class ForegroundHelperStubImpl implements ForegroundHelperVersionImpl {
+        @Override
+        public void setForeground(View view, Drawable drawable) {
+        }
+
+        @Override
+        public Drawable getForeground(View view) {
+            return null;
+        }
+    }
+
+    private ForegroundHelper() {
+        if (supportsForeground()) {
+            mImpl = new ForegroundHelperApi23Impl();
+        } else {
+            mImpl = new ForegroundHelperStubImpl();
+        }
+    }
+
+    public static ForegroundHelper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * Returns true if view.setForeground() is supported.
+     */
+    public static boolean supportsForeground() {
+        return Build.VERSION.SDK_INT >= 23;
+    }
+
+    public Drawable getForeground(View view) {
+        return mImpl.getForeground(view);
+    }
+
+    public void setForeground(View view, Drawable drawable) {
+        mImpl.setForeground(view, drawable);
+    }
+}
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 9e588eb..bf7d696 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -15,6 +15,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.system.Settings;
 import android.util.Log;
@@ -96,6 +97,13 @@
         }
 
         @Override
+        protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+            if (mShadowOverlayHelper != null) {
+                mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
+            }
+        }
+
+        @Override
         public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
             // Only when having an OnItemClickListner, we will attach the OnClickListener.
             if (mRowViewHolder.getOnItemViewClickedListener() != null) {
@@ -122,9 +130,9 @@
 
         @Override
         public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
-            if (needsDefaultListSelectEffect()) {
+            if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
                 int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor();
-                ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
+                mShadowOverlayHelper.setOverlayColor(viewHolder.itemView, dimmedColor);
             }
             mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
         }
@@ -145,6 +153,7 @@
     private int mBrowseRowsFadingEdgeLength = -1;
     private boolean mRoundedCornersEnabled = true;
     private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
+    private ShadowOverlayHelper mShadowOverlayHelper;
 
     private static int sSelectedRowTopPadding;
     private static int sExpandedSelectedRowTopPadding;
@@ -253,44 +262,24 @@
         return mUseFocusDimmer;
     }
 
-    private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() {
-        @Override
-        public View createWrapper(View root) {
-            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
-            wrapper.setLayoutParams(
-                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
-            if (isUsingZOrder(root.getContext())) {
-                wrapper.useDynamicShadow();
-            } else {
-                wrapper.useStaticShadow();
-            }
-            wrapper.initialize(needsDefaultShadow(),
-                    needsDefaultListSelectEffect(),
-                    areChildRoundedCornersEnabled());
-            return wrapper;
-        }
-        @Override
-        public void wrap(View wrapper, View wrapped) {
-            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
-        }
-    };
-
     @Override
     protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
         super.initializeRowViewHolder(holder);
         final ViewHolder rowViewHolder = (ViewHolder) holder;
+        Context context = holder.view.getContext();
+        if (mShadowOverlayHelper == null) {
+            mShadowOverlayHelper = new ShadowOverlayHelper(context, needsDefaultListSelectEffect(),
+                    needsDefaultShadow(), areChildRoundedCornersEnabled(), isUsingZOrder(context));
+        }
         rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
-        if (needsDefaultListSelectEffect() || needsDefaultShadow()
-                || areChildRoundedCornersEnabled()) {
-            rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
-        }
-        if (needsDefaultShadow()) {
-            ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
-        }
+        // set wrapper if needed
+        rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayHelper.getWrapper());
+        mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView);
+
         FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
                 mFocusZoomFactor, mUseFocusDimmer);
-        rowViewHolder.mGridView.setFocusDrawingOrderEnabled(
-                !isUsingZOrder(rowViewHolder.getGridView().getContext()));
+        rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
+                == ShadowOverlayHelper.SHADOW_STATIC);
         rowViewHolder.mGridView.setOnChildSelectedListener(
                 new OnChildSelectedListener() {
             @Override
@@ -545,7 +534,7 @@
      * Subclass may return false to disable.
      */
     public boolean isUsingDefaultShadow() {
-        return ShadowOverlayContainer.supportsShadow();
+        return ShadowOverlayHelper.supportsShadow();
     }
 
     /**
@@ -554,8 +543,7 @@
      * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
      */
     public boolean isUsingZOrder(Context context) {
-        return ShadowOverlayContainer.supportsDynamicShadow() &&
-                !Settings.getInstance(context).preferStaticShadows();
+        return !Settings.getInstance(context).preferStaticShadows();
     }
 
     /**
@@ -615,12 +603,11 @@
     @Override
     protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
         super.onSelectLevelChanged(holder);
-        if (needsDefaultListSelectEffect()) {
+        if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
             ViewHolder vh = (ViewHolder) holder;
             int dimmedColor = vh.mColorDimmer.getPaint().getColor();
             for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
-                ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i);
-                wrapper.setOverlayColor(dimmedColor);
+                mShadowOverlayHelper.setOverlayColor(vh.mGridView.getChildAt(i), dimmedColor);
             }
             if (vh.mGridView.getFadingLeftEdge()) {
                 vh.mGridView.invalidate();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
index e1c5979..f21670c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
@@ -33,6 +33,10 @@
         return sInstance;
     }
 
+    public static boolean supportsRoundedCorner() {
+        return Build.VERSION.SDK_INT >= 21;
+    }
+
     /**
      * Sets or removes a rounded rectangle outline on the given view.
      */
@@ -65,7 +69,7 @@
     }
 
     private RoundedRectHelper() {
-        if (Build.VERSION.SDK_INT >= 21) {
+        if (supportsRoundedCorner()) {
             mImpl = new Api21Impl();
         } else {
             mImpl = new StubImpl();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
index be70575..9532635 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -32,7 +32,7 @@
      */
     static interface ShadowHelperVersionImpl {
         public Object addDynamicShadow(
-                ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners);
+                View shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners);
         public void setZ(View view, float z);
         public void setShadowFocusLevel(Object impl, float level);
     }
@@ -43,7 +43,7 @@
     private static final class ShadowHelperStubImpl implements ShadowHelperVersionImpl {
         @Override
         public Object addDynamicShadow(
-                ViewGroup shadowContainer, float focusedZ, float unfocusedZ, boolean roundedCorners) {
+                View shadowContainer, float focusedZ, float unfocusedZ, boolean roundedCorners) {
             // do nothing
             return null;
         }
@@ -65,7 +65,7 @@
     private static final class ShadowHelperApi21Impl implements ShadowHelperVersionImpl {
         @Override
         public Object addDynamicShadow(
-                ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
+                View shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
             return ShadowHelperApi21.addDynamicShadow(
                     shadowContainer, unfocusedZ, focusedZ, roundedCorners);
         }
@@ -102,7 +102,7 @@
     }
 
     public Object addDynamicShadow(
-            ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
+            View shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
         return mImpl.addDynamicShadow(shadowContainer, unfocusedZ, focusedZ, roundedCorners);
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
index e367494..84a30e4 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
@@ -20,6 +20,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
 import android.graphics.Rect;
 
 /**
@@ -51,20 +54,19 @@
     /**
      * No shadow.
      */
-    public static final int SHADOW_NONE = 1;
+    public static final int SHADOW_NONE = ShadowOverlayHelper.SHADOW_NONE;
 
     /**
      * Shadows are fixed.
      */
-    public static final int SHADOW_STATIC = 2;
+    public static final int SHADOW_STATIC = ShadowOverlayHelper.SHADOW_STATIC;
 
     /**
      * Shadows depend on the size, shape, and position of the view.
      */
-    public static final int SHADOW_DYNAMIC = 3;
+    public static final int SHADOW_DYNAMIC = ShadowOverlayHelper.SHADOW_DYNAMIC;
 
     private boolean mInitialized;
-    private View mColorDimOverlay;
     private Object mShadowImpl;
     private View mWrappedView;
     private boolean mRoundedCorners;
@@ -72,15 +74,26 @@
     private float mUnfocusedZ;
     private float mFocusedZ;
     private static final Rect sTempRect = new Rect();
+    private Paint mOverlayPaint;
+    private int mOverlayColor;
 
+    /**
+     * Create ShadowOverlayContainer and auto select shadow type.
+     */
     public ShadowOverlayContainer(Context context) {
         this(context, null, 0);
     }
 
+    /**
+     * Create ShadowOverlayContainer and auto select shadow type.
+     */
     public ShadowOverlayContainer(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
+    /**
+     * Create ShadowOverlayContainer and auto select shadow type.
+     */
     public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         useStaticShadow();
@@ -88,6 +101,17 @@
     }
 
     /**
+     * Create ShadowOverlayContainer with specific shadowType.
+     */
+    public ShadowOverlayContainer(Context context,
+            int shadowType, boolean hasColorDimOverlay, boolean roundedCorners) {
+        super(context);
+        mUnfocusedZ = getResources().getDimension(R.dimen.lb_material_shadow_normal_z);
+        mFocusedZ = getResources().getDimension(R.dimen.lb_material_shadow_focused_z);
+        initialize(shadowType, hasColorDimOverlay, roundedCorners);
+    }
+
+    /**
      * Return true if the platform sdk supports shadow.
      */
     public static boolean supportsShadow() {
@@ -164,29 +188,59 @@
 
     /**
      * Initialize shadows, color overlay, and rounded corners.  All are optional.
+     * Shadow type are auto-selected based on {@link #useStaticShadow()} and
+     * {@link #useDynamicShadow()} call.
+     * @deprecated use {@link #initialize(int, boolean, boolean)} instead.
      */
+    @Deprecated
     public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) {
+        int shadowType;
+        if (!hasShadow) {
+            shadowType = SHADOW_NONE;
+        } else {
+            shadowType = mShadowType;
+        }
+        initialize(shadowType, hasColorDimOverlay, roundedCorners);
+    }
+
+    /**
+     * Initialize shadows, color overlay, and rounded corners.  All are optional.
+     */
+    public void initialize(int shadowType, boolean hasColorDimOverlay, boolean roundedCorners) {
         if (mInitialized) {
             throw new IllegalStateException();
         }
         mInitialized = true;
-        if (hasShadow) {
-            switch (mShadowType) {
-                case SHADOW_DYNAMIC:
-                    mShadowImpl = ShadowHelper.getInstance().addDynamicShadow(
-                            this, mUnfocusedZ, mFocusedZ, roundedCorners);
-                    break;
-                case SHADOW_STATIC:
-                    mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow(
-                            this, roundedCorners);
-                    break;
-            }
+        mShadowType = shadowType;
+        switch (mShadowType) {
+            case SHADOW_DYNAMIC:
+                mShadowImpl = ShadowHelper.getInstance().addDynamicShadow(
+                        this, mUnfocusedZ, mFocusedZ, roundedCorners);
+                break;
+            case SHADOW_STATIC:
+                mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow(this);
+                break;
         }
         mRoundedCorners = roundedCorners;
         if (hasColorDimOverlay) {
-            mColorDimOverlay = LayoutInflater.from(getContext())
-                    .inflate(R.layout.lb_card_color_overlay, this, false);
-            addView(mColorDimOverlay);
+            setWillNotDraw(false);
+            mOverlayColor = Color.TRANSPARENT;
+            mOverlayPaint = new Paint();
+            mOverlayPaint.setColor(mOverlayColor);
+            mOverlayPaint.setStyle(Paint.Style.FILL);
+        } else {
+            setWillNotDraw(true);
+            mOverlayPaint = null;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (mOverlayPaint != null && mOverlayColor != Color.TRANSPARENT) {
+            canvas.drawRect(mWrappedView.getLeft(), mWrappedView.getTop(),
+                    mWrappedView.getRight(), mWrappedView.getBottom(),
+                    mOverlayPaint);
         }
     }
 
@@ -195,19 +249,7 @@
      */
     public void setShadowFocusLevel(float level) {
         if (mShadowImpl != null) {
-            if (level < 0f) {
-                level = 0f;
-            } else if (level > 1f) {
-                level = 1f;
-            }
-            switch (mShadowType) {
-                case SHADOW_DYNAMIC:
-                    ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
-                    break;
-                case SHADOW_STATIC:
-                    StaticShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
-                    break;
-            }
+            ShadowOverlayHelper.setShadowFocusLevel(mShadowImpl, mShadowType, level);
         }
     }
 
@@ -215,8 +257,12 @@
      * Set color (with alpha) of the overlay.
      */
     public void setOverlayColor(@ColorInt int overlayColor) {
-        if (mColorDimOverlay != null) {
-            mColorDimOverlay.setBackgroundColor(overlayColor);
+        if (mOverlayPaint != null) {
+            if (overlayColor != mOverlayColor) {
+                mOverlayColor = overlayColor;
+                mOverlayPaint.setColor(overlayColor);
+                invalidate();
+            }
         }
     }
 
@@ -227,15 +273,11 @@
         if (!mInitialized || mWrappedView != null) {
             throw new IllegalStateException();
         }
-        if (mColorDimOverlay != null) {
-            addView(view, indexOfChild(mColorDimOverlay));
-        } else {
-            addView(view);
+        addView(view);
+        if (mRoundedCorners && mShadowType == SHADOW_STATIC) {
+            RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
         }
         mWrappedView = view;
-        if (mRoundedCorners) {
-            RoundedRectHelper.getInstance().setClipToRoundedOutline(mWrappedView, true);
-        }
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
new file mode 100644
index 0000000..2feb5c5
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.system.Settings;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.View;
+
+
+/**
+ * Helper for shadow.
+ * @hide
+ */
+public final class ShadowOverlayHelper {
+
+    /**
+     * No shadow.
+     */
+    public static final int SHADOW_NONE = 1;
+
+    /**
+     * Shadows are fixed.
+     */
+    public static final int SHADOW_STATIC = 2;
+
+    /**
+     * Shadows depend on the size, shape, and position of the view.
+     */
+    public static final int SHADOW_DYNAMIC = 3;
+
+    int mShadowType = SHADOW_NONE;
+    boolean mNeedsOverlay;
+    boolean mNeedsRoundedCorner;
+    boolean mNeedsShadow;
+    boolean mNeedsWrapper;
+    private ItemBridgeAdapter.Wrapper mCardWrapper;
+
+    float mUnfocusedZ;
+    float mFocusedZ;
+
+    /**
+     * Return true if the platform sdk supports shadow.
+     */
+    public static boolean supportsShadow() {
+        return StaticShadowHelper.getInstance().supportsShadow();
+    }
+
+    /**
+     * Returns true if the platform sdk supports dynamic shadows.
+     */
+    public static boolean supportsDynamicShadow() {
+        return ShadowHelper.getInstance().supportsDynamicShadow();
+    }
+
+    /**
+     * Returns true if the platform sdk supports rounded corner through outline.
+     */
+    public static boolean supportsRoundedCorner() {
+        return RoundedRectHelper.supportsRoundedCorner();
+    }
+
+    /**
+     * Returns true if view.setForeground() is supported.
+     */
+    public static boolean supportsForeground() {
+        return ForegroundHelper.supportsForeground();
+    }
+
+    /**
+     * Create ShadowHelper that includes all options.
+     *
+     * @param context               Context that required to query options
+     * @param needsOverlay          true if overlay (dim) is needed
+     * @param needsShadow           true if shadow is needed
+     * @param needsRoundedCorner    true if roundedCorner is needed.
+     * @param preferZOrder          true if prefer dynamic shadow otherwise static shadow is used.
+     */
+    public ShadowOverlayHelper(Context context,
+            boolean needsOverlay, boolean needsShadow, boolean needsRoundedCorner,
+            boolean preferZOrder) {
+        mNeedsOverlay = needsOverlay;
+        mNeedsRoundedCorner = needsRoundedCorner && supportsRoundedCorner();
+        mNeedsShadow = needsShadow && supportsShadow();
+
+        // Force to use wrapper to avoid rebuildOutline() on animating foreground drawable.
+        // See b/22724385
+        final boolean forceWrapperForOverlay = false;
+
+        // figure out shadow type and if we need use wrapper:
+        if (mNeedsShadow) {
+            // if static shadow is prefered or dynamic shadow is not supported,
+            // use static shadow,  otherwise use dynamic shadow.
+            if (!preferZOrder || !supportsDynamicShadow()) {
+                mShadowType = SHADOW_STATIC;
+                // static shadow requires ShadowOverlayContainer to support crossfading
+                // of two shadow views.
+                mNeedsWrapper = true;
+            } else {
+                useDynamicShadow(context);
+                mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
+            }
+        } else {
+            mShadowType = SHADOW_NONE;
+            mNeedsWrapper = ((!supportsForeground() || forceWrapperForOverlay) && mNeedsOverlay);
+        }
+
+        if (mNeedsWrapper) {
+            mCardWrapper = new ItemBridgeAdapter.Wrapper() {
+                @Override
+                public View createWrapper(View root) {
+                    Context context = root.getContext();
+                    ShadowOverlayContainer wrapper = createShadowOverlayContainer(context);
+                    wrapper.setLayoutParams(
+                            new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+                    return wrapper;
+                }
+                @Override
+                public void wrap(View wrapper, View wrapped) {
+                    ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+                }
+            };
+        }
+    }
+
+    /**
+     * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+     * before using shadow.  Depending on Shadow type, optical bounds might be applied.
+     */
+    public void prepareParentForShadow(ViewGroup parent) {
+        if (mShadowType == SHADOW_STATIC) {
+            StaticShadowHelper.getInstance().prepareParent(parent);
+        }
+    }
+
+    void useDynamicShadow(Context context) {
+        Resources res = context.getResources();
+        useDynamicShadow(res.getDimension(R.dimen.lb_material_shadow_normal_z),
+                res.getDimension(R.dimen.lb_material_shadow_focused_z));
+    }
+
+    void useDynamicShadow(float unfocusedZ, float focusedZ) {
+        mShadowType = SHADOW_DYNAMIC;
+        mUnfocusedZ = unfocusedZ;
+        mFocusedZ = focusedZ;
+    }
+
+    public int getShadowType() {
+        return mShadowType;
+    }
+
+    public boolean needsOverlay() {
+        return mNeedsOverlay;
+    }
+
+    public boolean needsRoundedCorner() {
+        return mNeedsRoundedCorner;
+    }
+
+    ShadowOverlayContainer createShadowOverlayContainer(Context context) {
+        return new ShadowOverlayContainer(context, mShadowType, mNeedsOverlay,
+                mNeedsRoundedCorner);
+    }
+
+    public ItemBridgeAdapter.Wrapper getWrapper() {
+        return mCardWrapper;
+    }
+
+    public void setOverlayColor(View view, int color) {
+        if (mNeedsWrapper) {
+            ((ShadowOverlayContainer) view).setOverlayColor(color);
+        } else {
+            Drawable d = ForegroundHelper.getInstance().getForeground(view);
+            if (d instanceof ColorDrawable) {
+                ((ColorDrawable) d).setColor(color);
+            } else {
+                ForegroundHelper.getInstance().setForeground(view, new ColorDrawable(color));
+            }
+        }
+    }
+
+    /**
+     * Called on view is created.
+     * @param view
+     */
+    public void onViewCreated(View view) {
+        if (!mNeedsWrapper) {
+            if (!mNeedsShadow) {
+                if (mNeedsRoundedCorner) {
+                    RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
+                }
+            } else {
+                if (mShadowType == SHADOW_DYNAMIC) {
+                    Object tag = ShadowHelper.getInstance().addDynamicShadow(
+                            view, mUnfocusedZ, mFocusedZ, mNeedsRoundedCorner);
+                    view.setTag(R.id.lb_shadow_impl, tag);
+                }
+            }
+        }
+    }
+
+    public static Object getNoneWrapperDyamicShadowImpl(View view) {
+        return view.getTag(R.id.lb_shadow_impl);
+    }
+
+    /**
+     * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
+     */
+    public static void setShadowFocusLevel(View view, float level) {
+        if (view instanceof ShadowOverlayContainer) {
+            ((ShadowOverlayContainer) view).setShadowFocusLevel(level);
+        } else {
+            setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level);
+        }
+    }
+
+    static void setShadowFocusLevel(Object impl, int shadowType, float level) {
+        if (impl != null) {
+            if (level < 0f) {
+                level = 0f;
+            } else if (level > 1f) {
+                level = 1f;
+            }
+            switch (shadowType) {
+                case SHADOW_DYNAMIC:
+                    ShadowHelper.getInstance().setShadowFocusLevel(impl, level);
+                    break;
+                case SHADOW_STATIC:
+                    StaticShadowHelper.getInstance().setShadowFocusLevel(impl, level);
+                    break;
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
index 4d8411b..022ff1d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
@@ -34,7 +34,7 @@
      */
     static interface ShadowHelperVersionImpl {
         public void prepareParent(ViewGroup parent);
-        public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners);
+        public Object addStaticShadow(ViewGroup shadowContainer);
         public void setShadowFocusLevel(Object impl, float level);
     }
 
@@ -48,7 +48,7 @@
         }
 
         @Override
-        public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
+        public Object addStaticShadow(ViewGroup shadowContainer) {
             // do nothing
             return null;
         }
@@ -69,8 +69,7 @@
         }
 
         @Override
-        public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
-            // Static shadows are always rounded
+        public Object addStaticShadow(ViewGroup shadowContainer) {
             return ShadowHelperJbmr2.addShadow(shadowContainer);
         }
 
@@ -105,8 +104,8 @@
         mImpl.prepareParent(parent);
     }
 
-    public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
-        return mImpl.addStaticShadow(shadowContainer, roundedCorners);
+    public Object addStaticShadow(ViewGroup shadowContainer) {
+        return mImpl.addStaticShadow(shadowContainer);
     }
 
     public void setShadowFocusLevel(Object impl, float level) {