Launcher state management cleanup

> Removing Widgets and related states
> Fixing different durations being used when opening/closing all-apps
> Removing some unnecessary object allocations when changing state without animation
> Differentiating widget bootm sheel and full sheet in logs

Bug: 67678570
Change-Id: Ic169528736d04ee0b38564b4f96595ba066eabda
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index e1b5e2c..1a1c319 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+
 import android.animation.AnimatorSet;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ObjectAnimator;
@@ -229,7 +231,7 @@
             public void run() {
                 completeDrop(d);
                 mDropTargetBar.onDragEnd();
-                mLauncher.exitSpringLoadedDragMode(true, 0);
+                mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
             }
         };
         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index d6c8575..3643971 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -104,8 +104,6 @@
     private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
     final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
 
-    private float mBackgroundAlpha;
-
     private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
     private static final int[] BACKGROUND_STATE_DEFAULT = new int[0];
     private final Drawable mBackground;
@@ -221,7 +219,7 @@
 
         mBackground = res.getDrawable(R.drawable.bg_celllayout);
         mBackground.setCallback(this);
-        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
+        mBackground.setAlpha(0);
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
 
@@ -440,7 +438,7 @@
         // When we're small, we are either drawn normally or in the "accepts drops" state (during
         // a drag). However, we also drag the mini hover background *over* one of those two
         // backgrounds
-        if (mBackgroundAlpha > 0.0f) {
+        if (mBackground.getAlpha() > 0) {
             mBackground.draw(canvas);
         }
 
@@ -847,15 +845,8 @@
         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
     }
 
-    public float getBackgroundAlpha() {
-        return mBackgroundAlpha;
-    }
-
-    public void setBackgroundAlpha(float alpha) {
-        if (mBackgroundAlpha != alpha) {
-            mBackgroundAlpha = alpha;
-            mBackground.setAlpha((int) (mBackgroundAlpha * 255));
-        }
+    public Drawable getScrimBackground() {
+        return mBackground;
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bfcd82a..a80e62b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_SHORT_TIMEOUT;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_APPS;
 import static com.android.launcher3.util.RunnableWithId.RUNNABLE_ID_BIND_WIDGETS;
 
@@ -117,6 +120,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -205,15 +209,13 @@
     static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
 
     /** The different states that Launcher can be in. */
-    enum State { WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
-        WIDGETS, WIDGETS_SPRING_LOADED }
+    enum State { WORKSPACE, WORKSPACE_SPRING_LOADED, APPS}
 
     @Thunk State mState = State.WORKSPACE;
     @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
 
     private boolean mIsSafeModeEnabled;
 
-    public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
     // How long to wait before the new-shortcut animation automatically pans the workspace
@@ -366,9 +368,6 @@
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
 
         mAppWidgetHost = new LauncherAppWidgetHost(this);
-        if (Utilities.ATLEAST_MARSHMALLOW) {
-            mAppWidgetHost.addProviderChangeListener(this);
-        }
         mAppWidgetHost.startListening();
 
         // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
@@ -582,8 +581,7 @@
         Runnable exitSpringLoaded = new Runnable() {
             @Override
             public void run() {
-                exitSpringLoadedDragMode((resultCode != RESULT_CANCELED),
-                        EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+                exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
             }
         };
 
@@ -635,7 +633,7 @@
                 final Runnable onComplete = new Runnable() {
                     @Override
                     public void run() {
-                        exitSpringLoadedDragMode(false, 0);
+                        exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
                     }
                 };
 
@@ -763,8 +761,7 @@
                 @Override
                 public void run() {
                     completeAddAppWidget(appWidgetId, requestArgs, layout, null);
-                    exitSpringLoadedDragMode((resultCode != RESULT_CANCELED),
-                            EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+                    exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
                 }
             };
         } else if (resultCode == RESULT_CANCELED) {
@@ -1439,8 +1436,9 @@
             if (topOpenView != null) {
                 topOpenView.logActionCommand(Action.Command.HOME_INTENT);
             } else if (alreadyOnHome) {
-                ued.logActionCommand(Action.Command.HOME_INTENT,
-                        mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
+                Target target = newContainerTarget(mWorkspace.getState().containerType);
+                target.pageIndex = mWorkspace.getCurrentPage();
+                ued.logActionCommand(Action.Command.HOME_INTENT, target);
             }
 
             // In all these cases, only animate if we're already on home
@@ -1714,8 +1712,7 @@
                 @Override
                 public void run() {
                     // Exit spring loaded mode if necessary after adding the widget
-                    exitSpringLoadedDragMode(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT
-                    );
+                    exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
                 }
             };
             completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
@@ -1899,9 +1896,6 @@
         } else if (isAppsViewVisible()) {
             ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS);
             showWorkspace(true);
-        } else if (isWidgetsViewVisible())  {
-            ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS);
-            showOverviewMode(true);
         } else if (mWorkspace.isInOverviewMode()) {
             ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW);
             showWorkspace(true);
@@ -2444,10 +2438,6 @@
         return mState == State.APPS;
     }
 
-    public boolean isWidgetsViewVisible() {
-        return mState == State.WIDGETS;
-    }
-
     @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
@@ -2546,29 +2536,18 @@
 
     /**
      * Shows the apps view.
-     */
-    public void showAppsView(boolean animated) {
-        markAppsViewShown();
-        showAppsOrWidgets(State.APPS, animated);
-    }
-
-    /**
-     * Sets up the transition to show the apps/widgets view.
      *
      * @return whether the current from and to state allowed this operation
      */
     // TODO: calling method should use the return value so that when {@code false} is returned
     // the workspace transition doesn't fall into invalid state.
-    private boolean showAppsOrWidgets(State toState, boolean animated) {
+    public boolean showAppsView(boolean animated) {
+        markAppsViewShown();
+
         if (!(mState == State.WORKSPACE ||
-                mState == State.APPS_SPRING_LOADED ||
-                mState == State.WIDGETS_SPRING_LOADED ||
                 (mState == State.APPS && mAllAppsController.isTransitioning()))) {
             return false;
         }
-        if (toState != State.APPS && toState != State.WIDGETS) {
-            return false;
-        }
 
         // This is a safe and supported transition to bypass spring_loaded mode.
         if (mExitSpringLoadedModeRunnable != null) {
@@ -2576,14 +2555,10 @@
             mExitSpringLoadedModeRunnable = null;
         }
 
-        if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(animated);
-        } else {
-            mStateTransitionAnimation.startAnimationToWidgets(animated);
-        }
+        mStateTransitionAnimation.startAnimationToAllApps(animated);
 
         // Change the state *after* we've called all the transition code
-        setState(toState);
+        setState(State.APPS);
         AbstractFloatingView.closeAllOpenViews(this);
 
         // Send an accessibility event to announce the context change
@@ -2613,12 +2588,11 @@
         setState(State.WORKSPACE_SPRING_LOADED);
     }
 
