Merge "Dark color on super light wallpaper support" into ub-launcher3-dorval-polish
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3f6f506..b7189d1 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -130,5 +130,6 @@
         <attr name="android:src" />
         <attr name="android:shadowColor" />
         <attr name="android:elevation" />
+        <attr name="android:tint" />
     </declare-styleable>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index d2272f2..db1a75d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -64,6 +64,9 @@
     <!-- The duration of the animation from search hint to text entry -->
     <integer name="config_searchHintAnimationDuration">50</integer>
 
+    <!-- View tag key used to store SpringAnimation data. -->
+    <item type="id" name="spring_animation_tag" />
+
 <!-- Workspace -->
     <!-- The duration (in ms) of the fade animation on the object outlines, used when
          we are dragging objects around on the home screen. -->
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 1e6d894..514cc07 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -48,6 +48,8 @@
     private int mDownY;
     private int mLastY;
 
+    private boolean mScrollBarVisible = true;
+
     public BaseRecyclerView(Context context) {
         this(context, null);
     }
@@ -199,8 +201,18 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        onUpdateScrollbar(0);
-        mScrollbar.draw(canvas);
+        if (mScrollBarVisible) {
+            onUpdateScrollbar(0);
+            mScrollbar.draw(canvas);
+        }
+    }
+
+    /**
+     * Sets the scrollbar visibility. The call does not refresh the UI, its the responsibility
+     * of the caller to call {@link #invalidate()}.
+     */
+    public void setScrollBarVisible(boolean visible) {
+        mScrollBarVisible = visible;
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a399d74..c3df073 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,7 +20,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.InsetDrawable;
-import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
@@ -92,9 +91,8 @@
 
         mLauncher = Launcher.getLauncher(context);
         mApps = new AlphabeticalAppsList(context);
-        mSpringAnimationHandler = new SpringAnimationHandler(SpringAnimationHandler.Y_DIRECTION);
-        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this,
-                mSpringAnimationHandler);
+        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
+        mSpringAnimationHandler = mAdapter.getSpringAnimationHandler();
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mSearchQueryBuilder = new SpannableStringBuilder();
@@ -426,8 +424,11 @@
 
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
-                mSpringAnimationHandler.skipToEnd();
+            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING
+                    || (dx == 0 && dy == 0)) {
+                if (mSpringAnimationHandler.isRunning()){
+                    mSpringAnimationHandler.skipToEnd();
+                }
                 return;
             }
 
@@ -436,7 +437,7 @@
 
             // We only show the spring animation when at the top or bottom, so we wait until the
             // first or last row is visible to ensure that all animations run in sync.
-            if (first == 0 || last >= mAdapter.getItemCount() - mAdapter.getNumAppsPerRow()) {
+            if ((first == 0 && dy < 0) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
                 mSpringAnimationHandler.animateToFinalPosition(0);
             }
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index d3d23ca..9c7372f 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.support.animation.DynamicAnimation;
 import android.support.animation.SpringAnimation;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
@@ -38,6 +39,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
@@ -96,11 +98,6 @@
      */
     public static class ViewHolder extends RecyclerView.ViewHolder {
 
-        /**
-         * Springs used for items where isViewType(viewType, VIEW_TYPE_MASK_HAS_SPRINGS) is true.
-         */
-        private SpringAnimation spring;
-
         public ViewHolder(View v) {
             super(v);
         }
@@ -213,11 +210,10 @@
     // The intent to send off to the market app, updated each time the search query changes.
     private Intent mMarketSearchIntent;
 
-    private SpringAnimationHandler mSpringAnimationHandler;
+    private SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
 
     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
-            iconClickListener, View.OnLongClickListener iconLongClickListener,
-            SpringAnimationHandler springAnimationHandler) {
+            iconClickListener, View.OnLongClickListener iconLongClickListener) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -228,7 +224,14 @@
         mLayoutInflater = LayoutInflater.from(launcher);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        mSpringAnimationHandler = springAnimationHandler;
+        if (FeatureFlags.LAUNCHER3_PHYSICS) {
+            mSpringAnimationHandler = new SpringAnimationHandler<>(
+                    SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
+        }
+    }
+
+    public SpringAnimationHandler getSpringAnimationHandler() {
+        return mSpringAnimationHandler;
     }
 
     public static boolean isDividerViewType(int viewType) {
@@ -292,8 +295,7 @@
                         R.layout.all_apps_icon, parent, false);
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
-                icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
-                        .getLongPressTimeout());
+                icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
                 // Ensure the all apps icon height matches the workspace icons
