From 57e261ceef0d404b2325baa9c1a26cc54b5df1d5 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Tue, 31 Aug 2021 11:36:53 -0700 Subject: [PATCH] Quickswitch with staged split * UI polish/animations needed. * One known bug (b/198310766), temp work around is to swipe up to home. Bug: 181704764 Test: * Open apps in staged split and quickswitch between GroupedTaskView and fullscreen apps. * QS to fullscreen app and then go into overview and re-launch split screen tasks * QS to fullscreen app, wait 5 seconds, swipe into overview, no GroupedTaskView shown Change-Id: I0ce10a944d86be5c927eeaaef922559a40f39923 --- .../launcher3/BaseQuickstepLauncher.java | 2 +- .../android/quickstep/AbsSwipeUpHandler.java | 2 +- .../android/quickstep/RecentsActivity.java | 2 +- .../quickstep/SwipeUpAnimationLogic.java | 3 +- .../util/LauncherSplitScreenListener.java | 51 ++++++++++++++++++- .../util/SplitSelectStateController.java | 27 +++++++--- .../quickstep/views/GroupedTaskView.java | 12 ++++- .../android/quickstep/views/RecentsView.java | 5 +- 8 files changed, 89 insertions(+), 15 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index b4b29aaae3..033cf8e17b 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -297,7 +297,7 @@ public abstract class BaseQuickstepLauncher extends Launcher mActionsView = findViewById(R.id.overview_actions_view); RecentsView overviewPanel = (RecentsView) getOverviewPanel(); SplitSelectStateController controller = - new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)); + new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this)); overviewPanel.init(mActionsView, controller); mActionsView.setDp(getDeviceProfile()); mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 6e90a3a5a7..0d83510e15 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -531,7 +531,7 @@ public abstract class AbsSwipeUpHandler, ActivityManager.RunningTaskInfo[] runningTasks; if (mIsSwipeForStagedSplit) { int[] splitTaskIds = - LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds(); + LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds(); runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length]; for (int i = 0; i < splitTaskIds.length; i++) { int taskId = splitTaskIds[i]; diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 03e2395029..cc6cfd72f7 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -122,7 +122,7 @@ public final class RecentsActivity extends StatefulActivity { SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f); SplitSelectStateController controller = - new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)); + new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this)); mDragLayer.recreateControllers(); mFallbackRecentsView.init(mActionsView, controller); } diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index 1f57e9962a..e14dbb181b 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -90,7 +90,8 @@ public abstract class SwipeUpAnimationLogic implements mGestureState = gestureState; mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() && - LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds().length > 1; + LauncherSplitScreenListener.INSTANCE.getNoCreate() + .getRunningSplitTaskIds().length > 1; TaskViewSimulator primaryTVS = new TaskViewSimulator(context, gestureState.getActivityInterface()); diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java index da665d4423..0f4ed014f0 100644 --- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java +++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java @@ -11,20 +11,54 @@ import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.launcher3.util.SplitConfigurationOptions.StageType; import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition; import com.android.quickstep.SystemUiProxy; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.wm.shell.splitscreen.ISplitScreenListener; /** * Listeners for system wide split screen position and stage changes. - * Use {@link #getSplitTaskIds()} to determine which tasks, if any, are in staged split. + * + * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in + * staged split. + * + * Use {@link #getPersistentSplitIds()} to know if tasks were in split screen before a quickswitch + * gesture happened. */ public class LauncherSplitScreenListener extends ISplitScreenListener.Stub { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(LauncherSplitScreenListener::new); + private static final int[] EMPTY_ARRAY = {}; + private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition(); private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition(); + private boolean mIsRecentsListFrozen = false; + private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + super.onRecentTaskListFrozenChanged(frozen); + mIsRecentsListFrozen = frozen; + + if (frozen) { + mPersistentGroupedIds = getRunningSplitTaskIds(); + } else { + // TODO(b/198310766) Need to also explicitly exit split screen if + // we're not currently viewing split screened apps + mPersistentGroupedIds = EMPTY_ARRAY; + } + } + }; + + /** + * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array + * whenever task list unfreezes. + * When not null, this indicates that we need to load a GroupedTaskView as the most recent + * page, so user can quickswitch back to a grouped task. + */ + private int[] mPersistentGroupedIds; + public LauncherSplitScreenListener(Context context) { mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN; mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE; @@ -33,17 +67,30 @@ public class LauncherSplitScreenListener extends ISplitScreenListener.Stub { /** Also call {@link #destroy()} when done. */ public void init() { SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this); + TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); } public void destroy() { SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this); + TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); } + /** + * This method returns the active split taskIDs that were active if a user quickswitched from + * split screen to a fullscreen app as long as the recents task list remains frozen. + */ + public int[] getPersistentSplitIds() { + if (mIsRecentsListFrozen) { + return mPersistentGroupedIds; + } else { + return getRunningSplitTaskIds(); + } + } /** * @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() { + public int[] getRunningSplitTaskIds() { if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) { return new int[]{}; } diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index ac5f5d81be..3069504943 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -22,7 +22,6 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import android.app.ActivityThread; import android.graphics.Rect; -import android.os.Handler; import android.os.IBinder; import android.view.RemoteAnimationAdapter; import android.view.SurfaceControl; @@ -40,6 +39,8 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteTransitionCompat; import com.android.systemui.shared.system.RemoteTransitionRunner; +import java.util.function.Consumer; + /** * Represent data needed for the transient state when user has selected one app for split screen * and is in the process of either a) selecting a second app or b) exiting intention to invoke split @@ -52,7 +53,7 @@ public class SplitSelectStateController { private Task mSecondTask; private Rect mInitialBounds; - public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) { + public SplitSelectStateController(SystemUiProxy systemUiProxy) { mSystemUiProxy = systemUiProxy; } @@ -71,13 +72,14 @@ public class SplitSelectStateController { */ public void setSecondTaskId(Task taskView) { mSecondTask = taskView; - launchTasks(mInitialTask, mSecondTask, mStagePosition); + launchTasks(mInitialTask, mSecondTask, mStagePosition, null /*callback*/); } /** * @param stagePosition representing location of task1 */ - public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition) { + public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition, + Consumer callback) { // Assume initial task is for top/left part of screen final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT ? new int[]{task1.key.id, task2.key.id} @@ -90,7 +92,7 @@ public class SplitSelectStateController { new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR)); } else { RemoteSplitLaunchAnimationRunner animationRunner = - new RemoteSplitLaunchAnimationRunner(task1, task2); + new RemoteSplitLaunchAnimationRunner(task1, task2, callback); final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner), 300, 150, @@ -136,10 +138,13 @@ public class SplitSelectStateController { private final Task mInitialTask; private final Task mSecondTask; + private final Consumer mSuccessCallback; - RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask) { + RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask, + Consumer successCallback) { mInitialTask = initialTask; mSecondTask = secondTask; + mSuccessCallback = successCallback; } @Override @@ -147,13 +152,21 @@ public class SplitSelectStateController { RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback) { TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTask, - mSecondTask, apps, wallpapers, nonApps, finishedCallback); + mSecondTask, apps, wallpapers, nonApps, () -> { + finishedCallback.run(); + if (mSuccessCallback != null) { + mSuccessCallback.accept(true); + } + }); // After successful launch, call resetState resetState(); } @Override public void onAnimationCancelled() { + if (mSuccessCallback != null) { + mSuccessCallback.accept(false); + } resetState(); } } diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index e1e1c65c55..d3077ad276 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -3,6 +3,8 @@ package com.android.quickstep.views; import android.content.Context; import android.util.AttributeSet; +import androidx.annotation.NonNull; + import com.android.launcher3.R; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SplitConfigurationOptions; @@ -12,6 +14,8 @@ import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; import com.android.systemui.shared.recents.model.Task; +import java.util.function.Consumer; + /** * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks * @@ -98,10 +102,16 @@ public class GroupedTaskView extends TaskView { @Override public RunnableList launchTaskAnimated() { getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask, - SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT); + SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, null /*callback*/); return null; } + @Override + public void launchTask(@NonNull Consumer callback, boolean freezeTaskList) { + getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask, + SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback); + } + @Override public void onRecycle() { super.onRecycle(); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 810eccea8a..c6e2a5d199 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1259,7 +1259,7 @@ public abstract class RecentsView