-    public void exitSpringLoadedDragMode(final boolean successfulDrop, int delay) {
-        exitSpringLoadedDragMode(successfulDrop, delay, null);
+    public void exitSpringLoadedDragMode(int delay) {
+        exitSpringLoadedDragMode(delay, null);
     }
 
-    public void exitSpringLoadedDragMode(final boolean successfulDrop, int delay,
-            final Runnable onCompleteRunnable) {
+    public void exitSpringLoadedDragMode(int delay, final Runnable onCompleteRunnable) {
         if (!isStateSpringLoaded()) return;
 
         // Unlock rotation lock
@@ -2634,18 +2608,12 @@
         mExitSpringLoadedModeRunnable = new Runnable() {
             @Override
             public void run() {
-                if (successfulDrop) {
-                    // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
-                    //
-                    // Before we show workspace, hide all apps again because
-                    // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
-                    // clean up our state transition functions
-                    showWorkspace(true, onCompleteRunnable);
-                } else if (mState == State.APPS_SPRING_LOADED) {
-                    showAppsView(true /* animated */);
-                } else if (mState == State.WORKSPACE_SPRING_LOADED) {
-                    showWorkspace(true);
-                }
+                // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
+                //
+                // Before we show workspace, hide all apps again because
+                // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
+                // clean up our state transition functions
+                showWorkspace(true, onCompleteRunnable);
                 mExitSpringLoadedModeRunnable = null;
             }
         };
@@ -2653,8 +2621,7 @@
     }
 
     boolean isStateSpringLoaded() {
-        return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
-                || mState == State.WIDGETS_SPRING_LOADED;
+        return mState == State.WORKSPACE_SPRING_LOADED;
     }
 
     @Override
@@ -3397,13 +3364,6 @@
         }
     }
 
-    @Override
-    public void notifyWidgetProvidersChanged() {
-        if (mWorkspace.getState().shouldUpdateWidget) {
-            refreshAndBindWidgetsForPackageUser(null);
-        }
-    }
-
     /**
      * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
      *                    refreshes the widgets and shortcuts associated with the given package/user
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index cfb9b57..02f05c3 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -30,6 +30,16 @@
 import java.util.WeakHashMap;
 
 public class LauncherAnimUtils {
+    /**
+     * Durations for various state animations. These are not defined in resources to allow
+     * easier access from static classes and enums
+     */
+    public static final int ALL_APPS_TRANSITION_MS = 320;
+    public static final int OVERVIEW_TRANSITION_MS = 250;
+    public static final int SPRING_LOADED_TRANSITION_MS = 150;
+    public static final int SPRING_LOADED_EXIT_SHORT_TIMEOUT = 500;
+    public static final int SPRING_LOADED_EXIT_NEXT_FRAME = 0;
+
     static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
     static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
         public void onAnimationStart(Animator animation) {
@@ -141,4 +151,18 @@
                     drawable.setAlpha(alpha);
                 }
             };
+
+    public static final Property<View, Float> SCALE_PROPERTY =
+            new Property<View, Float>(Float.class, "scale") {
+                @Override
+                public Float get(View view) {
+                    return view.getScaleX();
+                }
+
+                @Override
+                public void set(View view, Float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                }
+            };
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 618bd0f..469c5f1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -134,7 +134,7 @@
         }
     };
 
