Refactoring to single path for overview mode.

Change-Id: I590a813c6f031342d75c3a6c3e9c9afda1808f2e
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2bfb29e..7364a9f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,7 +214,6 @@
     /** The different states that Launcher can be in. */
     enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
     @Thunk State mState = State.WORKSPACE;
-    @Thunk AnimatorSet mStateAnimation;
     @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
 
     private boolean mIsSafeModeEnabled;
@@ -528,7 +527,8 @@
 
             @Override
             public void dismissAllApps() {
-                showWorkspace(true);
+                showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, null,
+                        false /* notifyLauncherCallbacks */);
             }
         });
         return true;
@@ -785,7 +785,7 @@
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
             if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
-                mWorkspace.exitOverviewMode(false);
+                showWorkspace(false);
             }
             return;
         }
@@ -1220,7 +1220,6 @@
         return false;
     }
 
-
     public void addToCustomContentPage(View customContent,
             CustomContentCallbacks callbacks, String description) {
         mWorkspace.addToCustomContentPage(customContent, callbacks, description);
@@ -2480,7 +2479,7 @@
         } else if (isWidgetsViewVisible())  {
             showOverviewMode(true);
         } else if (mWorkspace.isInOverviewMode()) {
-            mWorkspace.exitOverviewMode(true);
+            showWorkspace(true);
         } else if (mWorkspace.getOpenFolder() != null) {
             Folder openFolder = mWorkspace.getOpenFolder();
             if (openFolder.isEditingName()) {
@@ -2523,14 +2522,14 @@
 
         if (v instanceof Workspace) {
             if (mWorkspace.isInOverviewMode()) {
-                mWorkspace.exitOverviewMode(true);
+                showWorkspace(true);
             }
             return;
         }
 
         if (v instanceof CellLayout) {
             if (mWorkspace.isInOverviewMode()) {
-                mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
+                showWorkspace(mWorkspace.indexOfChild(v), true);
             }
         }
 
@@ -3178,7 +3177,9 @@
 
         if (v instanceof Workspace) {
             if (!mWorkspace.isInOverviewMode()) {
-                if (mWorkspace.enterOverviewMode()) {
+
+                if (!mWorkspace.isTouchActive()) {
+                    showOverviewMode(true);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                     return true;
@@ -3211,7 +3212,7 @@
                 if (mWorkspace.isInOverviewMode()) {
                     mWorkspace.startReordering(v);
                 } else {
-                    mWorkspace.enterOverviewMode();
+                    showOverviewMode(true);
                 }
             } else {
                 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
@@ -3302,17 +3303,28 @@
     }
 
     protected void showWorkspace(boolean animated) {
-        showWorkspace(animated, null);
+        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null,
+                true);
     }
 
-    void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+    protected void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
+                onCompleteRunnable, true);
+    }
+
+    protected void showWorkspace(int snapToPage, boolean animated) {
+        showWorkspace(snapToPage, animated, null, true);
+    }
+
+    void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable,
+            boolean notifyLauncherCallbacks) {
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
             boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
             mWorkspace.setVisibility(View.VISIBLE);
             mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
-                    animated, onCompleteRunnable);
+                    snapToPage, animated, onCompleteRunnable);
 
             // Show the search bar (only animate if we were showing the drop target bar in spring
             // loaded mode)
@@ -3345,7 +3357,8 @@
     void showOverviewMode(boolean animated) {
         mWorkspace.setVisibility(View.VISIBLE);
         mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
-                animated, null /* onCompleteRunnable */);
+                WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
+                null /* onCompleteRunnable */);
         mState = State.WORKSPACE;
         onWorkspaceShown(animated);
     }
@@ -3419,7 +3432,8 @@
         }
 
         mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
-                true /* animated */, null /* onCompleteRunnable */);
+                WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
+                null /* onCompleteRunnable */);
         mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
     }
 
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 2ce8b1c..a2c56a9 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -126,7 +126,7 @@
 
             @Override
             public boolean onLongClick(View v) {
-                mLauncher.getWorkspace().enterOverviewMode();
+                mLauncher.showOverviewMode(true);
                 dismissLongPressCling();
                 return true;
             }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 8ba5c60..f91cfa0 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,7 +23,6 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.content.res.Resources;
-import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewAnimationUtils;
@@ -174,7 +173,8 @@
             }
         };
         startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(),
-                toView.getRevealView(), animated, false /* hideSearchBar */, cb);
+                toView.getRevealView(), animated,
+                !mLauncher.isAllAppsSearchOverridden() /* hideSearchBar */, cb);
     }
 
     /**
@@ -207,8 +207,8 @@
      * Starts and animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final boolean animated,
-              final Runnable onCompleteRunnable) {
+              final Workspace.State toWorkspaceState, final int toWorkspacePage,
+              final boolean animated, final Runnable onCompleteRunnable) {
         if (toWorkspaceState != Workspace.State.NORMAL &&
                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
                 toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -216,11 +216,11 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, animated,
-                    onCompleteRunnable);
+            startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, toWorkspacePage,
+                    animated, onCompleteRunnable);
         } else {
-            startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, animated,
-                    onCompleteRunnable);
+            startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, toWorkspacePage,
+                    animated, onCompleteRunnable);
         }
     }
 
@@ -249,8 +249,8 @@
 
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.getWorkspace().getChangeStateAnimation(
-                toWorkspaceState, animated, layerViews);
+        Animator workspaceAnim = mLauncher.getWorkspace().setStateWithAnimation(
+                toWorkspaceState, -1, animated, layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@@ -424,8 +424,8 @@
      * Starts and animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final boolean animated,
-              final Runnable onCompleteRunnable) {
+              final Workspace.State toWorkspaceState, final int toWorkspacePage,
+              final boolean animated, final Runnable onCompleteRunnable) {
         AppsContainerView appsView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             int[] mAllAppsToPanelDelta;
@@ -477,16 +477,17 @@
                 };
             }
         };
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, appsView, appsView.getContentView(),
-                appsView.getRevealView(), animated, onCompleteRunnable, cb);
+        startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, appsView,
+                appsView.getContentView(), appsView.getRevealView(), animated, onCompleteRunnable,
+                cb);
     }
 
     /**
      * Starts and animation to the workspace from the widgets view.
      */
     private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final boolean animated,
