diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index e9ded8a07c..4415b516f1 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -219,7 +219,7 @@ public abstract class BaseQuickstepLauncher extends Launcher mSplitPlaceholderView = findViewById(R.id.split_placeholder); RecentsView overviewPanel = (RecentsView) getOverviewPanel(); mSplitPlaceholderView.init( - new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this)) + new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)) ); overviewPanel.init(mActionsView, mSplitPlaceholderView); mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 06137f22b0..7c453e7e9d 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -108,8 +108,7 @@ public final class RecentsActivity extends StatefulActivity { SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder); splitPlaceholderView.init( - new SplitSelectStateController( - SystemUiProxy.INSTANCE.get(this)) + new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this)) ); mDragLayer.recreateControllers(); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 1f332c4dda..0ee28bc63d 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -35,6 +35,7 @@ import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.SplitConfigurationOptions; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.RemoteTransitionCompat; @@ -491,6 +492,20 @@ public class SystemUiProxy implements ISystemUiProxy, } } + /** Start multiple tasks in split-screen simultaneously. */ + public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions, + @SplitConfigurationOptions.StagePosition int sidePosition, + RemoteTransitionCompat remoteTransition) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions, + sidePosition, remoteTransition.getTransition()); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startTask"); + } + } + } + public void startShortcut(String packageName, String shortcutId, int stage, int position, Bundle options, UserHandle user) { if (mSplitScreen != null) { diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index bea1250091..cbb2a66214 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -15,6 +15,9 @@ */ package com.android.quickstep; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; @@ -47,7 +50,9 @@ import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.view.SurfaceControl; import android.view.View; +import android.window.TransitionInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -352,7 +357,47 @@ public final class TaskViewUtils { * device is considered in multiWindowMode and things like insets and stuff change * and calculations have to be adjusted in the animations for that */ - public static void composeRecentsSplitLaunchAnimator(@NonNull AnimatorSet anim, + public static void composeRecentsSplitLaunchAnimator(@NonNull TaskView initialView, + @NonNull TaskView v, @NonNull TransitionInfo transitionInfo, + SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + + final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2]; + for (int i = 0; i < transitionInfo.getChanges().size(); ++i) { + final TransitionInfo.Change change = transitionInfo.getChanges().get(i); + final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1; + final int mode = change.getMode(); + // Find the target tasks' root tasks since those are the split stages that need to + // be animated (the tasks themselves are children and thus inherit animation). + if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) { + if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { + throw new IllegalStateException( + "Expected task to be showing, but it is " + mode); + } + if (change.getParent() == null) { + throw new IllegalStateException("Initiating multi-split launch but the split" + + "root of " + taskId + " is already visible or has broken hierarchy."); + } + splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] = + transitionInfo.getChange(change.getParent()); + } + } + + // This is where we should animate the split roots. For now, though, just make them visible. + for (int i = 0; i < 2; ++i) { + t.show(splitRoots[i].getLeash()); + t.setAlpha(splitRoots[i].getLeash(), 1.f); + } + + // This contains the initial state (before animation), so apply this at the beginning of + // the animation. + t.apply(); + + // Once there is an animation, this should be called AFTER the animation completes. + finishCallback.run(); + } + + /** Legacy version (until shell transitions are enabled) */ + public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim, @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index e3f29257c5..9576eac809 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -16,11 +16,17 @@ package com.android.quickstep.util; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; +import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; + import android.animation.AnimatorSet; import android.app.ActivityOptions; import android.os.Handler; import android.os.Looper; import android.util.Pair; +import android.view.SurfaceControl; +import android.window.TransitionInfo; import androidx.annotation.Nullable; @@ -31,12 +37,15 @@ import com.android.launcher3.WrappedAnimationRunnerImpl; import com.android.launcher3.WrappedLauncherAnimationRunner; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.RemoteTransitionCompat; +import com.android.systemui.shared.system.RemoteTransitionRunner; /** * Represent data needed for the transient state when user has selected one app for split screen @@ -47,9 +56,11 @@ public class SplitSelectStateController { private final SystemUiProxy mSystemUiProxy; private TaskView mInitialTaskView; private SplitPositionOption mInitialPosition; + private final Handler mHandler; - public SplitSelectStateController(SystemUiProxy systemUiProxy) { + public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) { mSystemUiProxy = systemUiProxy; + mHandler = handler; } /** @@ -64,6 +75,19 @@ public class SplitSelectStateController { * To be called after second task selected */ public void setSecondTaskId(TaskView taskView) { + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + // Assume initial task is for top/left part of screen + final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT + ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id} + : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id}; + + RemoteSplitLaunchAnimationRunner animationRunner = + new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView); + mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], + null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, + new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR)); + return; + } // Assume initial mInitialTaskId is for top/left part of screen WrappedAnimationRunnerImpl initialSplitRunnerWrapped = new SplitLaunchAnimationRunner( mInitialTaskView, 0); @@ -96,6 +120,30 @@ public class SplitSelectStateController { } /** + * Requires Shell Transitions + */ + private class RemoteSplitLaunchAnimationRunner implements RemoteTransitionRunner { + + private final TaskView mInitialTaskView; + private final TaskView mTaskView; + + RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) { + mInitialTaskView = initialTaskView; + mTaskView = taskView; + } + + @Override + public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + Runnable finishCallback) { + TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskView, mTaskView, + info, t, finishCallback); + // After successful launch, call resetState + resetState(); + } + } + + /** + * LEGACY * @return the opposite stage and position from the {@param position} provided as first and * second object, respectively * Ex. If position is has stage = Main and position = Top/Left, this will return @@ -109,6 +157,7 @@ public class SplitSelectStateController { } /** + * LEGACY * Remote animation runner for animation to launch an app. */ private class SplitLaunchAnimationRunner implements WrappedAnimationRunnerImpl { @@ -129,7 +178,7 @@ public class SplitSelectStateController { LauncherAnimationRunner.AnimationResult result) { AnimatorSet anim = new AnimatorSet(); BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext()); - TaskViewUtils.composeRecentsSplitLaunchAnimator(anim, mV, + TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(anim, mV, appTargets, wallpaperTargets, nonAppTargets, true, activity.getStateManager(), activity.getDepthController(), mTargetState); result.setAnimation(anim, activity);