-    public interface Callbacks extends LauncherAppWidgetHost.ProviderChangedListener {
+    public interface Callbacks {
         public boolean setLoadOnResume();
         public int getCurrentWorkspaceScreen();
         public void clearPendingBinds();
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 5823734..9c83e3c 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.content.res.Resources;
 import android.util.Log;
 import android.view.View;
 
@@ -74,6 +73,7 @@
 
     public static final String TAG = "LSTAnimation";
 
+    private final AnimationConfig mConfig = new AnimationConfig();
     @Thunk Launcher mLauncher;
     @Thunk AnimatorSet mCurrentAnimation;
     AllAppsTransitionController mAllAppsController;
@@ -86,24 +86,22 @@
     /**
      * Starts an animation to the apps view.
      */
-    public void startAnimationToAllApps(final boolean animated) {
+    public void startAnimationToAllApps(boolean animated) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
 
+        // If for some reason our views aren't initialized, don't animate
+        animated = animated && (toView != null);
+
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
 
         final AnimationLayerSet layerViews = new AnimationLayerSet();
 
-        // If for some reason our views aren't initialized, don't animate
-        boolean initialized = toView != null;
-
         // Cancel the current animation
         cancelAnimation();
 
-        playCommonTransitionAnimations(Workspace.State.NORMAL_HIDDEN,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
+        if (!animated) {
+            mLauncher.getWorkspace().setState(Workspace.State.NORMAL_HIDDEN);
+
             mAllAppsController.finishPullUp();
             toView.setTranslationX(0.0f);
             toView.setTranslationY(0.0f);
@@ -115,6 +113,7 @@
             mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             return;
         }
+
         if (!FeatureFlags.LAUNCHER3_PHYSICS) {
             // We are animating the content view alpha, so ensure we have a layer for it.
             layerViews.addView(toView);
@@ -127,12 +126,16 @@
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             }
         });
-        boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
+
+        mConfig.reset();
+        mAllAppsController.animateToAllApps(animation, mConfig);
+        mLauncher.getWorkspace().setStateWithAnimation(Workspace.State.NORMAL_HIDDEN,
+                layerViews, animation, mConfig);
 
         Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
         mCurrentAnimation = animation;
         mCurrentAnimation.addListener(layerViews);
-        if (shouldPost) {
+        if (mConfig.shouldPost) {
             toView.post(startAnimRunnable);
         } else {
             startAnimRunnable.run();
@@ -140,14 +143,6 @@
     }
 
     /**
-     * Starts an animation to the widgets view.
-     */
-    public void startAnimationToWidgets(final boolean animated) {
-        // TODO: Remove this
-        throw new RuntimeException("This cannot happen");
-    }
-
-    /**
      * Starts an animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
@@ -159,8 +154,7 @@
             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
         }
 
-        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
-                || mAllAppsController.isTransitioning()) {
+        if (fromState == Launcher.State.APPS || mAllAppsController.isTransitioning()) {
             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
         } else {
@@ -170,54 +164,27 @@
     }
 
     /**
-     * Plays animations used by various transitions.
-     */
-    private void playCommonTransitionAnimations(
-            Workspace.State toWorkspaceState,
-            boolean animated, boolean initialized, AnimatorSet animation,
-            AnimationLayerSet layerViews) {
-        // Create the workspace animation.
-        // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.getWorkspace().
-                setStateWithAnimation(toWorkspaceState, animated, layerViews);
-
-        if (animated && initialized) {
-            // Play the workspace animation
-            if (workspaceAnim != null) {
-                animation.play(workspaceAnim);
-            }
-        }
-    }
-
-    /**
      * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated,
+            final Workspace.State toWorkspaceState, boolean animated,
             final Runnable onCompleteRunnable) {
-
         final AllAppsContainerView fromView = mLauncher.getAppsView();
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-
-        final View toView = mLauncher.getWorkspace();
-
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
         // If for some reason our views aren't initialized, don't animate
-        boolean initialized = fromView != null;
+        animated = animated & (fromView != null);
+
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
 
         // Cancel the current animation
         cancelAnimation();
 
-        playCommonTransitionAnimations(toWorkspaceState,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
+        if (!animated) {
             if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
                 mAllAppsController.finishPullDown();
             }
             fromView.setVisibility(View.GONE);
+            mLauncher.getWorkspace().setState(toWorkspaceState);
             mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
 
             // Run any queued runnables
@@ -227,9 +194,6 @@
             return;
         }
 
-        // We are animating the content view alpha, so ensure we have a layer for it
-        layerViews.addView(toView);
-
         animation.addListener(new AnimatorListenerAdapter() {
             boolean canceled = false;
             @Override
@@ -250,12 +214,16 @@
             }
 
         });
-        boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
 
-        Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
+        mConfig.reset();
+        mAllAppsController.animateToWorkspace(animation, mConfig);
+        mLauncher.getWorkspace().setStateWithAnimation(toWorkspaceState, layerViews, animation,
+                mConfig);
+
+        Runnable startAnimRunnable = new StartAnimRunnable(animation, mLauncher.getWorkspace());
         mCurrentAnimation = animation;
         mCurrentAnimation.addListener(layerViews);
-        if (shouldPost) {
+        if (mConfig.shouldPost) {
             fromView.post(startAnimRunnable);
         } else {
             startAnimRunnable.run();
@@ -269,39 +237,41 @@
             final Workspace.State toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
         final View fromWorkspace = mLauncher.getWorkspace();
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-
         // Cancel the current animation
         cancelAnimation();
 
-        playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
         mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
 
-        if (animated) {
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                }
-            });
-            animation.addListener(layerViews);
-            fromWorkspace.post(new StartAnimRunnable(animation, null));
-            mCurrentAnimation = animation;
-        } else /* if (!animated) */ {
+        if (!animated) {
+            mLauncher.getWorkspace().setState(toWorkspaceState);
             // Run any queued runnables
             if (onCompleteRunnable != null) {
                 onCompleteRunnable.run();
             }
-
-            mCurrentAnimation = null;
+            return;
         }
+
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        mConfig.reset();
+        mLauncher.getWorkspace().setStateWithAnimation(toWorkspaceState,
+                layerViews, animation, mConfig);
+
+        animation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Run any queued runnables
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+
+                // This can hold unnecessary references to views.
+                cleanupAnimation();
+            }
+        });
+        animation.addListener(layerViews);
+        fromWorkspace.post(new StartAnimRunnable(animation, null));
+        mCurrentAnimation = animation;
     }
 
     /**
@@ -340,4 +310,23 @@
             mAnim.start();
         }
     }
+
+    public static class AnimationConfig {
+        public boolean shouldPost;
+
+        private long mOverriddenDuration = -1;
+
+        public void reset() {
+            shouldPost = false;
+            mOverriddenDuration = -1;
+        }
+
+        public void overrideDuration(long duration) {
+            mOverriddenDuration = duration;
+        }
+
+        public long getDuration(long defaultDuration) {
+            return mOverriddenDuration >= 0 ? mOverriddenDuration : defaultDuration;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index c3d3bb3..10f35bd 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -75,8 +75,7 @@
 
         mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
         mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
-        mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
-                .mOverviewTransitionTime;
+        mNormalOverviewTransitionDuration = LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
     }
 
     public int getNormalOverviewTransitionDuration() {
@@ -131,7 +130,8 @@
         mWorkspace.setScaleX(interpolatedScale);
         mWorkspace.setScaleY(interpolatedScale);
         mWorkspace.setTranslationY(interpolatedTranslationY);
-        setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
+        int alpha = (int) ((1f - interpolatedProgress) * 255);
+        setOverviewPanelsAlpha(alpha, 0);
     }
 
     /**
@@ -180,14 +180,15 @@
         }
     }
 
-    private void setOverviewPanelsAlpha(float alpha, int duration) {
+    private void setOverviewPanelsAlpha(int alpha, int duration) {
         int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
             if (duration == 0) {
-                cl.setBackgroundAlpha(alpha);
+                cl.getScrimBackground().setAlpha(alpha);
             } else {
-                ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start();
+                ObjectAnimator.ofInt(cl.getScrimBackground(),
+                        LauncherAnimUtils.DRAWABLE_ALPHA, alpha).setDuration(duration).start();
             }
         }
     }
@@ -202,9 +203,9 @@
     }
 
     private void animateScrim(boolean show) {
-        float endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
-        startAnimator(INDEX_SCRIM,
-                ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", endValue),
+        int endValue = show ? mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha : 0;
+        startAnimator(INDEX_SCRIM, ObjectAnimator.ofInt(
+                mLauncher.getDragLayer().getScrim(), LauncherAnimUtils.DRAWABLE_ALPHA, endValue),
                 mNormalOverviewTransitionDuration);
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1525e41..b57ce63 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,6 +16,12 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_SHORT_TIMEOUT;
+import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -49,12 +55,14 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
@@ -181,20 +189,26 @@
     // in all apps or customize mode)
 
     public enum State {
-        NORMAL          (false, false, ContainerType.WORKSPACE),
-        NORMAL_HIDDEN   (false, false, ContainerType.ALLAPPS),
-        SPRING_LOADED   (false, true, ContainerType.WORKSPACE),
-        OVERVIEW        (true, true, ContainerType.OVERVIEW),
-        OVERVIEW_HIDDEN (true, false, ContainerType.WIDGETS);
+        NORMAL          (false, ContainerType.WORKSPACE, false, 1, 0),
+        NORMAL_HIDDEN   (false, ContainerType.ALLAPPS, false, 1, ALL_APPS_TRANSITION_MS),
+        SPRING_LOADED   (true, ContainerType.WORKSPACE, true, 1, SPRING_LOADED_TRANSITION_MS),
+        OVERVIEW        (true, ContainerType.OVERVIEW, true, 0, OVERVIEW_TRANSITION_MS);
 
-        public final boolean shouldUpdateWidget;
         public final boolean hasMultipleVisiblePages;
         public final int containerType;
 
-        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) {
-            this.shouldUpdateWidget = shouldUpdateWidget;
+        // Properties related to state transition animation.
+        public final boolean hasScrim;
+        public final float hotseatAlpha;
+        public final int transitionDuration;
+
+        State(boolean hasMultipleVisiblePages, int containerType,
+                boolean hasScrim, float hotseatAlpha, int transitionDuration) {
             this.hasMultipleVisiblePages = hasMultipleVisiblePages;
             this.containerType = containerType;
+            this.hasScrim = hasScrim;
+            this.hotseatAlpha = hotseatAlpha;
+            this.transitionDuration = transitionDuration;
         }
     }
 
@@ -1296,9 +1310,7 @@
                 }
             });
 
-            AccessibilityManager am = (AccessibilityManager)
-                    mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-            final boolean accessibilityEnabled = am.isEnabled();
+            final boolean accessibilityEnabled = isAccessibilityEnabled();
             animator.addUpdateListener(
                     new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled));
             animator.addUpdateListener(
@@ -1307,6 +1319,12 @@
         }
     }
 
+    protected boolean isAccessibilityEnabled() {
+        AccessibilityManager am = (AccessibilityManager)
+                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        return am.isEnabled();
+    }
+
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
@@ -1562,7 +1580,7 @@
     }
 
     public void snapToPageFromOverView(int whichPage) {
-        mStateTransitionAnimation.snapToPageFromOverView(whichPage);
+        snapToPage(whichPage, OVERVIEW_TRANSITION_MS, new ZoomInInterpolator());
     }
 
     int getOverviewModeTranslationY() {
@@ -1606,45 +1624,42 @@
         return mOverviewModeShrinkFactor;
     }
 
+
     /**
-     * Sets the current workspace {@link State}, returning an animation transitioning the workspace
-     * to that new state.
+     * Sets the current workspace {@link State} and updates the UI without any animations
      */