-              final Runnable onCompleteRunnable) {
+              final Workspace.State toWorkspaceState, final int toWorkspacePage,
+              final boolean animated, final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         final Resources res = mLauncher.getResources();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -514,7 +515,7 @@
                 };
             }
         };
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, widgetsView,
+        startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView,
                 widgetsView.getContentView(), widgetsView.getRevealView(), animated,
                 onCompleteRunnable, cb);
     }
@@ -523,8 +524,8 @@
      * Creates and starts a new animation to the workspace.
      */
     private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
-              final View fromView, final View contentView, final View revealView,
-              final boolean animated, final Runnable onCompleteRunnable,
+              final int toWorkspacePage, final View fromView, final View contentView,
+              final View revealView, final boolean animated, final Runnable onCompleteRunnable,
               final PrivateTransitionCallbacks pCb) {
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.isLmpOrAbove();
@@ -545,8 +546,8 @@
 
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.getWorkspace().getChangeStateAnimation(
-                toWorkspaceState, animated, layerViews);
+        Animator workspaceAnim = mLauncher.getWorkspace().setStateWithAnimation(
+                toWorkspaceState, toWorkspacePage, animated, layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 88295c0..a4593ec 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1696,11 +1696,11 @@
         return OVERSCROLL_DAMP_FACTOR * f;
     }
 
-    protected void enableFreeScroll() {
+    public void enableFreeScroll() {
         setEnableFreeScroll(true);
     }
 
-    protected void disableFreeScroll() {
+    public void disableFreeScroll() {
         setEnableFreeScroll(false);
     }
 
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index a8dcd0f..af8bc75 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -33,18 +33,16 @@
  */
 public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
 
-    private static final int sTransitionInDuration = 200;
-    private static final int sTransitionOutDuration = 175;
+    private static final int TRANSITION_DURATION = 200;
 
-    private ObjectAnimator mDropTargetBarAnim;
-    private ValueAnimator mQSBSearchBarAnim;
+    private ObjectAnimator mShowDropTargetBarAnim;
+    private ValueAnimator mHideSearchBarAnim;
     private static final AccelerateInterpolator sAccelerateInterpolator =
             new AccelerateInterpolator();
 
     private boolean mIsSearchBarHidden;
     private View mQSBSearchBar;
     private View mDropTargetBar;
-    private int mBarHeight;
     private boolean mDeferOnDragEnd = false;
 
     // Drop targets
@@ -52,8 +50,6 @@
     private ButtonDropTarget mDeleteDropTarget;
     private ButtonDropTarget mUninstallDropTarget;
 
-    private boolean mEnableDropDownDropTargets;
-
     public SearchDropTargetBar(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -82,17 +78,12 @@
     public void setQsbSearchBar(View qsb) {
         mQSBSearchBar = qsb;
         if (mQSBSearchBar != null) {
-            if (mEnableDropDownDropTargets) {
-                mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "translationY", 0,
-                        -mBarHeight);
-            } else {
-                mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
-            }
-            setupAnimation(mQSBSearchBarAnim, mQSBSearchBar);
+            mHideSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
+            setupAnimation(mHideSearchBarAnim, mQSBSearchBar);
         } else {
             // Create a no-op animation of the search bar is null
-            mQSBSearchBarAnim = ValueAnimator.ofFloat(0, 0);
-            mQSBSearchBarAnim.setDuration(sTransitionInDuration);
+            mHideSearchBarAnim = ValueAnimator.ofFloat(0, 0);
+            mHideSearchBarAnim.setDuration(TRANSITION_DURATION);
         }
     }
 
@@ -106,7 +97,7 @@
 
     private void setupAnimation(ValueAnimator anim, final View v) {
         anim.setInterpolator(sAccelerateInterpolator);
-        anim.setDuration(sTransitionInDuration);
+        anim.setDuration(TRANSITION_DURATION);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -131,76 +122,74 @@
         mDeleteDropTarget.setSearchDropTargetBar(this);
         mUninstallDropTarget.setSearchDropTargetBar(this);
 
-        mEnableDropDownDropTargets =
-            getResources().getBoolean(R.bool.config_useDropTargetDownTransition);
-
         // Create the various fade animations
-        if (mEnableDropDownDropTargets) {
-            LauncherAppState app = LauncherAppState.getInstance();
-            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-            mBarHeight = grid.searchBarSpaceHeightPx;
-            mDropTargetBar.setTranslationY(-mBarHeight);
-            mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "translationY",
-                    -mBarHeight, 0f);
-
-        } else {
-            mDropTargetBar.setAlpha(0f);
-            mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
-        }
-        setupAnimation(mDropTargetBarAnim, mDropTargetBar);
+        mDropTargetBar.setAlpha(0f);
+        mShowDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
+        setupAnimation(mShowDropTargetBarAnim, mDropTargetBar);
     }
 
+    /**
+     * Finishes all the animations on the search and drop target bars.
+     */
     public void finishAnimations() {
         prepareStartAnimation(mDropTargetBar);
-        mDropTargetBarAnim.reverse();
+        mShowDropTargetBarAnim.reverse();
         prepareStartAnimation(mQSBSearchBar);
-        mQSBSearchBarAnim.reverse();
+        mHideSearchBarAnim.reverse();
     }
 
-    /*
-     * Shows and hides the search bar.
+    /**
+     * Shows the search bar.
      */
     public void showSearchBar(boolean animated) {
-        boolean needToCancelOngoingAnimation = mQSBSearchBarAnim.isRunning() && !animated;
-        if (!mIsSearchBarHidden && !needToCancelOngoingAnimation) return;
+        if (!mIsSearchBarHidden) return;
         if (animated) {
             prepareStartAnimation(mQSBSearchBar);
-            mQSBSearchBarAnim.reverse();
+            mHideSearchBarAnim.reverse();
         } else {
-            mQSBSearchBarAnim.cancel();
-            if (mQSBSearchBar != null && mEnableDropDownDropTargets) {
-                mQSBSearchBar.setTranslationY(0);
-            } else if (mQSBSearchBar != null) {
+            mHideSearchBarAnim.cancel();
+            if (mQSBSearchBar != null) {
                 mQSBSearchBar.setAlpha(1f);
             }
         }
         mIsSearchBarHidden = false;
     }
+
+    /**
+     * Hides the search bar.  We only use this for clings.
+     */
     public void hideSearchBar(boolean animated) {
-        boolean needToCancelOngoingAnimation = mQSBSearchBarAnim.isRunning() && !animated;
-        if (mIsSearchBarHidden && !needToCancelOngoingAnimation) return;
+        if (mIsSearchBarHidden) return;
         if (animated) {
             prepareStartAnimation(mQSBSearchBar);
-            mQSBSearchBarAnim.start();
+            mHideSearchBarAnim.start();
         } else {
-            mQSBSearchBarAnim.cancel();
-            if (mQSBSearchBar != null && mEnableDropDownDropTargets) {
-                mQSBSearchBar.setTranslationY(-mBarHeight);
-            } else if (mQSBSearchBar != null) {
+            mHideSearchBarAnim.cancel();
+            if (mQSBSearchBar != null) {
                 mQSBSearchBar.setAlpha(0f);
             }
         }
         mIsSearchBarHidden = true;
     }
 
-    /*
-     * Gets various transition durations.
+    /**
+     * Shows the drop target bar.
      */
-    public int getTransitionInDuration() {
-        return sTransitionInDuration;
+    public void showDeleteTarget() {
+        // Animate out the QSB search bar, and animate in the drop target bar
+        prepareStartAnimation(mDropTargetBar);
+        mShowDropTargetBarAnim.start();
+        hideSearchBar(true);
     }
-    public int getTransitionOutDuration() {
-        return sTransitionOutDuration;
+
+    /**
+     * Hides the drop target bar.
+     */
+    public void hideDeleteTarget() {
+        // Restore the QSB search bar, and animate out the drop target bar
+        prepareStartAnimation(mDropTargetBar);
+        mShowDropTargetBarAnim.reverse();
+        showSearchBar(true);
     }
 
     /*
@@ -211,26 +200,6 @@
         showDeleteTarget();
     }
 
-    public void showDeleteTarget() {
-        // Animate out the QSB search bar, and animate in the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mDropTargetBarAnim.start();
-        if (!mIsSearchBarHidden) {
-            prepareStartAnimation(mQSBSearchBar);
-            mQSBSearchBarAnim.start();
-        }
-    }
-
-    public void hideDeleteTarget() {
-        // Restore the QSB search bar, and animate out the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mDropTargetBarAnim.reverse();
-        if (!mIsSearchBarHidden) {
-            prepareStartAnimation(mQSBSearchBar);
-            mQSBSearchBarAnim.reverse();
-        }
-    }
-
     public void deferOnDragEnd() {
         mDeferOnDragEnd = true;
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6b03e31..9e680fb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -17,15 +17,10 @@
 package com.android.launcher3;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetHostView;
@@ -99,12 +94,9 @@
     protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
     protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
 
-    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
     private static final int FLING_THRESHOLD_VELOCITY = 500;
 
-    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
-
     static final boolean MAP_NO_RECURSE = false;
     static final boolean MAP_RECURSE = true;
 
@@ -113,10 +105,6 @@
     private ObjectAnimator mChildrenOutlineFadeOutAnimation;
     private float mChildrenOutlineAlpha = 0;
 
-    // These properties refer to the background protection gradient used for AllApps and Customize
-    private ValueAnimator mBackgroundFadeInAnimation;
-    private ValueAnimator mBackgroundFadeOutAnimation;
-
     private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
     private long mTouchDownTime = -1;
     private long mCustomContentShowTime = -1;
@@ -129,7 +117,6 @@
     private int mDefaultPage;
 
     private ShortcutAndWidgetContainer mDragSourceInternal;
-    @Thunk static boolean sAccessibilityEnabled;
 
     // The screen id used for the empty screen always present to the right.
     final static long EXTRA_EMPTY_SCREEN_ID = -201;
@@ -272,14 +259,7 @@
     private float mSavedTranslationX;
 
     private float mCurrentScale;
-    private float mNewScale;
-    @Thunk float[] mOldBackgroundAlphas;
-    private float[] mOldAlphas;
-    @Thunk float[] mNewBackgroundAlphas;
-    private float[] mNewAlphas;
-    private int mLastChildCount = -1;
     private float mTransitionProgress;
-    @Thunk Animator mStateAnimator = null;
 
     float mOverScrollEffect = 0f;
 
@@ -294,6 +274,9 @@
     boolean mShouldSendPageSettled;
     int mLastOverlaySroll = 0;
 
+    // Handles workspace state transitions
+    private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
+
     private final Runnable mBindPages = new Runnable() {
         @Override
         public void run() {
@@ -329,6 +312,7 @@
         setDataIsReady();
 
         mLauncher = (Launcher) context;
+        mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
         final Resources res = getResources();
         mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
                 getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
@@ -1583,37 +1567,6 @@
         return mChildrenOutlineAlpha;
     }
 
-    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-
-        if (mBackgroundFadeInAnimation != null) {
-            mBackgroundFadeInAnimation.cancel();
-            mBackgroundFadeInAnimation = null;
-        }
-        if (mBackgroundFadeOutAnimation != null) {
-            mBackgroundFadeOutAnimation.cancel();
-            mBackgroundFadeOutAnimation = null;
-        }
-        float startAlpha = dragLayer.getBackgroundAlpha();
-        if (finalAlpha != startAlpha) {
-            if (animated) {
-                mBackgroundFadeOutAnimation =
-                        LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
-                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        dragLayer.setBackgroundAlpha(
-                                ((Float)animation.getAnimatedValue()).floatValue());
-                    }
-                });
-                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
-                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
-                mBackgroundFadeOutAnimation.start();
-            } else {
-                dragLayer.setBackgroundAlpha(finalAlpha);
-            }
-        }
-    }
-
     float backgroundAlphaInterpolator(float r) {
         float pivotA = 0.1f;
         float pivotB = 0.4f;
@@ -1737,7 +1690,7 @@
         OnClickListener listener = new OnClickListener() {
             @Override
             public void onClick(View arg0) {
-                enterOverviewMode();
+                mLauncher.showOverviewMode(true);
             }
         };
         return listener;
@@ -1797,9 +1750,6 @@
                 getPageIndicator().setOnClickListener(listener);
             }
         }
-        AccessibilityManager am = (AccessibilityManager)
-                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-        sAccessibilityEnabled = am.isEnabled();
 
         // Update wallpaper dimensions if they were changed since last onResume
         // (we also always set the wallpaper dimensions in the constructor)
@@ -1970,64 +1920,6 @@
     }
 
     /*
-     * This interpolator emulates the rate at which the perceived scale of an object changes
-     * as its distance from a camera increases. When this interpolator is applied to a scale
-     * animation on a view, it evokes the sense that the object is shrinking due to moving away
-     * from the camera.
-     */
-    static class ZInterpolator implements TimeInterpolator {
-        private float focalLength;
-
-        public ZInterpolator(float foc) {
-            focalLength = foc;
-        }
-
-        public float getInterpolation(float input) {
-            return (1.0f - focalLength / (focalLength + input)) /
-                (1.0f - focalLength / (focalLength + 1.0f));
-        }
-    }
-
-    /*
-     * The exact reverse of ZInterpolator.
-     */
-    static class InverseZInterpolator implements TimeInterpolator {
-        private ZInterpolator zInterpolator;
-        public InverseZInterpolator(float foc) {
-            zInterpolator = new ZInterpolator(foc);
-        }
-        public float getInterpolation(float input) {
-            return 1 - zInterpolator.getInterpolation(1 - input);
-        }
-    }
-
-    /*
-     * ZInterpolator compounded with an ease-out.
-     */
-    static class ZoomOutInterpolator implements TimeInterpolator {
-        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
-        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
-
-        public float getInterpolation(float input) {
-            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
-        }
-    }
-
-    /*
-     * InvereZInterpolator compounded with an ease-out.
-     */
-    static class ZoomInInterpolator implements TimeInterpolator {
-        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
-        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
-
-        public float getInterpolation(float input) {
-            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
-        }
-    }
-
-    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
-
-    /*
     *
     * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
     * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
@@ -2090,21 +1982,6 @@
         dragLayer.clearAllResizeFrames();
     }
 
-    private void initAnimationArrays() {
-        final int childCount = getChildCount();
-        if (mLastChildCount == childCount) return;
-
-        mOldBackgroundAlphas = new float[childCount];
-        mOldAlphas = new float[childCount];
-        mNewBackgroundAlphas = new float[childCount];
-        mNewAlphas = new float[childCount];
-    }
-
-    Animator getChangeStateAnimation(final State state, boolean animated,
-            HashMap<View, Integer> layerViews) {
-        return getChangeStateAnimation(state, animated, 0, -1, layerViews);
-    }
-
     @Override
     protected void getFreeScrollPageRange(int[] range) {
         getOverviewModePages(range);
@@ -2151,41 +2028,6 @@
         return mState == State.OVERVIEW;
     }
 
-    public boolean enterOverviewMode() {
-        if (mTouchState != TOUCH_STATE_REST) {
-            return false;
-        }
-        enableOverviewMode(true, -1, true);
-        return true;
-    }
-
-    public void exitOverviewMode(boolean animated) {
-        exitOverviewMode(-1, animated);
-    }
-
-    public void exitOverviewMode(int snapPage, boolean animated) {
-        enableOverviewMode(false, snapPage, animated);
-    }
-
-    private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
-        State finalState = Workspace.State.OVERVIEW;
-        if (!enable) {
-            finalState = Workspace.State.NORMAL;
-        }
-
-        Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
-        if (workspaceAnim != null) {
-            onTransitionPrepare();
-            workspaceAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator arg0) {
-                    onTransitionEnd();
-                }
-            });
-            workspaceAnim.start();
-        }
-    }
-
     int getOverviewModeTranslationY() {
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -2208,10 +2050,22 @@
         }
     }
 
-    private void setState(State state) {
-        mState = state;
+    /**
+     * Sets the current workspace {@link State}, returning an animation transitioning the workspace
+     * to that new state.
+     */
+    public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
+                                          HashMap<View, Integer> layerViews) {
+        // Create the animation to the new state
+        Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(getState(),
+                toState, toPage, animated, layerViews);
+
+        // Update the current state
+        mState = toState;
         updateInteractionForState();
         updateAccessibilityFlags();
+
+        return workspaceAnim;
     }
 
     State getState() {
@@ -2225,321 +2079,15 @@
         setImportantForAccessibility(accessible);
     }
 
-    private static final int HIDE_WORKSPACE_DURATION = 100;
-
-    Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
-        return getChangeStateAnimation(state, animated, delay, snapPage, null);
-    }
-
-    Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage,
-            HashMap<View, Integer> layerViews) {
-        if (mState == state) {
-            return null;
-        }
-
-        // Initialize animation arrays for the first time if necessary
-        initAnimationArrays();
-
-        AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
-
-        // We only want a single instance of a workspace animation to be running at once, so
-        // we cancel any incomplete transition.
-        if (mStateAnimator != null) {
-            mStateAnimator.cancel();
-        }
-        mStateAnimator = anim;
-
-        final State oldState = mState;
-        final boolean oldStateIsNormal = (oldState == State.NORMAL);
-        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
-        final boolean oldStateIsNormalHidden = (oldState == State.NORMAL_HIDDEN);
-        final boolean oldStateIsOverviewHidden = (oldState == State.OVERVIEW_HIDDEN);
-        final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
-        setState(state);
-        final boolean stateIsNormal = (state == State.NORMAL);
-        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
-        final boolean stateIsNormalHidden = (state == State.NORMAL_HIDDEN);
-        final boolean stateIsOverviewHidden = (state == State.OVERVIEW_HIDDEN);
-        final boolean stateIsOverview = (state == State.OVERVIEW);
-        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
-        float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
-        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
-        // We keep the search bar visible on the workspace and in AllApps now
-        boolean showSearchBar = stateIsNormal ||
-                (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
-        float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
-        float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
-                getOverviewModeTranslationY() : 0;
-
-        boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
-        boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
-        boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
-        boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
-        boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
-
-        mNewScale = 1.0f;
-
-        if (oldStateIsOverview) {
-            disableFreeScroll();
-        } else if (stateIsOverview) {
-            enableFreeScroll();
-        }
-
-        if (state != State.NORMAL) {
-            if (stateIsSpringLoaded) {
-                mNewScale = mSpringLoadedShrinkFactor;
-            } else if (stateIsOverview || stateIsOverviewHidden) {
-                mNewScale = mOverviewModeShrinkFactor;
-            }
-        }
-
-        final int duration;
-        if (workspaceToAllApps || overviewToAllApps) {
-            duration = HIDE_WORKSPACE_DURATION; //getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
-        } else if (workspaceToOverview || overviewToWorkspace) {
-            duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
-        } else {
-            duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
-        }
-
-        if (snapPage == -1) {
-            snapPage = getPageNearestToCenterOfScreen();
-        }
-        snapToPage(snapPage, duration, mZoomInInterpolator);
-
-        for (int i = 0; i < getChildCount(); i++) {
-            final CellLayout cl = (CellLayout) getChildAt(i);
-            boolean isCurrentPage = (i == snapPage);
-            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
-            float finalAlpha;
-            if (stateIsNormalHidden || stateIsOverviewHidden) {
-                finalAlpha = 0f;
-            } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
-                finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
-            } else {
-                finalAlpha = 1f;
-            }
-
-            // If we are animating to/from the small state, then hide the side pages and fade the
-            // current page in
-            if (!mIsSwitchingState) {
-                if (workspaceToAllApps || allAppsToWorkspace) {
-                    if (allAppsToWorkspace && isCurrentPage) {
-                        initialAlpha = 0f;
-                    } else if (!isCurrentPage) {
-                        initialAlpha = finalAlpha = 0f;
-                    }
-                    cl.setShortcutAndWidgetAlpha(initialAlpha);
-                }
-            }
-
-            mOldAlphas[i] = initialAlpha;
-            mNewAlphas[i] = finalAlpha;
-            if (animated) {
-                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
-                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
-            } else {
-                cl.setBackgroundAlpha(finalBackgroundAlpha);
-                cl.setShortcutAndWidgetAlpha(finalAlpha);
-            }
-        }
-
-        final View searchBar = mLauncher.getOrCreateQsbBar();
-        final View overviewPanel = mLauncher.getOverviewPanel();
-        final View hotseat = mLauncher.getHotseat();
-        final View pageIndicator = getPageIndicator();
-        if (animated) {
-            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
-            scale.scaleX(mNewScale)
-                .scaleY(mNewScale)
-                .translationY(finalWorkspaceTranslationY)
-                .setDuration(duration)
-                .setInterpolator(mZoomInInterpolator);
-            anim.play(scale);
-            for (int index = 0; index < getChildCount(); index++) {
-                final int i = index;
-                final CellLayout cl = (CellLayout) getChildAt(i);
-                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
-                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
-                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
-                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
-                } else {
-                    if (layerViews != null) {
-                        layerViews.put(cl, LauncherStateTransitionAnimation.BUILD_LAYER);
-                    }
-                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
-                        LauncherViewPropertyAnimator alphaAnim =
-                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
-                        alphaAnim.alpha(mNewAlphas[i])
-                            .setDuration(duration)
-                            .setInterpolator(mZoomInInterpolator);
-                        anim.play(alphaAnim);
-                    }
-                    if (mOldBackgroundAlphas[i] != 0 ||
-                        mNewBackgroundAlphas[i] != 0) {
-                        ValueAnimator bgAnim =
-                                LauncherAnimUtils.ofFloat(cl, 0f, 1f);
-                        bgAnim.setInterpolator(mZoomInInterpolator);
-                        bgAnim.setDuration(duration);
-                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
-                                public void onAnimationUpdate(float a, float b) {
-                                    cl.setBackgroundAlpha(
-                                            a * mOldBackgroundAlphas[i] +
-                                            b * mNewBackgroundAlphas[i]);
-                                }
-                            });
-                        anim.play(bgAnim);
-                    }
-                }
-            }
-            Animator pageIndicatorAlpha = null;
-            if (pageIndicator != null) {
-                pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
-                    .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
-                pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
-            } else {
-                // create a dummy animation so we don't need to do null checks later
-                pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
-            }
-
-            LauncherViewPropertyAnimator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
-                .alpha(finalHotseatAndPageIndicatorAlpha);
-            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
-
-            LauncherViewPropertyAnimator overviewPanelAlpha =
-                    new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
-            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
-
-            // For animation optimations, we may need to provide the Launcher transition
-            // with a set of views on which to force build layers in certain scenarios.
-            hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            if (layerViews != null) {
-                // If layerViews is not null, we add these views, and indicate that
-                // the caller can manage layer state.
-                layerViews.put(hotseat, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-                layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-            } else {
-                // Otherwise let the animator handle layer management.
-                hotseatAlpha.withLayer();
-                overviewPanelAlpha.withLayer();
-            }
-
-            if (workspaceToOverview) {
-                pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
-                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
-                overviewPanelAlpha.setInterpolator(null);
-            } else if (overviewToWorkspace) {
-                pageIndicatorAlpha.setInterpolator(null);
-                hotseatAlpha.setInterpolator(null);
-                overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
-            }
-
-            overviewPanelAlpha.setDuration(duration);
-            pageIndicatorAlpha.setDuration(duration);
-            hotseatAlpha.setDuration(duration);
-
-            if (searchBar != null) {
-                LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
-                    .alpha(finalSearchBarAlpha);
-                searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
-                searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                if (layerViews != null) {
-                    // If layerViews is not null, we add these views, and indicate that
-                    // the caller can manage layer state.
-                    layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-                } else {
-                    // Otherwise let the animator handle layer management.
-                    searchBarAlpha.withLayer();
-                }
-                searchBarAlpha.setDuration(duration);
-                anim.play(searchBarAlpha);
-            }
-
-            anim.play(overviewPanelAlpha);
-            anim.play(hotseatAlpha);
-            anim.play(pageIndicatorAlpha);
-            anim.setStartDelay(delay);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mStateAnimator = null;
-                }
-            });
-        } else {
-            overviewPanel.setAlpha(finalOverviewPanelAlpha);
-            AlphaUpdateListener.updateVisibility(overviewPanel);
-            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
-            AlphaUpdateListener.updateVisibility(hotseat);
-            if (pageIndicator != null) {
-                pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
-                AlphaUpdateListener.updateVisibility(pageIndicator);
-            }
-            if (searchBar != null) {
-                searchBar.setAlpha(finalSearchBarAlpha);
-                AlphaUpdateListener.updateVisibility(searchBar);
-            }
-            updateCustomContentVisibility();
-            setScaleX(mNewScale);
-            setScaleY(mNewScale);
-            setTranslationY(finalWorkspaceTranslationY);
-        }
-
-        if (stateIsNormal) {
-            animateBackgroundGradient(0f, animated);
-        } else {
-            animateBackgroundGradient(getResources().getInteger(
-                    R.integer.config_workspaceScrimAlpha) / 100f, animated);
-        }
-        return anim;
-    }
-
-    static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
-        View view;
-        public AlphaUpdateListener(View v) {
-            view = v;
-        }
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator arg0) {
-            updateVisibility(view);
-        }
-
-        public static void updateVisibility(View view) {
-            // We want to avoid the extra layout pass by setting the views to GONE unless
-            // accessibility is on, in which case not setting them to GONE causes a glitch.
-            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
-            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
-                view.setVisibility(invisibleState);
-            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
-                    && view.getVisibility() != VISIBLE) {
-                view.setVisibility(VISIBLE);
-            }
-        }
-
-        @Override
-        public void onAnimationCancel(Animator arg0) {
-        }
-
-        @Override
-        public void onAnimationEnd(Animator arg0) {
-            updateVisibility(view);
-        }
-
-        @Override
-        public void onAnimationRepeat(Animator arg0) {
-        }
-
-        @Override
-        public void onAnimationStart(Animator arg0) {
-            // We want the views to be visible for animation, so fade-in/out is visible
-            view.setVisibility(VISIBLE);
-        }
-    }
-
     @Override
     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