@@ -386,8 +388,7 @@
     public void onViewAttachedToWindow(ViewHolder holder) {
         int type = holder.getItemViewType();
         if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            holder.spring = mSpringAnimationHandler.add(holder.itemView,
-                    holder.getAdapterPosition(), mApps, mAppsPerRow, holder.spring);
+            mSpringAnimationHandler.add(holder.itemView, holder);
         }
     }
 
@@ -395,7 +396,7 @@
     public void onViewDetachedFromWindow(ViewHolder holder) {
         int type = holder.getItemViewType();
         if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            holder.spring = mSpringAnimationHandler.remove(holder.spring);
+            mSpringAnimationHandler.remove(holder.itemView);
         }
     }
 
@@ -415,4 +416,121 @@
         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
+
+    /**
+     * Helper class to set the SpringAnimation values for an item in the adapter.
+     */
+    private class AllAppsSpringAnimationFactory
+            implements SpringAnimationHandler.AnimationFactory<ViewHolder> {
+        private static final float DEFAULT_MAX_VALUE_PX = 100;
+        private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX;
+
+        // Damping ratio range is [0, 1]
+        private static final float SPRING_DAMPING_RATIO = 0.55f;
+
+        // Stiffness is a non-negative number.
+        private static final float MIN_SPRING_STIFFNESS = 580f;
+        private static final float MAX_SPRING_STIFFNESS = 900f;
+
+        // The amount by which each adjacent rows' stiffness will differ.
+        private static final float ROW_STIFFNESS_COEFFICIENT = 50f;
+
+        @Override
+        public SpringAnimation initialize(ViewHolder vh) {
+            return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0);
+        }
+
+        /**
+         * @param spring A new or recycled SpringAnimation.
+         * @param vh The ViewHolder that {@param spring} is related to.
+         */
+        @Override
+        public void update(SpringAnimation spring, ViewHolder vh) {
+            int numPredictedApps = Math.min(mAppsPerRow, mApps.getPredictedApps().size());
+            int appPosition = getAppPosition(vh.getAdapterPosition(), numPredictedApps,
+                    mAppsPerRow);
+
+            int col = appPosition % mAppsPerRow;
+            int row = appPosition / mAppsPerRow;
+
+            int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count
+            if (row > (numTotalRows / 2)) {
+                // Mirror the rows so that the top row acts the same as the bottom row.
+                row = Math.abs(numTotalRows - row);
+            }
+
+            // We manipulate the stiffness, min, and max values based on the items distance to the
+            // first row and the items distance to the center column to create the ^-shaped motion
+            // effect.
+            float rowFactor = (1 + row) * 0.5f;
+            float colFactor = getColumnFactor(col, mAppsPerRow);
+
+            float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor);
+            float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor);
+
+            float stiffness = Utilities.boundToRange(
+                    MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT),
+                    MIN_SPRING_STIFFNESS,
+                    MAX_SPRING_STIFFNESS);
+
+            spring.setMinValue(minValue)
+                    .setMaxValue(maxValue)
+                    .getSpring()
+                    .setStiffness(stiffness)
+                    .setDampingRatio(SPRING_DAMPING_RATIO);
+        }
+
+        /**
+         * @return The app position is the position of the app in the Adapter if we ignored all
+         * other view types.
+         *
+         * The first app is at position 0, and the first app each following row is at a
+         * position that is a multiple of {@param appsPerRow}.
+         *
+         * ie. If there are 5 apps per row, and there are two rows of apps:
+         *     0 1 2 3 4
+         *     5 6 7 8 9
+         */
+        private int getAppPosition(int position, int numPredictedApps, int appsPerRow) {
+            int appPosition = position;
+            int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1);
+
+            int allAppsStartAt = numDividerViews + numPredictedApps;
+            if (numDividerViews == 1 || position < allAppsStartAt) {
+                appPosition -= 1;
+            } else {
+                // We cannot assume that the predicted row will always be full.
+                int numPredictedAppsOffset = appsPerRow - numPredictedApps;
+                appPosition = position + numPredictedAppsOffset - numDividerViews;
+            }
+
+            return appPosition;
+        }
+
+        /**
+         * Increase the column factor as the distance increases between the column and the center
+         * column(s).
+         */
+        private float getColumnFactor(int col, int numCols) {
+            float centerColumn = numCols / 2;
+            int distanceToCenter = (int) Math.abs(col - centerColumn);
+
+            boolean evenNumberOfColumns = numCols % 2 == 0;
+            if (evenNumberOfColumns && col < centerColumn) {
+                distanceToCenter -= 1;
+            }
+
+            float factor = 0;
+            while (distanceToCenter > 0) {
+                if (distanceToCenter == 1) {
+                    factor += 0.2f;
+                } else {
+                    factor += 0.1f;
+                }
+                --distanceToCenter;
+            }
+
+            return factor;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index d76abcc..b2a74ff 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -97,6 +97,10 @@
         mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
     }
 
