Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9812193 authored by Tony's avatar Tony
Browse files

Allow for continuous scrolling to previous tasks

Starting a new touch interaction on the nav bar while going to a new task
cancels that animation to allow for continuous scrolling.

Specifically, the new interaction still creates a new touch consumer and
WindowTransformSwipeHandler, but reuses the previous RecentsAnimationState
in order to defer finishing it.

Bug: 111926330
Change-Id: Ia4f5f8dbb2b3ae70791676f1e3e5ce84deb22f74
parent 51745853
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -95,6 +95,11 @@ public class DeferredTouchConsumer implements TouchConsumer {
        return mTarget.forceToLauncherConsumer();
    }

    @Override
    public OtherActivityTouchConsumer.RecentsAnimationState getRecentsAnimationStateToReuse() {
        return mTarget.getRecentsAnimationStateToReuse();
    }

    @Override
    public boolean deferNextEventToMainThread() {
        // If our target is still null, defer the next target as well
+55 −20
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;

import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -102,18 +101,21 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
    private WindowTransformSwipeHandler mInteractionHandler;
    private int mDisplayRotation;
    private Rect mStableInsets = new Rect();
    private boolean mCanGestureBeContinued;

    private VelocityTracker mVelocityTracker;
    private MotionPauseDetector mMotionPauseDetector;
    private MotionEventQueue mEventQueue;
    private boolean mIsGoingToLauncher;
    private RecentsAnimationState mRecentsAnimationState;

    public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
            MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
            @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
            TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) {
            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog,
            @Nullable RecentsAnimationState recentsAnimationStateToReuse) {
        super(base);

        mRunningTask = runningTaskInfo;
@@ -130,6 +132,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
        mTouchInteractionLog = touchInteractionLog;
        mTouchInteractionLog.setTouchConsumer(this);
        mInputConsumer = inputConsumer;
        mRecentsAnimationState = recentsAnimationStateToReuse;
    }

    @Override
@@ -150,7 +153,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
                mActivePointerId = ev.getPointerId(0);
                mDownPos.set(ev.getX(), ev.getY());
                mLastPos.set(mDownPos);
                mPassedInitialSlop = false;
                // If mRecentsAnimationState != null, we are continuing the previous gesture.
                mPassedInitialSlop = mRecentsAnimationState != null;
                mQuickStepDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();

                // Start the window animation on down to give more time for launcher to draw if the
@@ -256,10 +260,15 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
        mTouchInteractionLog.startRecentsAnimation();

        // Create the shared handler
        RecentsAnimationState animationState = new RecentsAnimationState();
        boolean reuseOldAnimState = mRecentsAnimationState != null;
        if (reuseOldAnimState) {
            mRecentsAnimationState.changeParent(this);
        } else {
            mRecentsAnimationState = new RecentsAnimationState(this);
        }
        final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
                mInputConsumer, mTouchInteractionLog);
                mRecentsAnimationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
                reuseOldAnimState, mInputConsumer, mTouchInteractionLog);

        // Preload the plan
        mRecentsModel.getTasks(null);
@@ -291,8 +300,18 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
                    }
                };

        Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
                mHomeIntent, assistDataReceiver, animationState, null, null);
        Runnable startActivity;
        if (reuseOldAnimState) {
            startActivity = () -> {
                handler.onRecentsAnimationStart(mRecentsAnimationState.mController,
                        mRecentsAnimationState.mTargets, mRecentsAnimationState.mHomeContentInsets,
                        mRecentsAnimationState.mMinimizedHomeBounds);
            };
        } else {
            startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
                    mHomeIntent, assistDataReceiver, mRecentsAnimationState, null, null);
        }


        if (Looper.myLooper() != Looper.getMainLooper()) {
            startActivity.run();
@@ -353,8 +372,10 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
        if (mInteractionHandler != null) {
            final WindowTransformSwipeHandler handler = mInteractionHandler;
            mInteractionHandler = null;
            mIsGoingToLauncher = handler.mIsGoingToRecents || handler.mIsGoingToHome;
            mMainThreadExecutor.execute(handler::reset);
            WindowTransformSwipeHandler.GestureEndTarget endTarget = handler.mGestureEndTarget;
            mIsGoingToLauncher = endTarget != null && endTarget.isLauncher;
            mCanGestureBeContinued = endTarget != null && endTarget.canBeContinued;
            mMainThreadExecutor.execute(mCanGestureBeContinued ? handler::cancel : handler::reset);
        }
    }

