From 0e44900863f34e167a20f4c4c9c0fb227fe9a741 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Fri, 26 Apr 2019 11:09:58 -0700 Subject: [PATCH] Continue gestures on interaction after recents animation finishes - Detect when start of the next task is interrupted with another gesture (after finishing the recents animation but before the next task is launched), and ensure that the next gesture is continued with another other activity input consumer (but without actual remote animation targets) Bug: 128376812 Test: Introduce artificial delay in recents animation finish, try to quick switch quickly Change-Id: I057a0b2c4b7e8636467e37f5bbc8800b46c24345 --- .../AppToOverviewAnimationProvider.java | 2 +- .../quickstep/OtherActivityInputConsumer.java | 2 +- .../quickstep/RecentsAnimationWrapper.java | 4 + .../android/quickstep/SwipeSharedState.java | 15 +++ .../com/android/quickstep/TaskViewUtils.java | 1 + .../quickstep/TouchInteractionService.java | 12 +- .../WindowTransformSwipeHandler.java | 71 +++++++---- .../util/SwipeAnimationTargetSet.java | 14 +++ .../android/quickstep/views/RecentsView.java | 117 ++++++++++++------ src/com/android/launcher3/PagedView.java | 4 + 10 files changed, 177 insertions(+), 65 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 624b3dc779..5e77e0adee 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -68,7 +68,7 @@ final class AppToOverviewAnimationProvider imple * @param wasVisible true if it was visible before */ boolean onActivityReady(T activity, Boolean wasVisible) { - activity.getOverviewPanel().setCurrentTask(mTargetTaskId); + activity.getOverviewPanel().showCurrentTask(mTargetTaskId); AbstractFloatingView.closeAllOpenViews(activity, wasVisible); ActivityControlHelper.AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible, diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index 7fc5d502f9..f8721744cb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -382,7 +382,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued; mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher; if (mSwipeSharedState.canGestureBeContinued) { - mInteractionHandler.cancelCurrentAnimation(); + mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState); } else { mInteractionHandler.reset(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java index ced9afab78..96d2dca971 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -59,6 +59,10 @@ public class RecentsAnimationWrapper { mInputProxySupplier = inputProxySupplier; } + public boolean hasTargets() { + return targetSet != null && targetSet.hasTargets(); + } + @UiThread public synchronized void setController(SwipeAnimationTargetSet targetSet) { Preconditions.assertUIThread(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java index f393387a7f..194d073359 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java @@ -38,6 +38,8 @@ public class SwipeSharedState implements SwipeAnimationListener { public boolean canGestureBeContinued; public boolean goingToLauncher; + public boolean recentsAnimationFinishInterrupted; + public int nextRunningTaskId = -1; public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) { mOverviewComponentObserver = overviewComponentObserver; @@ -114,9 +116,22 @@ public class SwipeSharedState implements SwipeAnimationListener { } } + /** + * Called when a recents animation has finished, but was interrupted before the next task was + * launched. The given {@param runningTaskId} should be used as the running task for the + * continuing input consumer. + */ + public void setRecentsAnimationFinishInterrupted(int runningTaskId) { + recentsAnimationFinishInterrupted = true; + nextRunningTaskId = runningTaskId; + mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets(); + } + public void clearAllState() { clearListenerState(); canGestureBeContinued = false; + recentsAnimationFinishInterrupted = false; + nextRunningTaskId = -1; goingToLauncher = false; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index f95f9c27e5..d0ea73a6fe 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -150,6 +150,7 @@ public final class TaskViewUtils { @Override public void onUpdate(float percent) { + // TODO: Take into account the current fullscreen progress for animating the insets params.setProgress(1 - percent); RectF taskBounds = inOutHelper.applyTransform(targetSet, params); if (!skipViewChanges) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index b25865e640..61ae880cef 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -26,6 +26,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import android.annotation.TargetApi; +import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.KeyguardManager; import android.app.Service; @@ -456,7 +457,7 @@ public class TouchInteractionService extends Service implements return new DeviceLockedInputConsumer(this); } - RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); + final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); if (!useSharedState) { mSwipeSharedState.clearAllState(); } @@ -465,8 +466,15 @@ public class TouchInteractionService extends Service implements mOverviewComponentObserver.getActivityControlHelper(); InputConsumer base; - if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) { + if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher + && !mSwipeSharedState.recentsAnimationFinishInterrupted) { base = InputConsumer.NO_OP; + } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) { + // If the finish animation was interrupted, then continue using the other activity input + // consumer but with the next task as the running task + RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); + info.id = mSwipeSharedState.nextRunningTaskId; + base = createOtherActivityInputConsumer(event, info); } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) { base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index 0caef6fae7..936f0aad06 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -256,7 +256,9 @@ public class WindowTransformSwipeHandler private AnimationFactory mAnimationFactory = (t) -> { }; private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay(); + private boolean mCanceled; private boolean mWasLauncherAlreadyVisible; + private int mFinishingRecentsAnimationForNewTaskId = -1; private boolean mPassedOverviewThreshold; private boolean mGestureStarted; @@ -412,7 +414,6 @@ public class WindowTransformSwipeHandler mRecentsAnimationWrapper.runOnInit(() -> mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier)); }); - mRecentsView.setEnableFreeScroll(false); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { if (mGestureEndTarget != HOME) { @@ -500,10 +501,7 @@ public class WindowTransformSwipeHandler if (mContinuingLastGesture) { return; } - mRecentsView.setEnableDrawingLiveTile(false); - mRecentsView.showTask(mRunningTaskId); - mRecentsView.setRunningTaskHidden(true); - mRecentsView.setRunningTaskIconScaledDown(true); + mRecentsView.onGestureAnimationStart(mRunningTaskId); } private void launcherFrameDrawn() { @@ -679,7 +677,7 @@ public class WindowTransformSwipeHandler int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex(); if (runningTaskIndex >= 0) { for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) { - if (i != runningTaskIndex) { + if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) { mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value); } } @@ -838,9 +836,15 @@ public class WindowTransformSwipeHandler Interpolator interpolator = DEACCEL; final boolean goingToNewTask; if (mRecentsView != null) { - final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); - final int taskToLaunch = mRecentsView.getNextPage(); - goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; + if (!mRecentsAnimationWrapper.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; } @@ -1124,14 +1128,22 @@ public class WindowTransformSwipeHandler mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */, true /* freezeTaskList */); } else { + int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id; + mFinishingRecentsAnimationForNewTaskId = taskId; mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { - mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask( - false /* animate */, true /* freezeTaskList */); + if (!mCanceled) { + TaskView nextTask = mRecentsView.getTaskView(taskId); + if (nextTask != null) { + nextTask.launchTask(false /* animate */, true /* freezeTaskList */); + doLogGesture(NEW_TASK); + } + reset(); + } + mCanceled = false; + mFinishingRecentsAnimationForNewTaskId = -1; }); } TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); - doLogGesture(NEW_TASK); - reset(); } public void reset() { @@ -1142,12 +1154,26 @@ public class WindowTransformSwipeHandler * Cancels any running animation so that the active target can be overriden by a new swipe * handle (in case of quick switch). */ - public void cancelCurrentAnimation() { + public void cancelCurrentAnimation(SwipeSharedState sharedState) { + mCanceled = true; mCurrentShift.cancelAnimation(); if (mLauncherTransitionController != null && mLauncherTransitionController .getAnimationPlayer().isStarted()) { mLauncherTransitionController.getAnimationPlayer().cancel(); } + + if (mFinishingRecentsAnimationForNewTaskId != -1) { + // If we are canceling mid-starting a new task, switch to the screenshot since the + // recents animation has finished + switchToScreenshot(); + TaskView newRunningTaskView = mRecentsView.getTaskView( + mFinishingRecentsAnimationForNewTaskId); + int newRunningTaskId = newRunningTaskView != null + ? newRunningTaskView.getTask().key.id + : -1; + mRecentsView.setCurrentTask(newRunningTaskId); + sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + } } private void invalidateHandler() { @@ -1169,11 +1195,7 @@ public class WindowTransformSwipeHandler mLauncherTransitionController = null; } - mRecentsView.setEnableFreeScroll(true); - mRecentsView.setRunningTaskIconScaledDown(false); - mRecentsView.setOnScrollChangeListener(null); - mRecentsView.setRunningTaskHidden(false); - mRecentsView.setEnableDrawingLiveTile(true); + mRecentsView.onGestureAnimationEnd(); mActivity.getRootView().setOnApplyWindowInsetsListener(null); mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); @@ -1194,6 +1216,9 @@ public class WindowTransformSwipeHandler private void switchToScreenshot() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + } else if (!mRecentsAnimationWrapper.hasTargets()) { + // If there are no targets, then we don't need to capture anything + setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else { boolean finishTransitionPosted = false; SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); @@ -1241,6 +1266,9 @@ public class WindowTransformSwipeHandler private void finishCurrentTransitionToRecents() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else if (!mRecentsAnimationWrapper.hasTargets()) { + // If there are no targets, then there is nothing to finish + setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); } else { synchronized (mRecentsAnimationWrapper) { mRecentsAnimationWrapper.finish(true /* toRecents */, @@ -1267,10 +1295,7 @@ public class WindowTransformSwipeHandler } mActivityControlHelper.onSwipeUpComplete(mActivity); mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true); - - // Animate the first icon. - mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation()); - mRecentsView.setSwipeDownShouldLaunchApp(true); + mRecentsView.onSwipeUpAnimationSuccess(); RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java index 83973fa7ec..f5a9e8a058 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java @@ -53,6 +53,20 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet { this.mOnFinishListener = onFinishListener; } + public boolean hasTargets() { + return unfilteredApps.length != 0; + } + + /** + * Clones the target set without any actual targets. Used only when continuing a gesture after + * the actual recents animation has finished. + */ + public SwipeAnimationTargetSet cloneWithoutTargets() { + return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0], + homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen, + mOnFinishListener); + } + public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) { mOnFinishListener.accept(this); BACKGROUND_EXECUTOR.execute(() -> { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index b5f90a5550..525ead836a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -744,9 +744,7 @@ public abstract class RecentsView extends PagedView impl public abstract void startHome(); public void reset() { - setRunningTaskViewShowScreenshot(false); - mRunningTaskId = -1; - mRunningTaskTileHidden = false; + setCurrentTask(-1); mIgnoreResetTaskId = -1; mTaskListChangeId = -1; @@ -757,6 +755,15 @@ public abstract class RecentsView extends PagedView impl setCurrentPage(0); } + public @Nullable TaskView getRunningTaskView() { + return getTaskView(mRunningTaskId); + } + + public int getRunningTaskIndex() { + TaskView tv = getRunningTaskView(); + return tv == null ? -1 : indexOfChild(tv); + } + /** * Reloads the view if anything in recents changed. */ @@ -767,14 +774,50 @@ public abstract class RecentsView extends PagedView impl } /** - * Ensures that the first task in the view represents {@param task} and reloads the view - * if needed. This allows the swipe-up gesture to assume that the first tile always - * corresponds to the correct task. - * All subsequent calls to reload will keep the task as the first item until {@link #reset()} - * is called. - * Also scrolls the view to this task + * Called when a gesture from an app is starting. */ - public void showTask(int runningTaskId) { + public void onGestureAnimationStart(int runningTaskId) { + // This needs to be called before the other states are set since it can create the task view + showCurrentTask(runningTaskId); + setEnableFreeScroll(false); + setEnableDrawingLiveTile(false); + setRunningTaskHidden(true); + setRunningTaskIconScaledDown(true); + } + + /** + * Called only when a swipe-up gesture from an app has completed. Only called after + * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. + */ + public void onSwipeUpAnimationSuccess() { + if (getRunningTaskView() != null) { + float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() + ? mLiveTileOverlay.cancelIconAnimation() + : 0f; + animateUpRunningTaskIconScale(startProgress); + } + setSwipeDownShouldLaunchApp(true); + } + + /** + * Called when a gesture from an app has finished. + */ + public void onGestureAnimationEnd() { + setEnableFreeScroll(true); + setEnableDrawingLiveTile(true); + setOnScrollChangeListener(null); + setRunningTaskViewShowScreenshot(true); + setRunningTaskHidden(false); + animateUpRunningTaskIconScale(); + } + + /** + * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. + * + * All subsequent calls to reload will keep the task as the first item until {@link #reset()} + * is called. Also scrolls the view to this task. + */ + public void showCurrentTask(int runningTaskId) { if (getChildCount() == 0) { // Add an empty view for now until the task plan is loaded and applied final TaskView taskView = mTaskViewPool.getView(); @@ -789,16 +832,33 @@ public abstract class RecentsView extends PagedView impl new ComponentName("", ""), false); taskView.bind(mTmpRunningTask); } + + boolean runningTaskTileHidden = mRunningTaskTileHidden; setCurrentTask(runningTaskId); + setCurrentPage(getRunningTaskIndex()); + setRunningTaskViewShowScreenshot(false); + setRunningTaskHidden(runningTaskTileHidden); + + // Reload the task list + mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); } - public @Nullable TaskView getRunningTaskView() { - return getTaskView(mRunningTaskId); - } + /** + * Sets the running task id, cleaning up the old running task if necessary. + * @param runningTaskId + */ + public void setCurrentTask(int runningTaskId) { + if (mRunningTaskId == runningTaskId) { + return; + } - public int getRunningTaskIndex() { - TaskView tv = getRunningTaskView(); - return tv == null ? -1 : indexOfChild(tv); + if (mRunningTaskId != -1) { + // Reset the state on the old running task view + setRunningTaskIconScaledDown(false); + setRunningTaskViewShowScreenshot(true); + setRunningTaskHidden(false); + } + mRunningTaskId = runningTaskId; } /** @@ -812,27 +872,6 @@ public abstract class RecentsView extends PagedView impl } } - /** - * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile. - */ - public void setCurrentTask(int runningTaskId) { - boolean runningTaskTileHidden = mRunningTaskTileHidden; - boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown; - - setRunningTaskIconScaledDown(false); - setRunningTaskHidden(false); - setRunningTaskViewShowScreenshot(true); - mRunningTaskId = runningTaskId; - setRunningTaskViewShowScreenshot(false); - setRunningTaskIconScaledDown(runningTaskIconScaledDown); - setRunningTaskHidden(runningTaskTileHidden); - - setCurrentPage(getRunningTaskIndex()); - - // Load the tasks (if the loading is already - mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); - } - private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { TaskView runningTaskView = getRunningTaskView(); @@ -1520,7 +1559,9 @@ public abstract class RecentsView extends PagedView impl public abstract boolean shouldUseMultiWindowTaskSizeStrategy(); protected void onTaskLaunched(boolean success) { - resetTaskVisuals(); + if (success) { + resetTaskVisuals(); + } } @Override diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index b6404301dd..80e17c9ff1 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1072,6 +1072,10 @@ public abstract class PagedView extends ViewGrou public void setEnableFreeScroll(boolean freeScroll) { + if (mFreeScroll == freeScroll) { + return; + } + boolean wasFreeScroll = mFreeScroll; mFreeScroll = freeScroll;