+    public AlphabeticalAppsList getApps() {
+        return mApps;
+    }
+
     /**
      * Sets the number of apps per row in this recycler view.
      */
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index 488657c..038f826 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.anim;
 
-import android.support.animation.DynamicAnimation;
+import android.support.animation.FloatPropertyCompat;
 import android.support.animation.SpringAnimation;
 import android.support.animation.SpringForce;
 import android.support.annotation.IntDef;
@@ -24,8 +24,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.R;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -35,77 +34,67 @@
  * Handler class that manages springs for a set of views that should all move based on the same
  * {@link MotionEvent}s.
  *
- * Supports using physics for X or Y translations.
+ * Supports setting either X or Y velocity on the list of springs added to this handler.
  */
-public class SpringAnimationHandler {
+public class SpringAnimationHandler<T> {
 
     private static final String TAG = "SpringAnimationHandler";
     private static final boolean DEBUG = false;
 
-    private static final float DEFAULT_MAX_VALUE = 100;
-    private static final float DEFAULT_MIN_VALUE = -DEFAULT_MAX_VALUE;
-
-    private static final float SPRING_DAMPING_RATIO = 0.55f;
-    private static final float MIN_SPRING_STIFFNESS = 580f;
-    private static final float MAX_SPRING_STIFFNESS = 900f;
+    private static final float VELOCITY_DAMPING_FACTOR = 0.175f;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({Y_DIRECTION, X_DIRECTION})
     public @interface Direction {}
     public static final int Y_DIRECTION = 0;
     public static final int X_DIRECTION = 1;
-    private int mDirection;
+    private int mVelocityDirection;
 
     private VelocityTracker mVelocityTracker;
     private float mCurrentVelocity = 0;
     private boolean mShouldComputeVelocity = false;
 
+    private AnimationFactory<T> mAnimationFactory;
+
     private ArrayList<SpringAnimation> mAnimations = new ArrayList<>();
 
-    public SpringAnimationHandler(@Direction int direction) {
-        mDirection = direction;
-        mVelocityTracker = VelocityTracker.obtain();
+    /**
+     * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}.
+     *                  Determines which direction we use to calculate and set the velocity.
+     * @param factory   The AnimationFactory is responsible for initializing and updating the
+     *                  SpringAnimations added to this class.
+     */
+    public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) {
+        mVelocityDirection = direction;
+        mAnimationFactory = factory;
     }
 
-    public SpringAnimation add(View view, int position, AlphabeticalAppsList apps, int appsPerRow,
-            SpringAnimation recycle) {
-        int numPredictedApps = Math.min(appsPerRow, apps.getPredictedApps().size());
-        int appPosition = getAppPosition(position, numPredictedApps, appsPerRow);
-
-        int col = appPosition % appsPerRow;
-        int row = appPosition / appsPerRow;
-
-        int numTotalRows = apps.getNumAppRows() - 1; // zero offset
-        if (row > (numTotalRows / 2)) {
-            // Mirror the rows so that the top row acts the same as the bottom row.
-            row = Math.abs(numTotalRows - row);
+    /**
+     * Adds a new or recycled animation to the list of springs handled by this class.
+     *
+     * @param view The view the spring is attached to.
+     * @param object Used to initialize and update the spring.
+     */
+    public void add(View view, T object) {
+        SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
+        if (spring == null) {
+            spring = mAnimationFactory.initialize(object);
+            view.setTag(R.id.spring_animation_tag, spring);
         }
-
-        // We manipulate the stiffness, min, and max values based on the items distance to the first
-        // row and the items distance to the center column to create the ^-shaped motion effect.
-        float rowFactor = (1 + row) * 0.5f;
-        float colFactor = getColumnFactor(col, appsPerRow);
-
-        float minValue = DEFAULT_MIN_VALUE * (rowFactor + colFactor);
-        float maxValue = DEFAULT_MAX_VALUE * (rowFactor + colFactor);
-
-        float stiffness = Utilities.boundToRange(MAX_SPRING_STIFFNESS - (row * 50f),
-                MIN_SPRING_STIFFNESS, MAX_SPRING_STIFFNESS);
-
-        SpringAnimation animation = (recycle != null ? recycle : createSpringAnimation(view))
-                .setStartVelocity(mCurrentVelocity)
-                .setMinValue(minValue)
-                .setMaxValue(maxValue);
-        animation.getSpring().setStiffness(stiffness);
-
-        mAnimations.add(animation);
-        return animation;
+        mAnimationFactory.update(spring, object);
+        spring.setStartVelocity(mCurrentVelocity);
+        mAnimations.add(spring);
     }
 
-    public SpringAnimation remove(SpringAnimation animation) {
-        animation.skipToEnd();
+    /**
+     * Stops and removes the spring attached to {@param view}.
+     */
+    public void remove(View view) {
+        SpringAnimation animation = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
+        if (animation.canSkipToEnd()) {
+            animation.skipToEnd();
+        }
         mAnimations.remove(animation);
-        return animation;
     }
 
     public void addMovement(MotionEvent event) {
@@ -138,13 +127,20 @@
         reset();
     }
 
+    public boolean isRunning() {
+        // All the animations run at the same time so we can just check the first one.
+        return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
+    }
+
     public void skipToEnd() {
         if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd");
         if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception());
 
         int size = mAnimations.size();
         for (int i = 0; i < size; ++i) {
-            mAnimations.get(i).skipToEnd();
+            if (mAnimations.get(i).canSkipToEnd()) {
+                mAnimations.get(i).skipToEnd();
+            }
         }
     }
 
@@ -164,78 +160,19 @@
     }
 
     private void computeVelocity() {
-        getVelocityTracker().computeCurrentVelocity(175);
+        getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
 
         mCurrentVelocity = isVerticalDirection()
                 ? getVelocityTracker().getYVelocity()
                 : getVelocityTracker().getXVelocity();
+        mCurrentVelocity *= VELOCITY_DAMPING_FACTOR;
         mShouldComputeVelocity = false;
 
         if (DEBUG) Log.d(TAG, "computeVelocity=" + mCurrentVelocity);
     }
 
     private boolean isVerticalDirection() {
-        return mDirection == Y_DIRECTION;
-    }
-
-    private SpringAnimation createSpringAnimation(View view) {
-        DynamicAnimation.ViewProperty property = isVerticalDirection()
-                ? DynamicAnimation.TRANSLATION_Y
-                : DynamicAnimation.TRANSLATION_X;
-
-        return new SpringAnimation(view, property, 0)
-                .setStartValue(1f)
-                .setSpring(new SpringForce(0)
-                .setDampingRatio(SPRING_DAMPING_RATIO));
-    }
-
-    /**
-     * @return The app position is the position of the app in the Adapter if we ignored all other
-     * view types.
-     *
-     * ie. The first predicted app is at position 0, and the first app of all apps is
-     *     at {@param appsPerRow}.
-     */
-    private int getAppPosition(int position, int numPredictedApps, int appsPerRow) {
-        int appPosition = position;
-        int numDividerViews = 1 + (numPredictedApps == 0 ? 0 : 1);
-
-        int allAppsStartAt = numDividerViews + numPredictedApps;
-        if (numDividerViews == 1 || position < allAppsStartAt) {
-            appPosition -= 1;
-        } else {
-            // We cannot assume that the predicted row will always be full.
-            int numPredictedAppsOffset = appsPerRow - numPredictedApps;
-            appPosition = position + numPredictedAppsOffset - numDividerViews;
-        }
-
-        return appPosition;
-    }
-
-    /**
-     * Increase the column factor as the distance increases between the column and the center
-     * column(s).
-     */
-    private float getColumnFactor(int col, int numCols) {
-        float centerColumn = numCols / 2;
-        int distanceToCenter = (int) Math.abs(col - centerColumn);
-
-        boolean evenNumberOfColumns = numCols % 2 == 0;
-        if (evenNumberOfColumns && col < centerColumn) {
-            distanceToCenter -= 1;
-        }
-
-        float factor = 0;
-        while (distanceToCenter > 0) {
-            if (distanceToCenter == 1) {
-                factor += 0.2f;
-            } else {
-                factor += 0.1f;
-            }
-            --distanceToCenter;
-        }
-
-        return factor;
+        return mVelocityDirection == Y_DIRECTION;
     }
 
     private VelocityTracker getVelocityTracker() {
@@ -244,4 +181,34 @@
         }
         return mVelocityTracker;
     }
+
+    /**
+     * This interface is used to initialize and update the SpringAnimations added to the
+     * {@link SpringAnimationHandler}.
+     *
+     * @param <T> The object that each SpringAnimation is attached to.
+     */
+    public interface AnimationFactory<T> {
+
+        /**
+         * Initializes a new Spring for {@param object}.
+         */
+        SpringAnimation initialize(T object);
+
+        /**
+         * Updates the value of {@param spring} based on {@param object}.
+         */
+        void update(SpringAnimation spring, T object);
+    }
+
+    /**
+     * Helper method to create a new SpringAnimation for {@param view}.
+     */
+    public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
+        SpringAnimation spring = new SpringAnimation(view, property, finalPos);
+        spring.setStartValue(1f);
+        spring.setSpring(new SpringForce(finalPos));
+        return spring;
+    }
+
 }
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index 5f4fc6c..45c1b6a 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.graphics;
 
