1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quickstep.util; 18 19 import static com.android.launcher3.Utilities.postAsyncCallback; 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO; 22 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 24 25 import android.app.ActivityOptions; 26 import android.app.ActivityThread; 27 import android.graphics.Rect; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.view.RemoteAnimationAdapter; 31 import android.view.SurfaceControl; 32 import android.window.TransitionInfo; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.launcher3.statehandlers.DepthController; 37 import com.android.launcher3.statemanager.StateManager; 38 import com.android.launcher3.util.SplitConfigurationOptions; 39 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 40 import com.android.quickstep.SystemUiProxy; 41 import com.android.quickstep.TaskAnimationManager; 42 import com.android.quickstep.TaskViewUtils; 43 import com.android.quickstep.views.GroupedTaskView; 44 import com.android.quickstep.views.TaskView; 45 import com.android.systemui.shared.recents.model.Task; 46 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 47 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 48 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 49 import com.android.systemui.shared.system.RemoteTransitionCompat; 50 import com.android.systemui.shared.system.RemoteTransitionRunner; 51 52 import java.util.function.Consumer; 53 54 /** 55 * Represent data needed for the transient state when user has selected one app for split screen 56 * and is in the process of either a) selecting a second app or b) exiting intention to invoke split 57 */ 58 public class SplitSelectStateController { 59 60 private final Handler mHandler; 61 private final SystemUiProxy mSystemUiProxy; 62 private final StateManager mStateManager; 63 private final DepthController mDepthController; 64 private @StagePosition int mStagePosition; 65 private Task mInitialTask; 66 private Task mSecondTask; 67 private boolean mRecentsAnimationRunning; 68 /** If not null, this is the TaskView we want to launch from */ 69 @Nullable 70 private GroupedTaskView mLaunchingTaskView; 71 SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy, StateManager stateManager, DepthController depthController)72 public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy, 73 StateManager stateManager, 74 DepthController depthController) { 75 mHandler = handler; 76 mSystemUiProxy = systemUiProxy; 77 mStateManager = stateManager; 78 mDepthController = depthController; 79 } 80 81 /** 82 * To be called after first task selected 83 */ setInitialTaskSelect(Task task, @StagePosition int stagePosition, Rect initialBounds)84 public void setInitialTaskSelect(Task task, @StagePosition int stagePosition, 85 Rect initialBounds) { 86 mInitialTask = task; 87 mStagePosition = stagePosition; 88 } 89 90 /** 91 * To be called after second task selected 92 */ setSecondTaskId(Task task, Consumer<Boolean> callback)93 public void setSecondTaskId(Task task, Consumer<Boolean> callback) { 94 mSecondTask = task; 95 launchTasks(mInitialTask, mSecondTask, mStagePosition, callback, 96 false /* freezeTaskList */, DEFAULT_SPLIT_RATIO); 97 } 98 99 /** 100 * To be called when we want to launch split pairs from an existing GroupedTaskView. 101 */ launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback, boolean freezeTaskList)102 public void launchTasks(GroupedTaskView groupedTaskView, 103 Consumer<Boolean> callback, boolean freezeTaskList) { 104 mLaunchingTaskView = groupedTaskView; 105 TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers = 106 groupedTaskView.getTaskIdAttributeContainers(); 107 launchTasks(taskIdAttributeContainers[0].getTask(), taskIdAttributeContainers[1].getTask(), 108 taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList, 109 groupedTaskView.getSplitRatio()); 110 } 111 112 /** 113 * @param stagePosition representing location of task1 114 */ launchTasks(Task task1, Task task2, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio)115 public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition, 116 Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) { 117 // Assume initial task is for top/left part of screen 118 final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT 119 ? new int[]{task1.key.id, task2.key.id} 120 : new int[]{task2.key.id, task1.key.id}; 121 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 122 RemoteSplitLaunchTransitionRunner animationRunner = 123 new RemoteSplitLaunchTransitionRunner(task1, task2); 124 mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], 125 null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio, 126 new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR, 127 ActivityThread.currentActivityThread().getApplicationThread())); 128 } else { 129 RemoteSplitLaunchAnimationRunner animationRunner = 130 new RemoteSplitLaunchAnimationRunner(task1, task2, callback); 131 final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( 132 RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner), 133 300, 150, 134 ActivityThread.currentActivityThread().getApplicationThread()); 135 136 ActivityOptions mainOpts = ActivityOptions.makeBasic(); 137 if (freezeTaskList) { 138 mainOpts.setFreezeRecentTasksReordering(); 139 } 140 mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(), 141 taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, 142 splitRatio, adapter); 143 } 144 } 145 getActiveSplitStagePosition()146 public @StagePosition int getActiveSplitStagePosition() { 147 return mStagePosition; 148 } 149 setRecentsAnimationRunning(boolean running)150 public void setRecentsAnimationRunning(boolean running) { 151 this.mRecentsAnimationRunning = running; 152 } 153 154 /** 155 * Requires Shell Transitions 156 */ 157 private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner { 158 159 private final Task mInitialTask; 160 private final Task mSecondTask; 161 RemoteSplitLaunchTransitionRunner(Task initialTask, Task secondTask)162 RemoteSplitLaunchTransitionRunner(Task initialTask, Task secondTask) { 163 mInitialTask = initialTask; 164 mSecondTask = secondTask; 165 } 166 167 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, Runnable finishCallback)168 public void startAnimation(IBinder transition, TransitionInfo info, 169 SurfaceControl.Transaction t, Runnable finishCallback) { 170 TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTask, 171 mSecondTask, info, t, finishCallback); 172 // After successful launch, call resetState 173 resetState(); 174 } 175 } 176 177 /** 178 * LEGACY 179 * Remote animation runner for animation to launch an app. 180 */ 181 private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat { 182 183 private final Task mInitialTask; 184 private final Task mSecondTask; 185 private final Consumer<Boolean> mSuccessCallback; 186 RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask, Consumer<Boolean> successCallback)187 RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask, 188 Consumer<Boolean> successCallback) { 189 mInitialTask = initialTask; 190 mSecondTask = secondTask; 191 mSuccessCallback = successCallback; 192 } 193 194 @Override onAnimationStart(int transit, RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback)195 public void onAnimationStart(int transit, RemoteAnimationTargetCompat[] apps, 196 RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps, 197 Runnable finishedCallback) { 198 postAsyncCallback(mHandler, 199 () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy( 200 mLaunchingTaskView, mInitialTask, mSecondTask, apps, wallpapers, 201 nonApps, mStateManager, mDepthController, () -> { 202 finishedCallback.run(); 203 if (mSuccessCallback != null) { 204 mSuccessCallback.accept(true); 205 } 206 resetState(); 207 })); 208 } 209 210 @Override onAnimationCancelled()211 public void onAnimationCancelled() { 212 postAsyncCallback(mHandler, () -> { 213 if (mSuccessCallback != null) { 214 // Launching legacy tasks while recents animation is running will always cause 215 // onAnimationCancelled to be called (should be fixed w/ shell transitions?) 216 mSuccessCallback.accept(mRecentsAnimationRunning); 217 } 218 resetState(); 219 }); 220 } 221 } 222 223 /** 224 * To be called if split select was cancelled 225 */ resetState()226 public void resetState() { 227 mInitialTask = null; 228 mSecondTask = null; 229 mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; 230 mRecentsAnimationRunning = false; 231 mLaunchingTaskView = null; 232 } 233 234 /** 235 * @return {@code true} if first task has been selected and waiting for the second task to be 236 * chosen 237 */ isSplitSelectActive()238 public boolean isSplitSelectActive() { 239 return mInitialTask != null && mSecondTask == null; 240 } 241 } 242