-        onTransitionPrepare();
+        mIsSwitchingState = true;
+
+        // Invalidate here to ensure that the pages are rendered during the state change transition.
+        invalidate();
+
+        updateChildrenLayersEnabled(false);
+        hideCustomContentIfNecessary();
     }
 
     @Override
@@ -2553,17 +2101,9 @@
 
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
-        onTransitionEnd();
-    }
-
-    private void onTransitionPrepare() {
-        mIsSwitchingState = true;
-
-        // Invalidate here to ensure that the pages are rendered during the state change transition.
-        invalidate();
-
+        mIsSwitchingState = false;
         updateChildrenLayersEnabled(false);
-        hideCustomContentIfNecessary();
+        showCustomContentIfNecessary();
     }
 
     void updateCustomContentVisibility() {
@@ -2589,12 +2129,6 @@
         }
     }
 
-    @Thunk void onTransitionEnd() {
-        mIsSwitchingState = false;
-        updateChildrenLayersEnabled(false);
-        showCustomContentIfNecessary();
-    }
-
     @Override
     public View getContent() {
         return this;
@@ -3106,7 +2640,7 @@
             }
         }
 
-        int snapScreen = -1;
+        int snapScreen = WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE;
         boolean resizeOnDrop = false;
         if (d.dragSource != this) {
             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
@@ -3267,7 +2801,9 @@
                     animateWidgetDrop(info, parent, d.dragView,
                             onCompleteRunnable, animationType, cell, false);
                 } else {
-                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+                    int duration = snapScreen < 0 ?
+                            WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE :
+                                    ADJACENT_SCREEN_DROP_DURATION;
                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                             onCompleteRunnable, this);
                 }