@@ -443,26 +464,34 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
        return mIsGoingToLauncher;
    }

    @Override
    public @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
        return mCanGestureBeContinued ? mRecentsAnimationState : null;
    }

    @Override
    public boolean deferNextEventToMainThread() {
        // TODO: Consider also check if the eventQueue is using mainThread of not.
        return mInteractionHandler != null;
    }

    private class RecentsAnimationState implements RecentsAnimationListener {
    public static class RecentsAnimationState implements RecentsAnimationListener {

        private static final String ANIMATION_START_EVT = "RecentsAnimationState.onAnimationStart";
        private final int id;

        private OtherActivityTouchConsumer mParent;

        private RecentsAnimationControllerCompat mController;
        private RemoteAnimationTargetSet mTargets;
        private Rect mHomeContentInsets;
        private Rect mMinimizedHomeBounds;
        private boolean mCancelled;

        public RecentsAnimationState() {
            id = mAnimationStates.size();
            mAnimationStates.put(id, this);
        public RecentsAnimationState(OtherActivityTouchConsumer parent) {
            mParent = parent;
            id = mParent.mAnimationStates.size();
            mParent.mAnimationStates.put(id, this);
        }

        @Override
@@ -475,31 +504,37 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
            mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
            mHomeContentInsets = homeContentInsets;
            mMinimizedHomeBounds = minimizedHomeBounds;
            mEventQueue.onCommand(id);
            mParent.mEventQueue.onCommand(id);
            RaceConditionTracker.onEvent(ANIMATION_START_EVT, EXIT);
        }

        @Override
        public void onAnimationCanceled() {
            mCancelled = true;
            mEventQueue.onCommand(id);
            mParent.mEventQueue.onCommand(id);
        }

        public void execute() {
            if (mInteractionHandler == null || mInteractionHandler.id != id) {
            WindowTransformSwipeHandler handler = mParent.mInteractionHandler;
            if (handler == null || handler.id != id) {
                if (!mCancelled && mController != null) {
                    TraceHelper.endSection("RecentsController", "Finishing no handler");
                    mController.finish(false /* toHome */);
                }
            } else if (mCancelled) {
                TraceHelper.endSection("RecentsController",
                        "Cancelled: " + mInteractionHandler);
                mInteractionHandler.onRecentsAnimationCanceled();
                        "Cancelled: " + handler);
                handler.onRecentsAnimationCanceled();
            } else {
                TraceHelper.partitionSection("RecentsController", "Received");
                mInteractionHandler.onRecentsAnimationStart(mController, mTargets,
                handler.onRecentsAnimationStart(mController, mTargets,
                        mHomeContentInsets, mMinimizedHomeBounds);
            }
        }

        public void changeParent(OtherActivityTouchConsumer newParent) {
            mParent = newParent;
            mParent.mAnimationStates.put(id, this);
        }
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import android.view.Choreographer;
import android.view.MotionEvent;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;

import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -73,5 +76,12 @@ public interface TouchConsumer extends Consumer<MotionEvent> {
        return false;
    }

    /**
     * When continuing a gesture, return the current non-null animation state that hasn't finished.
     */
    default @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
        return null;
    }

    default void onShowOverviewFromAltTab() {}
}
+10 −6
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -237,16 +238,18 @@ public class TouchInteractionService extends Service {
        if (oldConsumer.deferNextEventToMainThread()) {
            mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
                    new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
                            oldConsumer.forceToLauncherConsumer(), v)));
                            oldConsumer.forceToLauncherConsumer(),
                            oldConsumer.getRecentsAnimationStateToReuse(), v)));
            mEventQueue.deferInit();
        } else {
            mEventQueue = new MotionEventQueue(
                    mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
            mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
                    getCurrentTouchConsumer(downHitTarget, false, null, null));
        }
    }

    private TouchConsumer getCurrentTouchConsumer(
            @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
    private TouchConsumer getCurrentTouchConsumer(@HitTarget int downHitTarget,
            boolean forceToLauncher, RecentsAnimationState recentsAnimationStateToReuse,
            VelocityTracker tracker) {
        RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);

        if (runningTaskInfo == null && !forceToLauncher) {
@@ -269,7 +272,8 @@ public class TouchInteractionService extends Service {
                    mOverviewComponentObserver.getOverviewIntent(),
                    mOverviewComponentObserver.getActivityControlHelper(), mMainThreadExecutor,
                    mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
                    mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
                    mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog,
                    recentsAnimationStateToReuse);
        }
    }

