Exposing the state manager directly instead of providing various helper methods for state change

Bug: 67678570
Change-Id: If3d05c804c034ffa5e403da8eaa23e85e373c863
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 1a1c319..bebdbdb 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.animation.AnimatorSet;
 import android.animation.FloatArrayEvaluator;
@@ -231,7 +231,7 @@
             public void run() {
                 completeDrop(d);
                 mDropTargetBar.onDragEnd();
-                mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+                mLauncher.getStateManager().goToState(NORMAL);
             }
         };
         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e3acf70..cd9fffc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,8 +19,10 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 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;
@@ -58,7 +60,6 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.StrictMode;
@@ -203,7 +204,7 @@
     // Type: SparseArray<Parcelable>
     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
 
-    private LauncherStateTransitionAnimation mStateTransitionAnimation;
+    private LauncherStateManager mStateManager;
 
     private boolean mIsSafeModeEnabled;
 
@@ -257,7 +258,6 @@
     private ModelWriter mModelWriter;
     private IconCache mIconCache;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
-    private final Handler mHandler = new Handler();
     private boolean mHasFocus = false;
 
     private ObjectAnimator mScrimAnimator;
@@ -271,11 +271,6 @@
     // it from the context.
     private SharedPreferences mSharedPrefs;
 
-    // Exiting spring loaded mode happens with a delay. This runnable object triggers the
-    // state transition. If another state transition happened during this delay,
-    // simply unregister this runnable.
-    private Runnable mExitSpringLoadedModeRunnable;
-
     // Activity result which needs to be processed after workspace has loaded.
     private ActivityResultInfo mPendingActivityResult;
     /**
@@ -342,7 +337,7 @@
 
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
-        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
+        mStateManager = new LauncherStateManager(this, mAllAppsController);
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
 
@@ -430,8 +425,8 @@
         recreate();
     }
 
-    public LauncherStateTransitionAnimation getStateTransition() {
-        return mStateTransitionAnimation;
+    public LauncherStateManager getStateManager() {
+        return mStateManager;
     }
 
     protected void overrideTheme(boolean isDark, boolean supportsDarkText) {
@@ -565,7 +560,7 @@
         Runnable exitSpringLoaded = new Runnable() {
             @Override
             public void run() {
-                exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+                mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
             }
         };
 
@@ -585,11 +580,11 @@
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
-            if (resultCode == RESULT_OK && isInState(LauncherState.OVERVIEW)) {
+            if (resultCode == RESULT_OK && isInState(OVERVIEW)) {
                 // User could have free-scrolled between pages before picking a wallpaper; make sure
                 // we move to the closest one now.
                 mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
-                showWorkspace(false);
+                mStateManager.goToState(NORMAL, false);
             }
             return;
         }
@@ -617,7 +612,7 @@
                 final Runnable onComplete = new Runnable() {
                     @Override
                     public void run() {
-                        exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+                        getStateManager().goToState(NORMAL);
                     }
                 };
 
@@ -745,7 +740,7 @@
                 @Override
                 public void run() {
                     completeAddAppWidget(appWidgetId, requestArgs, layout, null);
-                    exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+                    mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
         } else if (resultCode == RESULT_CANCELED) {
@@ -990,9 +985,16 @@
                 AbstractFloatingView.closeAllOpenViews(this);
 
                 // Show the overview mode if we are on the workspace
-                if (isInState(LauncherState.NORMAL) && !mWorkspace.isSwitchingState()) {
-                    mOverviewPanel.requestFocus();
-                    showOverviewMode(true, true /* requestButtonFocus */);
+                if (isInState(NORMAL) && !mWorkspace.isSwitchingState()) {
+                    mStateManager.goToState(OVERVIEW, true /* animate */, new Runnable() {
+                        @Override
+                        public void run() {
+                            // Hitting the menu button when in touch mode does not trigger touch
+                            // mode to be disabled, so if requested, force focus on one of the
+                            // overview panel buttons.
+                            mOverviewPanel.requestFocusFromTouch();
+                        }
+                    });
                 }
             }
             return true;