-    public Animator setStateWithAnimation(State toState, boolean animated,
-            AnimationLayerSet layerViews) {
+    public void setState(State toState) {
+        // Update the current state
+        mState = toState;
+        mStateTransitionAnimation.setState(mState);
+
+        updateAccessibilityFlags();
+        onPrepareStateTransition(mState.hasMultipleVisiblePages);
+        onStartStateTransition();
+        onEndStateTransition();
+    }
+
+    /**
+     * Sets the current workspace {@link State}, while animating the UI
+     */
+    public void setStateWithAnimation(State toState, AnimationLayerSet layerViews, AnimatorSet anim,
+            AnimationConfig config) {
         final State fromState = mState;
 
         // Update the current state
         mState = toState;
-
-        // Create the animation to the new state
-        AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(fromState,
-                toState, animated, layerViews);
-
-        boolean shouldNotifyWidgetChange = !fromState.shouldUpdateWidget
-                && toState.shouldUpdateWidget;
+        mStateTransitionAnimation.setStateWithAnimation(
+                fromState, toState, anim, layerViews, config);
 
         updateAccessibilityFlags();
-
-        if (shouldNotifyWidgetChange) {
-            mLauncher.notifyWidgetProvidersChanged();
-        }
-
         onPrepareStateTransition(mState.hasMultipleVisiblePages);
 
         StateTransitionListener listener = new StateTransitionListener();
-        if (animated) {
-            ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
-            stepAnimator.addUpdateListener(listener);
+        ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
+        stepAnimator.addUpdateListener(listener);
 
-            workspaceAnim.play(stepAnimator);
-            workspaceAnim.addListener(listener);
-        } else {
-            listener.onAnimationStart(null);
-            listener.onAnimationEnd(null);
-        }
-
-        return workspaceAnim;
+        anim.play(stepAnimator);
+        anim.addListener(listener);
     }
 
     public State getState() {
@@ -1701,11 +1716,31 @@
         updateChildrenLayersEnabled();
     }
 
+    private void onStartStateTransition() {
+        if (mState == State.SPRING_LOADED) {
+            // Show the page indicator at the same time as the rest of the transition.
+            showPageIndicatorAtCurrentScroll();
+        }
+        getPageIndicator().setShouldAutoHide(mState != State.SPRING_LOADED);
+    }
+
     public void onEndStateTransition() {
         mIsSwitchingState = false;
         updateChildrenLayersEnabled();
         mForceDrawAdjacentPages = false;
         mTransitionProgress = 1;
+
+        if (mState == State.OVERVIEW) {
+            enableFreeScroll();
+        } else {
+            disableFreeScroll();
+        }
+
+        ViewGroup overviewPanel = mLauncher.getOverviewPanel();
+        if (isAccessibilityEnabled() && overviewPanel.getVisibility() == View.VISIBLE) {
+            overviewPanel.getChildAt(0).performAccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+        }
     }
 
     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -2076,8 +2111,7 @@
                         dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
                         addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                                 distance, d, false)) {
-                    mLauncher.exitSpringLoadedDragMode(true,
-                            Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+                    mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
                     return;
                 }
 
@@ -2191,9 +2225,8 @@
                     // Animate the item to its original position, while simultaneously exiting
                     // spring-loaded mode so the page meets the icon where it was picked up.
                     mLauncher.getDragController().animateDragViewToOriginalPosition(
-                            onCompleteRunnable, cell,
-                            mStateTransitionAnimation.mSpringLoadedTransitionTime);
-                    mLauncher.exitSpringLoadedDragMode(false, 0);
+                            onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
+                    mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
                     mLauncher.getDropTargetBar().onDragEnd();
                     parent.onDropChild(cell);
                     return;
@@ -2216,8 +2249,8 @@
             }
             parent.onDropChild(cell);
 
-            mLauncher.exitSpringLoadedDragMode(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, onCompleteRunnable);
+            mLauncher.exitSpringLoadedDragMode(
+                    SPRING_LOADED_EXIT_SHORT_TIMEOUT, onCompleteRunnable);
         }
 
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2824,8 +2857,7 @@
                     animationStyle, finalView, true);
         } else {
             // This is for other drag/drop cases, like dragging from All Apps
-            mLauncher.exitSpringLoadedDragMode(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+            mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
 
             View view;
 
@@ -2887,10 +2919,9 @@
                 // We wrap the animation call in the temporary set and reset of the current
                 // cellLayout to its final transform -- this means we animate the drag view to
                 // the correct final location.
-                setFinalTransitionTransform(cellLayout);
-                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
-                        this);
-                resetTransitionTransform(cellLayout);
+                setFinalTransitionTransform();
+                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
+                resetTransitionTransform();
             }
         }
     }
@@ -2926,10 +2957,10 @@
         loc[0] = r.left;
         loc[1] = r.top;
 
-        setFinalTransitionTransform(layout);
+        setFinalTransitionTransform();
         float cellLayoutScale =
                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
-        resetTransitionTransform(layout);
+        resetTransitionTransform();
 
         if (scale) {
             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
@@ -3013,14 +3044,14 @@
         }
     }
 
