Abstracting out state handler logic into base classes so that it can
also be used in different activity

Change-Id: Ic5e60b21e1429c71f1cd4e7041e70bf4fc9761b7
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 873b066..59476dd 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -67,7 +67,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.os.Handler;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.StrictMode;
@@ -87,13 +86,12 @@
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.LauncherStateManager.AtomicAnimationFactory;
-import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -131,6 +129,10 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.qsb.QsbContainerView;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -194,7 +196,7 @@
 /**
  * Default launcher application.
  */
-public class Launcher extends BaseDraggingActivity implements LauncherExterns,
+public class Launcher extends StatefulActivity<LauncherState> implements LauncherExterns,
         Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
     public static final String TAG = "Launcher";
 
@@ -241,7 +243,7 @@
     public static final String ON_RESUME_EVT = "Launcher.onResume";
     public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
 
-    private LauncherStateManager mStateManager;
+    private StateManager<LauncherState> mStateManager;
 
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
@@ -325,10 +327,6 @@
 
     private RotationHelper mRotationHelper;
 
-    final Handler mHandler = new Handler();
-    private final Runnable mHandleDeferredResume = this::handleDeferredResume;
-    private boolean mDeferredResumePending;
-
     private float mCurrentAssistantVisibility = 0f;
 
     protected LauncherOverlayManager mOverlayManager;
@@ -375,9 +373,9 @@
 
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
-        mStateManager = new LauncherStateManager(this);
+        mStateManager = new StateManager<>(this, NORMAL);
 