@@ -1025,15 +1027,11 @@
             return;
         }
 
-        int stateOrdinal = savedState.getInt(RUNTIME_STATE, LauncherState.NORMAL.ordinal);
+        int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
         LauncherState[] stateValues = LauncherState.values();
         LauncherState state = stateValues[stateOrdinal];
         if (!state.doNotRestore) {
-            if (state == LauncherState.ALL_APPS) {
-                showAppsView(false /* animated */);
-            } else if (state == LauncherState.OVERVIEW) {
-                showOverviewMode(false);
-            }
+            mStateManager.goToState(state, false /* animated */);
         }
 
         PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
@@ -1311,10 +1309,7 @@
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
                 if (mAppsView != null && mPendingRequestArgs == null) {
-                    if (!showWorkspace(false)) {
-                        // If we are already on the workspace, then manually reset all apps
-                        mAppsView.reset();
-                    }
+                    mStateManager.goToState(NORMAL);
                 }
                 mShouldFadeInScrim = true;
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
@@ -1414,7 +1409,7 @@
                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
 
         // Check this condition before handling isActionMain, as this will get reset.
-        boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(LauncherState.NORMAL)
+        boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
                 && AbstractFloatingView.getTopOpenView(this) == null;
 
         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
@@ -1438,7 +1433,7 @@
 
             // In all these cases, only animate if we're already on home
             AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
-            showWorkspace(alreadyOnHome /* animated */);
+            mStateManager.goToState(NORMAL, alreadyOnHome /* animated */);
 
             final View v = getWindow().peekDecorView();
             if (v != null && v.getWindowToken() != null) {
@@ -1615,7 +1610,7 @@
         }
 
         // We need to show the workspace after starting the search
-        showWorkspace(true);
+        mStateManager.goToState(NORMAL);
     }
 
     /**
@@ -1707,7 +1702,7 @@
                 @Override
                 public void run() {
                     // Exit spring loaded mode if necessary after adding the widget
-                    exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+                    mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
             completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
@@ -1888,9 +1883,9 @@
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null) {
             topView.onBackPressed();
-        } else if (!isInState(LauncherState.NORMAL)) {
+        } else if (!isInState(NORMAL)) {
             ued.logActionCommand(Action.Command.BACK, mWorkspace.getState().containerType);
-            showWorkspace(true);
+            mStateManager.goToState(NORMAL);
         } else {
             // Back button is a no-op here, but give at least some feedback for the button press
             mWorkspace.showOutlinesTemporarily();
@@ -1914,23 +1909,23 @@
         }
 
         if (v instanceof Workspace) {
-            if (isInState(LauncherState.OVERVIEW)) {
+            if (isInState(OVERVIEW)) {
                 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                         LauncherLogProto.Action.Direction.NONE,
                         LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                showWorkspace(true);
+                mStateManager.goToState(NORMAL);
             }
             return;
         }
 
         if (v instanceof CellLayout) {
-            if (isInState(LauncherState.OVERVIEW)) {
+            if (isInState(OVERVIEW)) {
                 int page = mWorkspace.indexOfChild(v);
                 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                         LauncherLogProto.Action.Direction.NONE,
                         LauncherLogProto.ContainerType.OVERVIEW, page);
                 mWorkspace.snapToPageFromOverView(page);
-                showWorkspace(true);
+                mStateManager.goToState(NORMAL);
             }
             return;
         }
@@ -2002,12 +1997,12 @@
      */
     protected void onClickAllAppsButton(View v) {
         if (LOGD) Log.d(TAG, "onClickAllAppsButton");
-        if (!isInState(LauncherState.ALL_APPS)) {
+        if (!isInState(ALL_APPS)) {
             getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                     ControlType.ALL_APPS_BUTTON);
-            showAppsView(true /* animated */);
+            mStateManager.goToState(ALL_APPS);
         } else {
-            showWorkspace(true);
+            mStateManager.goToState(NORMAL);
         }
     }
 