-    public void setFinalTransitionTransform(CellLayout layout) {
+    public void setFinalTransitionTransform() {
         if (isSwitchingState()) {
             mCurrentScale = getScaleX();
             setScaleX(mStateTransitionAnimation.getFinalScale());
             setScaleY(mStateTransitionAnimation.getFinalScale());
         }
     }
-    public void resetTransitionTransform(CellLayout layout) {
+    public void resetTransitionTransform() {
         if (isSwitchingState()) {
             setScaleX(mCurrentScale);
             setScaleY(mCurrentScale);
@@ -3641,11 +3672,8 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            if (mState == State.SPRING_LOADED) {
-                // Show the page indicator at the same time as the rest of the transition.
-                showPageIndicatorAtCurrentScroll();
-            }
             mTransitionProgress = 0;
+            onStartStateTransition();
         }
 
         @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index e84d3b4..9d9e9fb 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -24,17 +27,14 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.util.Property;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.Workspace.State;
 import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.util.Thunk;
 
 /**
  * A convenience class to update a view's visibility state after an alpha animation.
@@ -131,78 +131,24 @@
 }
 
 /**
- * Stores the transition states for convenience.
- */
-class TransitionStates {
-
-    // Raw states
-    final boolean oldStateIsNormal;
-    final boolean oldStateIsSpringLoaded;
-    final boolean oldStateIsNormalHidden;
-    final boolean oldStateIsOverviewHidden;
-    final boolean oldStateIsOverview;
-
-    final boolean stateIsNormal;
-    final boolean stateIsSpringLoaded;
-    final boolean stateIsNormalHidden;
-    final boolean stateIsOverviewHidden;
-    final boolean stateIsOverview;
-
-    // Convenience members
-    final boolean workspaceToAllApps;
-    final boolean overviewToAllApps;
-    final boolean allAppsToWorkspace;
-    final boolean workspaceToOverview;
-    final boolean overviewToWorkspace;
-
-    public TransitionStates(final Workspace.State fromState, final Workspace.State toState) {
-        oldStateIsNormal = (fromState == Workspace.State.NORMAL);
-        oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
-        oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
-        oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
-        oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
-
-        stateIsNormal = (toState == Workspace.State.NORMAL);
-        stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
-        stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
-        stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
-        stateIsOverview = (toState == Workspace.State.OVERVIEW);
-
-        workspaceToOverview = (oldStateIsNormal && stateIsOverview);
-        workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
-        overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
-        overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
-        allAppsToWorkspace = (oldStateIsNormalHidden && stateIsNormal);
-    }
-}
-
-/**
  * Manages the animations between each of the workspace states.
  */
 public class WorkspaceStateTransitionAnimation {
 
-    public static final String TAG = "WorkspaceStateTransitionAnimation";
+    private static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
 
-    @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
+    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
 
-    final @Thunk Launcher mLauncher;
-    final @Thunk Workspace mWorkspace;
+    public final int mWorkspaceScrimAlpha;
 
-    @Thunk AnimatorSet mStateAnimator;
+    private final Launcher mLauncher;
+    private final Workspace mWorkspace;
 
-    @Thunk float mCurrentScale;
-    @Thunk float mNewScale;
+    private final float mSpringLoadedShrinkFactor;
+    private final float mOverviewModeShrinkFactor;
+    private final boolean mWorkspaceFadeInAdjacentScreens;
 
-    @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
-
-    @Thunk float mSpringLoadedShrinkFactor;
-    @Thunk float mOverviewModeShrinkFactor;
-    @Thunk float mWorkspaceScrimAlpha;
-    @Thunk int mAllAppsTransitionTime;
-    @Thunk int mOverviewTransitionTime;
-    @Thunk int mOverlayTransitionTime;
-    @Thunk int mSpringLoadedTransitionTime;
-    @Thunk boolean mWorkspaceFadeInAdjacentScreens;
+    private float mNewScale;
 
     public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
         mLauncher = launcher;
@@ -210,32 +156,24 @@
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
         Resources res = launcher.getResources();
-        mAllAppsTransitionTime = res.getInteger(R.integer.config_allAppsTransitionTime);
-        mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
-        mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
-        mSpringLoadedTransitionTime = mOverlayTransitionTime / 2;
         mSpringLoadedShrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
         mOverviewModeShrinkFactor =
                 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
-        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
+        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha);
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
     }
 
-    public void snapToPageFromOverView(int whichPage) {
-        mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator);
+    public void setState(Workspace.State toState) {
+        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
     }
 
-    public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            boolean animated, AnimationLayerSet layerViews) {
-        AccessibilityManager am = (AccessibilityManager)
-                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        final boolean accessibilityEnabled = am.isEnabled();
-        TransitionStates states = new TransitionStates(fromState, toState);
-        int workspaceDuration = getAnimationDuration(states);
-        animateWorkspace(states, animated, workspaceDuration, layerViews,
-                accessibilityEnabled);
-        animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
-        return mStateAnimator;
+    public void setStateWithAnimation(Workspace.State fromState, Workspace.State toState,
+            AnimatorSet anim, AnimationLayerSet layerViews, AnimationConfig config) {
+        long duration = config.getDuration(toState == State.NORMAL
+                ? fromState.transitionDuration : toState.transitionDuration);
+        AnimatedPropertySetter proertSetter =
+                new AnimatedPropertySetter(duration, layerViews, anim);
+        setWorkspaceProperty(toState, proertSetter);
     }
 
     public float getFinalScale() {
@@ -243,242 +181,138 @@
     }
 
     /**
-     * Returns the proper animation duration for a transition.
-     */
-    private int getAnimationDuration(TransitionStates states) {
-        if (states.workspaceToAllApps || states.overviewToAllApps) {
-            return mAllAppsTransitionTime;
-        } else if (states.workspaceToOverview || states.overviewToWorkspace) {
-            return mOverviewTransitionTime;
-        } else if (mLauncher.mState == Launcher.State.WORKSPACE_SPRING_LOADED
-                || states.oldStateIsNormal && states.stateIsSpringLoaded) {
-            return mSpringLoadedTransitionTime;
-        } else {
-            return mOverlayTransitionTime;
-        }
-    }
-
-    /**
      * Starts a transition animation for the workspace.
      */
-    private void animateWorkspace(final TransitionStates states, final boolean animated,
-            final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
-        // Cancel existing workspace animations and create a new animator set if requested
-        cancelAnimation();
-        if (animated) {
-            mStateAnimator = LauncherAnimUtils.createAnimatorSet();
-        }
-
+    private void setWorkspaceProperty(Workspace.State state, PropertySetter propertySetter) {
         // Update the workspace state
-        float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
-                1.0f : 0f;
-        float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
-                states.stateIsNormalHidden) ? 1f : 0f;
+        int finalBackgroundAlpha = state.hasScrim ? 255 : 0;
 
-        float finalWorkspaceTranslationY = 0;
-        if (states.stateIsOverview || states.stateIsOverviewHidden) {
-            finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
-        } else if (states.stateIsSpringLoaded) {
-            finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
-        }
-
-        final int childCount = mWorkspace.getChildCount();
-
-        mNewScale = 1.0f;
-
-        if (states.oldStateIsOverview) {
-            mWorkspace.disableFreeScroll();
-        } else if (states.stateIsOverview) {
-            mWorkspace.enableFreeScroll();
-        }
-
-        if (!states.stateIsNormal) {
-            if (states.stateIsSpringLoaded) {
-                mNewScale = mSpringLoadedShrinkFactor;
-            } else if (states.stateIsOverview || states.stateIsOverviewHidden) {
+        final float finalWorkspaceTranslationY;
+        switch (state) {
+            case OVERVIEW:
                 mNewScale = mOverviewModeShrinkFactor;
-            }
+                finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
+                break;
+            case SPRING_LOADED:
+                mNewScale = mSpringLoadedShrinkFactor;
+                finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
+                break;
+            default:
+                mNewScale = 1f;
+                finalWorkspaceTranslationY = 0;
         }
 
         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
-        // TODO: Animate the celllayout alpha instead of the pages.
+        final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
-            float finalAlpha;
-            if (states.stateIsOverviewHidden) {
-                finalAlpha = 0f;
-            } else if(states.stateIsNormalHidden) {
-                finalAlpha = (i == mWorkspace.getNextPage()) ? 1 : 0;
-            } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
-                finalAlpha = (i == toPage) ? 1f : 0f;
-            } else {
-                finalAlpha = 1f;
-            }
+            propertySetter.setInt(cl.getScrimBackground(),
+                    DRAWABLE_ALPHA, finalBackgroundAlpha, mZoomInInterpolator);
 
-            // If we are animating to/from the small state, then hide the side pages and fade the
-            // current page in
-            if (!FeatureFlags.NO_ALL_APPS_ICON && !mWorkspace.isSwitchingState()) {
-                if (states.workspaceToAllApps || states.allAppsToWorkspace) {
-                    boolean isCurrentPage = (i == toPage);
-                    if (states.allAppsToWorkspace && isCurrentPage) {
-                        initialAlpha = 0f;
-                    } else if (!isCurrentPage) {
-                        initialAlpha = finalAlpha = 0f;
-                    }
-                    cl.setShortcutAndWidgetAlpha(initialAlpha);
-                }
-            }
-
-            if (animated) {
-                float oldBackgroundAlpha = cl.getBackgroundAlpha();
-                if (initialAlpha != finalAlpha) {
-                    Animator alphaAnim = ObjectAnimator.ofFloat(
-                            cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha);
-                    alphaAnim.setDuration(duration)
-                            .setInterpolator(mZoomInInterpolator);
-                    mStateAnimator.play(alphaAnim);
-                }
-                if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) {
-                    ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
-                            oldBackgroundAlpha, finalBackgroundAlpha);
-                    bgAnim.setInterpolator(mZoomInInterpolator);
-                    bgAnim.setDuration(duration);
-                    mStateAnimator.play(bgAnim);
-                }
-            } else {
-                cl.setBackgroundAlpha(finalBackgroundAlpha);
-                cl.setShortcutAndWidgetAlpha(finalAlpha);
+            // Only animate the page alpha when we actually fade pages
+            if (mWorkspaceFadeInAdjacentScreens) {
+                float finalAlpha = state == State.NORMAL && i != toPage ? 0 : 1f;
+                propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+                        finalAlpha, mZoomInInterpolator);
             }
         }
 