@@ -4165,8 +3701,8 @@
     public void setFinalTransitionTransform(CellLayout layout) {
         if (isSwitchingState()) {
             mCurrentScale = getScaleX();
-            setScaleX(mNewScale);
-            setScaleY(mNewScale);
+            setScaleX(mStateTransitionAnimation.getFinalScale());
+            setScaleY(mStateTransitionAnimation.getFinalScale());
         }
     }
     public void resetTransitionTransform(CellLayout layout) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
new file mode 100644
index 0000000..a0cedeb
--- /dev/null
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import com.android.launcher3.util.Thunk;
+
+import java.util.HashMap;
+
+/**
+ * A convenience class to update a view's visibility state after an alpha animation.
+ */
+class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimator.AnimatorUpdateListener {
+    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+
+    private View mView;
+    private boolean mAccessibilityEnabled;
+
+    public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
+        mView = v;
+        mAccessibilityEnabled = accessibilityEnabled;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator arg0) {
+        updateVisibility(mView, mAccessibilityEnabled);
+    }
+
+    public static void updateVisibility(View view, boolean accessibilityEnabled) {
+        // We want to avoid the extra layout pass by setting the views to GONE unless
+        // accessibility is on, in which case not setting them to GONE causes a glitch.
+        int invisibleState = accessibilityEnabled ? View.GONE : View.INVISIBLE;
+        if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
+            view.setVisibility(invisibleState);
+        } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
+                && view.getVisibility() != View.VISIBLE) {
+            view.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onAnimationEnd(Animator arg0) {
+        updateVisibility(mView, mAccessibilityEnabled);
+    }
+
+    @Override
+    public void onAnimationStart(Animator arg0) {
+        // We want the views to be visible for animation, so fade-in/out is visible
+        mView.setVisibility(View.VISIBLE);
+    }
+}
+
+/**
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+class ZInterpolator implements TimeInterpolator {
+    private float focalLength;
+
+    public ZInterpolator(float foc) {
+        focalLength = foc;
+    }
+
+    public float getInterpolation(float input) {
+        return (1.0f - focalLength / (focalLength + input)) /
+                (1.0f - focalLength / (focalLength + 1.0f));
+    }
+}
+
+/**
+ * The exact reverse of ZInterpolator.
+ */
+class InverseZInterpolator implements TimeInterpolator {
+    private ZInterpolator zInterpolator;
+    public InverseZInterpolator(float foc) {
+        zInterpolator = new ZInterpolator(foc);
+    }
+    public float getInterpolation(float input) {
+        return 1 - zInterpolator.getInterpolation(1 - input);
+    }
+}
+
+/**
+ * InverseZInterpolator compounded with an ease-out.
+ */
+class ZoomInInterpolator implements TimeInterpolator {
+    private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
+    private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
+
+    public float getInterpolation(float input) {
+        return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
+    }
+}
+
+/**
+ * Manages the animations between each of the workspace states.
+ */
+public class WorkspaceStateTransitionAnimation {
+
+    public static final String TAG = "WorkspaceStateTransitionAnimation";
+
+    public static final int SCROLL_TO_CURRENT_PAGE = -1;
+    @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350;
+
+    final @Thunk Launcher mLauncher;
+    final @Thunk Workspace mWorkspace;
+
+    @Thunk AnimatorSet mStateAnimator;
+    @Thunk float[] mOldBackgroundAlphas;
+    @Thunk float[] mOldAlphas;
+    @Thunk float[] mNewBackgroundAlphas;
+    @Thunk float[] mNewAlphas;
+    @Thunk int mLastChildCount = -1;
+
+    @Thunk float mCurrentScale;
+    @Thunk float mNewScale;
+
+    @Thunk final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
+
+    // These properties refer to the background protection gradient used for AllApps and Customize
+    @Thunk ValueAnimator mBackgroundFadeInAnimation;
+    @Thunk ValueAnimator mBackgroundFadeOutAnimation;
+
+    @Thunk float mSpringLoadedShrinkFactor;
+    @Thunk float mOverviewModeShrinkFactor;
+    @Thunk float mWorkspaceScrimAlpha;
+    @Thunk int mAllAppsTransitionTime;
+    @Thunk int mOverviewTransitionTime;
+    @Thunk int mOverlayTransitionTime;
+    @Thunk boolean mWorkspaceFadeInAdjacentScreens;
+
+    public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
+        mLauncher = launcher;
+        mWorkspace = workspace;
+
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        Resources res = launcher.getResources();
+        mAllAppsTransitionTime = res.getInteger(R.integer.config_workspaceUnshrinkTime);
+        mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime);
+        mOverlayTransitionTime = res.getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
+        mSpringLoadedShrinkFactor =
+                res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+        mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
+        mOverviewModeShrinkFactor = grid.getOverviewModeScale();
+        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
+    }
+
+    public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
+                                           int toPage, boolean animated,
+                                           HashMap<View, Integer> layerViews) {
+        getAnimation(fromState, toState, toPage, animated, layerViews);
+        return mStateAnimator;
+    }
+
+    public float getFinalScale() {
+        return mNewScale;
+    }
+
+    /**
+     * Starts a transition animation for the workspace.
+     */
+    private void getAnimation(final Workspace.State fromState, final Workspace.State toState,
+                              int toPage, final boolean animated,
+                              final HashMap<View, Integer> layerViews) {
+        AccessibilityManager am = (AccessibilityManager)
+                mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        boolean accessibilityEnabled = am.isEnabled();
+
+        // Reinitialize animation arrays for the current workspace state
+        reinitializeAnimationArrays();
+
+        // Cancel existing workspace animations and create a new animator set if requested
+        cancelAnimation();
+        if (animated) {
+            mStateAnimator = LauncherAnimUtils.createAnimatorSet();
+        }
+
+        // Update the workspace state
+        final boolean oldStateIsNormal = (fromState == Workspace.State.NORMAL);
+        final boolean oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED);
+        final boolean oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN);
+        final boolean oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN);
+        final boolean oldStateIsOverview = (fromState == Workspace.State.OVERVIEW);
+
+        final boolean stateIsNormal = (toState == Workspace.State.NORMAL);
+        final boolean stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED);
+        final boolean stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN);
+        final boolean stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN);
+        final boolean stateIsOverview = (toState == Workspace.State.OVERVIEW);
+
+        final boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
+        final boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
+        final boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
+        final boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
+        final boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
+
+        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
+        float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
+        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
+        // We keep the search bar visible on the workspace and in AllApps now
+        boolean showSearchBar = stateIsNormal ||
+                (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
+        float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
+        float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
+                mWorkspace.getOverviewModeTranslationY() : 0;
+
+        final int childCount = mWorkspace.getChildCount();
+        final int customPageCount = mWorkspace.numCustomPages();
+
+        mNewScale = 1.0f;
+
+        if (oldStateIsOverview) {
+            mWorkspace.disableFreeScroll();
+        } else if (stateIsOverview) {
+            mWorkspace.enableFreeScroll();
+        }
+
+        if (!stateIsNormal) {
+            if (stateIsSpringLoaded) {
+                mNewScale = mSpringLoadedShrinkFactor;
+            } else if (stateIsOverview || stateIsOverviewHidden) {
+                mNewScale = mOverviewModeShrinkFactor;
+            }
+        }
+
+        final int duration;
+        if (workspaceToAllApps || overviewToAllApps) {
+            duration = mAllAppsTransitionTime;
+        } else if (workspaceToOverview || overviewToWorkspace) {
+            duration = mOverviewTransitionTime;
+        } else {
+            duration = mOverlayTransitionTime;
+        }
+
+        if (toPage == SCROLL_TO_CURRENT_PAGE) {
+            toPage = mWorkspace.getPageNearestToCenterOfScreen();
+        }
+        mWorkspace.snapToPage(toPage, duration, mZoomInInterpolator);
+
+        for (int i = 0; i < childCount; i++) {
+            final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
+            boolean isCurrentPage = (i == toPage);
+            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
+            float finalAlpha;
+            if (stateIsNormalHidden || stateIsOverviewHidden) {
+                finalAlpha = 0f;
+            } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
+                finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
+            } else {
+                finalAlpha = 1f;
+            }
+
+            // If we are animating to/from the small state, then hide the side pages and fade the
+            // current page in
+            if (!mWorkspace.isSwitchingState()) {
+                if (workspaceToAllApps || allAppsToWorkspace) {
+                    if (allAppsToWorkspace && isCurrentPage) {
+                        initialAlpha = 0f;
+                    } else if (!isCurrentPage) {
+                        initialAlpha = finalAlpha = 0f;
+                    }
+                    cl.setShortcutAndWidgetAlpha(initialAlpha);
+                }
+            }
+
+            mOldAlphas[i] = initialAlpha;
+            mNewAlphas[i] = finalAlpha;
+            if (animated) {
+                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
+                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
+            } else {
+                cl.setBackgroundAlpha(finalBackgroundAlpha);
+                cl.setShortcutAndWidgetAlpha(finalAlpha);
+            }
+        }
+
+        final View searchBar = mLauncher.getOrCreateQsbBar();
+        final View overviewPanel = mLauncher.getOverviewPanel();
+        final View hotseat = mLauncher.getHotseat();
+        final View pageIndicator = mWorkspace.getPageIndicator();
+        if (animated) {
+            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
+            scale.scaleX(mNewScale)
+                    .scaleY(mNewScale)
+                    .translationY(finalWorkspaceTranslationY)
+                    .setDuration(duration)
+                    .setInterpolator(mZoomInInterpolator);
+            mStateAnimator.play(scale);
+            for (int index = 0; index < childCount; index++) {
+                final int i = index;
+                final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
+                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
+                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
+                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
+                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
+                } else {
+                    if (layerViews != null) {
+                        layerViews.put(cl, LauncherStateTransitionAnimation.BUILD_LAYER);
+                    }
+                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
+                        LauncherViewPropertyAnimator alphaAnim =
+                                new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+                        alphaAnim.alpha(mNewAlphas[i])
+                                .setDuration(duration)
+                                .setInterpolator(mZoomInInterpolator);
+                        mStateAnimator.play(alphaAnim);
+                    }
+                    if (mOldBackgroundAlphas[i] != 0 ||
+                            mNewBackgroundAlphas[i] != 0) {
+                        ValueAnimator bgAnim =
+                                LauncherAnimUtils.ofFloat(cl, 0f, 1f);
+                        bgAnim.setInterpolator(mZoomInInterpolator);
+                        bgAnim.setDuration(duration);
+                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+                            public void onAnimationUpdate(float a, float b) {
+                                cl.setBackgroundAlpha(
+                                        a * mOldBackgroundAlphas[i] +
+                                                b * mNewBackgroundAlphas[i]);
+                            }
+                        });
+                        mStateAnimator.play(bgAnim);
+                    }
+                }
+            }
+            Animator pageIndicatorAlpha = null;
+            if (pageIndicator != null) {
+                pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
+                        .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
+                pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator,
+                        accessibilityEnabled));
+            } else {
+                // create a dummy animation so we don't need to do null checks later
+                pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
+            }
+
+            LauncherViewPropertyAnimator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
+                    .alpha(finalHotseatAndPageIndicatorAlpha);
+            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat, accessibilityEnabled));
+
+            LauncherViewPropertyAnimator overviewPanelAlpha =
+                    new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
+            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
+                    accessibilityEnabled));
+
+            // For animation optimations, we may need to provide the Launcher transition
+            // with a set of views on which to force build layers in certain scenarios.
+            hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            if (layerViews != null) {
+                // If layerViews is not null, we add these views, and indicate that
+                // the caller can manage layer state.
+                layerViews.put(hotseat, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+            } else {
+                // Otherwise let the animator handle layer management.
+                hotseatAlpha.withLayer();
+                overviewPanelAlpha.withLayer();
+            }
+
+            if (workspaceToOverview) {
+                pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
+                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
+                overviewPanelAlpha.setInterpolator(null);
+            } else if (overviewToWorkspace) {
+                pageIndicatorAlpha.setInterpolator(null);
+                hotseatAlpha.setInterpolator(null);
+                overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
+            }
+
+            overviewPanelAlpha.setDuration(duration);
+            pageIndicatorAlpha.setDuration(duration);
+            hotseatAlpha.setDuration(duration);
+
+            // TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
+            //       bar has no idea that it is hidden, and this has no idea what state the bar is
+            //       actually in.
+            if (searchBar != null) {
+                LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
+                    .alpha(finalSearchBarAlpha);
+                searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, accessibilityEnabled));
+                searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                if (layerViews != null) {
+                    // If layerViews is not null, we add these views, and indicate that
+                    // the caller can manage layer state.
+                    layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                } else {
+                    // Otherwise let the animator handle layer management.
+                    searchBarAlpha.withLayer();
+                }
+                searchBarAlpha.setDuration(duration);
+                mStateAnimator.play(searchBarAlpha);
+            }
+
+            mStateAnimator.play(overviewPanelAlpha);
+            mStateAnimator.play(hotseatAlpha);
+            mStateAnimator.play(pageIndicatorAlpha);
+            mStateAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mStateAnimator = null;
+                }
+            });
+        } else {
+            overviewPanel.setAlpha(finalOverviewPanelAlpha);
+            AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
+            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
+            AlphaUpdateListener.updateVisibility(hotseat, accessibilityEnabled);
+            if (pageIndicator != null) {
+                pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
+                AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled);
+            }
+            if (searchBar != null) {
+                searchBar.setAlpha(finalSearchBarAlpha);
+                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
+            }
+            mWorkspace.updateCustomContentVisibility();
+            mWorkspace.setScaleX(mNewScale);
+            mWorkspace.setScaleY(mNewScale);
+            mWorkspace.setTranslationY(finalWorkspaceTranslationY);
+        }
+
+        if (stateIsNormal) {
+            animateBackgroundGradient(0f, animated);
+        } else {
+            animateBackgroundGradient(mWorkspaceScrimAlpha, animated);
+        }
+    }
+
+    /**
+     * Reinitializes the arrays that we need for the animations on each page.
+     */
+    private void reinitializeAnimationArrays() {
+        final int childCount = mWorkspace.getChildCount();
+        if (mLastChildCount == childCount) return;
+
+        mOldBackgroundAlphas = new float[childCount];
+        mOldAlphas = new float[childCount];
+        mNewBackgroundAlphas = new float[childCount];
+        mNewAlphas = new float[childCount];
+    }
+
+    /**
+     * Animates the background scrim.
+     * TODO(winsonc): Is there a better place for this?
+     *
+     * @param finalAlpha the final alpha for the background scrim
+     * @param animated whether or not to set the background alpha immediately
+     */
+    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
+        // Cancel any running background animations
+        cancelAnimator(mBackgroundFadeInAnimation);
+        cancelAnimator(mBackgroundFadeOutAnimation);
+
+        final DragLayer dragLayer = mLauncher.getDragLayer();
+        final float startAlpha = dragLayer.getBackgroundAlpha();
+        if (finalAlpha != startAlpha) {
+            if (animated) {
+                mBackgroundFadeOutAnimation =
+                        LauncherAnimUtils.ofFloat(mWorkspace, startAlpha, finalAlpha);
+                mBackgroundFadeOutAnimation.addUpdateListener(
+                        new ValueAnimator.AnimatorUpdateListener() {
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        dragLayer.setBackgroundAlpha(
+                                ((Float)animation.getAnimatedValue()).floatValue());
+                    }
+                });
+                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
+                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+                mBackgroundFadeOutAnimation.start();
+            } else {
+                dragLayer.setBackgroundAlpha(finalAlpha);
+            }
+        }
+    }
+
+    /**
+     * Cancels the current animation.
+     */
+    private void cancelAnimation() {
+        cancelAnimator(mStateAnimator);
+        mStateAnimator = null;
+    }
+
+    /**
+     * Cancels the specified animation.
+     */
+    private void cancelAnimator(Animator animator) {
+        if (animator != null) {
+            animator.setDuration(0);
+            animator.cancel();
+        }
+    }
+}
\ No newline at end of file