+import android.annotation.TargetApi;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -26,7 +28,9 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.AttributeSet;
 
 import com.android.launcher3.R;
@@ -40,6 +44,7 @@
 /**
  * A drawable which adds shadow around a child drawable.
  */
+@TargetApi(Build.VERSION_CODES.O)
 public class ShadowDrawable extends Drawable {
 
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -99,6 +104,24 @@
         return mState.mIntrinsicWidth;
     }
 
+    @Override
+    public boolean canApplyTheme() {
+        return mState.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Resources.Theme t) {
+        if (mState.canApplyTheme()) {
+            // Workaround since ColorStateList does not expose applyTheme method
+            ColorDrawable cd = new ColorDrawable();
+            cd.setTintList(mState.mTintColor);
+            cd.applyTheme(t);
+
+            mState.mLastDrawnBitmap = null;
+            invalidateSelf();
+        }
+    }
+
     private void regenerateBitmapCache() {
         Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
                 Bitmap.Config.ARGB_8888);
@@ -109,6 +132,9 @@
         d.setBounds(mState.mShadowSize, mState.mShadowSize,
                 mState.mIntrinsicWidth - mState.mShadowSize,
                 mState.mIntrinsicHeight - mState.mShadowSize);
+        if (mState.mTintColor != null) {
+            d.setTint(mState.mTintColor.getDefaultColor());
+        }
         d.draw(canvas);
 
         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -146,6 +172,7 @@
                     R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK);
             mState.mShadowSize = a.getDimensionPixelSize(
                     R.styleable.ShadowDrawable_android_elevation, 0);
+            mState.mTintColor = a.getColorStateList(R.styleable.ShadowDrawable_android_tint);
 
             mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize;
             mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize;
@@ -165,6 +192,7 @@
 
         int mShadowColor;
         int mShadowSize;
+        ColorStateList mTintColor;
 
         Bitmap mLastDrawnBitmap;
         ConstantState mChildState;
@@ -178,5 +206,10 @@
         public int getChangingConfigurations() {
             return mChangingConfigurations;
         }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mTintColor != null;
+        }
     }
 }