diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml new file mode 100644 index 0000000000..069ff86020 --- /dev/null +++ b/quickstep/res/layout/task_grouped.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java index 9df9ab1682..82e890344a 100644 --- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java +++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java @@ -63,7 +63,7 @@ public class RecentsActivityTest { RunningTaskInfo placeholderTask = new RunningTaskInfo(); placeholderTask.taskId = 22; - frv.showCurrentTask(placeholderTask); + frv.showCurrentTask(new RunningTaskInfo[]{placeholderTask}); doLayout(activity); ThumbnailData thumbnailData = new ThumbnailData(); diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 42c89fd006..c05ffd5c66 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -99,6 +99,7 @@ import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.InputConsumerProxy; import com.android.quickstep.util.InputProxyHandlerFactory; +import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.ProtoTracer; import com.android.quickstep.util.RecentsOrientedState; @@ -106,6 +107,7 @@ import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.SwipePipToHomeAnimator; +import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -222,7 +224,7 @@ public abstract class AbsSwipeUpHandler, protected final TaskAnimationManager mTaskAnimationManager; // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise - private RunningWindowAnim mRunningWindowAnim; + private RunningWindowAnim[] mRunningWindowAnim; // Possible second animation running at the same time as mRunningWindowAnim private Animator mParallelRunningAnim; private boolean mIsMotionPaused; @@ -253,6 +255,10 @@ public abstract class AbsSwipeUpHandler, private SwipePipToHomeAnimator mSwipePipToHomeAnimator; protected boolean mIsSwipingPipToHome; + // TODO(b/195473090) no split PIP for now, remove once we have more clarity + // can try to have RectFSpringAnim evaluate multiple rects at once + private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators = + new SwipePipToHomeAnimator[2]; // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold private final float mQuickSwitchScaleScrollThreshold; @@ -426,7 +432,8 @@ public abstract class AbsSwipeUpHandler, // RecentsView never updates the display rotation until swipe-up, force update // RecentsOrientedState before passing to TaskViewSimulator. mRecentsView.updateRecentsRotation(); - mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState()); + runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator + .setOrientationState(mRecentsView.getPagedViewOrientedState())); // 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. @@ -519,7 +526,21 @@ public abstract class AbsSwipeUpHandler, } protected void notifyGestureAnimationStartToRecents() { - mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask()); + ActivityManager.RunningTaskInfo[] runningTasks; + if (mIsSwipeForStagedSplit) { + int[] splitTaskIds = + LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds(); + runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length]; + for (int i = 0; i < splitTaskIds.length; i++) { + int taskId = splitTaskIds[i]; + ActivityManager.RunningTaskInfo rti = new ActivityManager.RunningTaskInfo(); + rti.taskId = taskId; + runningTasks[i] = rti; + } + } else { + runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()}; + } + mRecentsView.onGestureAnimationStart(runningTasks); } private void launcherFrameDrawn() { @@ -606,15 +627,15 @@ public abstract class AbsSwipeUpHandler, if (animate) { ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1); reapplyWindowTransformAnim.addUpdateListener(anim -> { - if (mRunningWindowAnim == null) { - applyWindowTransform(); + if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) { + applyScrollAndTransform(); } }); reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start(); mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, reapplyWindowTransformAnim::cancel); } else { - applyWindowTransform(); + applyScrollAndTransform(); } } @@ -678,7 +699,7 @@ public abstract class AbsSwipeUpHandler, } updateSysUiFlags(mCurrentShift.value); - applyWindowTransform(); + applyScrollAndTransform(); updateLauncherTransitionProgress(); } @@ -724,24 +745,23 @@ public abstract class AbsSwipeUpHandler, @Override public void onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets) { - ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length); + super.onRecentsAnimationStart(controller, targets); 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) { + RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0]; + // orientation state is independent of which remote target handle we use since both + // should be pointing to the same one. Just choose index 0 for now since that works for + // both split and non-split + RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator + .getOrientationState(); + DeviceProfile dp = orientationState.getLauncherDeviceProfile(); + if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) { Rect overviewStackBounds = mActivityInterface - .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget); + .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget); dp = dp.getMultiWindowProfile(mContext, new WindowBounds(overviewStackBounds, targets.homeContentInsets)); } else { @@ -751,7 +771,7 @@ public abstract class AbsSwipeUpHandler, dp.updateInsets(targets.homeContentInsets); dp.updateIsSeascape(mContext); initTransitionEndpoints(dp); - mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode); + orientationState.setMultiWindowMode(dp.isMultiWindowMode); } // Notify when the animation starts @@ -869,9 +889,17 @@ public abstract class AbsSwipeUpHandler, private void endRunningWindowAnim(boolean cancel) { if (mRunningWindowAnim != null) { if (cancel) { - mRunningWindowAnim.cancel(); + for (RunningWindowAnim r : mRunningWindowAnim) { + if (r != null) { + r.cancel(); + } + } } else { - mRunningWindowAnim.end(); + for (RunningWindowAnim r : mRunningWindowAnim) { + if (r != null) { + r.end(); + } + } } } if (mParallelRunningAnim != null) { @@ -1181,15 +1209,17 @@ public abstract class AbsSwipeUpHandler, createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip, runningTaskTarget); mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip; - final RectFSpringAnim windowAnim; + final RectFSpringAnim[] windowAnim; if (mIsSwipingPipToHome) { mSwipePipToHomeAnimator = createWindowAnimationToPip( homeAnimFactory, runningTaskTarget, start); - windowAnim = mSwipePipToHomeAnimator; + mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator; + windowAnim = mSwipePipToHomeAnimators; } else { mSwipePipToHomeAnimator = null; windowAnim = createWindowAnimationToHome(start, homeAnimFactory); - windowAnim.addAnimatorListener(new AnimationSuccessListener() { + + windowAnim[0].addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { if (mRecentsAnimationController == null) { @@ -1203,15 +1233,22 @@ public abstract class AbsSwipeUpHandler, } }); } - windowAnim.start(mContext, velocityPxPerMs); - mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); + mRunningWindowAnim = new RunningWindowAnim[windowAnim.length]; + for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) { + RectFSpringAnim windowAnimation = windowAnim[i]; + if (windowAnimation == null) { + continue; + } + windowAnimation.start(mContext, velocityPxPerMs); + mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation); + } homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y); homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); mLauncherTransitionController = null; if (mRecentsView != null) { mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(), - mTaskViewSimulator); + getRemoteTaskViewSimulators()); } } else { AnimatorSet animatorSet = new AnimatorSet(); @@ -1253,11 +1290,12 @@ public abstract class AbsSwipeUpHandler, animatorSet.play(windowAnim); if (mRecentsView != null) { mRecentsView.onPrepareGestureEndAnimation( - animatorSet, mGestureState.getEndTarget(), mTaskViewSimulator); + animatorSet, mGestureState.getEndTarget(), + getRemoteTaskViewSimulators()); } animatorSet.setDuration(duration).setInterpolator(interpolator); animatorSet.start(); - mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet); + mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)}; } } @@ -1272,16 +1310,21 @@ public abstract class AbsSwipeUpHandler, } } + /** + * TODO(b/195473090) handle multiple task simulators (if needed) for PIP + */ private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory, RemoteAnimationTargetCompat runningTaskTarget, float startProgress) { // Directly animate the app to PiP (picture-in-picture) mode final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask(); - final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState(); + final RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator + .getOrientationState(); final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState); final int homeRotation = orientationState.getRecentsActivityRotation(); final Matrix homeToWindowPositionMap = new Matrix(); - final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress); + final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, + startProgress)[0]; // Move the startRect to Launcher space as floatingIconView runs in Launcher final Matrix windowToHomePositionMap = new Matrix(); homeToWindowPositionMap.invert(windowToHomePositionMap); @@ -1310,7 +1353,7 @@ public abstract class AbsSwipeUpHandler, // is not ROTATION_0 (which implies the rotation is turned on in launcher settings). if (homeRotation == ROTATION_0 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) { - builder.setFromRotation(mTaskViewSimulator, windowRotation, + builder.setFromRotation(mRemoteTargetHandles[0].mTaskViewSimulator, windowRotation, taskInfo.displayCutoutInsets); } final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build(); @@ -1340,7 +1383,7 @@ public abstract class AbsSwipeUpHandler, mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); - setupWindowAnimation(swipePipToHomeAnimator); + setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator}); return swipePipToHomeAnimator; } @@ -1367,19 +1410,19 @@ public abstract class AbsSwipeUpHandler, * @param homeAnimationFactory The home animation factory. */ @Override - protected RectFSpringAnim createWindowAnimationToHome(float startProgress, + protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { - RectFSpringAnim anim = + RectFSpringAnim[] anim = super.createWindowAnimationToHome(startProgress, homeAnimationFactory); setupWindowAnimation(anim); return anim; } - private void setupWindowAnimation(RectFSpringAnim anim) { - anim.addOnUpdateListener((v, r, p) -> { + private void setupWindowAnimation(RectFSpringAnim[] anims) { + anims[0].addOnUpdateListener((v, r, p) -> { updateSysUiFlags(Math.max(p, mCurrentShift.value)); }); - anim.addAnimatorListener(new AnimationSuccessListener() { + anims[0].addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { if (mRecentsView != null) { @@ -1391,7 +1434,7 @@ public abstract class AbsSwipeUpHandler, } }); if (mRecentsAnimationTargets != null) { - mRecentsAnimationTargets.addReleaseCheck(anim); + mRecentsAnimationTargets.addReleaseCheck(anims[0]); } } @@ -1639,7 +1682,7 @@ public abstract class AbsSwipeUpHandler, * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}. */ private void maybeFinishSwipePipToHome() { - if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) { + if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) { SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome( mSwipePipToHomeAnimator.getComponentName(), mSwipePipToHomeAnimator.getDestinationBounds(), @@ -1680,8 +1723,8 @@ public abstract class AbsSwipeUpHandler, * depend on proper class initialization. */ protected void initAfterSubclassConstructor() { - initTransitionEndpoints( - mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile()); + initTransitionEndpoints(mRemoteTargetHandles[0].mTaskViewSimulator + .getOrientationState().getLauncherDeviceProfile()); } protected void performHapticFeedback() { @@ -1698,7 +1741,8 @@ public abstract class AbsSwipeUpHandler, protected void linkRecentsViewScroll() { SurfaceTransactionApplier.create(mRecentsView, applier -> { - mTransformParams.setSyncTransactionApplier(applier); + runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams + .setSyncTransactionApplier(applier)); runOnRecentsAnimationStart(() -> mRecentsAnimationTargets.addReleaseCheck(applier)); }); @@ -1824,19 +1868,25 @@ public abstract class AbsSwipeUpHandler, /** * Applies the transform on the recents animation */ - protected void applyWindowTransform() { - if (mWindowTransitionController != null) { - mWindowTransitionController.setProgress( - Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), - mDragLengthFactor); - } + protected void applyScrollAndTransform() { // No need to apply any transform if there is ongoing swipe-pip-to-home animator since // that animator handles the leash solely. - if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) { - if (mRecentsViewScrollLinked && mRecentsView != null) { - mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset()); + boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome; + boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null; + for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) { + AnimatorControllerWithResistance playbackController = remoteHandle.mPlaybackController; + if (playbackController != null) { + playbackController.setProgress(Math.max(mCurrentShift.value, + getScaleProgressDueToScroll()), mDragLengthFactor); + } + + if (notSwipingPipToHome) { + TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator; + if (setRecentsScroll) { + taskViewSimulator.setScroll(mRecentsView.getScrollOffset()); + } + taskViewSimulator.apply(remoteHandle.mTransformParams); } - mTaskViewSimulator.apply(mTransformParams); } ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate(); } @@ -1891,7 +1941,6 @@ public abstract class AbsSwipeUpHandler, } public interface Factory { - AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs); } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 624ade2304..76d39a0355 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -52,6 +52,7 @@ import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode.Mode; @@ -200,11 +201,37 @@ public abstract class BaseActivityInterface + remoteTargetHandle.mTransformParams.setHomeBuilderProxy( + FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp)); } } @@ -109,7 +112,8 @@ public class FallbackSwipeHandler extends protected void initTransitionEndpoints(DeviceProfile dp) { super.initTransitionEndpoints(dp); if (mRunningOverHome) { - mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale(); + // Full screen scale should be independent of remote target handle + mMaxLauncherScale = 1 / mRemoteTargetHandles[0].mTaskViewSimulator.getFullScreenScale(); } } @@ -174,7 +178,8 @@ public class FallbackSwipeHandler extends protected void notifyGestureAnimationStartToRecents() { if (mRunningOverHome) { if (SysUINavigationMode.getMode(mContext).hasGestures) { - mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask()); + mRecentsView.onGestureAnimationStartOnHome( + new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()}); } } else { super.notifyGestureAnimationStartToRecents(); @@ -202,19 +207,24 @@ public class FallbackSwipeHandler extends mHomeAlpha = new AnimatedFloat(); mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1); mVerticalShiftForScale.value = mCurrentShift.value; - mTransformParams.setHomeBuilderProxy( - this::updateHomeActivityTransformDuringHomeAnim); + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.mTransformParams.setHomeBuilderProxy( + FallbackHomeAnimationFactory.this + ::updateHomeActivityTransformDuringHomeAnim)); } else { mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha); mHomeAlpha.value = 0; - - mHomeAlphaParams.setHomeBuilderProxy( - this::updateHomeActivityTransformDuringHomeAnim); + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.mTransformParams.setHomeBuilderProxy( + FallbackHomeAnimationFactory.this + ::updateHomeActivityTransformDuringHomeAnim)); } mRecentsAlpha.value = 1; - mTransformParams.setBaseBuilderProxy( - this::updateRecentsActivityTransformDuringHomeAnim); + runActionOnRemoteHandles(remoteTargetHandle -> + remoteTargetHandle.mTransformParams.setHomeBuilderProxy( + FallbackHomeAnimationFactory.this + ::updateRecentsActivityTransformDuringHomeAnim)); } @NonNull diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index d91d5b06f9..c2613c01b6 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -71,7 +71,7 @@ public final class LauncherActivityInterface extends @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler) { - calculateTaskSize(context, dp, outRect, orientationHandler); + calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) { return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right); } else { diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index 3239b0064a..ce3406cd4f 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -110,7 +110,7 @@ public class LauncherSwipeHandlerV2 extends mActivity.setHintUserWillBeActive(); } - if (!canUseWorkspaceView || appCanEnterPip) { + if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) { return new LauncherHomeAnimationFactory(); } if (workspaceView instanceof LauncherAppWidgetHostView) { @@ -181,14 +181,16 @@ public class LauncherSwipeHandlerV2 extends final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1; RectF backgroundLocation = new RectF(); Rect crop = new Rect(); - mTaskViewSimulator.getCurrentCropRect().roundOut(crop); + // We can assume there is only one remote target here because staged split never animates + // into the app icon, only into the homescreen + mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCropRect().roundOut(crop); Size windowSize = new Size(crop.width(), crop.height()); int fallbackBackgroundColor = FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget); FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity, hostView, backgroundLocation, windowSize, - mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent, - fallbackBackgroundColor); + mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCornerRadius(), + isTargetTranslucent, fallbackBackgroundColor); return new FloatingViewHomeAnimationFactory(floatingWidgetView) { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java index 239233b191..e9482219de 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java @@ -16,6 +16,7 @@ package com.android.quickstep; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; import android.graphics.Rect; import android.util.ArraySet; @@ -30,6 +31,7 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import java.util.Arrays; import java.util.Set; /** @@ -93,8 +95,16 @@ public class RecentsAnimationCallbacks implements RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Rect homeContentInsets, Rect minimizedHomeBounds) { + // Convert appTargets to type RemoteAnimationTarget for all apps except Home app + RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets) + .filter(remoteAnimationTarget -> + remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME) + .map(RemoteAnimationTargetCompat::unwrap) + .toArray(RemoteAnimationTarget[]::new); + RemoteAnimationTarget[] nonAppTargets = - mSystemUiProxy.onGoingToRecentsLegacy(mCancelled); + mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps); + RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets, wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets), homeContentInsets, minimizedHomeBounds); diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java index c032889db5..b20d48806a 100644 --- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java @@ -77,8 +77,12 @@ public class RemoteAnimationTargets { * Gets the navigation bar remote animation target if exists. */ public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() { + return getNonAppTargetOfType(TYPE_NAVIGATION_BAR); + } + + public RemoteAnimationTargetCompat getNonAppTargetOfType(int type) { for (RemoteAnimationTargetCompat target : nonApps) { - if (target.windowType == TYPE_NAVIGATION_BAR) { + if (target.windowType == type) { return target; } } diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index 44954555b0..ec51599fbf 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -15,9 +15,13 @@ */ package com.android.quickstep; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; + import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT; import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import android.animation.Animator; import android.content.Context; @@ -36,8 +40,11 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.util.SplitConfigurationOptions; +import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.AppCloseConfig; +import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RectFSpringAnim2; import com.android.quickstep.util.TaskViewSimulator; @@ -46,7 +53,11 @@ import com.android.quickstep.util.TransformParams.BuilderProxy; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; -public abstract class SwipeUpAnimationLogic { +import java.util.Arrays; +import java.util.function.Consumer; + +public abstract class SwipeUpAnimationLogic implements + RecentsAnimationCallbacks.RecentsAnimationListener{ protected static final Rect TEMP_RECT = new Rect(); @@ -55,9 +66,9 @@ public abstract class SwipeUpAnimationLogic { protected final Context mContext; protected final RecentsAnimationDeviceState mDeviceState; protected final GestureState mGestureState; - protected final TaskViewSimulator mTaskViewSimulator; - protected final TransformParams mTransformParams; + protected final RemoteTargetHandle[] mRemoteTargetHandles; + protected SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds; // Shift in the range of [0, 1]. // 0 => preview snapShot is completely visible, and hotseat is completely translated down @@ -70,37 +81,56 @@ public abstract class SwipeUpAnimationLogic { // How much further we can drag past recents, as a factor of mTransitionDragLength. protected float mDragLengthFactor = 1; - protected AnimatorControllerWithResistance mWindowTransitionController; + protected boolean mIsSwipeForStagedSplit; public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState, TransformParams transformParams) { mContext = context; mDeviceState = deviceState; mGestureState = gestureState; - mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); - mTransformParams = transformParams; - mTaskViewSimulator.getOrientationState().update( + mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() && + LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds().length > 1; + + TaskViewSimulator primaryTVS = new TaskViewSimulator(context, + gestureState.getActivityInterface()); + primaryTVS.getOrientationState().update( mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), mDeviceState.getRotationTouchHelper().getDisplayRotation()); + mRemoteTargetHandles = new RemoteTargetHandle[mIsSwipeForStagedSplit ? 2 : 1]; + mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTVS, transformParams); + + if (mIsSwipeForStagedSplit) { + TaskViewSimulator secondaryTVS = new TaskViewSimulator(context, + gestureState.getActivityInterface()); + secondaryTVS.getOrientationState().update( + mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), + mDeviceState.getRotationTouchHelper().getDisplayRotation()); + mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTVS, new TransformParams()); + } } protected void initTransitionEndpoints(DeviceProfile dp) { mDp = dp; - - mTaskViewSimulator.setDp(dp); mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( - dp, mContext, TEMP_RECT, - mTaskViewSimulator.getOrientationState().getOrientationHandler()); + dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].mTaskViewSimulator + .getOrientationState().getOrientationHandler()); mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; - PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); - mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR); - AnimatorPlaybackController normalController = pa.createPlaybackController(); - mWindowTransitionController = AnimatorControllerWithResistance.createForRecents( - normalController, mContext, mTaskViewSimulator.getOrientationState(), - mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE, - mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE); + for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) { + PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2); + TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator; + taskViewSimulator.setDp(dp); + taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR); + AnimatorPlaybackController playbackController = + pendingAnimation.createPlaybackController(); + + remoteHandle.mPlaybackController = AnimatorControllerWithResistance.createForRecents( + playbackController, mContext, taskViewSimulator.getOrientationState(), + mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE, + taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE + ); + } } @UiThread @@ -125,7 +155,9 @@ public abstract class SwipeUpAnimationLogic { public abstract void updateFinalShift(); protected PagedOrientationHandler getOrientationHandler() { - return mTaskViewSimulator.getOrientationState().getOrientationHandler(); + // OrientationHandler should be independent of remote target, can directly take one + return mRemoteTargetHandles[0].mTaskViewSimulator + .getOrientationState().getOrientationHandler(); } protected abstract class HomeAnimationFactory { @@ -207,31 +239,102 @@ public abstract class SwipeUpAnimationLogic { * @param startProgress The progress of {@link #mCurrentShift} to start thw window from. * @return {@link RectF} represents the bounds as starting point in window space. */ - protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) { + protected RectF[] updateProgressForStartRect(Matrix outMatrix, float startProgress) { mCurrentShift.updateValue(startProgress); - mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); - RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); + RectF[] startRects = new RectF[mRemoteTargetHandles.length]; + for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; + i < mRemoteTargetHandlesLength; i++) { + RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; + TaskViewSimulator tvs = remoteHandle.mTaskViewSimulator; + tvs.apply(remoteHandle.mTransformParams.setProgress(startProgress)); - mTaskViewSimulator.applyWindowToHomeRotation(outMatrix); - - final RectF startRect = new RectF(cropRectF); - mTaskViewSimulator.getCurrentMatrix().mapRect(startRect); - return startRect; + startRects[i] = new RectF(tvs.getCurrentCropRect()); + tvs.applyWindowToHomeRotation(outMatrix); + tvs.getCurrentMatrix().mapRect(startRects[i]); + } + return startRects; } + /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */ + protected void runActionOnRemoteHandles(Consumer consumer) { + for (RemoteTargetHandle handle : mRemoteTargetHandles) { + consumer.accept(handle); + } + } + + /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */ + protected TaskViewSimulator[] getRemoteTaskViewSimulators() { + return Arrays.stream(mRemoteTargetHandles) + .map(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator) + .toArray(TaskViewSimulator[]::new); + } + + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length); + RemoteAnimationTargetCompat dividerTarget = targets.getNonAppTargetOfType( + TYPE_DOCK_DIVIDER); + RemoteAnimationTargetCompat primaryTaskTarget; + RemoteAnimationTargetCompat secondaryTaskTarget; + + if (!mIsSwipeForStagedSplit) { + primaryTaskTarget = targets.findTask(mGestureState.getRunningTaskId()); + mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets); + + if (primaryTaskTarget != null) { + mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget); + } + } else { + // We're in staged split + primaryTaskTarget = targets.apps[0]; + secondaryTaskTarget = targets.apps[1]; + mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds( + primaryTaskTarget.screenSpaceBounds, + secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds); + mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, + mStagedSplitBounds); + mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget, + mStagedSplitBounds); + mRemoteTargetHandles[0].mTransformParams.setTargetSet( + createRemoteAnimationTargetsForTarget(primaryTaskTarget)); + mRemoteTargetHandles[1].mTransformParams.setTargetSet( + createRemoteAnimationTargetsForTarget(secondaryTaskTarget)); + } + } + + private RemoteAnimationTargets createRemoteAnimationTargetsForTarget( + RemoteAnimationTargetCompat target) { + return new RemoteAnimationTargets(new RemoteAnimationTargetCompat[]{target}, + null, null, MODE_CLOSING); + } /** * 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. */ - protected RectFSpringAnim createWindowAnimationToHome(float startProgress, + protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { + // TODO(b/195473584) compute separate end targets for different staged split final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); - + RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length]; Matrix homeToWindowPositionMap = new Matrix(); - final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress); - RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); + RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress); + for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; + i < mRemoteTargetHandlesLength; i++) { + RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; + out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory, + targetRect, remoteHandle.mTransformParams, remoteHandle.mTaskViewSimulator, + startRects[i], homeToWindowPositionMap); + } + return out; + } + private RectFSpringAnim getWindowAnimationToHomeInternal( + HomeAnimationFactory homeAnimationFactory, RectF targetRect, + TransformParams transformParams, TaskViewSimulator taskViewSimulator, + RectF startRect, Matrix homeToWindowPositionMap) { + RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect()); // Move the startRect to Launcher space as floatingIconView runs in Launcher Matrix windowToHomePositionMap = new Matrix(); homeToWindowPositionMap.invert(windowToHomePositionMap); @@ -240,7 +343,7 @@ public abstract class SwipeUpAnimationLogic { RectFSpringAnim anim; if (PROTOTYPE_APP_CLOSE.get()) { anim = new RectFSpringAnim2(startRect, targetRect, mContext, - mTaskViewSimulator.getCurrentCornerRadius(), + taskViewSimulator.getCurrentCornerRadius(), homeAnimationFactory.getEndRadius(cropRectF)); } else { anim = new RectFSpringAnim(startRect, targetRect, mContext); @@ -248,9 +351,10 @@ public abstract class SwipeUpAnimationLogic { homeAnimationFactory.setAnimation(anim); SpringAnimationRunner runner = new SpringAnimationRunner( - homeAnimationFactory, cropRectF, homeToWindowPositionMap); - anim.addOnUpdateListener(runner); + homeAnimationFactory, cropRectF, homeToWindowPositionMap, + transformParams, taskViewSimulator); anim.addAnimatorListener(runner); + anim.addOnUpdateListener(runner); return anim; } @@ -262,6 +366,7 @@ public abstract class SwipeUpAnimationLogic { final RectF mWindowCurrentRect = new RectF(); final Matrix mHomeToWindowPositionMap; + private final TransformParams mLocalTransformParams; final HomeAnimationFactory mAnimationFactory; final AnimatorPlaybackController mHomeAnim; @@ -271,17 +376,19 @@ public abstract class SwipeUpAnimationLogic { final float mEndRadius; SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, - Matrix homeToWindowPositionMap) { + Matrix homeToWindowPositionMap, TransformParams transformParams, + TaskViewSimulator taskViewSimulator) { mAnimationFactory = factory; mHomeAnim = factory.createActivityAnimationToHome(); mCropRectF = cropRectF; mHomeToWindowPositionMap = homeToWindowPositionMap; + mLocalTransformParams = transformParams; cropRectF.roundOut(mCropRect); // End on a "round-enough" radius so that the shape reveal doesn't have to do too much // rounding at the end of the animation. - mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); + mStartRadius = taskViewSimulator.getCurrentCornerRadius(); mEndRadius = factory.getEndRadius(cropRectF); } @@ -300,10 +407,11 @@ public abstract class SwipeUpAnimationLogic { if (mAnimationFactory.keepWindowOpaque()) { alpha = 1f; } - mTransformParams + mLocalTransformParams .setTargetAlpha(alpha) .setCornerRadius(cornerRadius); - mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); + mLocalTransformParams.applySurfaceParams(mLocalTransformParams + .createSurfaceParams(this)); mAnimationFactory.update(config, currentRect, progress, mMatrix.mapRadius(cornerRadius)); } @@ -332,6 +440,21 @@ public abstract class SwipeUpAnimationLogic { } } + /** + * Container to keep together all the associated objects whose properties need to be updated to + * animate a single remote app target + */ + public static class RemoteTargetHandle { + public TaskViewSimulator mTaskViewSimulator; + public TransformParams mTransformParams; + public AnimatorControllerWithResistance mPlaybackController; + public RemoteTargetHandle(TaskViewSimulator taskViewSimulator, + TransformParams transformParams) { + mTransformParams = transformParams; + mTaskViewSimulator = taskViewSimulator; + } + } + public interface RunningWindowAnim { void end(); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 11ca4b1550..7d2d41326c 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -17,6 +17,7 @@ package com.android.quickstep; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import android.app.ActivityManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.content.ComponentName; @@ -632,10 +633,11 @@ public class SystemUiProxy implements ISystemUiProxy, * @param cancel true if recents starting is being cancelled. * @return RemoteAnimationTargets of windows that need to animate but only exist in shell. */ - public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) { + public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, + RemoteAnimationTarget[] apps) { if (mSplitScreen != null) { try { - return mSplitScreen.onGoingToRecentsLegacy(cancel); + return mSplitScreen.onGoingToRecentsLegacy(cancel, apps); } catch (RemoteException e) { Log.w(TAG, "Failed call onGoingToRecentsLegacy"); } diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index b5da097c31..5b9e214aba 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -154,9 +154,10 @@ public final class TaskViewUtils { boolean isRunningTask = v.isRunningTask(); TransformParams params = null; TaskViewSimulator tsv = null; + // TODO(b/195675206) handle two TSVs here if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) { - params = v.getRecentsView().getLiveTileParams(); - tsv = v.getRecentsView().getLiveTileTaskViewSimulator(); + params = v.getRecentsView().getRemoteTargetHandles()[0].mTransformParams; + tsv = v.getRecentsView().getRemoteTargetHandles()[0].mTaskViewSimulator; } createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets, depthController, out, params, tsv); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 4979206791..20eff34523 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -99,6 +99,7 @@ import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; +import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.util.ProtoTracer; import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.SplitScreenBounds; @@ -364,6 +365,7 @@ public class TouchInteractionService extends Service implements PluginListener 1) { + // can't be in split screen w/ home task + return super.shouldAddStubTaskView(runningTaskInfos); + } + + RunningTaskInfo runningTaskInfo = runningTaskInfos[0]; if (mHomeTaskInfo != null && runningTaskInfo != null && mHomeTaskInfo.taskId == runningTaskInfo.taskId && getTaskViewCount() == 0) { @@ -141,7 +149,7 @@ public class FallbackRecentsView extends RecentsView INSTANCE = + new MainThreadInitializedObject<>(LauncherSplitScreenListener::new); + + private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition(); + private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition(); + + public LauncherSplitScreenListener(Context context) { + mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN; + mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE; + } + + /** Also call {@link #destroy()} when done. */ + public void init() { + SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this); + } + + public void destroy() { + SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this); + } + + /** + * @return index 0 will be task in left/top position, index 1 in right/bottom position. + * Will return empty array if device is not in staged split + */ + public int[] getSplitTaskIds() { + if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) { + return new int[]{}; + } + int[] out = new int[2]; + if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) { + out[0] = mMainStagePosition.taskId; + out[1] = mSideStagePosition.taskId; + } else { + out[1] = mMainStagePosition.taskId; + out[0] = mSideStagePosition.taskId; + } + return out; + } + + @Override + public void onStagePositionChanged(@StageType int stage, @StagePosition int position) { + if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) { + mMainStagePosition.stagePosition = position; + } else { + mSideStagePosition.stagePosition = position; + } + } + + @Override + public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { + // If task is not visible but we are tracking it, stop tracking it + if (!visible) { + if (mMainStagePosition.taskId == taskId) { + resetTaskId(mMainStagePosition); + } else if (mSideStagePosition.taskId == taskId) { + resetTaskId(mSideStagePosition); + } // else it's an un-tracked child + return; + } + + // If stage has moved to undefined, stop tracking the task + if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) { + resetTaskId(taskId == mMainStagePosition.taskId ? + mMainStagePosition : mSideStagePosition); + return; + } + + if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) { + mMainStagePosition.taskId = taskId; + } else { + mSideStagePosition.taskId = taskId; + } + } + + private void resetTaskId(StagedSplitTaskPosition taskPosition) { + taskPosition.taskId = -1; + } + + @Override + public IBinder asBinder() { + return this; + } +} diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 8834dc2c6c..302526d07a 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -42,8 +42,7 @@ public class LayoutUtils { PagedOrientationHandler orientationHandler) { // Track the bottom of the window. Rect taskSize = new Rect(); - LauncherActivityInterface.INSTANCE.calculateTaskSize( - context, dp, taskSize, orientationHandler); + LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize); return orientationHandler.getDistanceToBottomOfRect(dp, taskSize); } diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 7eee415315..7b1c62e861 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -17,6 +17,7 @@ package com.android.quickstep.util; import static com.android.launcher3.states.RotationHelper.deltaRotation; import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE; +import static com.android.launcher3.util.SplitConfigurationOptions.*; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; @@ -29,6 +30,7 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.util.Log; import androidx.annotation.NonNull; @@ -49,8 +51,13 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat. */ public class TaskViewSimulator implements TransformParams.BuilderProxy { + private final String TAG = "TaskViewSimulator"; + private final boolean DEBUG = false; + private final Rect mTmpCropRect = new Rect(); private final RectF mTempRectF = new RectF(); + // Additional offset for split tasks + private final Point mSplitOffset = new Point(); private final float[] mTempPoint = new float[2]; private final Context mContext; @@ -63,6 +70,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final Rect mTaskRect = new Rect(); private final PointF mPivot = new PointF(); private DeviceProfile mDp; + @StagePosition + private int mStagePosition = STAGE_POSITION_UNDEFINED; private final Matrix mMatrix = new Matrix(); private final Matrix mMatrixTmp = new Matrix(); @@ -89,6 +98,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { // Cached calculations private boolean mLayoutValid = false; private int mOrientationStateId; + private StagedSplitBounds mStagedSplitBounds; public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; @@ -128,9 +138,19 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { if (mDp == null) { return 1; } - mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect, - mOrientationState.getOrientationHandler()); - return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot); + Rect fullTaskSize = new Rect(); + mSizeStrategy.calculateTaskSize(mContext, mDp, fullTaskSize); + + if (mStagedSplitBounds != null) { + // The task rect changes according to the staged split task sizes, but recents + // fullscreen scale and pivot remains the same since the task fits into the existing + // sized task space bounds + mSizeStrategy.calculateStagedSplitTaskSize(mContext, mDp, mTaskRect, mStagedSplitBounds, + mStagePosition); + } else { + mTaskRect.set(fullTaskSize); + } + return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot); } /** @@ -142,6 +162,24 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { runningTarget.screenSpaceBounds.top); } + /** + * Sets the targets which the simulator will control specifically for targets to animate when + * in split screen + * + * @param splitInfo set to {@code null} when not in staged split mode + */ + public void setPreview(RemoteAnimationTargetCompat runningTarget, StagedSplitBounds splitInfo) { + setPreview(runningTarget); + mStagedSplitBounds = splitInfo; + if (mStagedSplitBounds == null) { + mStagePosition = STAGE_POSITION_UNDEFINED; + return; + } + mStagePosition = mThumbnailPosition.equals(splitInfo.mLeftTopBounds) ? + STAGE_POSITION_TOP_OR_LEFT : + STAGE_POSITION_BOTTOM_OR_RIGHT; + } + /** * Sets the targets which the simulator will control */ @@ -239,6 +277,15 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { getFullScreenScale(); mThumbnailData.rotation = mOrientationState.getDisplayRotation(); + // TODO(b/195145340) handle non 50-50 split scenarios + if (mStagedSplitBounds != null) { + if (mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { + // The preview set is for the bottom/right, inset by top/left task + mSplitOffset.y = mStagedSplitBounds.mLeftTopBounds.height() + + mStagedSplitBounds.mDividerBounds.height() / 2; + } + } + // mIsRecentsRtl is the inverse of TaskView RTL. boolean isRtlEnabled = !mIsRecentsRtl; mPositionHelper.updateThumbnailMatrix( @@ -246,6 +293,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mTaskRect.width(), mTaskRect.height(), mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled); mPositionHelper.getMatrix().invert(mInversePositionMatrix); + if (DEBUG) { + Log.d(TAG, " taskRect: " + mTaskRect + " splitOffset: " + mSplitOffset); + } } float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1); @@ -280,6 +330,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { recentsViewPrimaryTranslation.value); applyWindowToHomeRotation(mMatrix); + // Move lower/right split window into correct position + mMatrix.postTranslate(0, mSplitOffset.y); + // Crop rect is the inverse of thumbnail matrix mTempRectF.set(-insets.left, -insets.top, taskWidth + insets.right, taskHeight + insets.bottom); @@ -287,6 +340,25 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mTempRectF.roundOut(mTmpCropRect); params.applySurfaceParams(params.createSurfaceParams(this)); + + if (!DEBUG) { + return; + } + Log.d(TAG, "progress: " + fullScreenProgress + + " scale: " + scale + + " recentsViewScale: " + recentsViewScale.value + + " crop: " + mTmpCropRect + + " radius: " + getCurrentCornerRadius() + + " translate: " + mSplitOffset + + " taskW: " + taskWidth + " H: " + taskHeight + + " taskRect: " + mTaskRect + + " taskPrimaryT: " + taskPrimaryTranslation.value + + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value + + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value + + " taskSecondaryT: " + taskSecondaryTranslation.value + + " recentsScroll: " + recentsViewScroll.value + + " pivot: " + mPivot + ); } @Override diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java new file mode 100644 index 0000000000..cd20f4bcbf --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -0,0 +1,137 @@ +package com.android.quickstep.views; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.quickstep.RecentsModel; +import com.android.quickstep.TaskThumbnailCache; +import com.android.quickstep.util.CancellableTask; +import com.android.quickstep.util.RecentsOrientedState; +import com.android.systemui.shared.recents.model.Task; + +/** + * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks + * + * That's right. If you call within the next 5 minutes we'll go ahead and double your order and + * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House. + * And not only that, we'll even clean up your thumbnail request if you don't like it. + * All the benefits of one TaskView, except DOUBLED! + * + * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included). + */ +public class GroupedTaskView extends TaskView { + + private Task mSecondaryTask; + private TaskThumbnailView mSnapshotView2; + private CancellableTask mThumbnailLoadRequest2; + + public GroupedTaskView(Context context) { + super(context); + } + + public GroupedTaskView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSnapshotView2 = findViewById(R.id.bottomright_snapshot); + } + + public void bind(Task primary, Task secondary, RecentsOrientedState orientedState) { + super.bind(primary, orientedState); + mSecondaryTask = secondary; + mTaskIdContainer[1] = secondary.key.id; + mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2); + mSnapshotView2.bind(secondary); + adjustThumbnailBoundsForSplit(); + } + + @Override + public void onTaskListVisibilityChanged(boolean visible, int changes) { + super.onTaskListVisibilityChanged(visible, changes); + if (visible) { + RecentsModel model = RecentsModel.INSTANCE.get(getContext()); + TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); + + if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { + mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask, + thumbnailData -> mSnapshotView2.setThumbnail( + mSecondaryTask, thumbnailData + )); + } + + if (needsUpdate(changes, FLAG_UPDATE_ICON)) { + // TODO What's the Icon for this going to look like? :o + } + } else { + if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { + mSnapshotView2.setThumbnail(null, null); + // Reset the task thumbnail reference as well (it will be fetched from the cache or + // reloaded next time we need it) + mSecondaryTask.thumbnail = null; + } + if (needsUpdate(changes, FLAG_UPDATE_ICON)) { + // TODO + } + } + } + + @Override + protected void cancelPendingLoadTasks() { + super.cancelPendingLoadTasks(); + if (mThumbnailLoadRequest2 != null) { + mThumbnailLoadRequest2.cancel(); + mThumbnailLoadRequest2 = null; + } + } + + @Override + public void onRecycle() { + super.onRecycle(); + mSnapshotView2.setThumbnail(mSecondaryTask, null); + } + + @Override + public void setOverlayEnabled(boolean overlayEnabled) { + super.setOverlayEnabled(overlayEnabled); + mSnapshotView2.setOverlayEnabled(overlayEnabled); + } + + private void adjustThumbnailBoundsForSplit() { + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams(); + primaryLp.width = mSecondaryTask == null ? + MATCH_PARENT : + getWidth(); + int spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx; + // TODO get divider height + int dividerBar = 20; + primaryLp.height = mSecondaryTask == null ? + MATCH_PARENT : + (getHeight() - spaceAboveSnapshot - dividerBar) / 2; + mSnapshotView.setLayoutParams(primaryLp); + + if (mSecondaryTask == null) { + mSnapshotView2.setVisibility(GONE); + return; + } + + mSnapshotView2.setVisibility(VISIBLE); + ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams(); + secondaryLp.width = getWidth(); + secondaryLp.height = primaryLp.height; + mSnapshotView2.setLayoutParams(secondaryLp); + mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar); + } +} diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 538e61e83e..03827d2063 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -19,6 +19,7 @@ package com.android.quickstep.views; import static android.view.Surface.ROTATION_0; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; @@ -51,6 +52,7 @@ import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -143,11 +145,13 @@ import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.RemoteAnimationTargets; +import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.ViewUtils; +import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.SplitScreenBounds; @@ -317,7 +321,13 @@ public abstract class RecentsView() { + @Override + public void accept(RemoteTargetHandle remoteTargetHandle) { + remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value = + scale; + } + }); view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation); view.updatePageOffsets(); } @@ -365,8 +375,7 @@ public abstract class RecentsView mTaskViewPool; + private final ViewPool mGroupedTaskViewPool; private final TaskOverlayFactory mTaskOverlayFactory; @@ -504,13 +514,13 @@ public abstract class RecentsView(context, this, R.layout.task, 20 /* max size */, 10 /* initial size */); + // There's only one pair of grouped tasks we can envision at the moment + mGroupedTaskViewPool = new ViewPool<>(context, this, + R.layout.task_grouped, 2 /* max size */, 1 /* initial size */); mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); @@ -656,10 +669,6 @@ public abstract class RecentsView remoteTargetHandle.mTransformParams + .setSyncTransactionApplier(mSyncTransactionApplier)); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIPipAnimationListener.setActivityAndRecentsView(mActivity, this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( @@ -830,7 +844,8 @@ public abstract class RecentsView remoteTargetHandle.mTransformParams + .setSyncTransactionApplier(null)); executeSideTaskLaunchCallback(); RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); @@ -851,9 +866,15 @@ public abstract class RecentsView requiredTaskCount) { + while (getTaskViewCount() > requiredTaskViewCount) { removeView(getChildAt(getChildCount() - 1)); } - if (requiredTaskCount > 0) { + while (requiredGroupTaskViews > 0) { + // Add to front of list + addView(getTaskViewFromPool(true), 0); + requiredGroupTaskViews--; + } + if (requiredTaskViewCount > 0) { addView(mClearAllButton); } } @@ -1245,12 +1296,28 @@ public abstract class RecentsView= 0; i--) { - final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; - final Task task = tasks.get(i); + for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = tasks.size() - 1; + taskViewIndex >= 0; + taskViewIndex--, taskDataIndex--) { + final int pageIndex = requiredTaskViewCount - taskViewIndex - 1 + mTaskViewStartIndex; + final Task task = tasks.get(taskDataIndex); final TaskView taskView = (TaskView) getChildAt(pageIndex); - taskView.bind(task, mOrientationState); + if (taskView instanceof GroupedTaskView) { + Task leftTop; + Task rightBottom; + if (task.key.id == splitTaskIds[0]) { + leftTop = task; + taskDataIndex--; + rightBottom = tasks.get(taskDataIndex); + } else { + rightBottom = task; + taskDataIndex--; + leftTop = tasks.get(taskDataIndex); + } + ((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState); + } else { + taskView.bind(task, mOrientationState); + } } // Keep same previous focused task @@ -1270,8 +1337,8 @@ public abstract class RecentsView= 0; i--) { - final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; + for (int i = requiredTaskViewCount - 1; i >= 0; i--) { + final int pageIndex = requiredTaskViewCount - i - 1 + mTaskViewStartIndex; final TaskView taskView = (TaskView) getChildAt(pageIndex); int taskViewId = taskView.getTaskViewId(); sb.append(" taskViewId: " + taskViewId @@ -1354,12 +1421,12 @@ public abstract class RecentsView { + remoteTargetHandle.mTaskViewSimulator.taskPrimaryTranslation.value = 0; + remoteTargetHandle.mTaskViewSimulator.taskSecondaryTranslation.value = 0; + remoteTargetHandle.mTaskViewSimulator.fullScreenProgress.value = 0; + remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value = 1; + }); // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is // null. @@ -1413,7 +1480,8 @@ public abstract class RecentsView remoteTargetHandle.mTaskViewSimulator.setDp(dp)); mActionsView.setDp(dp); mOrientationState.setDeviceProfile(dp); @@ -1524,8 +1592,7 @@ public abstract class RecentsView remoteTargetHandle.mTransformParams + .setTargetSet(null)); // These are relatively expensive and don't need to be done this frame (RecentsView isn't // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. @@ -1810,8 +1886,10 @@ public abstract class RecentsView T getTaskViewFromPool(boolean isGrouped) { + T taskView = isGrouped ? + (T) mGroupedTaskViewPool.getView() : + (T) mTaskViewPool.getView(); taskView.setTaskViewId(mTaskViewIdCount); if (mTaskViewIdCount == Integer.MAX_VALUE) { mTaskViewIdCount = 0; @@ -1847,7 +1925,7 @@ public abstract class RecentsView 1) { + // * Always create new view for GroupedTaskView + // * Remove existing associated taskViews for tasks currently in split + for (RunningTaskInfo rti : runningTaskInfos) { + TaskView taskView = getTaskViewByTaskId(rti.taskId); + if (taskView == null) { + continue; + } + taskView.onTaskListVisibilityChanged(false); + removeView(taskView); + } + return true; + } + RunningTaskInfo runningTaskInfo = runningTaskInfos[0]; return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null; } @@ -1976,29 +2071,44 @@ public abstract class RecentsView 1; + RunningTaskInfo taskInfo = runningTaskInfo[0]; if (shouldAddStubTaskView(runningTaskInfo)) { boolean wasEmpty = getChildCount() == 0; // Add an empty view for now until the task plan is loaded and applied - final TaskView taskView = getTaskViewFromPool(); + final TaskView taskView; + if (needGroupTaskView) { + taskView = getTaskViewFromPool(true); + RunningTaskInfo secondaryTaskInfo = runningTaskInfo[1]; + mTmpRunningTasks = new Task[]{ + Task.from(new TaskKey(taskInfo), taskInfo, false), + Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false) + }; + addView(taskView, mTaskViewStartIndex); + ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1], + mOrientationState); + } else { + taskView = getTaskViewFromPool(false); + addView(taskView, mTaskViewStartIndex); + // The temporary running task is only used for the duration between the start of the + // gesture and the task list is loaded and applied + mTmpRunningTasks = new Task[]{Task.from(new TaskKey(taskInfo), taskInfo, false)}; + taskView.bind(mTmpRunningTasks[0], mOrientationState); + } runningTaskViewId = taskView.getTaskViewId(); - addView(taskView, mTaskViewStartIndex); if (wasEmpty) { addView(mClearAllButton); } - // The temporary running task is only used for the duration between the start of the - // gesture and the task list is loaded and applied - mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false); - taskView.bind(mTmpRunningTask, mOrientationState); // Measure and layout immediately so that the scroll values is updated instantly // as the user might be quick-switching measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), makeMeasureSpec(getMeasuredHeight(), EXACTLY)); layout(getLeft(), getTop(), getRight(), getBottom()); - } else if (getTaskViewByTaskId(runningTaskInfo.taskId) != null) { - runningTaskViewId = getTaskViewByTaskId(runningTaskInfo.taskId).getTaskViewId(); + } else if (!needGroupTaskView && getTaskViewByTaskId(taskInfo.taskId) != null) { + runningTaskViewId = getTaskViewByTaskId(taskInfo.taskId).getTaskViewId(); } boolean runningTaskTileHidden = mRunningTaskTileHidden; @@ -2413,8 +2523,11 @@ public abstract class RecentsView { + TransformParams params = remoteTargetHandle.mTransformParams; + anim.setFloat(params, TransformParams.TARGET_ALPHA, 0, + clampToProgress(ACCEL, 0, 0.5f)); + }); } anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f)); FloatProperty secondaryViewTranslate = @@ -2433,10 +2546,12 @@ public abstract class RecentsView { - mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = - mOrientationHandler.getSecondaryValue( - taskView.getTranslationX(), - taskView.getTranslationY()); + runActionOnRemoteHandles( + remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator + .taskSecondaryTranslation.value = mOrientationHandler + .getSecondaryValue(taskView.getTranslationX(), + taskView.getTranslationY() + )); redrawLiveTile(); }); } @@ -2487,7 +2602,6 @@ public abstract class RecentsView { - mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = - mOrientationHandler.getPrimaryValue(child.getTranslationX(), - child.getTranslationY()); + runActionOnRemoteHandles( + remoteTargetHandle -> + remoteTargetHandle.mTaskViewSimulator + .taskPrimaryTranslation.value = + mOrientationHandler.getPrimaryValue( + child.getTranslationX(), + child.getTranslationY() + )); redrawLiveTile(); }); } @@ -2669,9 +2788,9 @@ public abstract class RecentsView removeTaskInternal(dismissedTaskId)); + () -> removeTaskInternal(dismissedTaskViewId)); } else { - removeTaskInternal(dismissedTaskId); + removeTaskInternal(dismissedTaskViewId); } mActivity.getStatsLogManager().logger() .withItemInfo(dismissedTaskView.getItemInfo()) @@ -2801,9 +2920,17 @@ public abstract class RecentsView ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId), + () -> { + ActivityManagerWrapper.getInstance().removeTask(primaryTaskId); + if (secondaryTaskId != -1) { + ActivityManagerWrapper.getInstance().removeTask(secondaryTaskId); + } + }, REMOVE_TASK_WAIT_FOR_APP_STOP_MS); } @@ -3136,7 +3263,9 @@ public abstract class RecentsView remoteTargetHandle.mTaskViewSimulator + .setScroll(getScrollOffset())); setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_AUTO); } @@ -3200,7 +3329,9 @@ public abstract class RecentsView remoteTargetHandle.mTaskViewSimulator + .taskPrimaryTranslation.value = totalTranslation); redrawLiveTile(); } } @@ -3303,7 +3434,9 @@ public abstract class RecentsView remoteTargetHandle.mTaskViewSimulator + .recentsViewSecondaryTranslation.value = translation); } protected void setTaskViewsPrimarySplitTranslation(float translation) { @@ -3505,8 +3638,6 @@ public abstract class RecentsView remoteTargetHandle.mTaskViewSimulator + .addOverviewToAppAnim(mPendingAnimation, interpolator)); mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); } mPendingAnimation.addEndListener(isSuccess -> { @@ -3784,31 +3921,87 @@ public abstract class RecentsView { + TransformParams params = remoteTargetHandle.mTransformParams; + if (params.getTargetSet() != null) { + remoteTargetHandle.mTaskViewSimulator.apply(params); + } + }); } - public TaskViewSimulator getLiveTileTaskViewSimulator() { - return mLiveTileTaskViewSimulator; - } - - public TransformParams getLiveTileParams() { - return mLiveTileParams; + public RemoteTargetHandle[] getRemoteTargetHandles() { + return mRemoteTargetHandles; } // TODO: To be removed in a follow up CL public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets) { mRecentsAnimationController = recentsAnimationController; - if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) { - if (mSyncTransactionApplier != null) { - recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier); - } - mLiveTileTaskViewSimulator.setPreview( - recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]); - mLiveTileParams.setTargetSet(recentsAnimationTargets); + if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) { + return; } + + if (mSyncTransactionApplier != null) { + recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier); + } + + // TODO Consolidate this shared code with SwipeUpAnimationLogic (or mabe just reuse + // what that class has and pass it into here + mRemoteTargetHandles = new RemoteTargetHandle[recentsAnimationTargets.apps.length]; + TaskViewSimulator primaryTvs = createTaskViewSimulator(); + mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTvs, new TransformParams()); + if (recentsAnimationTargets.apps.length == 1) { + mRemoteTargetHandles[0].mTaskViewSimulator + .setPreview(recentsAnimationTargets.apps[0], null); + mRemoteTargetHandles[0].mTransformParams.setTargetSet(recentsAnimationTargets); + } else { + TaskViewSimulator secondaryTvs = createTaskViewSimulator(); + secondaryTvs.setOrientationState(mOrientationState); + secondaryTvs.recentsViewScale.value = 1; + + mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTvs, new TransformParams()); + RemoteAnimationTargetCompat dividerTarget = + recentsAnimationTargets.getNonAppTargetOfType(TYPE_DOCK_DIVIDER); + RemoteAnimationTargetCompat primaryTaskTarget = recentsAnimationTargets.apps[0]; + RemoteAnimationTargetCompat secondaryTaskTarget = recentsAnimationTargets.apps[1]; + SplitConfigurationOptions.StagedSplitBounds + info = new SplitConfigurationOptions.StagedSplitBounds( + primaryTaskTarget.screenSpaceBounds, + secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds); + mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, info); + mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget, info); + RemoteAnimationTargets rats = new RemoteAnimationTargets( + new RemoteAnimationTargetCompat[]{primaryTaskTarget}, + recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps, + MODE_CLOSING + ); + RemoteAnimationTargets splitRats = new RemoteAnimationTargets( + new RemoteAnimationTargetCompat[]{secondaryTaskTarget}, + recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps, + MODE_CLOSING + ); + mRemoteTargetHandles[0].mTransformParams.setTargetSet(rats); + mRemoteTargetHandles[1].mTransformParams.setTargetSet(splitRats); + } + } + + /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */ + private void runActionOnRemoteHandles(Consumer consumer) { + if (mRemoteTargetHandles == null) { + return; + } + + for (RemoteTargetHandle handle : mRemoteTargetHandles) { + consumer.accept(handle); + } + } + + private TaskViewSimulator createTaskViewSimulator() { + TaskViewSimulator tvs = new TaskViewSimulator(getContext(), getSizeStrategy()); + tvs.setOrientationState(mOrientationState); + tvs.setDp(mActivity.getDeviceProfile()); + tvs.recentsViewScale.value = 1; + return tvs; } public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { @@ -4114,14 +4307,42 @@ public abstract class RecentsView + remoteTargetHandle.mTaskViewSimulator.setScroll(getScrollOffset())); for (int i = mScrollListeners.size() - 1; i >= 0; i--) { mScrollListeners.get(i).onScrollChanged(); } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 2c33b6d30b..edd09db68d 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -91,6 +91,7 @@ import com.android.launcher3.util.TransformingTouchDelegate; import com.android.launcher3.util.ViewPool.Reusable; import com.android.quickstep.RecentsModel; import com.android.quickstep.RemoteAnimationTargets; +import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; @@ -100,16 +101,20 @@ import com.android.quickstep.TaskViewUtils; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.TaskCornerRadius; +import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.lang.annotation.Retention; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Stream; /** * A task in the Recents view. @@ -329,8 +334,8 @@ public class TaskView extends FrameLayout implements Reusable { private final TaskOutlineProvider mOutlineProvider; - private Task mTask; - private TaskThumbnailView mSnapshotView; + protected Task mTask; + protected TaskThumbnailView mSnapshotView; private IconView mIconView; private final DigitalWellBeingToast mDigitalWellBeingToast; private float mFullscreenProgress; @@ -338,7 +343,7 @@ public class TaskView extends FrameLayout implements Reusable { private float mNonGridScale = 1; private float mDismissScale = 1; private final FullscreenDrawParams mCurrentFullscreenParams; - private final StatefulActivity mActivity; + protected final StatefulActivity mActivity; // Various causes of changing primary translation, which we aggregate to setTranslationX/Y(). private float mDismissTranslationX; @@ -367,7 +372,12 @@ public class TaskView extends FrameLayout implements Reusable { private float mStableAlpha = 1; private int mTaskViewId = -1; - private final int[] mTaskIdContainer = new int[]{-1, -1}; + /** + * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right + */ + protected final int[] mTaskIdContainer = new int[]{-1, -1}; + protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer = + new TaskIdAttributeContainer[2]; private boolean mShowScreenshot; @@ -518,10 +528,15 @@ public class TaskView extends FrameLayout implements Reusable { cancelPendingLoadTasks(); mTask = task; mTaskIdContainer[0] = mTask.key.id; + mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView); mSnapshotView.bind(task); setOrientationState(orientedState); } + public TaskIdAttributeContainer[] getTaskIdAttributeContainers() { + return mTaskIdAttributeContainer; + } + public Task getTask() { return mTask; } @@ -563,7 +578,29 @@ public class TaskView extends FrameLayout implements Reusable { mIsClickableAsLiveTile = false; RecentsView recentsView = getRecentsView(); - final RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet(); + RemoteAnimationTargets targets; + RemoteTargetHandle[] remoteTargetHandles = + recentsView.mRemoteTargetHandles; + if (remoteTargetHandles.length == 1) { + targets = remoteTargetHandles[0].mTransformParams.getTargetSet(); + } else { + TransformParams topLeftParams = remoteTargetHandles[0].mTransformParams; + TransformParams rightBottomParams = remoteTargetHandles[1].mTransformParams; + RemoteAnimationTargetCompat[] apps = Stream.concat( + Arrays.stream(topLeftParams.getTargetSet().apps), + Arrays.stream(rightBottomParams.getTargetSet().apps)) + .toArray(RemoteAnimationTargetCompat[]::new); + RemoteAnimationTargetCompat[] wallpapers = Stream.concat( + Arrays.stream(topLeftParams.getTargetSet().wallpapers), + Arrays.stream(rightBottomParams.getTargetSet().wallpapers)) + .toArray(RemoteAnimationTargetCompat[]::new); + RemoteAnimationTargetCompat[] nonApps = Stream.concat( + Arrays.stream(topLeftParams.getTargetSet().nonApps), + Arrays.stream(rightBottomParams.getTargetSet().nonApps)) + .toArray(RemoteAnimationTargetCompat[]::new); + targets = new RemoteAnimationTargets(apps, wallpapers, nonApps, + topLeftParams.getTargetSet().targetMode); + } if (targets == null) { // If the recents animation is cancelled somehow between the parent if block and // here, try to launch the task as a non live tile task. @@ -723,11 +760,11 @@ public class TaskView extends FrameLayout implements Reusable { } } - private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { + protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { return (dataChange & flag) == flag; } - private void cancelPendingLoadTasks() { + protected void cancelPendingLoadTasks() { if (mThumbnailLoadRequest != null) { mThumbnailLoadRequest.cancel(); mThumbnailLoadRequest = null; @@ -1509,4 +1546,22 @@ public class TaskView extends FrameLayout implements Reusable { } } + + public class TaskIdAttributeContainer { + private final TaskThumbnailView thumbnailView; + private final Task task; + + public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView) { + this.task = task; + this.thumbnailView = thumbnailView; + } + + public TaskThumbnailView getThumbnailView() { + return thumbnailView; + } + + public Task getTask() { + return task; + } + } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index eb058e8df4..3f6e7cd6a1 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -221,6 +221,7 @@ public class DeviceProfile { // DragController public int flingToDeleteThresholdVelocity; + /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */ DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean useTwoPanels) { diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java index 573c8bd367..1f1db9dfac 100644 --- a/src/com/android/launcher3/util/SplitConfigurationOptions.java +++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java @@ -18,6 +18,8 @@ package com.android.launcher3.util; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.graphics.Rect; + import androidx.annotation.IntDef; import java.lang.annotation.Retention; @@ -82,4 +84,25 @@ public final class SplitConfigurationOptions { mStageType = stageType; } } + + public static class StagedSplitBounds { + public final Rect mLeftTopBounds; + public final Rect mRightBottomBounds; + public final Rect mDividerBounds; + + + public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, Rect dividerBounds) { + mLeftTopBounds = leftTopBounds; + mRightBottomBounds = rightBottomBounds; + mDividerBounds = dividerBounds; + } + } + + public static class StagedSplitTaskPosition { + public int taskId = -1; + @StagePosition + public int stagePosition = STAGE_POSITION_UNDEFINED; + @StageType + public int stageType = STAGE_TYPE_UNDEFINED; + } }