-        mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs, mStateManager);
+        mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
 
         mAppWidgetManager = new WidgetManagerHelper(this);
         mAppWidgetHost = new LauncherAppWidgetHost(this,
@@ -440,7 +438,7 @@
 
         mRotationHelper.initialize();
 
-        mStateManager.addStateListener(new LauncherStateManager.StateListener() {
+        mStateManager.addStateListener(new StateListener<LauncherState>() {
 
             @Override
             public void onStateTransitionComplete(LauncherState finalState) {
@@ -467,9 +465,8 @@
         return new LauncherOverlayManager() { };
     }
 
-    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs,
-            LauncherStateManager stateManager) {
-        return new OnboardingPrefs<>(this, sharedPrefs, stateManager);
+    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+        return new OnboardingPrefs<>(this, sharedPrefs);
     }
 
     public OnboardingPrefs getOnboardingPrefs() {
@@ -523,13 +520,9 @@
     }
 
     @Override
-    public void reapplyUi() {
-        reapplyUi(true /* cancelCurrentAnimation */);
-    }
-
     public void reapplyUi(boolean cancelCurrentAnimation) {
         getRootView().dispatchInsets();
-        getStateManager().reapplyState(cancelCurrentAnimation);
+        super.reapplyUi(cancelCurrentAnimation);
     }
 
     @Override
@@ -583,7 +576,8 @@
         return mFocusHandler;
     }
 
-    public LauncherStateManager getStateManager() {
+    @Override
+    public StateManager<LauncherState> getStateManager() {
         return mStateManager;
     }
 
@@ -890,11 +884,7 @@
 
     @Override
     protected void onStop() {
-        final boolean wasActive = isUserActive();
-        final LauncherState origState = getStateManager().getState();
-        final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
-
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -902,28 +892,8 @@
         }
 
         logStopAndResume(Action.Command.STOP);
-
         mAppWidgetHost.setListenIfResumed(false);
-
         NotificationListener.removeNotificationsChangedListener();
-        getStateManager().moveToRestState();
-
-        // Workaround for b/78520668, explicitly trim memory once UI is hidden
-        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
-
-        if (wasActive) {
-            // The expected condition is that this activity is stopped because the device goes to
-            // sleep and the UI may have noticeable changes.
-            mDragLayer.post(() -> {
-                if ((!getStateManager().isInStableState(origState)
-                        // The drag layer may be animating (e.g. dismissing QSB).
-                        || mDragLayer.getAlpha() < 1
-                        // Maybe an ArrowPopup is closed.
-                        || mDragLayer.getChildCount() != origDragLayerChildCount)) {
-                    onUiChangedWhileSleeping();
-                }
-            });
-        }
     }
 
     @Override
@@ -939,35 +909,27 @@
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
-    private void handleDeferredResume() {
-        if (hasBeenResumed() && !mStateManager.getState().hasFlag(FLAG_NON_INTERACTIVE)) {
-            logStopAndResume(Action.Command.RESUME);
-            getUserEventDispatcher().startSession();
+    @Override
+    @CallSuper
+    protected void onDeferredResumed() {
+        logStopAndResume(Action.Command.RESUME);
+        getUserEventDispatcher().startSession();
 
-            AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
+        AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
 
-            // Process any items that were added while Launcher was away.
-            InstallShortcutReceiver.disableAndFlushInstallQueue(
-                    InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
+        // Process any items that were added while Launcher was away.
+        InstallShortcutReceiver.disableAndFlushInstallQueue(
+                InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
 
-            // Refresh shortcuts if the permission changed.
-            mModel.refreshShortcutsIfRequired();
+        // Refresh shortcuts if the permission changed.
+        mModel.refreshShortcutsIfRequired();
 
-            // Set the notification listener and fetch updated notifications when we resume
-            NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+        // Set the notification listener and fetch updated notifications when we resume
+        NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
 
-            DiscoveryBounce.showForHomeIfNeeded(this);
-
-            onDeferredResumed();
-            addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
-
-            mDeferredResumePending = false;
-        } else {
-            mDeferredResumePending = true;
-        }
+        DiscoveryBounce.showForHomeIfNeeded(this);
     }
 
-    protected void onDeferredResumed() { }
 
     private void logStopAndResume(int command) {
         int containerType = mStateManager.getState().containerType;
@@ -1016,10 +978,9 @@
         return mOverlayManager;
     }
 
+    @Override
     public void onStateSetStart(LauncherState state) {
-        if (mDeferredResumePending) {
-            handleDeferredResume();
-        }
+        super.onStateSetStart(state);
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         }
@@ -1042,7 +1003,9 @@
         mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
     }
 
+    @Override
     public void onStateSetEnd(LauncherState state) {
+        super.onStateSetStart(state);
         getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
         getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
 
@@ -1068,9 +1031,6 @@
                 TraceHelper.FLAG_UI_EVENT);
         super.onResume();
 
-        mHandler.removeCallbacks(mHandleDeferredResume);
-        Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
-
         if (!mOnResumeCallbacks.isEmpty()) {
             final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
             mOnResumeCallbacks.clear();
@@ -1113,10 +1073,6 @@
         }
     }
 
-    public boolean isInState(LauncherState state) {
-        return mStateManager.getState() == state;
-    }
-
     /**
      * Restores the previous state, if it exists.
      *
@@ -1355,8 +1311,6 @@
         }
     };
 
-    protected void onUiChangedWhileSleeping() { }
-
     private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
         mWorkspace.updateNotificationDots(updatedDots);
         mAppsView.getAppsStore().updateNotificationDots(updatedDots);
@@ -2721,17 +2675,10 @@
         return super.onKeyUp(keyCode, event);
     }
 
-    protected StateHandler[] createStateHandlers() {
+    protected StateHandler<LauncherState>[] createStateHandlers() {
         return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
-    /**
-     * Creates a factory for atomic state animations
-     */
-    public AtomicAnimationFactory createAtomicAnimationFactory() {
-        return new AtomicAnimationFactory(0);
-    }
-
     public TouchController[] createTouchControllers() {
         return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
     }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index e133d31..db2a6cd 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -29,6 +29,8 @@
 import android.content.Context;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.uioverrides.states.AllAppsState;
@@ -40,7 +42,7 @@
 /**
  * Base state for various states used for the Launcher
  */
-public abstract class LauncherState {
+public abstract class LauncherState implements BaseState<LauncherState> {
 
     /**
      * Set of elements indicating various workspace elements which change visibility across states
@@ -60,25 +62,22 @@
             HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
 
     // Flag indicating workspace has multiple pages visible.
-    public static final int FLAG_MULTI_PAGE = 1 << 0;
+    public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
     // Flag indicating that workspace and its contents are not accessible
-    public static final int FLAG_WORKSPACE_INACCESSIBLE = 1 << 1;
+    public static final int FLAG_WORKSPACE_INACCESSIBLE = BaseState.getFlag(1);
 
-    public static final int FLAG_DISABLE_RESTORE = 1 << 2;
     // Flag indicating the state allows workspace icons to be dragged.
-    public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = 1 << 3;
+    public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = BaseState.getFlag(2);
     // Flag to indicate that workspace should draw page background
-    public static final int FLAG_WORKSPACE_HAS_BACKGROUNDS = 1 << 4;
-    // Flag to indicate that Launcher is non-interactive in this state
-    public static final int FLAG_NON_INTERACTIVE = 1 << 5;
+    public static final int FLAG_WORKSPACE_HAS_BACKGROUNDS = BaseState.getFlag(3);
     // True if the back button should be hidden when in this state (assuming no floating views are
     // open, launcher has window focus, etc).
-    public static final int FLAG_HIDE_BACK_BUTTON = 1 << 6;
+    public static final int FLAG_HIDE_BACK_BUTTON = BaseState.getFlag(4);
     // Flag to indicate if the state would have scrim over sysui region: statu sbar and nav bar
-    public static final int FLAG_HAS_SYS_UI_SCRIM = 1 << 7;
+    public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(5);
     // Flag to inticate that all popups should be closed when this state is enabled.
-    public static final int FLAG_CLOSE_POPUPS = 1 << 8;
-    public static final int FLAG_OVERVIEW_UI = 1 << 9;
+    public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(6);
+    public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
 
 
     public static final float NO_OFFSET = 0;
@@ -151,26 +150,15 @@
     /**
      * Returns if the state has the provided flag
      */
+    @Override
     public final boolean hasFlag(int mask) {
         return (mFlags & mask) != 0;
     }
 
-    /**
-     * @return true if the state can be persisted across activity restarts.
-     */
-    public final boolean shouldDisableRestore() {
-        return hasFlag(FLAG_DISABLE_RESTORE);
-    }
-
     public static LauncherState[] values() {
         return Arrays.copyOf(sAllStates, sAllStates.length);
     }
 
-    /**
-     * @return How long the animation to this state should take (or from this state to NORMAL).
-     */
-    public abstract int getTransitionDuration(Context context);
-
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
     }
@@ -264,14 +252,20 @@
         };
     }
 
+    @Override
     public LauncherState getHistoryForState(LauncherState previousState) {
         // No history is supported
         return NORMAL;
     }
 
+    @Override
+    public String toString() {
+        return "Ordinal-" + ordinal;
+    }
+
     public void onBackPressed(Launcher launcher) {
         if (this != NORMAL) {
-            LauncherStateManager lsm = launcher.getStateManager();
+            StateManager<LauncherState> lsm = launcher.getStateManager();
             LauncherState lastState = lsm.getLastState();
             lsm.goToState(lastState);
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8dab818..286b522 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -89,6 +89,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -119,7 +120,7 @@
  */
 public class Workspace extends PagedView<WorkspacePageIndicator>
         implements DropTarget, DragSource, View.OnTouchListener,
-        DragController.DragListener, Insettable, LauncherStateManager.StateHandler,
+        DragController.DragListener, Insettable, StateHandler<LauncherState>,
         WorkspaceLayoutManager {
 
     /** The value that {@link #mTransitionProgress} must be greater than for
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 29cf803..06a73db 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -66,7 +66,7 @@
     }
 
     /**
-     * @see com.android.launcher3.LauncherStateManager.StateHandler#setStateWithAnimation
+     * @see com.android.launcher3.statemanager.StateManager.StateHandler#setStateWithAnimation
      */
     public void setStateWithAnimation(
             LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 21dd141..f057036 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -28,11 +28,11 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.Themes;
@@ -50,8 +50,8 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener,
-        PluginListener<AllAppsSearchPlugin> {
+public class AllAppsTransitionController implements StateHandler<LauncherState>,
+        OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
 
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 0648682..5397942 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -31,9 +31,9 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.OnboardingPrefs;
 
 /**
@@ -46,7 +46,7 @@
     private final Launcher mLauncher;
     private final Animator mDiscoBounceAnimation;
 
-    private final StateListener mStateListener = new StateListener() {
+    private final StateListener<LauncherState> mStateListener = new StateListener<LauncherState>() {
         @Override
         public void onStateTransitionStart(LauncherState toState) {
             handleClose(false);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index f6766c4..80b6a5a 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -22,7 +22,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.views.WorkEduView;
 
 /**
@@ -32,7 +32,7 @@
 
     private final Launcher mLauncher;
 
-    private LauncherStateManager.StateListener mWorkTabListener;
+    private StateListener<LauncherState> mWorkTabListener;
 
     public LauncherAllAppsContainerView(Context context) {
         this(context, null);
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 1e23bb6..de0fa1a 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -49,18 +49,18 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 import java.util.Arrays;
 
-public class DragView extends View implements LauncherStateManager.StateListener {
+public class DragView extends View implements StateListener<LauncherState> {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
 
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
new file mode 100644
index 0000000..daec1d8
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.statemanager;
+
+import android.content.Context;
+
+/**
+ * Interface representing a state of a StatefulActivity
+ */
+public interface BaseState<T extends BaseState> {
+
+    // Flag to indicate that Launcher is non-interactive in this state
+    int FLAG_NON_INTERACTIVE = 1 << 0;
+    int FLAG_DISABLE_RESTORE = 1 << 1;
+
+    static int getFlag(int index) {
+        // reserve few spots to base flags
+        return 1 << (index + 2);
+    }
+
+    /**
+     * @return How long the animation to this state should take (or from this state to NORMAL).
+     */
+    int getTransitionDuration(Context context);
+
+    /**
+     * Returns the state to go back to from this state
+     */
+    T getHistoryForState(T previousState);
+
+    /**
+     * @return true if the state can be persisted across activity restarts.
+     */
+    default boolean shouldDisableRestore() {
+        return hasFlag(FLAG_DISABLE_RESTORE);
+    }
+
+    /**
+     * Returns if the state has the provided flag
+     */
+    boolean hasFlag(int flagMask);
+}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
similarity index 76%
rename from src/com/android/launcher3/LauncherStateManager.java
rename to src/com/android/launcher3/statemanager/StateManager.java
index f6de48e..4447166 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.statemanager;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 
 import android.animation.Animator;
@@ -27,6 +26,7 @@
 import android.os.Looper;
 import android.util.Log;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
@@ -38,83 +38,48 @@
 import java.util.ArrayList;
 
 /**
- * TODO: figure out what kind of tests we can write for this
- *
- * Things to test when changing the following class.
- *   - Home from workspace
- *          - from center screen
- *          - from other screens
- *   - Home from all apps
- *          - from center screen
- *          - from other screens
- *   - Back from all apps
- *          - from center screen
- *          - from other screens
- *   - Launch app from workspace and quit
- *          - with back
- *          - with home
- *   - Launch app from all apps and quit
- *          - with back
- *          - with home
- *   - Go to a screen that's not the default, then all
- *     apps, and launch and app, and go back
- *          - with back
- *          -with home
- *   - On workspace, long press power and go back
- *          - with back
- *          - with home
- *   - On all apps, long press power and go back
- *          - with back
- *          - with home
- *   - On workspace, power off
- *   - On all apps, power off
- *   - Launch an app and turn off the screen while in that app
- *          - Go back with home key
- *          - Go back with back key  TODO: make this not go to workspace
- *          - From all apps
- *          - From workspace
- *   - Enter and exit car mode (becase it causes an extra configuration changed)
- *          - From all apps
- *          - From the center workspace
- *          - From another workspace
+ * Class to manage transitions between different states for a StatefulActivity based on different
+ * states
  */
-public class LauncherStateManager {
+public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
 
     public static final String TAG = "StateManager";
 
     private final AnimationState mConfig = new AnimationState();
     private final Handler mUiHandler;
-    private final Launcher mLauncher;
-    private final ArrayList<StateListener> mListeners = new ArrayList<>();
+    private final StatefulActivity<STATE_TYPE> mActivity;
+    private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
+    private final STATE_TYPE mBaseState;
 
     // Animators which are run on properties also controlled by state animations.
     private final AtomicAnimationFactory mAtomicAnimationFactory;
 
-    private StateHandler[] mStateHandlers;
-    private LauncherState mState = NORMAL;
+    private StateHandler<STATE_TYPE>[] mStateHandlers;
+    private STATE_TYPE mState;
 
-    private LauncherState mLastStableState = NORMAL;
-    private LauncherState mCurrentStableState = NORMAL;
+    private STATE_TYPE mLastStableState;
+    private STATE_TYPE mCurrentStableState;
 
-    private LauncherState mRestState;
+    private STATE_TYPE mRestState;
 
-    public LauncherStateManager(Launcher l) {
+    public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) {
         mUiHandler = new Handler(Looper.getMainLooper());
-        mLauncher = l;
-
+        mActivity = l;
+        mBaseState = baseState;
+        mState = mLastStableState = mCurrentStableState = baseState;
         mAtomicAnimationFactory = l.createAtomicAnimationFactory();
     }
 
-    public LauncherState getState() {
+    public STATE_TYPE getState() {
         return mState;
     }
 
-    public LauncherState getCurrentStableState() {
+    public STATE_TYPE getCurrentStableState() {
         return mCurrentStableState;
     }
 
     public void dump(String prefix, PrintWriter writer) {
-        writer.println(prefix + "LauncherState:");
+        writer.println(prefix + "StateManager:");
         writer.println(prefix + "\tmLastStableState:" + mLastStableState);
         writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
         writer.println(prefix + "\tmState:" + mState);
@@ -124,7 +89,7 @@
 
     public StateHandler[] getStateHandlers() {
         if (mStateHandlers == null) {
-            mStateHandlers = mLauncher.createStateHandlers();
+            mStateHandlers = mActivity.createStateHandlers();
         }
         return mStateHandlers;
     }
@@ -141,29 +106,29 @@
      * Returns true if the state changes should be animated.
      */
     public boolean shouldAnimateStateChange() {
-        return !mLauncher.isForceInvisible() && mLauncher.isStarted();
+        return !mActivity.isForceInvisible() && mActivity.isStarted();
     }
 
     /**
      * @return {@code true} if the state matches the current state and there is no active
      *         transition to different state.
      */
-    public boolean isInStableState(LauncherState state) {
+    public boolean isInStableState(STATE_TYPE state) {
         return mState == state && mCurrentStableState == state
                 && (mConfig.targetState == null || mConfig.targetState == state);
     }
 
     /**
-     * @see #goToState(LauncherState, boolean, Runnable)
+     * @see #goToState(STATE_TYPE, boolean, Runnable)
      */
-    public void goToState(LauncherState state) {
+    public void goToState(STATE_TYPE state) {
         goToState(state, shouldAnimateStateChange());
     }
 
     /**
-     * @see #goToState(LauncherState, boolean, Runnable)
+     * @see #goToState(STATE_TYPE, boolean, Runnable)
      */
-    public void goToState(LauncherState state, boolean animated) {
+    public void goToState(STATE_TYPE state, boolean animated) {
         goToState(state, animated, 0, null);
     }
 
@@ -174,21 +139,21 @@
      *                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) {
+    public void goToState(STATE_TYPE 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) {
+    public void goToState(STATE_TYPE 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) {
+    public void goToState(STATE_TYPE state, long delay) {
         goToState(state, true, delay, null);
     }
 
@@ -212,10 +177,10 @@
         }
     }
 
-    private void goToState(LauncherState state, boolean animated, long delay,
+    private void goToState(STATE_TYPE state, boolean animated, long delay,
             final Runnable onCompleteRunnable) {
-        animated &= Utilities.areAnimationsEnabled(mLauncher);
-        if (mLauncher.isInState(state)) {
+        animated &= Utilities.areAnimationsEnabled(mActivity);
+        if (mActivity.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
                 if (onCompleteRunnable != null) {
@@ -233,7 +198,7 @@
         }
 
         // Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
-        LauncherState fromState = mState;
+        STATE_TYPE fromState = mState;
         mConfig.reset();
 
         if (!animated) {
@@ -266,13 +231,13 @@
         }
     }
 
-    private void goToStateAnimated(LauncherState state, LauncherState fromState,
+    private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
             Runnable onCompleteRunnable) {
-        // Since state NORMAL can be reached from multiple states, just assume that the
+        // Since state mBaseState can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
-        mConfig.duration = state == NORMAL
-                ? fromState.getTransitionDuration(mLauncher)
-                : state.getTransitionDuration(mLauncher);
+        mConfig.duration = state == mBaseState
+                ? fromState.getTransitionDuration(mActivity)
+                : state.getTransitionDuration(mActivity);
         prepareForAtomicAnimation(fromState, state, mConfig);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).getAnim();
         if (onCompleteRunnable != null) {
@@ -286,7 +251,7 @@
      * - Setting interpolators for various animations included in the state transition.
      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
      */
-    public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
+    public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState,
             StateAnimationConfig config) {
         mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
     }
@@ -295,11 +260,11 @@
      * Creates an animation representing atomic transitions between the provided states
      */
     public AnimatorSet createAtomicAnimation(
-            LauncherState fromState, LauncherState toState, StateAnimationConfig config) {
+            STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
         PendingAnimation builder = new PendingAnimation(config.duration);
         prepareForAtomicAnimation(fromState, toState, config);
 
-        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+        for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) {
             handler.setStateWithAnimation(toState, config, builder);
         }
         return builder.getAnim();
@@ -309,23 +274,23 @@
      * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
      * state transition.
      * @param state the final state for the transition.
-     * @param duration intended duration for normal playback. Use higher duration for better
+     * @param duration intended duration for state playback. Use higher duration for better
      *                accuracy.
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState state, long duration) {
+            STATE_TYPE state, long duration) {
         return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState state, long duration, @AnimationFlags int animComponents) {
+            STATE_TYPE state, long duration, @AnimationFlags int animComponents) {
         StateAnimationConfig config = new StateAnimationConfig();
         config.duration = duration;
         config.animFlags = animComponents;
         return createAnimationToNewWorkspace(state, config);
     }
 
-    public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
+    public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
             StateAnimationConfig config) {
         config.userControlled = true;
         mConfig.reset();
@@ -335,10 +300,10 @@
         return mConfig.playbackController;
     }
 
-    private PendingAnimation createAnimationToNewWorkspaceInternal(final LauncherState state) {
+    private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "createAnimationToNewWorkspaceInternal: "
-                    + state.ordinal);
+                    + state);
         }
         PendingAnimation builder = new PendingAnimation(mConfig.duration);
         for (StateHandler handler : getStateHandlers()) {
@@ -355,7 +320,7 @@
             @Override
             public void onAnimationSuccess(Animator animator) {
                 if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state.ordinal);
+                    Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state);
                 }
                 onStateTransitionEnd(state);
             }
@@ -364,24 +329,24 @@
         return builder;
     }
 
-    private void onStateTransitionStart(LauncherState state) {
+    private void onStateTransitionStart(STATE_TYPE state) {
         mState = state;
-        mLauncher.onStateSetStart(mState);
+        mActivity.onStateSetStart(mState);
 
         for (int i = mListeners.size() - 1; i >= 0; i--) {
             mListeners.get(i).onStateTransitionStart(state);
         }
     }
 
-    private void onStateTransitionEnd(LauncherState state) {
+    private void onStateTransitionEnd(STATE_TYPE state) {
         // Only change the stable states after the transitions have finished
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
             mCurrentStableState = state;
         }
 
-        mLauncher.onStateSetEnd(state);
-        if (state == NORMAL) {
+        mActivity.onStateSetEnd(state);
+        if (state == mBaseState) {
             setRestState(null);
         }
 
@@ -390,7 +355,7 @@
         }
     }
 
-    public LauncherState getLastState() {
+    public STATE_TYPE getLastState() {
         return mLastStableState;
     }
 
@@ -402,15 +367,15 @@
         if (mState.shouldDisableRestore()) {
             goToState(getRestState());
             // Reset history
-            mLastStableState = NORMAL;
+            mLastStableState = mBaseState;
         }
     }
 
-    public LauncherState getRestState() {
-        return mRestState == null ? NORMAL : mRestState;
+    public STATE_TYPE getRestState() {
+        return mRestState == null ? mBaseState : mRestState;
     }
 
-    public void setRestState(LauncherState restState) {
+    public void setRestState(STATE_TYPE restState) {
         mRestState = restState;
     }
 
@@ -505,13 +470,14 @@
         }
     }
 
-    private static class AnimationState extends StateAnimationConfig implements AnimatorListener {
+    private static class AnimationState<STATE_TYPE> extends StateAnimationConfig
+            implements AnimatorListener {
 
         private static final StateAnimationConfig DEFAULT = new StateAnimationConfig();
 
         public AnimatorPlaybackController playbackController;
         public AnimatorSet currentAnimation;
-        public LauncherState targetState;
+        public STATE_TYPE targetState;
 
         // Id to keep track of config changes, to tie an animation with the corresponding request
         public int changeId = 0;
@@ -546,7 +512,7 @@
             }
         }
 
-        public void setAnimation(AnimatorSet animation, LauncherState targetState) {
+        public void setAnimation(AnimatorSet animation, STATE_TYPE targetState) {
             currentAnimation = animation;
             this.targetState = targetState;
             currentAnimation.addListener(this);
@@ -562,31 +528,31 @@
         public void onAnimationRepeat(Animator animator) { }
     }
 
-    public interface StateHandler {
+    public interface StateHandler<STATE_TYPE> {
 
         /**
          * Updates the UI to {@param state} without any animations
          */
-        void setState(LauncherState state);
+        void setState(STATE_TYPE state);
 
         /**
          * Sets the UI to {@param state} by animating any changes.
          */
         void setStateWithAnimation(
-                LauncherState toState, StateAnimationConfig config, PendingAnimation animation);
+                STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation);
     }
 
-    public interface StateListener {
+    public interface StateListener<STATE_TYPE> {
 
-        default void onStateTransitionStart(LauncherState toState) { }
+        default void onStateTransitionStart(STATE_TYPE toState) { }
 
-        default void onStateTransitionComplete(LauncherState finalState) { }
+        default void onStateTransitionComplete(STATE_TYPE finalState) { }
     }
 
     /**
      * Factory class to configure and create atomic animations.
      */
-    public static class AtomicAnimationFactory {
+    public static class AtomicAnimationFactory<STATE_TYPE> {
 
         private final Animator[] mStateElementAnimators;
 
@@ -622,6 +588,6 @@
          * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
          */
         public void prepareForAtomicAnimation(
-                LauncherState fromState, LauncherState toState, StateAnimationConfig config) { }
+                STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { }
     }
 }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
new file mode 100644
index 0000000..0a1607c
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 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.statemanager;
+
+import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
+
+import android.os.Handler;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * Abstract activity with state management
+ * @param <STATE_TYPE> Type of state object
+ */
+public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
+        extends BaseDraggingActivity {
+
+    public final Handler mHandler = new Handler();
+    private final Runnable mHandleDeferredResume = this::handleDeferredResume;
+    private boolean mDeferredResumePending;
+
+    /**
+     * Create handlers to control the property changes for this activity
+     */
+    protected abstract StateHandler<STATE_TYPE>[] createStateHandlers();
+
+    /**
+     * Returns true if the activity is in the provided state
+     */
+    public boolean isInState(STATE_TYPE state) {
+        return getStateManager().getState() == state;
+    }
+
+    /**
+     * Returns the state manager for this activity
+     */
+    public abstract StateManager<STATE_TYPE> getStateManager();
+
+    /**
+     * Called when transition to the state starts
+     */
+    @CallSuper
+    public void onStateSetStart(STATE_TYPE state) {
+        if (mDeferredResumePending) {
+            handleDeferredResume();
+        }
+    }
+
+    /**
+     * Called when transition to state ends
+     */
+    public void onStateSetEnd(STATE_TYPE state) { }
+
+    /**
+     * Creates a factory for atomic state animations
+     */
+    public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
+        return new AtomicAnimationFactory(0);
+    }
+
+    @Override
+    public void reapplyUi() {
+        reapplyUi(true /* cancelCurrentAnimation */);
+    }
+
+    /**
+     * Re-applies if any state transition is not running, optionally cancelling
+     * the transition if requested.
+     */
+    public void reapplyUi(boolean cancelCurrentAnimation) {
+        getStateManager().reapplyState(cancelCurrentAnimation);
+    }
+
+    @Override
+    protected void onStop() {
+        BaseDragLayer dragLayer = getDragLayer();
+        final boolean wasActive = isUserActive();
+        final STATE_TYPE origState = getStateManager().getState();
+        final int origDragLayerChildCount = dragLayer.getChildCount();
+        super.onStop();
+
+        getStateManager().moveToRestState();
+
+        // Workaround for b/78520668, explicitly trim memory once UI is hidden
+        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
+
+        if (wasActive) {
+            // The expected condition is that this activity is stopped because the device goes to
+            // sleep and the UI may have noticeable changes.
+            dragLayer.post(() -> {
+                if ((!getStateManager().isInStableState(origState)
+                        // The drag layer may be animating (e.g. dismissing QSB).
+                        || dragLayer.getAlpha() < 1
+                        // Maybe an ArrowPopup is closed.
+                        || dragLayer.getChildCount() != origDragLayerChildCount)) {
+                    onUiChangedWhileSleeping();
+                }
+            });
+        }
+    }
+
+    /**
+     * Called if the Activity UI changed while the activity was not visible
+     */
+    protected void onUiChangedWhileSleeping() { }
+
+    private void handleDeferredResume() {
+        if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) {
+            onDeferredResumed();
+            addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+
+            mDeferredResumePending = false;
+        } else {
+            mDeferredResumePending = true;
+        }
+    }
+
+    /**
+     * Called want the activity has stayed resumed for 1 frame.
+     */
+    protected void onDeferredResumed() { }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mHandler.removeCallbacks(mHandleDeferredResume);
+        Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
+    }
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index baa1eee..1620289 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -21,7 +21,6 @@
 import androidx.annotation.StringDef;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherStateManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -71,13 +70,10 @@
 
     protected final T mLauncher;
     protected final SharedPreferences mSharedPrefs;
-    protected final LauncherStateManager mStateManager;
 
-    public OnboardingPrefs(T launcher, SharedPreferences sharedPrefs,
-            LauncherStateManager stateManager) {
+    public OnboardingPrefs(T launcher, SharedPreferences sharedPrefs) {
         mLauncher = launcher;
         mSharedPrefs = sharedPrefs;
-        mStateManager = stateManager;
     }
 
     /** @return The number of times we have seen the given event. */
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index da874cf..a2c7d14 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -62,10 +62,10 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -116,7 +116,8 @@
     private final AccessibilityManager mAM;
     protected final int mEndScrim;
 
-    private final StateListener mAccessibilityLauncherStateListener = new StateListener() {
+    private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
+            new StateListener<LauncherState>() {
         @Override
         public void onStateTransitionComplete(LauncherState finalState) {
             setImportantForAccessibility(finalState == ALL_APPS
@@ -383,7 +384,7 @@
 
     @Override
     public void onAccessibilityStateChanged(boolean enabled) {
-        LauncherStateManager stateManager = mLauncher.getStateManager();
+        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
         stateManager.removeStateListener(mAccessibilityLauncherStateListener);
 
         if (enabled) {
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index 859b9d0..d35a38f 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -32,19 +32,19 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 /**
  * On boarding flow for users right after setting up work profile
  */
-public class WorkEduView extends AbstractSlideInView implements Insettable, StateListener {
+public class WorkEduView extends AbstractSlideInView
+        implements Insettable, StateListener<LauncherState> {
 
     private static final int DEFAULT_CLOSE_DURATION = 200;
     public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
@@ -185,8 +185,8 @@
     /**
      * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
      */
-    public static LauncherStateManager.StateListener showEduFlowIfNeeded(Launcher launcher,
-            @Nullable LauncherStateManager.StateListener oldListener) {
+    public static StateListener<LauncherState> showEduFlowIfNeeded(Launcher launcher,
+            @Nullable StateListener<LauncherState> oldListener) {
         if (oldListener != null) {
             launcher.getStateManager().removeStateListener(oldListener);
         }
@@ -195,7 +195,7 @@
             return null;
         }
 
-        LauncherStateManager.StateListener listener = new LauncherStateManager.StateListener() {
+        StateListener<LauncherState> listener = new StateListener<LauncherState>() {
             @Override
             public void onStateTransitionComplete(LauncherState finalState) {
                 if (finalState != LauncherState.ALL_APPS) return;