@@ -2333,18 +2328,18 @@
     public boolean onLongClick(View v) {
         if (!isDraggingEnabled()) return false;
         if (isWorkspaceLocked()) return false;
-        if (!isInState(LauncherState.NORMAL) && !isInState(LauncherState.OVERVIEW)) return false;
+        if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
 
         boolean ignoreLongPressToOverview =
                 mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
 
         if (v instanceof Workspace) {
-            if (!isInState(LauncherState.OVERVIEW)) {
+            if (!isInState(OVERVIEW)) {
                 if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
                     getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                             Action.Direction.NONE, ContainerType.WORKSPACE,
                             mWorkspace.getCurrentPage());
-                    showOverviewMode(true);
+                    getStateManager().goToState(OVERVIEW);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                     return true;
@@ -2381,7 +2376,7 @@
                     getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                             Action.Direction.NONE, ContainerType.WORKSPACE,
                             mWorkspace.getCurrentPage());
-                    showOverviewMode(true);
+                    getStateManager().goToState(OVERVIEW);
                 }
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -2436,134 +2431,16 @@
         }
     }
 
-    public boolean showWorkspace(boolean animated) {
-        return showWorkspace(animated, null);
-    }
-
-    public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
-        boolean changed = !isInState(LauncherState.NORMAL);
-        if (changed || mAllAppsController.isTransitioning()) {
-            mWorkspace.setVisibility(View.VISIBLE);
-            mStateTransitionAnimation.goToState(LauncherState.NORMAL, animated, onCompleteRunnable);
-
-            // Set focus to the AppsCustomize button
-            if (mAllAppsButton != null) {
-                mAllAppsButton.requestFocus();
-            }
-        }
-
-        if (changed) {
-            // Send an accessibility event to announce the context change
-            getWindow().getDecorView()
-                    .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-        return changed;
-    }
-
-    /**
-     * Shows the overview button.
-     */
-    public void showOverviewMode(boolean animated) {
-        showOverviewMode(animated, false);
-    }
-
-    /**
-     * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
-     * onto one of the overview panel buttons.
-     */
-    void showOverviewMode(boolean animated, boolean requestButtonFocus) {
-        Runnable postAnimRunnable = null;
-        if (requestButtonFocus) {
-            postAnimRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    // Hitting the menu button when in touch mode does not trigger touch mode to
-                    // be disabled, so if requested, force focus on one of the overview panel
-                    // buttons.
-                    mOverviewPanel.requestFocusFromTouch();
-                }
-            };
-        }
-        mWorkspace.setVisibility(View.VISIBLE);
-        mStateTransitionAnimation.goToState(LauncherState.OVERVIEW, animated, postAnimRunnable);
-
-        // If animated from long press, then don't allow any of the controller in the drag
-        // layer to intercept any remaining touch.
-        mWorkspace.requestDisallowInterceptTouchEvent(animated);
-    }
-
-    /**
-     * Shows the apps 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.
-    public boolean showAppsView(boolean animated) {
-        if (!(isInState(LauncherState.NORMAL) ||
-                (isInState(LauncherState.ALL_APPS) && mAllAppsController.isTransitioning()))) {
-            return false;
-        }
-
-        // This is a safe and supported transition to bypass spring_loaded mode.
-        if (mExitSpringLoadedModeRunnable != null) {
-            mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
-            mExitSpringLoadedModeRunnable = null;
-        }
-
-        mStateTransitionAnimation.goToState(LauncherState.ALL_APPS, animated, null);
-
-        // Change the state *after* we've called all the transition code
-        AbstractFloatingView.closeAllOpenViews(this);
-
-        // Send an accessibility event to announce the context change
-        getWindow().getDecorView()
-                .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        return true;
-    }
-
-    public void enterSpringLoadedDragMode() {
-        if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
-                mWorkspace.getState().ordinal));
-        if (isInState(LauncherState.SPRING_LOADED)) {
-            return;
-        }
-        mStateTransitionAnimation.goToState(LauncherState.SPRING_LOADED, true, null);
-    }
-
-    public void exitSpringLoadedDragMode(int delay) {
-        exitSpringLoadedDragMode(delay, null);
-    }
-
-    public void exitSpringLoadedDragMode(int delay, final Runnable onCompleteRunnable) {
-        if (!isInState(LauncherState.SPRING_LOADED)) return;
-
-        if (mExitSpringLoadedModeRunnable != null) {
-            mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
-        }
-        mExitSpringLoadedModeRunnable = new Runnable() {
-            @Override
-            public void run() {
-                showWorkspace(true, onCompleteRunnable);
-                mExitSpringLoadedModeRunnable = null;
-            }
-        };
-        mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay);
-    }
-
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
         final List<CharSequence> text = event.getText();
         text.clear();
         // Populate event with a fake title based on the current state.
-        if (isInState(LauncherState.ALL_APPS)) {
-            text.add(getString(R.string.all_apps_button_label));
-        } else if (mWorkspace != null) {
-            text.add(mWorkspace.getCurrentPageDescription());
-        } else {
-            text.add(getString(R.string.all_apps_home_button_label));
-        }
+        // TODO: When can workspace be null?
+        text.add(mWorkspace == null
+                ? getString(R.string.all_apps_home_button_label)
+                : mWorkspace.getState().getDescription(this));
         return result;
     }
 
@@ -3114,7 +2991,7 @@
 
         if (mAppsView != null) {
             Executor pendingExecutor = getPendingExecutor();
-            if (pendingExecutor != null && !isInState(LauncherState.ALL_APPS)) {
+            if (pendingExecutor != null && !isInState(ALL_APPS)) {
                 // Wait until the fade in animation has finished before setting all apps list.
                 pendingExecutor.execute(r);
                 return;
@@ -3304,7 +3181,7 @@
     }
 
     private boolean shouldShowDiscoveryBounce() {
-        return isInState(LauncherState.NORMAL)
+        return isInState(NORMAL)
                 && !mSharedPrefs.getBoolean(AllAppsState.APPS_VIEW_SHOWN, false)
                 && !UserManagerCompat.getInstance(this).isDemoUser();
     }
@@ -3364,7 +3241,7 @@
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
 
         ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
-        if (isInState(LauncherState.NORMAL)) {
+        if (isInState(NORMAL)) {
             shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
                     KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
         }
@@ -3390,8 +3267,8 @@
         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_A:
-                    if (isInState(LauncherState.NORMAL)) {
-                        showAppsView(true);
+                    if (isInState(NORMAL)) {
+                        getStateManager().goToState(ALL_APPS);
                         return true;
                     }
                     break;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 3585110..dfe51af 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -38,7 +38,6 @@
     public static final int OVERVIEW_TRANSITION_MS = 250;
     public static final int SPRING_LOADED_TRANSITION_MS = 150;
     public static final int SPRING_LOADED_EXIT_DELAY = 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() {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 9d01ed8..de3f441 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -17,8 +17,7 @@
 
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-
-import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import android.view.View;
 
@@ -121,11 +120,21 @@
         return new float[] {1, 0};
     }
 
-    public void onStateEnabled(Launcher launcher) { }
+    public void onStateEnabled(Launcher launcher) {
+        dispatchWindowStateChanged(launcher);
+    }
 
     public void onStateDisabled(Launcher launcher) { }
 
     public View getFinalFocus(Launcher launcher) {
         return launcher.getWorkspace();
     }
+
+    public String getDescription(Launcher launcher) {
+        return launcher.getWorkspace().getCurrentPageDescription();
+    }
+
+    protected static void dispatchWindowStateChanged(Launcher launcher) {
+        launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateManager.java
similarity index 77%
rename from src/com/android/launcher3/LauncherStateTransitionAnimation.java
rename to src/com/android/launcher3/LauncherStateManager.java
index eec8b31..4298174 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -68,23 +68,73 @@
  *          - From the center workspace
  *          - From another workspace
  */
