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
This commit is contained in:
Winson Chung
2019-04-26 11:09:58 -07:00
parent 227b6e021c
commit 0e44900863
10 changed files with 177 additions and 65 deletions
@@ -68,7 +68,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
* @param wasVisible true if it was visible before
*/
boolean onActivityReady(T activity, Boolean wasVisible) {
activity.<RecentsView>getOverviewPanel().setCurrentTask(mTargetTaskId);
activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
ActivityControlHelper.AnimationFactory factory =
mHelper.prepareRecentsUI(activity, wasVisible,
@@ -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();
}
@@ -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();
@@ -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;
}
}
@@ -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) {
@@ -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() &&
@@ -256,7 +256,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
* 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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
}
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);
@@ -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(() -> {
@@ -744,9 +744,7 @@ public abstract class RecentsView<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> extends PagedView impl
public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
protected void onTaskLaunched(boolean success) {
resetTaskVisuals();
if (success) {
resetTaskVisuals();
}
}
@Override
+4
View File
@@ -1072,6 +1072,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
public void setEnableFreeScroll(boolean freeScroll) {
if (mFreeScroll == freeScroll) {
return;
}
boolean wasFreeScroll = mFreeScroll;
mFreeScroll = freeScroll;