-        final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
+        float finalHotseatAlpha = state.hotseatAlpha;
 
-        float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
-        if (animated) {
-            // This is true when transitioning between:
-            // - Overview <-> Workspace
-            // - Overview <-> Widget Tray
-            if (finalOverviewPanelAlpha != overviewPanel.getAlpha()) {
-                Animator overviewPanelAlpha = ObjectAnimator.ofFloat(
-                        overviewPanel, View.ALPHA, finalOverviewPanelAlpha);
-                overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
-                        accessibilityEnabled));
-                layerViews.addView(overviewPanel);
+        // This is true when transitioning between:
+        // - Overview <-> Workspace
+        propertySetter.setViewAlpha(null, mLauncher.getOverviewPanel(), 1 - finalHotseatAlpha);
+        propertySetter.setViewAlpha(mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha),
+                mLauncher.getHotseat(), finalHotseatAlpha);
 
-                if (states.overviewToWorkspace) {
-                    overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
-                } else if (states.workspaceToOverview) {
-                    overviewPanelAlpha.setInterpolator(null);
-                }
+        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, mZoomInInterpolator);
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+                finalWorkspaceTranslationY, mZoomInInterpolator);
 
-                overviewPanelAlpha.setDuration(duration);
-                mStateAnimator.play(overviewPanelAlpha);
+        // Set scrim
+        propertySetter.setInt(mLauncher.getDragLayer().getScrim(), DRAWABLE_ALPHA,
+                state.hasScrim ? mWorkspaceScrimAlpha : 0, new DecelerateInterpolator(1.5f));
+    }
+
+    private static class PropertySetter {
+
+        public void setViewAlpha(Animator anim, View view, float alpha) {
+            if (anim != null) {
+                anim.end();
+                return;
             }
+            view.setAlpha(alpha);
+            AlphaUpdateListener.updateVisibility(view, isAccessibilityEnabled(view));
+        }
 
-            Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace,
-                    new PropertyListBuilder().scale(mNewScale)
-                            .translationY(finalWorkspaceTranslationY).build())
-                    .setDuration(duration);
-            scale.setInterpolator(mZoomInInterpolator);
-            mStateAnimator.play(scale);
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
+                TimeInterpolator interpolator) {
+            property.set(target, value);
+        }
 
-            // For animation optimization, we may need to provide the Launcher transition
-            // with a set of views on which to force build and manage layers in certain scenarios.
-            layerViews.addView(mLauncher.getHotseat());
-            layerViews.addView(mWorkspace.getPageIndicator());
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
+                TimeInterpolator interpolator) {
+            property.set(target, value);
+        }
 
-            Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
-            if (states.workspaceToOverview) {
-                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
-            } else if (states.overviewToWorkspace) {
-                hotseatAlpha.setInterpolator(null);
-            }
-            hotseatAlpha.setDuration(duration);
-            mStateAnimator.play(hotseatAlpha);
-            mStateAnimator.addListener(new AnimatorListenerAdapter() {
-                boolean canceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    canceled = true;
-                }
-
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mStateAnimator = null;
-                    if (canceled) return;
-                    if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
-                        overviewPanel.getChildAt(0).performAccessibilityAction(
-                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-                    }
-                }
-            });
-        } else {
-            overviewPanel.setAlpha(finalOverviewPanelAlpha);
-            AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
-            mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
-
-            mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
-            mWorkspace.setScaleX(mNewScale);
-            mWorkspace.setScaleY(mNewScale);
-            mWorkspace.setTranslationY(finalWorkspaceTranslationY);
-
-            if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
-                overviewPanel.getChildAt(0).performAccessibilityAction(
-                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-            }
+        protected boolean isAccessibilityEnabled(View v) {
+            AccessibilityManager am = (AccessibilityManager)
+                    v.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            return am.isEnabled();
         }
     }
 
