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;