diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 1e861d2fb5..58c616fbb3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -312,6 +312,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mControllers.taskbarEduTooltipController.maybeShowSwipeEdu(); } + /** Will make the next onRecentsAnimationFinished() animation a no-op. */ + public void setSkipNextRecentsAnimEnd() { + mTaskbarLauncherStateController.setSkipNextRecentsAnimEnd(); + } + /** * Returns {@code true} if a Taskbar education should be shown on application launch. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 1047374a50..6dfba64710 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -1232,13 +1232,13 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return; } - boolean findExactPairMatch = itemInfos.size() == 2; + boolean isLaunchingAppPair = itemInfos.size() == 2; // Convert the list of ItemInfo instances to a list of ComponentKeys List componentKeys = itemInfos.stream().map(ItemInfo::getComponentKey).toList(); recents.getSplitSelectController().findLastActiveTasksAndRunCallback( componentKeys, - findExactPairMatch, + isLaunchingAppPair, foundTasks -> { @Nullable Task foundTask = foundTasks[0]; if (foundTask != null) { @@ -1252,10 +1252,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } } - if (findExactPairMatch) { - // We did not find the app pair we were looking for, so launch one. - recents.getSplitSelectController().getAppPairsController().launchAppPair( - (AppPairIcon) launchingIconView, -1 /*cuj*/); + if (isLaunchingAppPair) { + // Finish recents animation if it's running before launching to ensure + // we get both leashes for the animation + mControllers.uiController.setSkipNextRecentsAnimEnd(); + recents.switchToScreenshot(() -> + recents.finishRecentsAnimation(true /*toRecents*/, + false /*shouldPip*/, + () -> recents + .getSplitSelectController() + .getAppPairsController() + .launchAppPair((AppPairIcon) launchingIconView, + -1 /*cuj*/))); } else { startItemInfoActivity(itemInfos.get(0)); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index 259af1d53a..fb9a9761e7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -148,6 +148,7 @@ public class TaskbarLauncherStateController { private Integer mPrevState; private int mState; private LauncherState mLauncherState = LauncherState.NORMAL; + private boolean mSkipNextRecentsAnimEnd; // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds). private long mLastUnlockTimeMs = 0; @@ -292,12 +293,12 @@ public class TaskbarLauncherStateController { if (mTaskBarRecentsAnimationListener != null) { mTaskBarRecentsAnimationListener.endGestureStateOverride( - !mLauncher.isInState(LauncherState.OVERVIEW)); + !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/); } mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); callbacks.addListener(mTaskBarRecentsAnimationListener); ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() -> - mTaskBarRecentsAnimationListener.endGestureStateOverride(true)); + mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /*canceled*/)); ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> { updateStateForUserFinishedToApp(false /* finishedToApp */); @@ -318,6 +319,11 @@ public class TaskbarLauncherStateController { mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim; } + /** Will make the next onRecentsAnimationFinished() a no-op. */ + public void setSkipNextRecentsAnimEnd() { + mSkipNextRecentsAnimEnd = true; + } + /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */ public void updateStateForSysuiFlags(int systemUiStateFlags) { updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true); @@ -770,19 +776,33 @@ public class TaskbarLauncherStateController { @Override public void onRecentsAnimationCanceled(HashMap thumbnailDatas) { boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW); - endGestureStateOverride(!isInOverview); + endGestureStateOverride(!isInOverview, true /*canceled*/); } @Override public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(!controller.getFinishTargetIsLauncher()); + endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/); } - private void endGestureStateOverride(boolean finishedToApp) { + /** + * Handles whatever cleanup is needed after the recents animation is completed. + * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled + * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)} + * + * @param finishedToApp {@code true} if the recents animation finished to showing an app and + * not workspace or overview + * @param canceled {@code true} if the recents animation was canceled instead of finishing + * to completion + */ + private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { mCallbacks.removeListener(this); mTaskBarRecentsAnimationListener = null; ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null); + if (mSkipNextRecentsAnimEnd && !canceled) { + mSkipNextRecentsAnimEnd = false; + return; + } updateStateForUserFinishedToApp(finishedToApp); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index efe1e39f97..c74fd83c48 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -394,4 +394,12 @@ public class TaskbarUIController { mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, !isVisible); mControllers.taskbarStashController.applyState(); } + + /** + * Request for UI controller to ignore animations for the next callback for the end of recents + * animation + */ + public void setSkipNextRecentsAnimEnd() { + // Overridden + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 5fb936dfdb..22e62b763b 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1255,16 +1255,23 @@ public abstract class RecentsView { float percent = valueAnimator.getAnimatedFraction(); SurfaceTransaction transaction = new SurfaceTransaction(); - Matrix matrix = new Matrix(); - matrix.postScale(percent, percent); - matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2, - mActivity.getDeviceProfile().heightPx * (1 - percent) / 2); - transaction.forSurface(apps[apps.length - 1].leash) - .setAlpha(percent) - .setMatrix(matrix); + for (int i = apps.length - 1; i >= 0; --i) { + RemoteAnimationTarget app = apps[i]; + + float dx = mActivity.getDeviceProfile().widthPx * (1 - percent) / 2 + + app.screenSpaceBounds.left * percent; + float dy = mActivity.getDeviceProfile().heightPx * (1 - percent) / 2 + + app.screenSpaceBounds.top * percent; + matrix.setScale(percent, percent); + matrix.postTranslate(dx, dy); + transaction.forSurface(app.leash) + .setAlpha(percent) + .setMatrix(matrix); + } surfaceApplier.scheduleApply(transaction); }); appAnimator.addListener(new AnimatorListenerAdapter() { @@ -5423,6 +5430,10 @@ public abstract class RecentsView