-public class LauncherStateTransitionAnimation {
+public class LauncherStateManager {
 
-    public static final String TAG = "LSTAnimation";
+    public static final String TAG = "StateManager";
 
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
     private final Launcher mLauncher;
     private final AllAppsTransitionController mAllAppsController;
 
-    public LauncherStateTransitionAnimation(
+    public LauncherStateManager(
             Launcher l, AllAppsTransitionController allAppsController) {
         mUiHandler = new Handler(Looper.getMainLooper());
         mLauncher = l;
         mAllAppsController = allAppsController;
     }
 
+    /**
+     * @see #goToState(LauncherState, boolean, Runnable)
+     */
+    public void goToState(LauncherState state) {
+        goToState(state, true, 0, null);
+    }
+
+    /**
+     * @see #goToState(LauncherState, boolean, Runnable)
+     */
+    public void goToState(LauncherState state, boolean animated) {
+        goToState(state, animated, 0, null);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state.
+     *
+     * @param animated false if the state should change immediately without any animation,
+     *                true otherwise
+     * @paras onCompleteRunnable any action to perform at the end of the transition, of null.
+     */
     public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
+        goToState(state, animated, 0, onCompleteRunnable);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state after the given delay.
+     */
+    public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
+        goToState(state, true, delay, onCompleteRunnable);
+    }
+
+    /**
+     * Changes the Launcher state to the provided state after the given delay.
+     */
+    public void goToState(LauncherState state, long delay) {
+        goToState(state, true, delay, null);
+    }
+
+    private void goToState(LauncherState state, boolean animated, long delay,
+            Runnable onCompleteRunnable) {
+        if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null
+                && !mAllAppsController.isTransitioning()) {
+
+            // Run any queued runnable
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+            return;
+        }
+
         // Cancel the current animation
         mConfig.reset();
 
@@ -102,7 +152,9 @@
 
         AnimatorSet animation = createAnimationToNewWorkspace(state, onCompleteRunnable);
         Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
-        if (mConfig.shouldPost) {
+        if (delay > 0) {
+            mUiHandler.postDelayed(runnable, delay);
+        } else if (mConfig.shouldPost) {
             mUiHandler.post(runnable);
         } else {
             runnable.run();
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 407f0b5..fc75fe3 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
@@ -75,8 +78,8 @@
 
     @Override
     public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (!mLauncher.isInState(LauncherState.NORMAL)
-                && !mLauncher.isInState(LauncherState.OVERVIEW)) {
+        if (!mLauncher.isInState(NORMAL)
+                && !mLauncher.isInState(OVERVIEW)) {
             // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
             return false;
         }
@@ -105,9 +108,8 @@
             mLauncher.getDragController().cancelDrag();
         }
 
-        mToState = mLauncher.isInState(LauncherState.OVERVIEW)
-                ? LauncherState.NORMAL : LauncherState.OVERVIEW;
-        mCurrentAnimation = mLauncher.getStateTransition()
+        mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
+        mCurrentAnimation = mLauncher.getStateManager()
                 .createAnimationToNewWorkspace(mToState, this);
         mPinchStarted = true;
         mCurrentScale = 1;
@@ -132,11 +134,8 @@
             mCurrentAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (mToState == LauncherState.OVERVIEW) {
-                        mLauncher.showWorkspace(false);
-                    } else {
-                        mLauncher.showOverviewMode(false);
-                    }
+                    mLauncher.getStateManager().goToState(
+                            mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
                 }
             });
             mCurrentAnimation.reverse();
@@ -150,12 +149,11 @@
         // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
         // 0 => Animation complete
         // 1=> Animation started
-        float animationFraction = mToState ==
-                LauncherState.OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
+        float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
 
         float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
         if (Math.abs(velocity) >= FLING_VELOCITY) {
-            LauncherState toState = velocity > 0 ? LauncherState.OVERVIEW : LauncherState.NORMAL;
+            LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
             mShouldGoToFinalState = toState == mToState;
         } else {
             mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 27d860b..900e5bf 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,10 +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_DELAY;
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.isAccessibilityEnabled;
 
 import android.animation.Animator;
@@ -60,7 +62,7 @@
 
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
@@ -205,7 +207,7 @@
     private final float[] mHotseatAlpha = new float[] {1, 1, 1};
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private LauncherState mState = LauncherState.NORMAL;
+    private LauncherState mState = NORMAL;
     private boolean mIsSwitchingState = false;
 
     boolean mChildrenLayersEnabled = true;
@@ -411,7 +413,7 @@
         }
 
         // Always enter the spring loaded mode
-        mLauncher.enterSpringLoadedDragMode();
+        mLauncher.getStateManager().goToState(SPRING_LOADED);
     }
 
     public void deferRemoveExtraEmptyScreen() {
@@ -1342,7 +1344,7 @@
     @Override
     public void announceForAccessibility(CharSequence text) {
         // Don't announce if apps is on top of us.
-        if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+        if (!mLauncher.isInState(ALL_APPS)) {
             super.announceForAccessibility(text);
         }
     }
@@ -1410,12 +1412,12 @@
     }
 
     public boolean workspaceInModalState() {
-        return mState != LauncherState.NORMAL;
+        return mState != NORMAL;
     }
 
     /** Returns whether a drag should be allowed to be started from the current workspace state. */
     public boolean workspaceIconsCanBeDragged() {
-        return mState == LauncherState.NORMAL || mState == LauncherState.SPRING_LOADED;
+        return mState == NORMAL || mState == SPRING_LOADED;
     }
 
     private void updateChildrenLayersEnabled() {
@@ -2014,7 +2016,7 @@
                         dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
                         addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                                 distance, d, false)) {
-                    mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+                    mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                     return;
                 }
 
@@ -2129,7 +2131,7 @@
                     // spring-loaded mode so the page meets the icon where it was picked up.
                     mLauncher.getDragController().animateDragViewToOriginalPosition(
                             onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
-                    mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+                    mLauncher.getStateManager().goToState(NORMAL);
                     mLauncher.getDropTargetBar().onDragEnd();
                     parent.onDropChild(cell);
                     return;
@@ -2152,8 +2154,8 @@
             }
             parent.onDropChild(cell);
 
-            mLauncher.exitSpringLoadedDragMode(
-                    SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
+            mLauncher.getStateManager().goToState(
+                    NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
         }
 
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
@@ -2685,7 +2687,7 @@
         final long screenId = getIdForScreen(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
-                && mState != LauncherState.SPRING_LOADED) {
+                && mState != SPRING_LOADED) {
             snapToPage(getPageIndexForScreenId(screenId));
         }
 
@@ -2760,7 +2762,7 @@
                     animationStyle, finalView, true);
         } else {
             // This is for other drag/drop cases, like dragging from All Apps
-            mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
 
             View view;
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index ff653d7..0ccb8ad 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -32,7 +32,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimationLayerSet;
 
 /**
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 583492e..2b3a113 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.accessibility;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.DialogInterface;
@@ -27,6 +29,7 @@
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
@@ -161,14 +164,14 @@
         } else if (action == ADD_TO_WORKSPACE) {
             final int[] coordinates = new int[2];
             final long screenId = findSpaceOnWorkspace(item, coordinates);
-            mLauncher.showWorkspace(true, new Runnable() {
+            mLauncher.getStateManager().goToState(NORMAL, true, new Runnable() {
 
                 @Override
                 public void run() {
                     if (item instanceof AppInfo) {
                         ShortcutInfo info = ((AppInfo) item).makeShortcut();
                         mLauncher.getModelWriter().addItemToDatabase(info,
-                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                                Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates[0], coordinates[1]);
 
                         ArrayList<ItemInfo> itemList = new ArrayList<>();
@@ -178,7 +181,7 @@
                         PendingAddItemInfo info = (PendingAddItemInfo) item;
                         Workspace workspace = mLauncher.getWorkspace();
                         workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
-                        mLauncher.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                        mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates, info.spanX, info.spanY);
                     }
                     announceConfirmation(R.string.item_added_to_workspace);
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
index 29dd95c..771353e 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -55,7 +56,7 @@
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         Launcher launcher = Launcher.getLauncher(host.getContext());
         if (action == OVERVIEW) {
-            launcher.showOverviewMode(true);
+            launcher.getStateManager().goToState(LauncherState.OVERVIEW);
             return true;
         } else if (action == WALLPAPERS) {
             launcher.onClickWallpaperPicker(host);
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 5b7353a..cfb0520 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.accessibility;
 
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -79,9 +81,7 @@
                 }
             };
 
-            if (!mLauncher.showWorkspace(true, onComplete)) {
-                onComplete.run();
-            }
+            mLauncher.getStateManager().goToState(NORMAL, true, onComplete);
             return true;
         } else if (action == DISMISS_NOTIFICATION) {
             if (!(host instanceof NotificationMainView)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 35dfa81..d62cd4b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,5 +1,8 @@
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
@@ -19,7 +22,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateTransitionAnimation.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
@@ -193,7 +196,7 @@
     @Override
     public void onDragStart(boolean start) {
         mCaretController.onDragStart();
-        mLauncher.getStateTransition().cancelAnimation();
+        mLauncher.getStateManager().cancelAnimation();
         cancelDiscoveryAnimation();
         mShiftStart = mAppsView.getTranslationY();
         onProgressAnimationStart();
@@ -230,7 +233,7 @@
                 if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
                     logSwipeOnContainer(Touch.FLING, Direction.UP, containerType);
                 }
-                mLauncher.showAppsView(true /* animated */);
+                mLauncher.getStateManager().goToState(ALL_APPS);
                 if (hasSpringAnimationHandler()) {
                     mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
                     // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
@@ -241,7 +244,7 @@
                 if (mLauncher.isInState(LauncherState.ALL_APPS)) {
                     logSwipeOnContainer(Touch.FLING, Direction.DOWN, ContainerType.ALLAPPS);
                 }
-                mLauncher.showWorkspace(true /* animated */);
+                mLauncher.getStateManager().goToState(NORMAL);
             }
             // snap to top or bottom using the release velocity
         } else {
@@ -250,13 +253,13 @@
                 if (mLauncher.isInState(LauncherState.ALL_APPS)) {
                     logSwipeOnContainer(Touch.SWIPE, Direction.DOWN, ContainerType.ALLAPPS);
                 }
-                mLauncher.showWorkspace(true /* animated */);
+                mLauncher.getStateManager().goToState(NORMAL);
             } else {
                 calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
                 if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
                     logSwipeOnContainer(Touch.SWIPE, Direction.UP, containerType);
                 }