+58 −31
Original line number Diff line number Diff line
@@ -197,19 +197,21 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    };

    enum GestureEndTarget {
        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, ContainerType.WORKSPACE),
        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),

        RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
                | STATE_SCREENSHOT_VIEW_SHOWN, true, ContainerType.TASKSWITCHER),
                | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER),

        NEW_TASK(0, STATE_START_NEW_TASK, false, ContainerType.APP),
        NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),

        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, ContainerType.APP);
        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);

        GestureEndTarget(float endShift, int endState, boolean isLauncher, int containerType) {
        GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
                int containerType) {
            this.endShift = endShift;
            this.endState = endState;
            this.isLauncher = isLauncher;
            this.canBeContinued = canBeContinued;
            this.containerType = containerType;
        }

@@ -217,6 +219,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        public final float endShift;
        public final int endState;
        public final boolean isLauncher;
        public final boolean canBeContinued;
        public final int containerType;
    }

@@ -235,8 +238,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    private final ClipAnimationHelper.TransformParams mTransformParams;

    protected Runnable mGestureEndCallback;
    protected boolean mIsGoingToRecents;
    protected boolean mIsGoingToHome;
    protected GestureEndTarget mGestureEndTarget;
    private boolean mIsShelfPeeking;
    private DeviceProfile mDp;
    private int mTransitionDragLength;
@@ -247,6 +249,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    // visible.
    private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
    private boolean mDispatchedDownEvent;
    private boolean mContinuingLastGesture;
    // To avoid UI jump when gesture is started, we offset the animation by the threshold.
    private float mShiftAtGestureStart = 0;

@@ -302,7 +305,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    private Bundle mAssistData;

    WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
            long touchTimeMs, ActivityControlHelper<T> controller,
            long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
            InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
        this.id = id;
        mContext = context;
@@ -312,6 +315,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        mActivityControlHelper = controller;
        mActivityInitListener = mActivityControlHelper
                .createActivityInitListener(this::onActivityInit);
        mContinuingLastGesture = continuingLastGesture;
        mTouchInteractionLog = touchInteractionLog;
        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
                this::createNewTouchProxyHandler);
@@ -480,7 +484,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        });
        mRecentsView.setEnableFreeScroll(false);
        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
            if (!mBgLongSwipeMode && !mIsGoingToHome) {
            if (!mBgLongSwipeMode && mGestureEndTarget != HOME) {
                updateFinalShift();
            }
        });