-    /**
-     * Animates the background scrim. Add to the state animator to prevent jankiness.
-     *
-     * @param states the current and final workspace states
-     * @param animated whether or not to set the background alpha immediately
-     * @duration duration of the animation
-     */
-    private void animateBackgroundGradient(TransitionStates states,
-            boolean animated, int duration) {
+    private static class AnimatedPropertySetter extends PropertySetter {
 
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        final float startAlpha = dragLayer.getBackgroundAlpha();
-        float finalAlpha = states.stateIsNormal || states.stateIsNormalHidden ?
-                0 : mWorkspaceScrimAlpha;
+        private final long mDuration;
+        private final AnimationLayerSet mLayerViews;
+        private final AnimatorSet mStateAnimator;
 
-        if (finalAlpha != startAlpha) {
-            if (animated) {
-                // These properties refer to the background protection gradient used for AllApps
-                // and Widget tray.
-                ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha);
-                bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        dragLayer.setBackgroundAlpha(
-                                ((Float)animation.getAnimatedValue()).floatValue());
-                    }
-                });
-                bgFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
-                bgFadeOutAnimation.setDuration(duration);
-                mStateAnimator.play(bgFadeOutAnimation);
-            } else {
-                dragLayer.setBackgroundAlpha(finalAlpha);
+        AnimatedPropertySetter(long duration, AnimationLayerSet layerView, AnimatorSet anim) {
+            mDuration = duration;
+            mLayerViews = layerView;
+            mStateAnimator = anim;
+        }
+
+        @Override
+        public void setViewAlpha(Animator anim, View view, float alpha) {
+            if (anim == null) {
+                if (view.getAlpha() == alpha) {
+                    return;
+                }
+                anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
+                anim.addListener(new AlphaUpdateListener(view, isAccessibilityEnabled(view)));
             }
-        }
-    }
 
-    /**
-     * Cancels the current animation.
-     */
-    private void cancelAnimation() {
-        if (mStateAnimator != null) {
-            mStateAnimator.setDuration(0);
-            mStateAnimator.cancel();
+            anim.setDuration(mDuration).setInterpolator(getFadeInterpolator(alpha));
+            mLayerViews.addView(view);
+            mStateAnimator.play(anim);
         }
-        mStateAnimator = null;
+
+        @Override
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofFloat(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        @Override
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
+                TimeInterpolator interpolator) {
+            if (property.get(target) == value) {
+                return;
+            }
+            Animator anim = ObjectAnimator.ofInt(target, property, value);
+            anim.setDuration(mDuration).setInterpolator(interpolator);
+            mStateAnimator.play(anim);
+        }
+
+        private TimeInterpolator getFadeInterpolator(float finalAlpha) {
+            return finalAlpha == 0 ? new DecelerateInterpolator(2) : null;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index bb0822f..5642b4c 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,12 +11,14 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
@@ -329,15 +331,15 @@
         mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange);
     }
 
-    public boolean animateToAllApps(AnimatorSet animationOut, long duration) {
-        boolean shouldPost = true;
+    public void animateToAllApps(AnimatorSet animationOut, AnimationConfig outConfig) {
+        outConfig.shouldPost = true;
         if (animationOut == null) {
-            return shouldPost;
+            return;
         }
         Interpolator interpolator;
         if (mDetector.isIdleState()) {
             preparePull(true);
-            mAnimationDuration = duration;
+            mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
             mShiftStart = mAppsView.getTranslationY();
             interpolator = mFastOutSlowInInterpolator;
         } else {
@@ -347,9 +349,10 @@
             if (nextFrameProgress >= 0f) {
                 mProgress = nextFrameProgress;
             }
-            shouldPost = false;
+            outConfig.shouldPost = false;
         }
 
+        outConfig.overrideDuration(mAnimationDuration);
         ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
                 mProgress, 0f);
         driftAndAlpha.setDuration(mAnimationDuration);
@@ -396,7 +399,6 @@
             }
         });
         mCurrentAnimation = animationOut;
-        return shouldPost;
     }
 
     public void showDiscoveryBounce() {
@@ -432,15 +434,15 @@
         });
     }
 
-    public boolean animateToWorkspace(AnimatorSet animationOut, long duration) {
-        boolean shouldPost = true;
+    public void animateToWorkspace(AnimatorSet animationOut, AnimationConfig outconfig) {
+        outconfig.shouldPost = true;
         if (animationOut == null) {
-            return shouldPost;
+            return;
         }
         Interpolator interpolator;
         if (mDetector.isIdleState()) {
             preparePull(true);
-            mAnimationDuration = duration;
+            mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
             mShiftStart = mAppsView.getTranslationY();
             interpolator = mFastOutSlowInInterpolator;
         } else {
@@ -450,9 +452,10 @@
             if (nextFrameProgress <= 1f) {
                 mProgress = nextFrameProgress;
             }
-            shouldPost = false;
+            outconfig.shouldPost = false;
         }
 
+        outconfig.overrideDuration(mAnimationDuration);
         ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
                 mProgress, 1f);
         driftAndAlpha.setDuration(mAnimationDuration);
@@ -479,7 +482,6 @@
             }
         });
         mCurrentAnimation = animationOut;
-        return shouldPost;
     }
 
     public void finishPullUp() {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 31255b7..8386f8f 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_SHORT_TIMEOUT;
+
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -261,8 +263,7 @@
         if (!accepted) {
             // If it was not accepted, cleanup the state. If it was accepted, it is the
             // responsibility of the drop target to cleanup the state.
-            mLauncher.exitSpringLoadedDragMode(false,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+            mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
             mDragObject.deferDragViewCleanupPostAnimation = false;
         }
 
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 95223c3..bc5aafc 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -25,8 +25,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.support.v4.graphics.ColorUtils;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -50,7 +49,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dynamicui.WallpaperColorInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -83,19 +81,15 @@
 
     private boolean mHoverPointClosesFolder = false;
     private final Rect mHitRect = new Rect();
-    private final Rect mHighlightRect = new Rect();
 
     private TouchCompleteListener mTouchCompleteListener;
 
     private int mTopViewIndex;
     private int mChildCountOnLastUpdate = -1;
 
-    // Darkening scrim
-    private float mBackgroundAlpha = 0;
-
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
-    private final WallpaperColorInfo mWallpaperColorInfo;
+    private final PageCutOutScrimDrawable mPageCutOutScrim;
 
     // Related to pinch-to-go-to-overview gesture.
     private PinchToOverviewListener mPinchListener = null;
@@ -118,7 +112,8 @@
         setChildrenDrawingOrderEnabled(true);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
-        mWallpaperColorInfo = WallpaperColorInfo.getInstance(getContext());
+        mPageCutOutScrim = new PageCutOutScrimDrawable(this);
+        mPageCutOutScrim.setCallback(this);
     }
 
     public void setup(Launcher launcher, DragController dragController,
@@ -141,6 +136,11 @@
         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
     }
 
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mPageCutOutScrim;
+    }
+
     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
                 ? null : new PinchToOverviewListener(mLauncher);