-                mLauncher.showAppsView(true /* animated */);
+                mLauncher.getStateManager().goToState(ALL_APPS);
             }
         }
     }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 10bd67d..9402383 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.dragndrop;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.content.ComponentName;
 import android.content.res.Resources;
@@ -263,7 +264,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(SPRING_LOADED_EXIT_DELAY);
+            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
             mDragObject.deferDragViewCleanupPostAnimation = false;
         }
 
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fcc4f0e..2168001 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.folder;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1243,7 +1244,7 @@
             mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
         }
 
-        mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_DELAY);
+        mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java
index 9922d99..ee35b4d 100644
--- a/src/com/android/launcher3/states/AllAppsState.java
+++ b/src/com/android/launcher3/states/AllAppsState.java
@@ -19,8 +19,10 @@
 
 import android.view.View;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -41,6 +43,14 @@
         if (!launcher.getSharedPrefs().getBoolean(APPS_VIEW_SHOWN, false)) {
             launcher.getSharedPrefs().edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
         }
+
+        AbstractFloatingView.closeAllOpenViews(launcher);
+        dispatchWindowStateChanged(launcher);
+    }
+
+    @Override
+    public String getDescription(Launcher launcher) {
+        return launcher.getString(R.string.all_apps_button_label);
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index ec62764..fe0571b 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,6 +1,6 @@
 package com.android.launcher3.util;
 
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_NEXT_FRAME;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -97,7 +97,7 @@
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
-                mLauncher.exitSpringLoadedDragMode(SPRING_LOADED_EXIT_NEXT_FRAME);
+                mLauncher.getStateManager().goToState(NORMAL);
                 mDropTarget.completeDrop(mDragObject);
             }
         };