@@ -538,6 +542,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    }

    private void setupRecentsViewUi() {
        if (mContinuingLastGesture) {
            return;
        }
        mRecentsView.setEnableDrawingLiveTile(false);
        mRecentsView.showTask(mRunningTaskId);
        mRecentsView.setRunningTaskHidden(true);
@@ -767,16 +774,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
            }
        }
        // Update insets of the adjacent tasks, as we might switch to them.
        // Update insets of the non-running tasks, as we might switch to them.
        int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
        if (mInteractionType == INTERACTION_NORMAL && runningTaskIndex >= 0) {
            TaskView nextTaskView = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
            TaskView prevTaskView = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
            if (nextTaskView != null) {
                nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
            for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
                if (i != runningTaskIndex) {
                    mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
                }
            if (prevTaskView != null) {
                prevTaskView.setFullscreenProgress(1 - mCurrentShift.value);
            }
        }

@@ -788,7 +792,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
    }

    private void updateLauncherTransitionProgress() {
        if (mIsGoingToHome) {
        if (mGestureEndTarget == HOME) {
            return;
        }
        float progress = mCurrentShift.value;
@@ -909,10 +913,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        float endShift;
        final float startShift;
        Interpolator interpolator = DEACCEL;
        final int nextPage = mRecentsView != null ? mRecentsView.getNextPage() : -1;
        final int runningTaskIndex = mRecentsView != null ? mRecentsView.getRunningTaskIndex() : -1;
        boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex
                && mRecentsView.getTaskViewAt(nextPage) != null;
        int nextPage = 0;
        int taskToLaunch = 0;
        final boolean goingToNewTask;
        if (mRecentsView != null) {
            nextPage = mRecentsView.getNextPage();
            final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
            final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
            taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
            goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
        } else {
            goingToNewTask = false;
        }
        final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
        if (!isFling) {
            if (SWIPE_HOME.get()) {
@@ -973,6 +985,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
            }
        }

        if (mRecentsView != null && !endTarget.isLauncher && taskToLaunch != nextPage) {
            // Scrolled to Clear all button, snap back to last task and launch it.
            mRecentsView.snapToPage(taskToLaunch, Math.toIntExact(duration), interpolator);
        }

        if (endTarget == HOME) {
            setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
            duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
@@ -989,11 +1006,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
            if (mRecentsView != null) {
                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
            }
        } else if (endTarget == LAST_TASK) {
            if (mRecentsView != null && nextPage != runningTaskIndex) {
                // Scrolled to Clear all button, snap back to current task and resume it.
                mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration));
            }
        }
        animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
    }
@@ -1028,11 +1040,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {

    private void animateToProgressInternal(float start, float end, long duration,
            Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
        mIsGoingToHome = target == HOME;
        mIsGoingToRecents = target == RECENTS;
        mGestureEndTarget = target;
        ActivityControlHelper.HomeAnimationFactory homeAnimFactory;
        Animator windowAnim;
        if (mIsGoingToHome) {
        if (mGestureEndTarget == HOME) {
            if (mActivity != null) {
                homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
            } else {
@@ -1071,7 +1082,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        long startMillis = SystemClock.uptimeMillis();
        // Always play the entire launcher animation when going home, since it is separate from
        // the animation that has been controlled thus far.
        final float finalStart = mIsGoingToHome ? 0 : start;
        final float finalStart = mGestureEndTarget == HOME ? 0 : start;
        executeOnUiThread(() -> {
            // Animate the launcher components at the same time as the window, always on UI thread.
            // Adjust start progress and duration in case we are on a different thread.
@@ -1144,6 +1155,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
                    .setSyncTransactionApplier(syncTransactionApplier);
            mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
        });
        anim.addListener(new AnimationSuccessListener() {
            @Override
            public void onAnimationSuccess(Animator animator) {
                if (mRecentsView != null) {
                    mRecentsView.post(mRecentsView::resetTaskVisuals);
                }
            }
        });
        return anim;
    }

@@ -1187,6 +1206,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
        }
    }

    public void cancel() {
        mCurrentShift.cancelAnimation();
        if (mLauncherTransitionController != null && mLauncherTransitionController
                .getAnimationPlayer().isStarted()) {
            mLauncherTransitionController.getAnimationPlayer().cancel();
        }
    }

    private void invalidateHandler() {
        mCurrentShift.finishAnimation();