@@ -772,48 +772,23 @@
     }
 
     public void invalidateScrim() {
-        if (mBackgroundAlpha > 0.0f) {
+        if (mPageCutOutScrim.getAlpha() > 0) {
             invalidate();
         }
     }
 
+    public Drawable getScrim() {
+        return mPageCutOutScrim;
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
-        if (mBackgroundAlpha > 0.0f) {
-            // Update the scroll position first to ensure scrim cutout is in the right place.
-            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
-
-            int alpha = (int) (mBackgroundAlpha * 255);
-            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
-            canvas.save();
-            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
-                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
-                getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
-                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
-            }
-            // for super light wallpaper it needs to be darken for contrast to workspace
-            // for dark wallpapers the text is white so darkening works as well
-            int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor());
-            canvas.drawColor(ColorUtils.setAlphaComponent(color, alpha));
-            canvas.restore();
-        }
-
+        mPageCutOutScrim.draw(canvas);
         mFocusIndicatorHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
 
-    public void setBackgroundAlpha(float alpha) {
-        if (alpha != mBackgroundAlpha) {
-            mBackgroundAlpha = alpha;
-            invalidate();
-        }
-    }
-
-    public float getBackgroundAlpha() {
-        return mBackgroundAlpha;
-    }
-
     @Override
     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
diff --git a/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
new file mode 100644
index 0000000..367124b
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PageCutOutScrimDrawable.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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 com.android.launcher3.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dynamicui.WallpaperColorInfo;
+
+/**
+ * Scrim drawable which draws a hole for the current drop target page.
+ */
+public class PageCutOutScrimDrawable extends Drawable {
+
+    private final Rect mHighlightRect = new Rect();
+    private final DragLayer mDragLayer;
+    private final Launcher mLauncher;
+    private final WallpaperColorInfo mWallpaperColorInfo;
+
+    private int mAlpha = 0;
+
+    public PageCutOutScrimDrawable(DragLayer dragLayer) {
+        mDragLayer = dragLayer;
+        mLauncher = Launcher.getLauncher(mDragLayer.getContext());
+        mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // Draw the background below children.
+        if (mAlpha > 0) {
+            // Update the scroll position first to ensure scrim cutout is in the right place.
+            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
+            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
+            canvas.save();
+            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
+                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+                mDragLayer.getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+            }
+            // for super light wallpaper it needs to be darken for contrast to workspace
+            // for dark wallpapers the text is white so darkening works as well
+            int color = ColorUtils.compositeColors(0x66000000, mWallpaperColorInfo.getMainColor());
+            canvas.drawColor(ColorUtils.setAlphaComponent(color, mAlpha));
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) { }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index a70a9bb..1da7834 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -29,7 +29,7 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.IconCache;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -83,8 +83,8 @@
     public com.android.launcher3.ShortcutInfo createShortcutInfo() {
         // Total duration for the drop animation to complete.
         long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT +
-                mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2;
+                LauncherAnimUtils.SPRING_LOADED_EXIT_SHORT_TIMEOUT +
+                LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
         // Delay the actual accept() call until the drop animation is complete.
         return LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest(
                 mContext, mRequest, duration);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4a60d4c..fae420e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_SHORT_TIMEOUT;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -1241,8 +1243,7 @@
             mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
         }
 
-        mLauncher.exitSpringLoadedDragMode(true,
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT);
+        mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_SHORT_TIMEOUT);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index cdb0ce3..ec448e9 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -45,8 +48,6 @@
 
 import java.util.List;
 
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
 /**
  * Manages the opening and closing animations for a {@link Folder}.
  *
@@ -77,19 +78,6 @@
 
     private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
 
-    private static final Property<View, Float> SCALE_PROPERTY =
-            new Property<View, Float>(Float.class, "scale") {
-                @Override
-                public Float get(View view) {
-                    return view.getScaleX();
-                }
-
-                @Override
-                public void set(View view, Float scale) {
-                    view.setScaleX(scale);
-                    view.setScaleY(scale);
-                }
-            };
 
     public FolderAnimationManager(Folder folder, boolean isOpening) {
         mFolder = folder;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d469eb2..900781c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -296,7 +296,7 @@
                 to = new Rect();
                 Workspace workspace = mLauncher.getWorkspace();
                 // Set cellLayout and this to it's final state to compute final animation locations
-                workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
+                workspace.setFinalTransitionTransform();
                 float scaleX = getScaleX();
                 float scaleY = getScaleY();
                 setScaleX(1.0f);
@@ -305,7 +305,7 @@
                 // Finished computing final animation locations, restore current state
                 setScaleX(scaleX);
                 setScaleY(scaleY);
-                workspace.resetTransitionTransform((CellLayout) getParent().getParent());
+                workspace.resetTransitionTransform();
             }
 
             int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index d5c6515..1a6dade 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -177,13 +177,11 @@
     }
 
     public void logActionCommand(int command, int containerType) {
-        logActionCommand(command, containerType, 0);
+        logActionCommand(command, newContainerTarget(containerType));
     }
 
-    public void logActionCommand(int command, int containerType, int pageIndex) {
-        LauncherEvent event = newLauncherEvent(
-                newCommandAction(command), newContainerTarget(containerType));
-        event.srcTarget[0].pageIndex = pageIndex;
+    public void logActionCommand(int command, Target target) {
+        LauncherEvent event = newLauncherEvent(newCommandAction(command), target);
         dispatchUserEvent(event, null);
     }
 
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 78ecbc6..53a9862 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -336,17 +336,7 @@
             });
         }
 
-        // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
-        // get widget update signals.
-        if (!Utilities.ATLEAST_MARSHMALLOW &&
-                (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.notifyWidgetProvidersChanged();
-                }
-            });
-        } else if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
+        if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
             for (int i = 0; i < N; i++) {
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index 15f5af6..ec62764 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.util;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -95,7 +97,7 @@
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
-                mLauncher.exitSpringLoadedDragMode(false, 0);
+                mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
                 mDropTarget.completeDrop(mDragObject);
             }
         };
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index b6b3089..61a7333 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -32,7 +34,6 @@
 import android.widget.Toast;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
@@ -41,7 +42,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -264,11 +264,16 @@
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         targetParent.containerType = ContainerType.WIDGETS;
+        targetParent.cardinality = getElementsRowCount();
     }
 
     @Override
     public final void logActionCommand(int command) {
-        // TODO: be more specific
-        mLauncher.getUserEventDispatcher().logActionCommand(command, ContainerType.WIDGETS);
+        Target target = newContainerTarget(ContainerType.WIDGETS);
+        target.cardinality = getElementsRowCount();
+        mLauncher.getUserEventDispatcher().logActionCommand(command, target);
     }
+
+    protected abstract int getElementsRowCount();
+
 }
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 201bd1c..3bb3fcb 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -181,4 +181,9 @@
         setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
                 getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
     }
+
+    @Override
+    protected int getElementsRowCount() {
+        return 1;
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 72277a2..a40ea1b 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -219,4 +219,9 @@
         sheet.open(animate);
         return sheet;
     }
+
+    @Override
+    protected int getElementsRowCount() {
+        return mAdapter.getItemCount();
+    }
 }