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 android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.PendingIntent.FLAG_MUTABLE; 21 22 import static com.android.launcher3.Utilities.postAsyncCallback; 23 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 24 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO; 25 import static com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition; 26 27 import android.annotation.NonNull; 28 import android.app.ActivityManager; 29 import android.app.ActivityOptions; 30 import android.app.ActivityThread; 31 import android.app.PendingIntent; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.PackageManager; 35 import android.content.pm.ShortcutInfo; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.util.Log; 41 import android.util.Pair; 42 import android.view.RemoteAnimationAdapter; 43 import android.view.RemoteAnimationTarget; 44 import android.view.SurfaceControl; 45 import android.window.IRemoteTransition; 46 import android.window.IRemoteTransitionFinishedCallback; 47 import android.window.RemoteTransition; 48 import android.window.TransitionInfo; 49 50 import androidx.annotation.Nullable; 51 52 import com.android.internal.logging.InstanceId; 53 import com.android.launcher3.logging.StatsLogManager; 54 import com.android.launcher3.model.data.ItemInfo; 55 import com.android.launcher3.shortcuts.ShortcutKey; 56 import com.android.launcher3.statehandlers.DepthController; 57 import com.android.launcher3.statemanager.StateManager; 58 import com.android.launcher3.testing.TestLogging; 59 import com.android.launcher3.testing.shared.TestProtocol; 60 import com.android.launcher3.util.ComponentKey; 61 import com.android.launcher3.util.SplitConfigurationOptions; 62 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 63 import com.android.quickstep.RecentsModel; 64 import com.android.quickstep.SystemUiProxy; 65 import com.android.quickstep.TaskAnimationManager; 66 import com.android.quickstep.TaskViewUtils; 67 import com.android.quickstep.views.FloatingTaskView; 68 import com.android.quickstep.views.GroupedTaskView; 69 import com.android.quickstep.views.TaskView; 70 import com.android.systemui.shared.recents.model.Task; 71 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 72 73 import java.util.function.Consumer; 74 75 /** 76 * Represent data needed for the transient state when user has selected one app for split screen 77 * and is in the process of either a) selecting a second app or b) exiting intention to invoke split 78 */ 79 public class SplitSelectStateController { 80 private static final String TAG = "SplitSelectStateCtor"; 81 82 private final Context mContext; 83 private final Handler mHandler; 84 private final RecentsModel mRecentTasksModel; 85 private final SplitAnimationController mSplitAnimationController; 86 private StatsLogManager mStatsLogManager; 87 private final SystemUiProxy mSystemUiProxy; 88 private final StateManager mStateManager; 89 @Nullable 90 private DepthController mDepthController; 91 private @StagePosition int mInitialStagePosition; 92 private ItemInfo mItemInfo; 93 /** {@link #mInitialTaskIntent} and {@link #mInitialUser} (the user of the Intent) are set 94 * together when split is initiated from an Intent. */ 95 private Intent mInitialTaskIntent; 96 private UserHandle mInitialUser; 97 private int mInitialTaskId = INVALID_TASK_ID; 98 /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set 99 * together when split is confirmed with an Intent. */ 100 private Intent mSecondTaskIntent; 101 private UserHandle mSecondUser; 102 private int mSecondTaskId = INVALID_TASK_ID; 103 private boolean mRecentsAnimationRunning; 104 /** If {@code true}, animates the existing task view split placeholder view */ 105 private boolean mAnimateCurrentTaskDismissal; 106 /** 107 * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a 108 * split pair task view without wanting to animate current task dismissal overall 109 */ 110 private boolean mDismissingFromSplitPair; 111 /** If not null, this is the TaskView we want to launch from */ 112 @Nullable 113 private GroupedTaskView mLaunchingTaskView; 114 /** Represents where split is intended to be invoked from. */ 115 private StatsLogManager.EventEnum mSplitEvent; 116 117 private FloatingTaskView mFirstFloatingTaskView; 118 SplitSelectStateController(Context context, Handler handler, StateManager stateManager, DepthController depthController, StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel)119 public SplitSelectStateController(Context context, Handler handler, StateManager stateManager, 120 DepthController depthController, StatsLogManager statsLogManager, 121 SystemUiProxy systemUiProxy, RecentsModel recentsModel) { 122 mContext = context; 123 mHandler = handler; 124 mStatsLogManager = statsLogManager; 125 mSystemUiProxy = systemUiProxy; 126 mStateManager = stateManager; 127 mDepthController = depthController; 128 mRecentTasksModel = recentsModel; 129 mSplitAnimationController = new SplitAnimationController(this); 130 } 131 132 /** 133 * @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID} 134 * then @param intent will be used to launch the initial task 135 * @param intent will be ignored if @param alreadyRunningTask is set 136 */ setInitialTaskSelect(@ullable Intent intent, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, int alreadyRunningTask)137 public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition, 138 @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, 139 int alreadyRunningTask) { 140 if (alreadyRunningTask != INVALID_TASK_ID) { 141 mInitialTaskId = alreadyRunningTask; 142 } else { 143 mInitialTaskIntent = intent; 144 mInitialUser = itemInfo.user; 145 } 146 147 setInitialData(stagePosition, splitEvent, itemInfo); 148 } 149 150 /** 151 * To be called after first task selected from using a split shortcut from the fullscreen 152 * running app. 153 */ setInitialTaskSelect(ActivityManager.RunningTaskInfo info, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)154 public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info, 155 @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, 156 StatsLogManager.EventEnum splitEvent) { 157 mInitialTaskId = info.taskId; 158 setInitialData(stagePosition, splitEvent, itemInfo); 159 } 160 setInitialData(@tagePosition int stagePosition, StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo)161 private void setInitialData(@StagePosition int stagePosition, 162 StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) { 163 mItemInfo = itemInfo; 164 mInitialStagePosition = stagePosition; 165 mSplitEvent = splitEvent; 166 } 167 168 /** 169 * Pulls the list of active Tasks from RecentsModel, and finds the most recently active Task 170 * matching a given ComponentName. Then uses that Task (which could be null) with the given 171 * callback. 172 * 173 * Used in various task-switching or splitscreen operations when we need to check if there is a 174 * currently running Task of a certain type and use the most recent one. 175 */ findLastActiveTaskAndRunCallback(ComponentKey componentKey, Consumer<Task> callback)176 public void findLastActiveTaskAndRunCallback(ComponentKey componentKey, 177 Consumer<Task> callback) { 178 mRecentTasksModel.getTasks(taskGroups -> { 179 Task lastActiveTask = null; 180 // Loop through tasks in reverse, since they are ordered with most-recent tasks last. 181 for (int i = taskGroups.size() - 1; i >= 0; i--) { 182 GroupTask groupTask = taskGroups.get(i); 183 Task task1 = groupTask.task1; 184 if (isInstanceOfComponent(task1, componentKey)) { 185 lastActiveTask = task1; 186 break; 187 } 188 Task task2 = groupTask.task2; 189 if (isInstanceOfComponent(task2, componentKey)) { 190 lastActiveTask = task2; 191 break; 192 } 193 } 194 195 callback.accept(lastActiveTask); 196 }); 197 } 198 199 /** 200 * Checks if a given Task is the most recently-active Task of type componentName. Used for 201 * selecting already-running Tasks for splitscreen. 202 */ isInstanceOfComponent(@ullable Task task, ComponentKey componentKey)203 public boolean isInstanceOfComponent(@Nullable Task task, ComponentKey componentKey) { 204 // Exclude the task that is already staged 205 if (task == null || task.key.id == mInitialTaskId) { 206 return false; 207 } 208 209 return task.key.baseIntent.getComponent().equals(componentKey.componentName) 210 && task.key.userId == componentKey.user.getIdentifier(); 211 } 212 213 /** 214 * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are 215 * to be launched. Call after launcher side animations are complete. 216 */ launchSplitTasks(Consumer<Boolean> callback)217 public void launchSplitTasks(Consumer<Boolean> callback) { 218 Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds = 219 LogUtils.getShellShareableInstanceId(); 220 launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent, 221 mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO, 222 instanceIds.first); 223 224 mStatsLogManager.logger() 225 .withItemInfo(mItemInfo) 226 .withInstanceId(instanceIds.second) 227 .log(mSplitEvent); 228 } 229 230 /** 231 * To be called as soon as user selects the second task (even if animations aren't complete) 232 * @param task The second task that will be launched. 233 */ setSecondTask(Task task)234 public void setSecondTask(Task task) { 235 mSecondTaskId = task.key.id; 236 } 237 238 /** 239 * To be called as soon as user selects the second app (even if animations aren't complete) 240 * @param intent The second intent that will be launched. 241 * @param user The user of that intent. 242 */ setSecondTask(Intent intent, UserHandle user)243 public void setSecondTask(Intent intent, UserHandle user) { 244 mSecondTaskIntent = intent; 245 mSecondUser = user; 246 } 247 248 /** 249 * To be called when we want to launch split pairs from an existing GroupedTaskView. 250 */ launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback, boolean freezeTaskList)251 public void launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback, 252 boolean freezeTaskList) { 253 mLaunchingTaskView = groupedTaskView; 254 TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers = 255 groupedTaskView.getTaskIdAttributeContainers(); 256 launchTasks(taskIdAttributeContainers[0].getTask().key.id, 257 taskIdAttributeContainers[1].getTask().key.id, 258 taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList, 259 groupedTaskView.getSplitRatio()); 260 } 261 262 /** 263 * To be called when we want to launch split pairs from Overview when split is initiated from 264 * Overview. 265 */ launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio)266 public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition, 267 Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) { 268 launchTasks(taskId1, null /* intent1 */, taskId2, null /* intent2 */, stagePosition, 269 callback, freezeTaskList, splitRatio, null); 270 } 271 272 /** 273 * To be called when we want to launch split pairs from Overview. Split can be initiated from 274 * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a 275 * fill in intent with a taskId2 are set. 276 * @param intent1 is null when split is initiated from Overview 277 * @param stagePosition representing location of task1 278 * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that 279 * create a split instance, null for cases that bring existing instaces to the 280 * foreground (quickswitch, launching previous pairs from overview) 281 */ launchTasks(int taskId1, @Nullable Intent intent1, int taskId2, @Nullable Intent intent2, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio, @Nullable InstanceId shellInstanceId)282 public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2, 283 @Nullable Intent intent2, @StagePosition int stagePosition, 284 Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio, 285 @Nullable InstanceId shellInstanceId) { 286 TestLogging.recordEvent( 287 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks"); 288 final ActivityOptions options1 = ActivityOptions.makeBasic(); 289 if (freezeTaskList) { 290 options1.setFreezeRecentTasksReordering(); 291 } 292 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 293 final RemoteSplitLaunchTransitionRunner animationRunner = 294 new RemoteSplitLaunchTransitionRunner(taskId1, taskId2, callback); 295 final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, 296 ActivityThread.currentActivityThread().getApplicationThread()); 297 if (intent1 == null && intent2 == null) { 298 mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2, 299 null /* options2 */, stagePosition, splitRatio, remoteTransition, 300 shellInstanceId); 301 } else if (intent2 == null) { 302 launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition, 303 splitRatio, remoteTransition, shellInstanceId); 304 } else if (intent1 == null) { 305 launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1, 306 getOppositeStagePosition(stagePosition), splitRatio, remoteTransition, 307 shellInstanceId); 308 } else { 309 mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser), 310 options1.toBundle(), getPendingIntent(intent2, mSecondUser), 311 null /* options2 */, stagePosition, splitRatio, remoteTransition, 312 shellInstanceId); 313 } 314 } else { 315 final RemoteSplitLaunchAnimationRunner animationRunner = 316 new RemoteSplitLaunchAnimationRunner(taskId1, taskId2, callback); 317 final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( 318 animationRunner, 300, 150, 319 ActivityThread.currentActivityThread().getApplicationThread()); 320 321 if (intent1 == null && intent2 == null) { 322 mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(), 323 taskId2, null /* options2 */, stagePosition, splitRatio, adapter, 324 shellInstanceId); 325 } else if (intent2 == null) { 326 launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2, 327 stagePosition, splitRatio, adapter, shellInstanceId); 328 } else if (intent1 == null) { 329 launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1, 330 getOppositeStagePosition(stagePosition), splitRatio, adapter, 331 shellInstanceId); 332 } else { 333 mSystemUiProxy.startIntentsWithLegacyTransition( 334 getPendingIntent(intent1, mInitialUser), 335 getShortcutInfo(intent1, mInitialUser), options1.toBundle(), 336 getPendingIntent(intent2, mSecondUser), 337 getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition, 338 splitRatio, adapter, shellInstanceId); 339 } 340 } 341 } 342 launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1, int taskId, @StagePosition int stagePosition, float splitRatio, RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId)343 private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1, 344 int taskId, @StagePosition int stagePosition, float splitRatio, 345 RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) { 346 final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user); 347 if (shortcutInfo != null) { 348 mSystemUiProxy.startShortcutAndTask(shortcutInfo, 349 options1.toBundle(), taskId, null /* options2 */, stagePosition, 350 splitRatio, remoteTransition, shellInstanceId); 351 } else { 352 mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user), 353 options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio, 354 remoteTransition, shellInstanceId); 355 } 356 } 357 launchIntentOrShortcutLegacy(Intent intent, UserHandle user, ActivityOptions options1, int taskId, @StagePosition int stagePosition, float splitRatio, RemoteAnimationAdapter adapter, @Nullable InstanceId shellInstanceId)358 private void launchIntentOrShortcutLegacy(Intent intent, UserHandle user, 359 ActivityOptions options1, int taskId, @StagePosition int stagePosition, 360 float splitRatio, RemoteAnimationAdapter adapter, 361 @Nullable InstanceId shellInstanceId) { 362 final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user); 363 if (shortcutInfo != null) { 364 mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo, 365 options1.toBundle(), taskId, null /* options2 */, stagePosition, 366 splitRatio, adapter, shellInstanceId); 367 } else { 368 mSystemUiProxy.startIntentAndTaskWithLegacyTransition( 369 getPendingIntent(intent, user), options1.toBundle(), taskId, 370 null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId); 371 } 372 } 373 getPendingIntent(Intent intent, UserHandle user)374 private PendingIntent getPendingIntent(Intent intent, UserHandle user) { 375 return intent == null ? null : (user != null 376 ? PendingIntent.getActivityAsUser(mContext, 0, intent, 377 FLAG_MUTABLE, null /* options */, user) 378 : PendingIntent.getActivity(mContext, 0, intent, FLAG_MUTABLE)); 379 } 380 getActiveSplitStagePosition()381 public @StagePosition int getActiveSplitStagePosition() { 382 return mInitialStagePosition; 383 } 384 getSplitEvent()385 public StatsLogManager.EventEnum getSplitEvent() { 386 return mSplitEvent; 387 } 388 setRecentsAnimationRunning(boolean running)389 public void setRecentsAnimationRunning(boolean running) { 390 mRecentsAnimationRunning = running; 391 } 392 393 @Nullable getShortcutInfo(Intent intent, UserHandle user)394 private ShortcutInfo getShortcutInfo(Intent intent, UserHandle user) { 395 if (intent == null || intent.getPackage() == null) { 396 return null; 397 } 398 399 final String shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID); 400 if (shortcutId == null) { 401 return null; 402 } 403 404 try { 405 final Context context = mContext.createPackageContextAsUser( 406 intent.getPackage(), 0 /* flags */, user); 407 return new ShortcutInfo.Builder(context, shortcutId).build(); 408 } catch (PackageManager.NameNotFoundException e) { 409 Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage()); 410 } 411 412 return null; 413 } 414 isAnimateCurrentTaskDismissal()415 public boolean isAnimateCurrentTaskDismissal() { 416 return mAnimateCurrentTaskDismissal; 417 } 418 setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal)419 public void setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal) { 420 mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal; 421 } 422 isDismissingFromSplitPair()423 public boolean isDismissingFromSplitPair() { 424 return mDismissingFromSplitPair; 425 } 426 setDismissingFromSplitPair(boolean dismissingFromSplitPair)427 public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) { 428 mDismissingFromSplitPair = dismissingFromSplitPair; 429 } 430 getSplitAnimationController()431 public SplitAnimationController getSplitAnimationController() { 432 return mSplitAnimationController; 433 } 434 435 /** 436 * Requires Shell Transitions 437 */ 438 private class RemoteSplitLaunchTransitionRunner extends IRemoteTransition.Stub { 439 440 private final int mInitialTaskId; 441 private final int mSecondTaskId; 442 private final Consumer<Boolean> mSuccessCallback; 443 RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, Consumer<Boolean> callback)444 RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, 445 Consumer<Boolean> callback) { 446 mInitialTaskId = initialTaskId; 447 mSecondTaskId = secondTaskId; 448 mSuccessCallback = callback; 449 } 450 451 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback)452 public void startAnimation(IBinder transition, TransitionInfo info, 453 SurfaceControl.Transaction t, 454 IRemoteTransitionFinishedCallback finishedCallback) { 455 final Runnable finishAdapter = () -> { 456 try { 457 finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); 458 } catch (RemoteException e) { 459 Log.e(TAG, "Failed to call transition finished callback", e); 460 } 461 }; 462 463 MAIN_EXECUTOR.execute(() -> { 464 TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager, 465 mDepthController, mInitialTaskId, mSecondTaskId, info, t, () -> { 466 finishAdapter.run(); 467 if (mSuccessCallback != null) { 468 mSuccessCallback.accept(true); 469 } 470 }); 471 // After successful launch, call resetState 472 resetState(); 473 }); 474 } 475 476 @Override mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishedCallback)477 public void mergeAnimation(IBinder transition, TransitionInfo info, 478 SurfaceControl.Transaction t, IBinder mergeTarget, 479 IRemoteTransitionFinishedCallback finishedCallback) { } 480 } 481 482 /** 483 * LEGACY 484 * Remote animation runner for animation to launch an app. 485 */ 486 private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat { 487 488 private final int mInitialTaskId; 489 private final int mSecondTaskId; 490 private final Consumer<Boolean> mSuccessCallback; 491 RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, Consumer<Boolean> successCallback)492 RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, 493 Consumer<Boolean> successCallback) { 494 mInitialTaskId = initialTaskId; 495 mSecondTaskId = secondTaskId; 496 mSuccessCallback = successCallback; 497 } 498 499 @Override onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)500 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, 501 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, 502 Runnable finishedCallback) { 503 postAsyncCallback(mHandler, 504 () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy( 505 mLaunchingTaskView, mInitialTaskId, mSecondTaskId, apps, wallpapers, 506 nonApps, mStateManager, mDepthController, () -> { 507 finishedCallback.run(); 508 if (mSuccessCallback != null) { 509 mSuccessCallback.accept(true); 510 } 511 resetState(); 512 })); 513 } 514 515 @Override onAnimationCancelled(boolean isKeyguardOccluded)516 public void onAnimationCancelled(boolean isKeyguardOccluded) { 517 postAsyncCallback(mHandler, () -> { 518 if (mSuccessCallback != null) { 519 // Launching legacy tasks while recents animation is running will always cause 520 // onAnimationCancelled to be called (should be fixed w/ shell transitions?) 521 mSuccessCallback.accept(mRecentsAnimationRunning); 522 } 523 resetState(); 524 }); 525 } 526 } 527 528 /** 529 * To be called if split select was cancelled 530 */ resetState()531 public void resetState() { 532 mInitialTaskId = INVALID_TASK_ID; 533 mInitialTaskIntent = null; 534 mSecondTaskId = INVALID_TASK_ID; 535 mSecondTaskIntent = null; 536 mInitialUser = null; 537 mSecondUser = null; 538 mInitialStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; 539 mRecentsAnimationRunning = false; 540 mLaunchingTaskView = null; 541 mItemInfo = null; 542 mSplitEvent = null; 543 mAnimateCurrentTaskDismissal = false; 544 mDismissingFromSplitPair = false; 545 } 546 547 /** 548 * @return {@code true} if first task has been selected and waiting for the second task to be 549 * chosen 550 */ isSplitSelectActive()551 public boolean isSplitSelectActive() { 552 return isInitialTaskIntentSet() && !isSecondTaskIntentSet(); 553 } 554 555 /** 556 * @return {@code true} if the first and second task have been chosen and split is waiting to 557 * be launched 558 */ isBothSplitAppsConfirmed()559 public boolean isBothSplitAppsConfirmed() { 560 return isInitialTaskIntentSet() && isSecondTaskIntentSet(); 561 } 562 isInitialTaskIntentSet()563 private boolean isInitialTaskIntentSet() { 564 return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null); 565 } 566 getInitialTaskId()567 public int getInitialTaskId() { 568 return mInitialTaskId; 569 } 570 getSecondTaskId()571 public int getSecondTaskId() { 572 return mSecondTaskId; 573 } 574 isSecondTaskIntentSet()575 private boolean isSecondTaskIntentSet() { 576 return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null); 577 } 578 setFirstFloatingTaskView(FloatingTaskView floatingTaskView)579 public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) { 580 mFirstFloatingTaskView = floatingTaskView; 581 } 582 getFirstFloatingTaskView()583 public FloatingTaskView getFirstFloatingTaskView() { 584 return mFirstFloatingTaskView; 585 } 586 } 587