Combining two handlers into one
Change-Id: I059b042f6c816b4239c81748068c7003dc4743c1
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
index feaa8b9..5a3449b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -17,83 +17,1418 @@
import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
+import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
+import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
+import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.WindowInsets;
+import android.view.animation.Interpolator;
import android.widget.Toast;
-import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory;
+import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TaskInfoCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
import java.util.function.Consumer;
/**
- * Base class for swipe up handler with some utility methods
+ * Handles the navigation gestures when Launcher is the default home activity.
*/
-@TargetApi(Build.VERSION_CODES.Q)
+@TargetApi(Build.VERSION_CODES.R)
public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
- extends SwipeUpAnimationLogic implements RecentsAnimationListener {
-
+ extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
+ RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
+ private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
+
protected final BaseActivityInterface<?, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
-
protected final ActivityInitListener mActivityInitListener;
-
- protected RecentsAnimationController mRecentsAnimationController;
- protected RecentsAnimationTargets mRecentsAnimationTargets;
-
// Callbacks to be made once the recents animation starts
private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
-
+ protected RecentsAnimationController mRecentsAnimationController;
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
protected Q mRecentsView;
-
protected Runnable mGestureEndCallback;
-
protected MultiStateCallback mStateCallback;
-
protected boolean mCanceled;
-
private boolean mRecentsViewScrollLinked = false;
- protected AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
- GestureState gestureState, InputConsumerController inputConsumer) {
+ private static int getFlagForIndex(int index, String name) {
+ if (DEBUG_STATES) {
+ STATE_NAMES[index] = name;
+ }
+ return 1 << index;
+ }
+
+ // Launcher UI related states
+ protected static final int STATE_LAUNCHER_PRESENT =
+ getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
+ protected static final int STATE_LAUNCHER_STARTED =
+ getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
+ protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+
+ // Internal initialization states
+ private static final int STATE_APP_CONTROLLER_RECEIVED =
+ getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
+
+ // Interaction finish states
+ private static final int STATE_SCALED_CONTROLLER_HOME =
+ getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
+ private static final int STATE_SCALED_CONTROLLER_RECENTS =
+ getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
+
+ protected static final int STATE_HANDLER_INVALIDATED =
+ getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
+ private static final int STATE_GESTURE_STARTED =
+ getFlagForIndex(7, "STATE_GESTURE_STARTED");
+ private static final int STATE_GESTURE_CANCELLED =
+ getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
+ private static final int STATE_GESTURE_COMPLETED =
+ getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
+
+ private static final int STATE_CAPTURE_SCREENSHOT =
+ getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
+ protected static final int STATE_SCREENSHOT_CAPTURED =
+ getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
+ private static final int STATE_SCREENSHOT_VIEW_SHOWN =
+ getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
+
+ private static final int STATE_RESUME_LAST_TASK =
+ getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
+ private static final int STATE_START_NEW_TASK =
+ getFlagForIndex(14, "STATE_START_NEW_TASK");
+ private static final int STATE_CURRENT_TASK_FINISHED =
+ getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
+
+ private static final int LAUNCHER_UI_STATES =
+ STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
+
+ public static final long MAX_SWIPE_DURATION = 350;
+ public static final long MIN_OVERSHOOT_DURATION = 120;
+
+ public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
+ private static final float SWIPE_DURATION_MULTIPLIER =
+ Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
+ private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
+
+ public static final long RECENTS_ATTACH_DURATION = 300;
+
+ /**
+ * Used as the page index for logging when we return to the last task at the end of the gesture.
+ */
+ private static final int LOG_NO_OP_PAGE_INDEX = -1;
+
+ protected final TaskAnimationManager mTaskAnimationManager;
+
+ // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
+ private RunningWindowAnim mRunningWindowAnim;
+ private boolean mIsShelfPeeking;
+
+ private boolean mContinuingLastGesture;
+
+ private ThumbnailData mTaskSnapshot;
+
+ // Used to control launcher components throughout the swipe gesture.
+ private AnimatorPlaybackController mLauncherTransitionController;
+ private boolean mHasLauncherTransitionControllerStarted;
+
+ private AnimationFactory mAnimationFactory = (t) -> { };
+
+ private boolean mWasLauncherAlreadyVisible;
+
+ private boolean mPassedOverviewThreshold;
+ private boolean mGestureStarted;
+ private int mLogAction = Touch.SWIPE;
+ private int mLogDirection = Direction.UP;
+ private PointF mDownPos;
+ private boolean mIsLikelyToStartNewTask;
+
+ private final long mTouchTimeMs;
+ private long mLauncherFrameDrawnTime;
+
+ private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+
+ public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ long touchTimeMs, boolean continuingLastGesture,
+ InputConsumerController inputConsumer) {
super(context, deviceState, gestureState, new TransformParams());
mActivityInterface = gestureState.getActivityInterface();
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
mInputConsumerProxy =
new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
+ mTaskAnimationManager = taskAnimationManager;
+ mTouchTimeMs = touchTimeMs;
+ mContinuingLastGesture = continuingLastGesture;
+
+ initAfterSubclassConstructor();
+ initStateCallbacks();
+ }
+
+ private void initStateCallbacks() {
+ mStateCallback = new MultiStateCallback(STATE_NAMES);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+ this::onLauncherPresentAndGestureStarted);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
+ this::initializeLauncherAnimationController);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
+ this::launcherFrameDrawn);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
+ | STATE_GESTURE_CANCELLED,
+ this::resetStateForAnimationCancel);
+
+ mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
+ this::resumeLastTask);
+ mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
+ this::startNewTask);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
+ this::switchToScreenshot);
+
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ | STATE_SCALED_CONTROLLER_RECENTS,
+ this::finishCurrentTransitionToRecents);
+
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ | STATE_SCALED_CONTROLLER_HOME,
+ this::finishCurrentTransitionToHome);
+ mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+ this::reset);
+
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
+ | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
+ | STATE_GESTURE_STARTED,
+ this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
+
+ mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
+ this::continueComputingRecentsScrollIfNecessary);
+ mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
+ | STATE_RECENTS_SCROLLING_FINISHED,
+ this::onSettledOnEndTarget);
+
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+ this::invalidateHandlerWithLauncher);
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
+ this::notifyTransitionCancelled);
+
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+ | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
+ (b) -> mRecentsView.setRunningTaskHidden(!b));
+ }
+ }
+
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ T createdActivity = mActivityInterface.getCreatedActivity();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+ }
+ if (createdActivity != null) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+ }
+ initTransitionEndpoints(createdActivity.getDeviceProfile());
+ }
+ final T activity = mActivityInterface.getCreatedActivity();
+ if (mActivity == activity) {
+ return true;
+ }
+
+ if (mActivity != null) {
+ // The launcher may have been recreated as a result of device rotation.
+ int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
+ initStateCallbacks();
+ mStateCallback.setState(oldState);
+ }
+ mWasLauncherAlreadyVisible = alreadyOnHome;
+ mActivity = activity;
+ // Override the visibility of the activity until the gesture actually starts and we swipe
+ // up, or until we transition home and the home animation is composed
+ if (alreadyOnHome) {
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ } else {
+ mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ }
+
+ mRecentsView = activity.getOverviewPanel();
+ mRecentsView.setOnPageTransitionEndCallback(null);
+ addLiveTileOverlay();
+
+ mStateCallback.setState(STATE_LAUNCHER_PRESENT);
+ if (alreadyOnHome) {
+ onLauncherStart();
+ } else {
+ activity.runOnceOnStart(this::onLauncherStart);
+ }
+
+ setupRecentsViewUi();
+ linkRecentsViewScroll();
+
+ return true;
+ }
+
+ /**
+ * Return true if the window should be translated horizontally if the recents view scrolls
+ */
+ protected boolean moveWindowWithRecentsScroll() {
+ return mGestureState.getEndTarget() != HOME;
+ }
+
+ private void onLauncherStart() {
+ final T activity = mActivityInterface.getCreatedActivity();
+ if (mActivity != activity) {
+ return;
+ }
+ if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+ return;
+ }
+ mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation());
+
+ // If we've already ended the gesture and are going home, don't prepare recents UI,
+ // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
+ if (mGestureState.getEndTarget() != HOME) {
+ Runnable initAnimFactory = () -> {
+ mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
+ mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
+ maybeUpdateRecentsAttachedState(false /* animate */);
+ };
+ if (mWasLauncherAlreadyVisible) {
+ // Launcher is visible, but might be about to stop. Thus, if we prepare recents
+ // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
+ // wait until the next gesture (and possibly launcher) starts.
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
+ } else {
+ initAnimFactory.run();
+ }
+ }
+ AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
+ AbstractFloatingView.TYPE_LISTENER);
+
+ if (mWasLauncherAlreadyVisible) {
+ mStateCallback.setState(STATE_LAUNCHER_DRAWN);
+ } else {
+ Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
+ View dragLayer = activity.getDragLayer();
+ dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
+ boolean mHandled = false;
+
+ @Override
+ public void onDraw() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ TraceHelper.INSTANCE.endSection(traceToken);
+ dragLayer.post(() ->
+ dragLayer.getViewTreeObserver().removeOnDrawListener(this));
+ if (activity != mActivity) {
+ return;
+ }
+
+ mStateCallback.setState(STATE_LAUNCHER_DRAWN);
+ }
+ });
+ }
+
+ activity.getRootView().setOnApplyWindowInsetsListener(this);
+ mStateCallback.setState(STATE_LAUNCHER_STARTED);
+ }
+
+ private void onLauncherPresentAndGestureStarted() {
+ // Re-setup the recents UI when gesture starts, as the state could have been changed during
+ // that time by a previous window transition.
+ setupRecentsViewUi();
+
+ // For the duration of the gesture, in cases where an activity is launched while the
+ // activity is not yet resumed, finish the animation to ensure we get resumed
+ mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
+ mOnDeferredActivityLaunch);
+
+ mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+ () -> mDeviceState.getRotationTouchHelper().
+ onEndTargetCalculated(mGestureState.getEndTarget(),
+ mActivityInterface));
+
+ notifyGestureStartedAsync();
+ }
+
+ private void onDeferredActivityLaunch() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mActivityInterface.switchRunningTaskViewToScreenshot(
+ null, () -> {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ });
+ } else {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ }
+ }
+
+ private void setupRecentsViewUi() {
+ if (mContinuingLastGesture) {
+ updateSysUiFlags(mCurrentShift.value);
+ return;
+ }
+ notifyGestureAnimationStartToRecents();
+ }
+
+ protected void notifyGestureAnimationStartToRecents() {
+ mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
+ }
+
+ private void launcherFrameDrawn() {
+ mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
+ }
+
+ private void initializeLauncherAnimationController() {
+ buildAnimationController();
+
+ Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
+ TraceHelper.FLAG_IGNORE_BINDERS);
+ // Only used in debug builds
+ if (LatencyTrackerCompat.isEnabled(mContext)) {
+ LatencyTrackerCompat.logToggleRecents(
+ (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+ }
+ TraceHelper.INSTANCE.endSection(traceToken);
+
+ // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
+ // high-res thumbnail loader here once we are sure that we will end up in an overview state
+ RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
+ }
+
+ /**
+ * Called when motion pause is detected
+ */
+ public void onMotionPauseChanged(boolean isPaused) {
+ setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
+ }
+
+ public void maybeUpdateRecentsAttachedState() {
+ maybeUpdateRecentsAttachedState(true /* animate */);
+ }
+
+ /**
+ * Determines whether to show or hide RecentsView. The window is always
+ * synchronized with its corresponding TaskView in RecentsView, so if
+ * RecentsView is shown, it will appear to be attached to the window.
+ *
+ * Note this method has no effect unless the navigation mode is NO_BUTTON.
+ */
+ private void maybeUpdateRecentsAttachedState(boolean animate) {
+ if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
+ return;
+ }
+ RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+ ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ : null;
+ final boolean recentsAttachedToAppWindow;
+ if (mGestureState.getEndTarget() != null) {
+ recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
+ } else if (mContinuingLastGesture
+ && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
+ recentsAttachedToAppWindow = true;
+ } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
+ // The window is going away so make sure recents is always visible in this case.
+ recentsAttachedToAppWindow = true;
+ } else {
+ recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
+ }
+ mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+ }
+
+ public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+ }
+
+ private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
+ if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
+ mIsLikelyToStartNewTask = isLikelyToStartNewTask;
+ maybeUpdateRecentsAttachedState(animate);
+ }
+ }
+
+ @UiThread
+ public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+ mAnimationFactory.setShelfState(shelfState, interpolator, duration);
+ boolean wasShelfPeeking = mIsShelfPeeking;
+ mIsShelfPeeking = shelfState == PEEK;
+ if (mIsShelfPeeking != wasShelfPeeking) {
+ maybeUpdateRecentsAttachedState();
+ }
+ if (shelfState.shouldPreformHaptic) {
+ performHapticFeedback();
+ }
+ }
+
+ private void buildAnimationController() {
+ if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
+ return;
+ }
+ initTransitionEndpoints(mActivity.getDeviceProfile());
+ mAnimationFactory.createActivityInterface(mTransitionDragLength);
+ }
+
+ /**
+ * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
+ * (it has its own animation) or if we're already animating the current controller.
+ * @return Whether we can create the launcher controller or update its progress.
+ */
+ private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
+ return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+ WindowInsets result = view.onApplyWindowInsets(windowInsets);
+ buildAnimationController();
+ return result;
+ }
+
+ private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
+ mLauncherTransitionController = anim;
+ mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
+ mLauncherTransitionController.dispatchOnStart();
+ updateLauncherTransitionProgress();
+ }
+
+ public Intent getLaunchIntent() {
+ return mGestureState.getOverviewIntent();
+ }
+
+ /**
+ * Called when the value of {@link #mCurrentShift} changes
+ */
+ @UiThread
+ @Override
+ public void updateFinalShift() {
+ final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
+ if (passed != mPassedOverviewThreshold) {
+ mPassedOverviewThreshold = passed;
+ if (!mDeviceState.isFullyGesturalNavMode()) {
+ performHapticFeedback();
+ }
+ }
+
+ updateSysUiFlags(mCurrentShift.value);
+ applyWindowTransform();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mRecentsAnimationTargets != null) {
+ LiveTileOverlay.INSTANCE.update(
+ mTaskViewSimulator.getCurrentRect(),
+ mTaskViewSimulator.getCurrentCornerRadius());
+ }
+ }
+
+ updateLauncherTransitionProgress();
+ }
+
+ private void updateLauncherTransitionProgress() {
+ if (mLauncherTransitionController == null
+ || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
+ return;
+ }
+ // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
+ // anyway. The controller mimics the drag length factor by applying it to its interpolators.
+ float progress = mCurrentShift.value / mDragLengthFactor;
+ mLauncherTransitionController.setPlayFraction(progress);
+ }
+
+ /**
+ * @param windowProgress 0 == app, 1 == overview
+ */
+ private void updateSysUiFlags(float windowProgress) {
+ if (mRecentsAnimationController != null && mRecentsView != null) {
+ TaskView runningTask = mRecentsView.getRunningTaskView();
+ TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
+ int centermostTaskFlags = centermostTask == null ? 0
+ : centermostTask.getThumbnail().getSysUiStatusNavFlags();
+ boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
+ boolean quickswitchThresholdPassed = centermostTask != runningTask;
+
+ // We will handle the sysui flags based on the centermost task view.
+ mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
+ || (quickswitchThresholdPassed && centermostTaskFlags != 0));
+ mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
+
+ int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
+ mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
+ }
+ }
+
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ mRecentsAnimationController = controller;
+ mRecentsAnimationTargets = targets;
+ mTransformParams.setTargetSet(mRecentsAnimationTargets);
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+ mGestureState.getRunningTaskId());
+
+ if (runningTaskTarget != null) {
+ mTaskViewSimulator.setPreview(runningTaskTarget);
+ }
+
+ // Only initialize the device profile, if it has not been initialized before, as in some
+ // configurations targets.homeContentInsets may not be correct.
+ if (mActivity == null) {
+ DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ Rect overviewStackBounds = mActivityInterface
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext,
+ new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+ } else {
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ dp = dp.copy(mContext);
+ }
+ dp.updateInsets(targets.homeContentInsets);
+ dp.updateIsSeascape(mContext);
+ initTransitionEndpoints(dp);
+ }
+
+ // Notify when the animation starts
+ if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+ for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+ action.run();
+ }
+ mRecentsAnimationStartCallbacks.clear();
+ }
+
+ // Only add the callback to enable the input consumer after we actually have the controller
+ mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+ mRecentsAnimationController::enableInputConsumer);
+ mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+
+ mPassedOverviewThreshold = false;
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
+ mActivityInitListener.unregister();
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+
+ // Defer clearing the controller and the targets until after we've updated the state
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
+ }
+
+ @UiThread
+ public void onGestureStarted(boolean isLikelyToStartNewTask) {
+ notifyGestureStartedAsync();
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
+ mGestureStarted = true;
+ }
+
+ /**
+ * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
+ */
+ @UiThread
+ private void notifyGestureStartedAsync() {
+ final T curActivity = mActivity;
+ if (curActivity != null) {
+ // Once the gesture starts, we can no longer transition home through the button, so
+ // reset the force override of the activity visibility
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ }
+ }
+
+ /**
+ * Called as a result on ACTION_CANCEL to return the UI to the start state.
+ */
+ @UiThread
+ public void onGestureCancelled() {
+ updateDisplacement(0);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ mLogAction = Touch.SWIPE_NOOP;
+ handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
+ }
+
+ /**
+ * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
+ * @param velocity The x and y components of the velocity when the gesture ends.
+ * @param downPos The x and y value of where the gesture started.
+ */
+ @UiThread
+ public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+ float flingThreshold = mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
+
+ mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
+ boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
+ if (isVelocityVertical) {
+ mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
+ } else {
+ mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
+ }
+ mDownPos = downPos;
+ handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
+ }
+
+ /**
+ * Called to create a input proxy for the running task
+ */
+ @UiThread
+ protected InputConsumer createNewInputProxyHandler() {
+ endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
+ endLauncherTransitionController();
+
+ StatefulActivity activity = mActivityInterface.getCreatedActivity();
+ return activity == null ? InputConsumer.NO_OP
+ : new OverviewInputConsumer(mGestureState, activity, null, true);
+ }
+
+ private void endRunningWindowAnim(boolean cancel) {
+ if (mRunningWindowAnim != null) {
+ if (cancel) {
+ mRunningWindowAnim.cancel();
+ } else {
+ mRunningWindowAnim.end();
+ }
+ }
+ }
+
+ private void onSettledOnEndTarget() {
+ switch (mGestureState.getEndTarget()) {
+ case HOME:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
+ // Notify swipe-to-home (recents animation) is finished
+ SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ break;
+ case RECENTS:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+ | STATE_SCREENSHOT_VIEW_SHOWN);
+ break;
+ case NEW_TASK:
+ mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
+ break;
+ case LAST_TASK:
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ break;
+ }
+ ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
+ }
+
+ /** @return Whether this was the task we were waiting to appear, and thus handled it. */
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+ return false;
+ }
+ if (mStateCallback.hasStates(STATE_START_NEW_TASK)
+ && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
+ reset();
+ return true;
+ }
+ return false;
+ }
+
+ private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
+ boolean isCancel) {
+ final GestureEndTarget endTarget;
+ final boolean goingToNewTask;
+ if (mRecentsView != null) {
+ if (!hasTargets()) {
+ // If there are no running tasks, then we can assume that this is a continuation of
+ // the last gesture, but after the recents animation has finished
+ goingToNewTask = true;
+ } else {
+ final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+ final int taskToLaunch = mRecentsView.getNextPage();
+ goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
+ }
+ } else {
+ goingToNewTask = false;
+ }
+ final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
+ if (!isFling) {
+ if (isCancel) {
+ endTarget = LAST_TASK;
+ } else if (mDeviceState.isFullyGesturalNavMode()) {
+ if (mIsShelfPeeking) {
+ endTarget = RECENTS;
+ } else if (goingToNewTask) {
+ endTarget = NEW_TASK;
+ } else {
+ endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
+ }
+ } else {
+ endTarget = reachedOverviewThreshold && mGestureStarted
+ ? RECENTS
+ : goingToNewTask
+ ? NEW_TASK
+ : LAST_TASK;
+ }
+ } else {
+ // If swiping at a diagonal, base end target on the faster velocity.
+ boolean isSwipeUp = endVelocity < 0;
+ boolean willGoToNewTaskOnSwipeUp =
+ goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
+
+ if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
+ endTarget = HOME;
+ } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
+ // If swiping at a diagonal, base end target on the faster velocity.
+ endTarget = NEW_TASK;
+ } else if (isSwipeUp) {
+ endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
+ ? NEW_TASK : RECENTS;
+ } else {
+ endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
+ }
+ }
+
+ if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
+ return LAST_TASK;
+ }
+ return endTarget;
+ }
+
+ @UiThread
+ private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
+ boolean isCancel) {
+ PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
+ long duration = MAX_SWIPE_DURATION;
+ float currentShift = mCurrentShift.value;
+ final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
+ isFling, isCancel);
+ float endShift = endTarget.isLauncher ? 1 : 0;
+ final float startShift;
+ Interpolator interpolator = DEACCEL;
+ if (!isFling) {
+ long expectedDuration = Math.abs(Math.round((endShift - currentShift)
+ * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
+ duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
+ startShift = currentShift;
+ interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
+ } else {
+ startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
+ * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
+ float minFlingVelocity = mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
+ if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
+ Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
+ startShift, endShift, endShift, endVelocity / 1000,
+ mTransitionDragLength, mContext);
+ endShift = overshoot.end;
+ interpolator = overshoot.interpolator;
+ duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
+ MAX_SWIPE_DURATION);
+ } else {
+ float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
+
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 2.
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
+ duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+
+ if (endTarget == RECENTS) {
+ interpolator = OVERSHOOT_1_2;
+ }
+ }
+ }
+ }
+
+ if (endTarget.isLauncher) {
+ mInputConsumerProxy.enable();
+ }
+ if (endTarget == HOME) {
+ setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+ duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
+ } else if (endTarget == RECENTS) {
+ LiveTileOverlay.INSTANCE.startIconAnimation();
+ if (mRecentsView != null) {
+ int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
+ if (mRecentsView.getNextPage() != nearestPage) {
+ // We shouldn't really scroll to the next page when swiping up to recents.
+ // Only allow settling on the next page if it's nearest to the center.
+ mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
+ }
+ if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
+ mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+ }
+ duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ }
+ if (mDeviceState.isFullyGesturalNavMode()) {
+ setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
+ }
+ }
+
+ // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
+ // or resumeLastTask().
+ if (mRecentsView != null) {
+ mRecentsView.setOnPageTransitionEndCallback(
+ () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
+ } else {
+ mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
+ }
+
+ animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
+ }
+
+ private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+ StatsLogManager.EventEnum event;
+ switch (endTarget) {
+ case HOME:
+ event = LAUNCHER_HOME_GESTURE;
+ break;
+ case RECENTS:
+ event = LAUNCHER_OVERVIEW_GESTURE;
+ break;
+ case LAST_TASK:
+ case NEW_TASK:
+ event = (mLogDirection == Direction.LEFT)
+ ? LAUNCHER_QUICKSWITCH_LEFT
+ : LAUNCHER_QUICKSWITCH_RIGHT;
+ break;
+ default:
+ event = IGNORE;
+ }
+ StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
+ .withSrcState(LAUNCHER_STATE_BACKGROUND)
+ .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
+ if (targetTask != null) {
+ logger.withItemInfo(targetTask.getItemInfo());
+ }
+ logger.log(event);
+
+
+ DeviceProfile dp = mDp;
+ if (dp == null || mDownPos == null) {
+ // We probably never received an animation controller, skip logging.
+ return;
+ }
+ int pageIndex = endTarget == LAST_TASK
+ ? LOG_NO_OP_PAGE_INDEX
+ : mRecentsView.getNextPage();
+ UserEventDispatcher.newInstance(mContext).logStateChangeAction(
+ mLogAction, mLogDirection,
+ (int) mDownPos.x, (int) mDownPos.y,
+ ContainerType.NAVBAR, ContainerType.APP,
+ endTarget.containerType,
+ pageIndex);
+ }
+
+ /** Animates to the given progress, where 0 is the current app and 1 is overview. */
+ @UiThread
+ private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
+ GestureEndTarget target, PointF velocityPxPerMs) {
+ runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
+ interpolator, target, velocityPxPerMs));
+ }
+
+ protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
+
+ private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (task.taskId == mGestureState.getRunningTaskId()
+ && TaskInfoCompat.getActivityType(task) != ACTIVITY_TYPE_HOME) {
+ // Since this is an edge case, just cancel and relaunch with default activity
+ // options (since we don't know if there's an associated app icon to launch from)
+ endRunningWindowAnim(true /* cancel */);
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+ mActivityRestartListener);
+ ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
+ }
+ }
+ };
+
+ @UiThread
+ private void animateToProgressInternal(float start, float end, long duration,
+ Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
+ // Set the state, but don't notify until the animation completes
+ mGestureState.setEndTarget(target, false /* isAtomic */);
+ maybeUpdateRecentsAttachedState();
+
+ // If we are transitioning to launcher, then listen for the activity to be restarted while
+ // the transition is in progress
+ if (mGestureState.getEndTarget().isLauncher) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(
+ mActivityRestartListener);
+ }
+
+ if (mGestureState.getEndTarget() == HOME) {
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case, we can
+ // skip doing any future work here for the current gesture.
+ return;
+ }
+ // Finalize the state and notify of the change
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+ }
+ });
+ getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
+ windowAnim.start(mContext, velocityPxPerMs);
+ homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
+ mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ mLauncherTransitionController = null;
+ } else {
+ ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
+ windowAnim.setDuration(duration).setInterpolator(interpolator);
+ windowAnim.addUpdateListener(valueAnimator -> {
+ computeRecentsScrollIfInvisible();
+ });
+ windowAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case, we can
+ // skip doing any future work here for the current gesture.
+ return;
+ }
+ if (mRecentsView != null) {
+ int taskToLaunch = mRecentsView.getNextPage();
+ int runningTask = getLastAppearedTaskIndex();
+ boolean hasStartedNewTask = hasStartedNewTask();
+ if (target == NEW_TASK && taskToLaunch == runningTask
+ && !hasStartedNewTask) {
+ // We are about to launch the current running task, so use LAST_TASK
+ // state instead of NEW_TASK. This could happen, for example, if our
+ // scroll is aborted after we determined the target to be NEW_TASK.
+ mGestureState.setEndTarget(LAST_TASK);
+ } else if (target == LAST_TASK && hasStartedNewTask) {
+ // We are about to re-launch the previously running task, but we can't
+ // just finish the controller like we normally would because that would
+ // instead resume the last task that appeared, and not ensure that this
+ // task is restored to the top. To address this, re-launch the task as
+ // if it were a new task.
+ mGestureState.setEndTarget(NEW_TASK);
+ }
+ }
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+ }
+ });
+ windowAnim.start();
+ mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ }
+ // Always play the entire launcher animation when going home, since it is separate from
+ // the animation that has been controlled thus far.
+ if (mGestureState.getEndTarget() == HOME) {
+ start = 0;
+ }
+
+ // We want to use the same interpolator as the window, but need to adjust it to
+ // interpolate over the remaining progress (end - start).
+ TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
+ interpolator, start, end);
+ if (mLauncherTransitionController == null) {
+ return;
+ }
+ if (start == end || duration <= 0) {
+ mLauncherTransitionController.dispatchSetInterpolator(t -> end);
+ } else {
+ mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
+ }
+ mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
+
+ if (UNSTABLE_SPRINGS.get()) {
+ mLauncherTransitionController.dispatchOnStart();
+ }
+ mLauncherTransitionController.getAnimationPlayer().start();
+ mHasLauncherTransitionControllerStarted = true;
+ }
+
+ private void computeRecentsScrollIfInvisible() {
+ if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
+ // Views typically don't compute scroll when invisible as an optimization,
+ // but in our case we need to since the window offset depends on the scroll.
+ mRecentsView.computeScroll();
+ }
+ }
+
+ private void continueComputingRecentsScrollIfNecessary() {
+ if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
+ && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
+ && !mCanceled) {
+ computeRecentsScrollIfInvisible();
+ mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
+ }
+ }
+
+ /**
+ * Creates an animation that transforms the current app window into the home app.
+ * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+ * @param homeAnimationFactory The home animation factory.
+ */
+ @Override
+ protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ HomeAnimationFactory homeAnimationFactory) {
+ RectFSpringAnim anim =
+ super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
+ anim.addOnUpdateListener((r, p) -> {
+ updateSysUiFlags(Math.max(p, mCurrentShift.value));
+ });
+ anim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mActivity != null) {
+ removeLiveTileOverlay();
+ }
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mRecentsView != null) {
+ mRecentsView.post(mRecentsView::resetTaskVisuals);
+ }
+ // Make sure recents is in its final state
+ maybeUpdateRecentsAttachedState(false);
+ mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
+ }
+ });
+ if (mRecentsAnimationTargets != null) {
+ mRecentsAnimationTargets.addReleaseCheck(anim);
+ }
+ return anim;
+ }
+
+ public void onConsumerAboutToBeSwitched() {
+ if (mActivity != null) {
+ // In the off chance that the gesture ends before Launcher is started, we should clear
+ // the callback here so that it doesn't update with the wrong state
+ mActivity.clearRunOnceOnStartCallback();
+ resetLauncherListenersAndOverlays();
+ }
+ if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
+ cancelCurrentAnimation();
+ } else {
+ reset();
+ }
+ }
+
+ public boolean isCanceled() {
+ return mCanceled;
+ }
+
+ @UiThread
+ private void resumeLastTask() {
+ mRecentsAnimationController.finish(false /* toRecents */, null);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ doLogGesture(LAST_TASK, null);
+ reset();
+ }
+
+ @UiThread
+ private void startNewTask() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
+ } else {
+ startNewTaskInternal();
+ }
+ }
+
+ @UiThread
+ private void startNewTaskInternal() {
+ TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
+ startNewTask(success -> {
+ if (!success) {
+ reset();
+ // We couldn't launch the task, so take user to overview so they can
+ // decide what to do instead of staying in this broken state.
+ endLauncherTransitionController();
+ updateSysUiFlags(1 /* windowProgress == overview */);
+ }
+ doLogGesture(NEW_TASK, taskToLaunch);
+ });
+ }
+
+ /**
+ * Called when we successfully startNewTask() on the task that was previously running. Normally
+ * we call resumeLastTask() when returning to the previously running task, but this handles a
+ * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+ * start A again to ensure it stays on top.
+ */
+ @androidx.annotation.CallSuper
+ protected void onRestartPreviouslyAppearedTask() {
+ // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+ // appeared.
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.finish(false, null);
+ }
+ reset();
+ }
+
+ private void reset() {
+ mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ }
+
+ /**
+ * Cancels any running animation so that the active target can be overriden by a new swipe
+ * handle (in case of quick switch).
+ */
+ private void cancelCurrentAnimation() {
+ mCanceled = true;
+ mCurrentShift.cancelAnimation();
+ if (mLauncherTransitionController != null && mLauncherTransitionController
+ .getAnimationPlayer().isStarted()) {
+ mLauncherTransitionController.getAnimationPlayer().cancel();
+ }
+ }
+
+ private void invalidateHandler() {
+ mInputConsumerProxy.destroy();
+ endRunningWindowAnim(false /* cancel */);
+
+ if (mGestureEndCallback != null) {
+ mGestureEndCallback.run();
+ }
+
+ mActivityInitListener.unregister();
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
+ mTaskSnapshot = null;
+ }
+
+ private void invalidateHandlerWithLauncher() {
+ endLauncherTransitionController();
+
+ mRecentsView.onGestureAnimationEnd();
+ resetLauncherListenersAndOverlays();
+ }
+
+ private void endLauncherTransitionController() {
+ setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+ if (mLauncherTransitionController != null) {
+ mLauncherTransitionController.getAnimationPlayer().end();
+ mLauncherTransitionController = null;
+ }
+ }
+
+ private void resetLauncherListenersAndOverlays() {
+ // Reset the callback for deferred activity launches
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mActivityInterface.setOnDeferredActivityLaunchCallback(null);
+ }
+ mActivity.getRootView().setOnApplyWindowInsetsListener(null);
+ removeLiveTileOverlay();
+ }
+
+ private void notifyTransitionCancelled() {
+ mAnimationFactory.onTransitionCancelled();
+ }
+
+ private void resetStateForAnimationCancel() {
+ boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
+ mActivityInterface.onTransitionCancelled(wasVisible);
+
+ // Leave the pending invisible flag, as it may be used by wallpaper open animation.
+ mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ }
+
+ protected void switchToScreenshot() {
+ final int runningTaskId = mGestureState.getRunningTaskId();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.getController().setWillFinishToHome(true);
+ // Update the screenshot of the task
+ if (mTaskSnapshot == null) {
+ mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
+ }
+ mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
+ }
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else if (!hasTargets()) {
+ // If there are no targets, then we don't need to capture anything
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else {
+ boolean finishTransitionPosted = false;
+ if (mRecentsAnimationController != null) {
+ // Update the screenshot of the task
+ if (mTaskSnapshot == null) {
+ mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
+ }
+ final TaskView taskView;
+ if (mGestureState.getEndTarget() == HOME) {
+ // Capture the screenshot before finishing the transition to home to ensure it's
+ // taken in the correct orientation, but no need to update the thumbnail.
+ taskView = null;
+ } else {
+ taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
+ }
+ if (taskView != null && !mCanceled) {
+ // Defer finishing the animation until the next launcher frame with the
+ // new thumbnail
+ finishTransitionPosted = ViewUtils.postDraw(taskView,
+ () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
+ this::isCanceled);
+ }
+ }
+ if (!finishTransitionPosted) {
+ // If we haven't posted a draw callback, set the state immediately.
+ Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
+ TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ TraceHelper.INSTANCE.endSection(traceToken);
+ }
+ }
+ }
+
+ private void finishCurrentTransitionToRecents() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else {
+ mRecentsAnimationController.finish(true /* toRecents */,
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+ }
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+ }
+
+ private void finishCurrentTransitionToHome() {
+ if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else {
+ finishRecentsControllerToHome(
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+ }
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+ doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
+ }
+
+ protected abstract void finishRecentsControllerToHome(Runnable callback);
+
+ private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
+ endLauncherTransitionController();
+ mActivityInterface.onSwipeUpToRecentsComplete();
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
+ true /* screenshot */);
+ }
+ mRecentsView.onSwipeUpAnimationSuccess();
+
+ SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
+ doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
+ reset();
+ }
+
+ private void addLiveTileOverlay() {
+ if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
+ mRecentsView.setLiveTileOverlayAttached(true);
+ }
+ }
+
+ private void removeLiveTileOverlay() {
+ LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
+ mRecentsView.setLiveTileOverlayAttached(false);
+ }
+
+ private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
+ return app.isNotInRecents
+ || app.activityType == ACTIVITY_TYPE_HOME;
}
/**
@@ -117,8 +1452,6 @@
mGestureEndCallback = gestureEndCallback;
}
- public abstract Intent getLaunchIntent();
-
protected void linkRecentsViewScroll() {
SurfaceTransactionApplier.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
@@ -175,21 +1508,6 @@
}
/**
- * Called when we successfully startNewTask() on the task that was previously running. Normally
- * we call resumeLastTask() when returning to the previously running task, but this handles a
- * specific edge case: if we switch from A to B, and back to A before B appears, we need to
- * start A again to ensure it stays on top.
- */
- @CallSuper
- protected void onRestartPreviouslyAppearedTask() {
- // Finish the controller here, since we won't get onTaskAppeared() for a task that already
- // appeared.
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.finish(false, null);
- }
- }
-
- /**
* Runs the given {@param action} if the recents animation has already started, or queues it to
* be run when it is next started.
*/
@@ -210,55 +1528,6 @@
}
@Override
- public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
- RecentsAnimationTargets targets) {
- mRecentsAnimationController = recentsAnimationController;
- mRecentsAnimationTargets = targets;
- mTransformParams.setTargetSet(mRecentsAnimationTargets);
- RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
- mGestureState.getRunningTaskId());
-
- if (runningTaskTarget != null) {
- mTaskViewSimulator.setPreview(runningTaskTarget);
- }
-
- // Only initialize the device profile, if it has not been initialized before, as in some
- // configurations targets.homeContentInsets may not be correct.
- if (mActivity == null) {
- DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
- Rect overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
- dp = dp.getMultiWindowProfile(mContext,
- new WindowBounds(overviewStackBounds, targets.homeContentInsets));
- } else {
- // If we are not in multi-window mode, home insets should be same as system insets.
- dp = dp.copy(mContext);
- }
- dp.updateInsets(targets.homeContentInsets);
- dp.updateIsSeascape(mContext);
- initTransitionEndpoints(dp);
- }
-
- // Notify when the animation starts
- if (!mRecentsAnimationStartCallbacks.isEmpty()) {
- for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
- action.run();
- }
- mRecentsAnimationStartCallbacks.clear();
- }
- }
-
- @Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- mRecentsAnimationController = null;
- mRecentsAnimationTargets = null;
- if (mRecentsView != null) {
- mRecentsView.setRecentsAnimationTargets(null, null);
- }
- }
-
- @Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
@@ -279,9 +1548,6 @@
}
}
- /** @return Whether this was the task we were waiting to appear, and thus handled it. */
- protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
-
/**
* @return The index of the TaskView in RecentsView whose taskId matches the task that will
* resume if we finish the controller.
@@ -301,55 +1567,6 @@
}
/**
- * Return true if the window should be translated horizontally if the recents view scrolls
- */
- protected abstract boolean moveWindowWithRecentsScroll();
-
- protected boolean onActivityInit(Boolean alreadyOnHome) {
- T createdActivity = mActivityInterface.getCreatedActivity();
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
- }
- if (createdActivity != null) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
- }
- initTransitionEndpoints(createdActivity.getDeviceProfile());
- }
- return true;
- }
-
- /**
- * Called to create a input proxy for the running task
- */
- @UiThread
- protected abstract InputConsumer createNewInputProxyHandler();
-
- /**
- * Called when the value of {@link #mCurrentShift} changes
- */
- @UiThread
- public abstract void updateFinalShift();
-
- /**
- * Called when motion pause is detected
- */
- public abstract void onMotionPauseChanged(boolean isPaused);
-
- @UiThread
- public void onGestureStarted(boolean isLikelyToStartNewTask) { }
-
- @UiThread
- public abstract void onGestureCancelled();
-
- @UiThread
- public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
-
- public abstract void onConsumerAboutToBeSwitched();
-
- public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
-
- /**
* Registers a callback to run when the activity is ready.
* @param intent The intent that will be used to start the activity if it doesn't exist already.
*/
@@ -376,20 +1593,9 @@
}
}
- @Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
- HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
- super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
- if (mRecentsAnimationTargets != null) {
- mRecentsAnimationTargets.addReleaseCheck(anim);
- }
- return anim;
- }
-
public interface Factory {
- AbsSwipeUpHandler newHandler(
+ AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
deleted file mode 100644
index 434fed2..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ /dev/null
@@ -1,1330 +0,0 @@
-/*
- * Copyright (C) 2018 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.quickstep;
-
-import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
-import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
-import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
-import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
-import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
-import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
-import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-
-import android.animation.Animator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.os.Build;
-import android.os.SystemClock;
-import android.view.View;
-import android.view.View.OnApplyWindowInsetsListener;
-import android.view.ViewTreeObserver.OnDrawListener;
-import android.view.WindowInsets;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.BaseActivityInterface.AnimationFactory;
-import com.android.quickstep.GestureState.GestureEndTarget;
-import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.views.LiveTileOverlay;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TaskInfoCompat;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-
-/**
- * Handles the navigation gestures when Launcher is the default home activity.
- * TODO: Merge this with BaseSwipeUpHandler
- */
-@TargetApi(Build.VERSION_CODES.O)
-public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
- extends AbsSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
- private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
-
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
-
- private static int getFlagForIndex(int index, String name) {
- if (DEBUG_STATES) {
- STATE_NAMES[index] = name;
- }
- return 1 << index;
- }
-
- // Launcher UI related states
- protected static final int STATE_LAUNCHER_PRESENT =
- getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
- protected static final int STATE_LAUNCHER_STARTED =
- getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
- protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
-
- // Internal initialization states
- private static final int STATE_APP_CONTROLLER_RECEIVED =
- getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
-
- // Interaction finish states
- private static final int STATE_SCALED_CONTROLLER_HOME =
- getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
- private static final int STATE_SCALED_CONTROLLER_RECENTS =
- getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
-
- protected static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
- private static final int STATE_GESTURE_STARTED =
- getFlagForIndex(7, "STATE_GESTURE_STARTED");
- private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
- private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
-
- private static final int STATE_CAPTURE_SCREENSHOT =
- getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
- protected static final int STATE_SCREENSHOT_CAPTURED =
- getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
- private static final int STATE_SCREENSHOT_VIEW_SHOWN =
- getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
-
- private static final int STATE_RESUME_LAST_TASK =
- getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
- private static final int STATE_START_NEW_TASK =
- getFlagForIndex(14, "STATE_START_NEW_TASK");
- private static final int STATE_CURRENT_TASK_FINISHED =
- getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
-
- private static final int LAUNCHER_UI_STATES =
- STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
-
- public static final long MAX_SWIPE_DURATION = 350;
- public static final long MIN_OVERSHOOT_DURATION = 120;
-
- public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
- private static final float SWIPE_DURATION_MULTIPLIER =
- Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
- private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
-
- public static final long RECENTS_ATTACH_DURATION = 300;
-
- /**
- * Used as the page index for logging when we return to the last task at the end of the gesture.
- */
- private static final int LOG_NO_OP_PAGE_INDEX = -1;
-
- protected final TaskAnimationManager mTaskAnimationManager;
-
- // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
- private RunningWindowAnim mRunningWindowAnim;
- private boolean mIsShelfPeeking;
-
- private boolean mContinuingLastGesture;
-
- private ThumbnailData mTaskSnapshot;
-
- // Used to control launcher components throughout the swipe gesture.
- private AnimatorPlaybackController mLauncherTransitionController;
- private boolean mHasLauncherTransitionControllerStarted;
-
- private AnimationFactory mAnimationFactory = (t) -> { };
-
- private boolean mWasLauncherAlreadyVisible;
-
- private boolean mPassedOverviewThreshold;
- private boolean mGestureStarted;
- private int mLogAction = Touch.SWIPE;
- private int mLogDirection = Direction.UP;
- private PointF mDownPos;
- private boolean mIsLikelyToStartNewTask;
-
- private final long mTouchTimeMs;
- private long mLauncherFrameDrawnTime;
-
- private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
-
- public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
- TaskAnimationManager taskAnimationManager, GestureState gestureState,
- long touchTimeMs, boolean continuingLastGesture,
- InputConsumerController inputConsumer) {
- super(context, deviceState, gestureState, inputConsumer);
- mTaskAnimationManager = taskAnimationManager;
- mTouchTimeMs = touchTimeMs;
- mContinuingLastGesture = continuingLastGesture;
-
- initAfterSubclassConstructor();
- initStateCallbacks();
- }
-
- private void initStateCallbacks() {
- mStateCallback = new MultiStateCallback(STATE_NAMES);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
- this::onLauncherPresentAndGestureStarted);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
- this::initializeLauncherAnimationController);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
- this::launcherFrameDrawn);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
- | STATE_GESTURE_CANCELLED,
- this::resetStateForAnimationCancel);
-
- mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
- this::resumeLastTask);
- mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
- this::startNewTask);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
- this::switchToScreenshot);
-
- mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
- | STATE_SCALED_CONTROLLER_RECENTS,
- this::finishCurrentTransitionToRecents);
-
- mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
- | STATE_SCALED_CONTROLLER_HOME,
- this::finishCurrentTransitionToHome);
- mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
- this::reset);
-
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
- | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
- | STATE_GESTURE_STARTED,
- this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
-
- mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
- this::continueComputingRecentsScrollIfNecessary);
- mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
- | STATE_RECENTS_SCROLLING_FINISHED,
- this::onSettledOnEndTarget);
-
- mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
- mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
- this::invalidateHandlerWithLauncher);
- mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
- this::notifyTransitionCancelled);
-
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
- | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
- (b) -> mRecentsView.setRunningTaskHidden(!b));
- }
- }
-
- @Override
- protected boolean onActivityInit(Boolean alreadyOnHome) {
- super.onActivityInit(alreadyOnHome);
- final T activity = mActivityInterface.getCreatedActivity();
- if (mActivity == activity) {
- return true;
- }
-
- if (mActivity != null) {
- // The launcher may have been recreated as a result of device rotation.
- int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
- initStateCallbacks();
- mStateCallback.setState(oldState);
- }
- mWasLauncherAlreadyVisible = alreadyOnHome;
- mActivity = activity;
- // Override the visibility of the activity until the gesture actually starts and we swipe
- // up, or until we transition home and the home animation is composed
- if (alreadyOnHome) {
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
- } else {
- mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
- }
-
- mRecentsView = activity.getOverviewPanel();
- mRecentsView.setOnPageTransitionEndCallback(null);
- addLiveTileOverlay();
-
- mStateCallback.setState(STATE_LAUNCHER_PRESENT);
- if (alreadyOnHome) {
- onLauncherStart();
- } else {
- activity.runOnceOnStart(this::onLauncherStart);
- }
-
- setupRecentsViewUi();
- linkRecentsViewScroll();
-
- return true;
- }
-
- @Override
- protected boolean moveWindowWithRecentsScroll() {
- return mGestureState.getEndTarget() != HOME;
- }
-
- private void onLauncherStart() {
- final T activity = mActivityInterface.getCreatedActivity();
- if (mActivity != activity) {
- return;
- }
- if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
- return;
- }
- mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation());
-
- // If we've already ended the gesture and are going home, don't prepare recents UI,
- // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
- if (mGestureState.getEndTarget() != HOME) {
- Runnable initAnimFactory = () -> {
- mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
- mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
- maybeUpdateRecentsAttachedState(false /* animate */);
- };
- if (mWasLauncherAlreadyVisible) {
- // Launcher is visible, but might be about to stop. Thus, if we prepare recents
- // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
- // wait until the next gesture (and possibly launcher) starts.
- mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
- } else {
- initAnimFactory.run();
- }
- }
- AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
- AbstractFloatingView.TYPE_LISTENER);
-
- if (mWasLauncherAlreadyVisible) {
- mStateCallback.setState(STATE_LAUNCHER_DRAWN);
- } else {
- Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
- View dragLayer = activity.getDragLayer();
- dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
- boolean mHandled = false;
-
- @Override
- public void onDraw() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- TraceHelper.INSTANCE.endSection(traceToken);
- dragLayer.post(() ->
- dragLayer.getViewTreeObserver().removeOnDrawListener(this));
- if (activity != mActivity) {
- return;
- }
-
- mStateCallback.setState(STATE_LAUNCHER_DRAWN);
- }
- });
- }
-
- activity.getRootView().setOnApplyWindowInsetsListener(this);
- mStateCallback.setState(STATE_LAUNCHER_STARTED);
- }
-
- private void onLauncherPresentAndGestureStarted() {
- // Re-setup the recents UI when gesture starts, as the state could have been changed during
- // that time by a previous window transition.
- setupRecentsViewUi();
-
- // For the duration of the gesture, in cases where an activity is launched while the
- // activity is not yet resumed, finish the animation to ensure we get resumed
- mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
- mOnDeferredActivityLaunch);
-
- mGestureState.runOnceAtState(STATE_END_TARGET_SET,
- () -> mDeviceState.getRotationTouchHelper().
- onEndTargetCalculated(mGestureState.getEndTarget(),
- mActivityInterface));
-
- notifyGestureStartedAsync();
- }
-
- private void onDeferredActivityLaunch() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mActivityInterface.switchRunningTaskViewToScreenshot(
- null, () -> {
- mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
- });
- } else {
- mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
- }
- }
-
- private void setupRecentsViewUi() {
- if (mContinuingLastGesture) {
- updateSysUiFlags(mCurrentShift.value);
- return;
- }
- notifyGestureAnimationStartToRecents();
- }
-
- protected void notifyGestureAnimationStartToRecents() {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
- }
-
- private void launcherFrameDrawn() {
- mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
- }
-
- private void initializeLauncherAnimationController() {
- buildAnimationController();
-
- Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
- TraceHelper.FLAG_IGNORE_BINDERS);
- // Only used in debug builds
- if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents(
- (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
- }
- TraceHelper.INSTANCE.endSection(traceToken);
-
- // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
- // high-res thumbnail loader here once we are sure that we will end up in an overview state
- RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
- .getHighResLoadingState().setVisible(true);
- }
-
- @Override
- public void onMotionPauseChanged(boolean isPaused) {
- setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
- }
-
- public void maybeUpdateRecentsAttachedState() {
- maybeUpdateRecentsAttachedState(true /* animate */);
- }
-
- /**
- * Determines whether to show or hide RecentsView. The window is always
- * synchronized with its corresponding TaskView in RecentsView, so if
- * RecentsView is shown, it will appear to be attached to the window.
- *
- * Note this method has no effect unless the navigation mode is NO_BUTTON.
- */
- private void maybeUpdateRecentsAttachedState(boolean animate) {
- if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
- return;
- }
- RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
- ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
- : null;
- final boolean recentsAttachedToAppWindow;
- if (mGestureState.getEndTarget() != null) {
- recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
- } else if (mContinuingLastGesture
- && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
- recentsAttachedToAppWindow = true;
- } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
- // The window is going away so make sure recents is always visible in this case.
- recentsAttachedToAppWindow = true;
- } else {
- recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
- }
- mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
- }
-
- @Override
- public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
- setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
- }
-
- private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
- if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
- mIsLikelyToStartNewTask = isLikelyToStartNewTask;
- maybeUpdateRecentsAttachedState(animate);
- }
- }
-
- @UiThread
- public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
- mAnimationFactory.setShelfState(shelfState, interpolator, duration);
- boolean wasShelfPeeking = mIsShelfPeeking;
- mIsShelfPeeking = shelfState == PEEK;
- if (mIsShelfPeeking != wasShelfPeeking) {
- maybeUpdateRecentsAttachedState();
- }
- if (shelfState.shouldPreformHaptic) {
- performHapticFeedback();
- }
- }
-
- private void buildAnimationController() {
- if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
- return;
- }
- initTransitionEndpoints(mActivity.getDeviceProfile());
- mAnimationFactory.createActivityInterface(mTransitionDragLength);
- }
-
- /**
- * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
- * (it has its own animation) or if we're already animating the current controller.
- * @return Whether we can create the launcher controller or update its progress.
- */
- private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
- return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
- WindowInsets result = view.onApplyWindowInsets(windowInsets);
- buildAnimationController();
- return result;
- }
-
- private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
- mLauncherTransitionController = anim;
- mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
- mLauncherTransitionController.dispatchOnStart();
- updateLauncherTransitionProgress();
- }
-
- @Override
- public Intent getLaunchIntent() {
- return mGestureState.getOverviewIntent();
- }
-
- @Override
- public void updateFinalShift() {
- final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
- if (passed != mPassedOverviewThreshold) {
- mPassedOverviewThreshold = passed;
- if (!mDeviceState.isFullyGesturalNavMode()) {
- performHapticFeedback();
- }
- }
-
- updateSysUiFlags(mCurrentShift.value);
- applyWindowTransform();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsAnimationTargets != null) {
- LiveTileOverlay.INSTANCE.update(
- mTaskViewSimulator.getCurrentRect(),
- mTaskViewSimulator.getCurrentCornerRadius());
- }
- }
-
- updateLauncherTransitionProgress();
- }
-
- private void updateLauncherTransitionProgress() {
- if (mLauncherTransitionController == null
- || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
- return;
- }
- // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
- // anyway. The controller mimics the drag length factor by applying it to its interpolators.
- float progress = mCurrentShift.value / mDragLengthFactor;
- mLauncherTransitionController.setPlayFraction(progress);
- }
-
- /**
- * @param windowProgress 0 == app, 1 == overview
- */
- private void updateSysUiFlags(float windowProgress) {
- if (mRecentsAnimationController != null && mRecentsView != null) {
- TaskView runningTask = mRecentsView.getRunningTaskView();
- TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
- int centermostTaskFlags = centermostTask == null ? 0
- : centermostTask.getThumbnail().getSysUiStatusNavFlags();
- boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
- boolean quickswitchThresholdPassed = centermostTask != runningTask;
-
- // We will handle the sysui flags based on the centermost task view.
- mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
- || (quickswitchThresholdPassed && centermostTaskFlags != 0));
- mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
-
- int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
- mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
- }
- }
-
- @Override
- public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets) {
- ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
- super.onRecentsAnimationStart(controller, targets);
-
- // Only add the callback to enable the input consumer after we actually have the controller
- mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
- mRecentsAnimationController::enableInputConsumer);
- mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
-
- mPassedOverviewThreshold = false;
- }
-
- @Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
- mActivityInitListener.unregister();
- mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-
- // Defer clearing the controller and the targets until after we've updated the state
- super.onRecentsAnimationCanceled(thumbnailData);
- }
-
- @Override
- public void onGestureStarted(boolean isLikelyToStartNewTask) {
- notifyGestureStartedAsync();
- setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
- mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
- mGestureStarted = true;
- }
-
- /**
- * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
- */
- @UiThread
- private void notifyGestureStartedAsync() {
- final T curActivity = mActivity;
- if (curActivity != null) {
- // Once the gesture starts, we can no longer transition home through the button, so
- // reset the force override of the activity visibility
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
- }
- }
-
- /**
- * Called as a result on ACTION_CANCEL to return the UI to the start state.
- */
- @Override
- public void onGestureCancelled() {
- updateDisplacement(0);
- mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
- mLogAction = Touch.SWIPE_NOOP;
- handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
- }
-
- /**
- * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
- * @param velocity The x and y components of the velocity when the gesture ends.
- * @param downPos The x and y value of where the gesture started.
- */
- @Override
- public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
- float flingThreshold = mContext.getResources()
- .getDimension(R.dimen.quickstep_fling_threshold_velocity);
- boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
- mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
-
- mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
- boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
- if (isVelocityVertical) {
- mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
- } else {
- mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
- }
- mDownPos = downPos;
- handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
- }
-
- @Override
- protected InputConsumer createNewInputProxyHandler() {
- endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
- endLauncherTransitionController();
-
- StatefulActivity activity = mActivityInterface.getCreatedActivity();
- return activity == null ? InputConsumer.NO_OP
- : new OverviewInputConsumer(mGestureState, activity, null, true);
- }
-
- private void endRunningWindowAnim(boolean cancel) {
- if (mRunningWindowAnim != null) {
- if (cancel) {
- mRunningWindowAnim.cancel();
- } else {
- mRunningWindowAnim.end();
- }
- }
- }
-
- private void onSettledOnEndTarget() {
- switch (mGestureState.getEndTarget()) {
- case HOME:
- mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
- // Notify swipe-to-home (recents animation) is finished
- SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
- break;
- case RECENTS:
- mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
- | STATE_SCREENSHOT_VIEW_SHOWN);
- break;
- case NEW_TASK:
- mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
- break;
- case LAST_TASK:
- mStateCallback.setState(STATE_RESUME_LAST_TASK);
- break;
- }
- ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
- }
-
- @Override
- protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
- if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
- return false;
- }
- if (mStateCallback.hasStates(STATE_START_NEW_TASK)
- && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
- reset();
- return true;
- }
- return false;
- }
-
- private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
- boolean isCancel) {
- final GestureEndTarget endTarget;
- final boolean goingToNewTask;
- if (mRecentsView != null) {
- if (!hasTargets()) {
- // If there are no running tasks, then we can assume that this is a continuation of
- // the last gesture, but after the recents animation has finished
- goingToNewTask = true;
- } else {
- final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- final int taskToLaunch = mRecentsView.getNextPage();
- goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
- }
- } else {
- goingToNewTask = false;
- }
- final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
- if (!isFling) {
- if (isCancel) {
- endTarget = LAST_TASK;
- } else if (mDeviceState.isFullyGesturalNavMode()) {
- if (mIsShelfPeeking) {
- endTarget = RECENTS;
- } else if (goingToNewTask) {
- endTarget = NEW_TASK;
- } else {
- endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
- }
- } else {
- endTarget = reachedOverviewThreshold && mGestureStarted
- ? RECENTS
- : goingToNewTask
- ? NEW_TASK
- : LAST_TASK;
- }
- } else {
- // If swiping at a diagonal, base end target on the faster velocity.
- boolean isSwipeUp = endVelocity < 0;
- boolean willGoToNewTaskOnSwipeUp =
- goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
-
- if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
- endTarget = HOME;
- } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
- // If swiping at a diagonal, base end target on the faster velocity.
- endTarget = NEW_TASK;
- } else if (isSwipeUp) {
- endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
- ? NEW_TASK : RECENTS;
- } else {
- endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
- }
- }
-
- if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
- return LAST_TASK;
- }
- return endTarget;
- }
-
- @UiThread
- private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
- boolean isCancel) {
- PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
- long duration = MAX_SWIPE_DURATION;
- float currentShift = mCurrentShift.value;
- final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
- isFling, isCancel);
- float endShift = endTarget.isLauncher ? 1 : 0;
- final float startShift;
- Interpolator interpolator = DEACCEL;
- if (!isFling) {
- long expectedDuration = Math.abs(Math.round((endShift - currentShift)
- * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
- duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
- startShift = currentShift;
- interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
- } else {
- startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
- * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
- float minFlingVelocity = mContext.getResources()
- .getDimension(R.dimen.quickstep_fling_min_velocity);
- if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
- if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
- Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
- startShift, endShift, endShift, endVelocity / 1000,
- mTransitionDragLength, mContext);
- endShift = overshoot.end;
- interpolator = overshoot.interpolator;
- duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
- MAX_SWIPE_DURATION);
- } else {
- float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
-
- // we want the page's snap velocity to approximately match the velocity at
- // which the user flings, so we scale the duration by a value near to the
- // derivative of the scroll interpolator at zero, ie. 2.
- long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
- duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-
- if (endTarget == RECENTS) {
- interpolator = OVERSHOOT_1_2;
- }
- }
- }
- }
-
- if (endTarget.isLauncher) {
- mInputConsumerProxy.enable();
- }
- if (endTarget == HOME) {
- setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
- duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
- } else if (endTarget == RECENTS) {
- LiveTileOverlay.INSTANCE.startIconAnimation();
- if (mRecentsView != null) {
- int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
- if (mRecentsView.getNextPage() != nearestPage) {
- // We shouldn't really scroll to the next page when swiping up to recents.
- // Only allow settling on the next page if it's nearest to the center.
- mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
- }
- if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
- mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
- }
- duration = Math.max(duration, mRecentsView.getScroller().getDuration());
- }
- if (mDeviceState.isFullyGesturalNavMode()) {
- setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
- }
- }
-
- // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
- // or resumeLastTask().
- if (mRecentsView != null) {
- mRecentsView.setOnPageTransitionEndCallback(
- () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
- } else {
- mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
- }
-
- animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
- }
-
- private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
- StatsLogManager.EventEnum event;
- switch (endTarget) {
- case HOME:
- event = LAUNCHER_HOME_GESTURE;
- break;
- case RECENTS:
- event = LAUNCHER_OVERVIEW_GESTURE;
- break;
- case LAST_TASK:
- case NEW_TASK:
- event = (mLogDirection == Direction.LEFT)
- ? LAUNCHER_QUICKSWITCH_LEFT
- : LAUNCHER_QUICKSWITCH_RIGHT;
- break;
- default:
- event = IGNORE;
- }
- StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
- .withSrcState(LAUNCHER_STATE_BACKGROUND)
- .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
- if (targetTask != null) {
- logger.withItemInfo(targetTask.getItemInfo());
- }
- logger.log(event);
-
-
- DeviceProfile dp = mDp;
- if (dp == null || mDownPos == null) {
- // We probably never received an animation controller, skip logging.
- return;
- }
- int pageIndex = endTarget == LAST_TASK
- ? LOG_NO_OP_PAGE_INDEX
- : mRecentsView.getNextPage();
- UserEventDispatcher.newInstance(mContext).logStateChangeAction(
- mLogAction, mLogDirection,
- (int) mDownPos.x, (int) mDownPos.y,
- ContainerType.NAVBAR, ContainerType.APP,
- endTarget.containerType,
- pageIndex);
- }
-
- /** Animates to the given progress, where 0 is the current app and 1 is overview. */
- @UiThread
- private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
- GestureEndTarget target, PointF velocityPxPerMs) {
- runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
- interpolator, target, velocityPxPerMs));
- }
-
- protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
-
- private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.taskId == mGestureState.getRunningTaskId()
- && TaskInfoCompat.getActivityType(task) != ACTIVITY_TYPE_HOME) {
- // Since this is an edge case, just cancel and relaunch with default activity
- // options (since we don't know if there's an associated app icon to launch from)
- endRunningWindowAnim(true /* cancel */);
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
- mActivityRestartListener);
- ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
- }
- }
- };
-
- @UiThread
- private void animateToProgressInternal(float start, float end, long duration,
- Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
- // Set the state, but don't notify until the animation completes
- mGestureState.setEndTarget(target, false /* isAtomic */);
- maybeUpdateRecentsAttachedState();
-
- // If we are transitioning to launcher, then listen for the activity to be restarted while
- // the transition is in progress
- if (mGestureState.getEndTarget().isLauncher) {
- ActivityManagerWrapper.getInstance().registerTaskStackListener(
- mActivityRestartListener);
- }
-
- if (mGestureState.getEndTarget() == HOME) {
- HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
- RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
- windowAnim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (mRecentsAnimationController == null) {
- // If the recents animation is interrupted, we still end the running
- // animation (not canceled) so this is still called. In that case, we can
- // skip doing any future work here for the current gesture.
- return;
- }
- // Finalize the state and notify of the change
- mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
- }
- });
- getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
- windowAnim.start(mContext, velocityPxPerMs);
- homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
- mLauncherTransitionController = null;
- } else {
- ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
- windowAnim.setDuration(duration).setInterpolator(interpolator);
- windowAnim.addUpdateListener(valueAnimator -> {
- computeRecentsScrollIfInvisible();
- });
- windowAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (mRecentsAnimationController == null) {
- // If the recents animation is interrupted, we still end the running
- // animation (not canceled) so this is still called. In that case, we can
- // skip doing any future work here for the current gesture.
- return;
- }
- if (mRecentsView != null) {
- int taskToLaunch = mRecentsView.getNextPage();
- int runningTask = getLastAppearedTaskIndex();
- boolean hasStartedNewTask = hasStartedNewTask();
- if (target == NEW_TASK && taskToLaunch == runningTask
- && !hasStartedNewTask) {
- // We are about to launch the current running task, so use LAST_TASK
- // state instead of NEW_TASK. This could happen, for example, if our
- // scroll is aborted after we determined the target to be NEW_TASK.
- mGestureState.setEndTarget(LAST_TASK);
- } else if (target == LAST_TASK && hasStartedNewTask) {
- // We are about to re-launch the previously running task, but we can't
- // just finish the controller like we normally would because that would
- // instead resume the last task that appeared, and not ensure that this
- // task is restored to the top. To address this, re-launch the task as
- // if it were a new task.
- mGestureState.setEndTarget(NEW_TASK);
- }
- }
- mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
- }
- });
- windowAnim.start();
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
- }
- // Always play the entire launcher animation when going home, since it is separate from
- // the animation that has been controlled thus far.
- if (mGestureState.getEndTarget() == HOME) {
- start = 0;
- }
-
- // We want to use the same interpolator as the window, but need to adjust it to
- // interpolate over the remaining progress (end - start).
- TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
- interpolator, start, end);
- if (mLauncherTransitionController == null) {
- return;
- }
- if (start == end || duration <= 0) {
- mLauncherTransitionController.dispatchSetInterpolator(t -> end);
- } else {
- mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
- }
- mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
-
- if (UNSTABLE_SPRINGS.get()) {
- mLauncherTransitionController.dispatchOnStart();
- }
- mLauncherTransitionController.getAnimationPlayer().start();
- mHasLauncherTransitionControllerStarted = true;
- }
-
- private void computeRecentsScrollIfInvisible() {
- if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
- // Views typically don't compute scroll when invisible as an optimization,
- // but in our case we need to since the window offset depends on the scroll.
- mRecentsView.computeScroll();
- }
- }
-
- private void continueComputingRecentsScrollIfNecessary() {
- if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
- && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
- && !mCanceled) {
- computeRecentsScrollIfInvisible();
- mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
- }
- }
-
- /**
- * Creates an animation that transforms the current app window into the home app.
- * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
- * @param homeAnimationFactory The home animation factory.
- */
- @Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
- HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
- super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
- anim.addOnUpdateListener((r, p) -> {
- updateSysUiFlags(Math.max(p, mCurrentShift.value));
- });
- anim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- if (mActivity != null) {
- removeLiveTileOverlay();
- }
- }
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (mRecentsView != null) {
- mRecentsView.post(mRecentsView::resetTaskVisuals);
- }
- // Make sure recents is in its final state
- maybeUpdateRecentsAttachedState(false);
- mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
- }
- });
- return anim;
- }
-
- @Override
- public void onConsumerAboutToBeSwitched() {
- if (mActivity != null) {
- // In the off chance that the gesture ends before Launcher is started, we should clear
- // the callback here so that it doesn't update with the wrong state
- mActivity.clearRunOnceOnStartCallback();
- resetLauncherListenersAndOverlays();
- }
- if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
- cancelCurrentAnimation();
- } else {
- reset();
- }
- }
-
- public boolean isCanceled() {
- return mCanceled;
- }
-
- @UiThread
- private void resumeLastTask() {
- mRecentsAnimationController.finish(false /* toRecents */, null);
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
- doLogGesture(LAST_TASK, null);
- reset();
- }
-
- @UiThread
- private void startNewTask() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
- } else {
- startNewTaskInternal();
- }
- }
-
- @UiThread
- private void startNewTaskInternal() {
- TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
- startNewTask(success -> {
- if (!success) {
- reset();
- // We couldn't launch the task, so take user to overview so they can
- // decide what to do instead of staying in this broken state.
- endLauncherTransitionController();
- updateSysUiFlags(1 /* windowProgress == overview */);
- }
- doLogGesture(NEW_TASK, taskToLaunch);
- });
- }
-
- @Override
- protected void onRestartPreviouslyAppearedTask() {
- super.onRestartPreviouslyAppearedTask();
- reset();
- }
-
- private void reset() {
- mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
-
- /**
- * Cancels any running animation so that the active target can be overriden by a new swipe
- * handle (in case of quick switch).
- */
- private void cancelCurrentAnimation() {
- mCanceled = true;
- mCurrentShift.cancelAnimation();
- if (mLauncherTransitionController != null && mLauncherTransitionController
- .getAnimationPlayer().isStarted()) {
- mLauncherTransitionController.getAnimationPlayer().cancel();
- }
- }
-
- private void invalidateHandler() {
- mInputConsumerProxy.destroy();
- endRunningWindowAnim(false /* cancel */);
-
- if (mGestureEndCallback != null) {
- mGestureEndCallback.run();
- }
-
- mActivityInitListener.unregister();
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
- mTaskSnapshot = null;
- }
-
- private void invalidateHandlerWithLauncher() {
- endLauncherTransitionController();
-
- mRecentsView.onGestureAnimationEnd();
- resetLauncherListenersAndOverlays();
- }
-
- private void endLauncherTransitionController() {
- setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
- if (mLauncherTransitionController != null) {
- mLauncherTransitionController.getAnimationPlayer().end();
- mLauncherTransitionController = null;
- }
- }
-
- private void resetLauncherListenersAndOverlays() {
- // Reset the callback for deferred activity launches
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mActivityInterface.setOnDeferredActivityLaunchCallback(null);
- }
- mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- removeLiveTileOverlay();
- }
-
- private void notifyTransitionCancelled() {
- mAnimationFactory.onTransitionCancelled();
- }
-
- private void resetStateForAnimationCancel() {
- boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
- mActivityInterface.onTransitionCancelled(wasVisible);
-
- // Leave the pending invisible flag, as it may be used by wallpaper open animation.
- mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
- }
-
- protected void switchToScreenshot() {
- final int runningTaskId = mGestureState.getRunningTaskId();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.getController().setWillFinishToHome(true);
- // Update the screenshot of the task
- if (mTaskSnapshot == null) {
- mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
- }
- mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
- }
- mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- } else if (!hasTargets()) {
- // If there are no targets, then we don't need to capture anything
- mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- } else {
- boolean finishTransitionPosted = false;
- if (mRecentsAnimationController != null) {
- // Update the screenshot of the task
- if (mTaskSnapshot == null) {
- mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
- }
- final TaskView taskView;
- if (mGestureState.getEndTarget() == HOME) {
- // Capture the screenshot before finishing the transition to home to ensure it's
- // taken in the correct orientation, but no need to update the thumbnail.
- taskView = null;
- } else {
- taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
- }
- if (taskView != null && !mCanceled) {
- // Defer finishing the animation until the next launcher frame with the
- // new thumbnail
- finishTransitionPosted = ViewUtils.postDraw(taskView,
- () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
- this::isCanceled);
- }
- }
- if (!finishTransitionPosted) {
- // If we haven't posted a draw callback, set the state immediately.
- Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
- TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
- mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- TraceHelper.INSTANCE.endSection(traceToken);
- }
- }
- }
-
- private void finishCurrentTransitionToRecents() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- } else if (!hasTargets() || mRecentsAnimationController == null) {
- // If there are no targets or the animation not started, then there is nothing to finish
- mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- } else {
- mRecentsAnimationController.finish(true /* toRecents */,
- () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
- }
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
- }
-
- private void finishCurrentTransitionToHome() {
- if (!hasTargets() || mRecentsAnimationController == null) {
- // If there are no targets or the animation not started, then there is nothing to finish
- mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- } else {
- finishRecentsControllerToHome(
- () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
- }
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
- doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
- }
-
- protected abstract void finishRecentsControllerToHome(Runnable callback);
-
- private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
- endLauncherTransitionController();
- mActivityInterface.onSwipeUpToRecentsComplete();
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
- true /* screenshot */);
- }
- mRecentsView.onSwipeUpAnimationSuccess();
-
- SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
- doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
- reset();
- }
-
- private void addLiveTileOverlay() {
- if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
- mRecentsView.setLiveTileOverlayAttached(true);
- }
- }
-
- private void removeLiveTileOverlay() {
- LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
- mRecentsView.setLiveTileOverlayAttached(false);
- }
-
- private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
- return app.isNotInRecents
- || app.activityType == ACTIVITY_TYPE_HOME;
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index f60a50b..ffb05df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -71,7 +71,7 @@
*/
@TargetApi(Build.VERSION_CODES.R)
public class FallbackSwipeHandler extends
- BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
+ AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
/**
* Message used for receiving gesture nav contract information. We use a static messenger to
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 052d0a6..4411455 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -39,7 +39,7 @@
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
- BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+ AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index a676390..db1948b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,7 +21,7 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index f4a394a..0ae386f 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -20,7 +20,7 @@
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 4110b33..044e010 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -18,7 +18,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;