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.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; 21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM; 22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTED_SECOND_APP; 23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_COMPLETE; 24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME; 25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_INITIATED; 26 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 27 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 28 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 29 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT; 30 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK; 31 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK; 32 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_INTENT_FULLSCREEN; 33 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_SHORTCUT_FULLSCREEN; 34 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK_FULLSCREEN; 35 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT; 36 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT; 37 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK; 38 import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; 39 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50; 40 41 import android.animation.Animator; 42 import android.animation.AnimatorListenerAdapter; 43 import android.annotation.NonNull; 44 import android.annotation.UiThread; 45 import android.app.ActivityManager; 46 import android.app.ActivityOptions; 47 import android.app.ActivityThread; 48 import android.app.PendingIntent; 49 import android.content.Intent; 50 import android.content.pm.PackageManager; 51 import android.content.pm.ShortcutInfo; 52 import android.graphics.Rect; 53 import android.graphics.RectF; 54 import android.graphics.drawable.Drawable; 55 import android.os.Bundle; 56 import android.os.Handler; 57 import android.os.IBinder; 58 import android.os.RemoteException; 59 import android.os.SystemClock; 60 import android.os.UserHandle; 61 import android.util.Log; 62 import android.util.Pair; 63 import android.view.RemoteAnimationAdapter; 64 import android.view.RemoteAnimationTarget; 65 import android.view.SurfaceControl; 66 import android.window.IRemoteTransitionFinishedCallback; 67 import android.window.RemoteTransition; 68 import android.window.RemoteTransitionStub; 69 import android.window.TransitionInfo; 70 71 import androidx.annotation.Nullable; 72 import androidx.annotation.VisibleForTesting; 73 74 import com.android.internal.logging.InstanceId; 75 import com.android.launcher3.R; 76 import com.android.launcher3.anim.PendingAnimation; 77 import com.android.launcher3.apppairs.AppPairIcon; 78 import com.android.launcher3.config.FeatureFlags; 79 import com.android.launcher3.icons.IconProvider; 80 import com.android.launcher3.logging.StatsLogManager; 81 import com.android.launcher3.model.data.ItemInfo; 82 import com.android.launcher3.statehandlers.DepthController; 83 import com.android.launcher3.statemanager.StateManager; 84 import com.android.launcher3.testing.TestLogging; 85 import com.android.launcher3.testing.shared.TestProtocol; 86 import com.android.launcher3.uioverrides.QuickstepLauncher; 87 import com.android.launcher3.util.BackPressHandler; 88 import com.android.launcher3.util.ComponentKey; 89 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 90 import com.android.quickstep.OverviewComponentObserver; 91 import com.android.quickstep.RecentsAnimationCallbacks; 92 import com.android.quickstep.RecentsAnimationController; 93 import com.android.quickstep.RecentsAnimationTargets; 94 import com.android.quickstep.RecentsModel; 95 import com.android.quickstep.SplitSelectionListener; 96 import com.android.quickstep.SystemUiProxy; 97 import com.android.quickstep.TaskAnimationManager; 98 import com.android.quickstep.views.FloatingTaskView; 99 import com.android.quickstep.views.GroupedTaskView; 100 import com.android.quickstep.views.RecentsView; 101 import com.android.quickstep.views.RecentsViewContainer; 102 import com.android.quickstep.views.SplitInstructionsView; 103 import com.android.systemui.animation.RemoteAnimationRunnerCompat; 104 import com.android.systemui.shared.recents.model.Task; 105 import com.android.systemui.shared.system.ActivityManagerWrapper; 106 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 107 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; 108 import com.android.wm.shell.splitscreen.ISplitSelectListener; 109 110 import java.io.PrintWriter; 111 import java.util.ArrayList; 112 import java.util.Arrays; 113 import java.util.List; 114 import java.util.function.Consumer; 115 116 /** 117 * Represent data needed for the transient state when user has selected one app for split screen 118 * and is in the process of either a) selecting a second app or b) exiting intention to invoke split 119 */ 120 public class SplitSelectStateController { 121 private static final String TAG = "SplitSelectStateCtor"; 122 123 private RecentsViewContainer mContainer; 124 private final Handler mHandler; 125 private final RecentsModel mRecentTasksModel; 126 @Nullable 127 private Runnable mActivityBackCallback; 128 private final SplitAnimationController mSplitAnimationController; 129 private final AppPairsController mAppPairsController; 130 private final SplitSelectDataHolder mSplitSelectDataHolder; 131 private final StatsLogManager mStatsLogManager; 132 private final SystemUiProxy mSystemUiProxy; 133 private final StateManager mStateManager; 134 @Nullable 135 private SplitFromDesktopController mSplitFromDesktopController; 136 @Nullable 137 private DepthController mDepthController; 138 private boolean mRecentsAnimationRunning; 139 /** If {@code true}, animates the existing task view split placeholder view */ 140 private boolean mAnimateCurrentTaskDismissal; 141 /** 142 * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a 143 * split pair task view without wanting to animate current task dismissal overall 144 */ 145 private boolean mDismissingFromSplitPair; 146 /** If not null, this is the TaskView we want to launch from */ 147 @Nullable 148 private GroupedTaskView mLaunchingTaskView; 149 /** If not null, this is the icon we want to launch from */ 150 private AppPairIcon mLaunchingIconView; 151 152 /** True when the first selected split app is being launched in fullscreen. */ 153 private boolean mLaunchingFirstAppFullscreen; 154 155 /** 156 * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be 157 * set for all launches. 158 */ 159 private int mLaunchCuj = -1; 160 161 private FloatingTaskView mFirstFloatingTaskView; 162 private SplitInstructionsView mSplitInstructionsView; 163 164 private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>(); 165 /** 166 * Tracks metrics from when first app is selected to split launch or cancellation. This also 167 * gets passed over to shell when attempting to invoke split. 168 */ 169 private Pair<InstanceId, com.android.launcher3.logging.InstanceId> mSessionInstanceIds; 170 171 private final BackPressHandler mSplitBackHandler = new BackPressHandler() { 172 @Override 173 public boolean canHandleBack() { 174 return FeatureFlags.enableSplitContextually() && isSplitSelectActive(); 175 } 176 177 @Override 178 public void onBackInvoked() { 179 // When exiting from split selection, leave current context to go to 180 // homescreen as well 181 getSplitAnimationController().playPlaceholderDismissAnim(mContainer, 182 LAUNCHER_SPLIT_SELECTION_EXIT_HOME); 183 if (mActivityBackCallback != null) { 184 mActivityBackCallback.run(); 185 } 186 } 187 }; 188 SplitSelectStateController(RecentsViewContainer container, Handler handler, StateManager stateManager, DepthController depthController, StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, Runnable activityBackCallback)189 public SplitSelectStateController(RecentsViewContainer container, Handler handler, 190 StateManager stateManager, DepthController depthController, 191 StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel, 192 Runnable activityBackCallback) { 193 mContainer = container; 194 mHandler = handler; 195 mStatsLogManager = statsLogManager; 196 mSystemUiProxy = systemUiProxy; 197 mStateManager = stateManager; 198 mDepthController = depthController; 199 mRecentTasksModel = recentsModel; 200 mActivityBackCallback = activityBackCallback; 201 mSplitAnimationController = new SplitAnimationController(this); 202 mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager); 203 mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext()); 204 } 205 onDestroy()206 public void onDestroy() { 207 mContainer = null; 208 mActivityBackCallback = null; 209 mAppPairsController.onDestroy(); 210 mSplitSelectDataHolder.onDestroy(); 211 if (mSplitFromDesktopController != null) { 212 mSplitFromDesktopController.onDestroy(); 213 } 214 } 215 216 /** 217 * @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID} 218 * then @param intent will be used to launch the initial task 219 * @param intent will be ignored if @param alreadyRunningTask is set 220 */ setInitialTaskSelect(@ullable Intent intent, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, int alreadyRunningTask)221 public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition, 222 @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, 223 int alreadyRunningTask) { 224 mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent, 225 alreadyRunningTask); 226 createAndLogInstanceIdsForSession(); 227 } 228 229 /** 230 * To be called after first task selected from using a split shortcut from the fullscreen 231 * running app. 232 */ setInitialTaskSelect(ActivityManager.RunningTaskInfo info, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)233 public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info, 234 @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, 235 StatsLogManager.EventEnum splitEvent) { 236 mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent); 237 createAndLogInstanceIdsForSession(); 238 } 239 240 /** 241 * Given a list of task keys, searches through active Tasks in RecentsModel to find the last 242 * active instances of these tasks. Returns an empty array if there is no such running task. 243 * 244 * @param componentKeys The list of ComponentKeys to search for. 245 * @param callback The callback that will be executed on the list of found tasks. 246 * @param findExactPairMatch If {@code true}, only finds tasks that contain BOTH of the wanted 247 * tasks (i.e. searching for a running pair of tasks.) 248 */ findLastActiveTasksAndRunCallback(@ullable List<ComponentKey> componentKeys, boolean findExactPairMatch, Consumer<Task[]> callback)249 public void findLastActiveTasksAndRunCallback(@Nullable List<ComponentKey> componentKeys, 250 boolean findExactPairMatch, Consumer<Task[]> callback) { 251 mRecentTasksModel.getTasks(taskGroups -> { 252 if (componentKeys == null || componentKeys.isEmpty()) { 253 callback.accept(new Task[]{}); 254 return; 255 } 256 257 Task[] lastActiveTasks = new Task[componentKeys.size()]; 258 259 if (findExactPairMatch) { 260 // Loop through tasks in reverse, since they are ordered with most-recent tasks last 261 for (int i = taskGroups.size() - 1; i >= 0; i--) { 262 GroupTask groupTask = taskGroups.get(i); 263 if (isInstanceOfAppPair( 264 groupTask, componentKeys.get(0), componentKeys.get(1))) { 265 lastActiveTasks[0] = groupTask.task1; 266 break; 267 } 268 } 269 } else { 270 // For each key we are looking for, add to lastActiveTasks with the corresponding 271 // Task (or do nothing if not found). 272 for (int i = 0; i < componentKeys.size(); i++) { 273 ComponentKey key = componentKeys.get(i); 274 Task lastActiveTask = null; 275 // Loop through tasks in reverse, since they are ordered with recent tasks last 276 for (int j = taskGroups.size() - 1; j >= 0; j--) { 277 GroupTask groupTask = taskGroups.get(j); 278 Task task1 = groupTask.task1; 279 // Don't add duplicate Tasks 280 if (isInstanceOfComponent(task1, key) 281 && !Arrays.asList(lastActiveTasks).contains(task1)) { 282 lastActiveTask = task1; 283 break; 284 } 285 Task task2 = groupTask.task2; 286 if (isInstanceOfComponent(task2, key) 287 && !Arrays.asList(lastActiveTasks).contains(task2)) { 288 lastActiveTask = task2; 289 break; 290 } 291 } 292 293 lastActiveTasks[i] = lastActiveTask; 294 } 295 } 296 297 callback.accept(lastActiveTasks); 298 }); 299 } 300 301 /** 302 * Checks if a given Task is the most recently-active Task of type componentName. Used for 303 * selecting already-running Tasks for splitscreen. 304 */ isInstanceOfComponent(@ullable Task task, @NonNull ComponentKey componentKey)305 public boolean isInstanceOfComponent(@Nullable Task task, @NonNull ComponentKey componentKey) { 306 // Exclude the task that is already staged 307 if (task == null || task.key.id == mSplitSelectDataHolder.getInitialTaskId()) { 308 return false; 309 } 310 311 return task.key.baseIntent.getComponent().equals(componentKey.componentName) 312 && task.key.userId == componentKey.user.getIdentifier(); 313 } 314 315 /** 316 * Checks if a given GroupTask is a pair of apps that matches two given ComponentKeys. We check 317 * both permutations because task order is not guaranteed in GroupTasks. 318 */ isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1, @NonNull ComponentKey componentKey2)319 public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1, 320 @NonNull ComponentKey componentKey2) { 321 return ((isInstanceOfComponent(groupTask.task1, componentKey1) 322 && isInstanceOfComponent(groupTask.task2, componentKey2)) 323 || 324 (isInstanceOfComponent(groupTask.task1, componentKey2) 325 && isInstanceOfComponent(groupTask.task2, componentKey1))); 326 } 327 328 /** 329 * Listener will only get callbacks going forward from the point of registration. No 330 * methods will be fired upon registering. 331 */ registerSplitListener(@onNull SplitSelectionListener listener)332 public void registerSplitListener(@NonNull SplitSelectionListener listener) { 333 if (mSplitSelectionListeners.contains(listener)) { 334 return; 335 } 336 mSplitSelectionListeners.add(listener); 337 } 338 unregisterSplitListener(@onNull SplitSelectionListener listener)339 public void unregisterSplitListener(@NonNull SplitSelectionListener listener) { 340 mSplitSelectionListeners.remove(listener); 341 } 342 dispatchOnSplitSelectionExit()343 private void dispatchOnSplitSelectionExit() { 344 for (SplitSelectionListener listener : mSplitSelectionListeners) { 345 listener.onSplitSelectionExit(false); 346 } 347 } 348 349 /** 350 * To be called when the both split tasks are ready to be launched. Call after launcher side 351 * animations are complete. 352 */ launchSplitTasks(@ersistentSnapPosition int snapPosition, @Nullable Consumer<Boolean> callback)353 public void launchSplitTasks(@PersistentSnapPosition int snapPosition, 354 @Nullable Consumer<Boolean> callback) { 355 launchTasks(callback, false /* freezeTaskList */, snapPosition, mSessionInstanceIds.first); 356 357 mStatsLogManager.logger() 358 .withItemInfo(mSplitSelectDataHolder.getSecondItemInfo()) 359 .withInstanceId(mSessionInstanceIds.second) 360 .log(LAUNCHER_SPLIT_SELECTED_SECOND_APP); 361 } 362 363 /** 364 * A version of {@link #launchTasks(Consumer, boolean, int, InstanceId)} with no success 365 * callback. 366 */ launchSplitTasks(@ersistentSnapPosition int snapPosition)367 public void launchSplitTasks(@PersistentSnapPosition int snapPosition) { 368 launchSplitTasks(snapPosition, /* callback */ null); 369 } 370 371 /** 372 * A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio. 373 */ launchSplitTasks(@ullable Consumer<Boolean> callback)374 public void launchSplitTasks(@Nullable Consumer<Boolean> callback) { 375 launchSplitTasks(SNAP_TO_50_50, callback); 376 } 377 378 /** 379 * A version of {@link #launchSplitTasks(int, Consumer)} that launches with a default split 380 * ratio and no callback. 381 */ launchSplitTasks()382 public void launchSplitTasks() { 383 launchSplitTasks(SNAP_TO_50_50, null); 384 } 385 386 /** 387 * Use to log an event when user exists split selection when the second app **IS NOT** selected. 388 * This must be called before playing any exit animations since most animations will call 389 * {@link #resetState()} which removes {@link #mSessionInstanceIds}. 390 */ logExitReason(StatsLogManager.EventEnum splitExitEvent)391 public void logExitReason(StatsLogManager.EventEnum splitExitEvent) { 392 StatsLogManager.StatsLogger logger = mStatsLogManager.logger(); 393 if (mSessionInstanceIds != null) { 394 logger.withInstanceId(mSessionInstanceIds.second); 395 } else { 396 Log.w(TAG, "Missing session instanceIds"); 397 } 398 logger.log(splitExitEvent); 399 } 400 401 /** 402 * To be called as soon as user selects the second task (even if animations aren't complete) 403 * @param task The second task that will be launched. 404 */ setSecondTask(Task task, ItemInfo itemInfo)405 public void setSecondTask(Task task, ItemInfo itemInfo) { 406 mSplitSelectDataHolder.setSecondTask(task.key.id, itemInfo); 407 } 408 409 /** 410 * To be called as soon as user selects the second app (even if animations aren't complete) 411 * @param intent The second intent that will be launched. 412 * @param user The user of that intent. 413 */ setSecondTask(Intent intent, UserHandle user, ItemInfo itemInfo)414 public void setSecondTask(Intent intent, UserHandle user, ItemInfo itemInfo) { 415 mSplitSelectDataHolder.setSecondTask(intent, user, itemInfo); 416 } 417 418 /** 419 * To be called as soon as user selects the second app (even if animations aren't complete) 420 * @param pendingIntent The second PendingIntent that will be launched. 421 */ setSecondTask(PendingIntent pendingIntent, ItemInfo itemInfo)422 public void setSecondTask(PendingIntent pendingIntent, ItemInfo itemInfo) { 423 mSplitSelectDataHolder.setSecondTask(pendingIntent, itemInfo); 424 } 425 setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent)426 public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) { 427 mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent, null /*itemInfo*/); 428 } 429 430 /** 431 * To be called when we want to launch split pairs from Overview. Split can be initiated from 432 * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a 433 * fill in intent with a taskId2 are set. 434 * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that 435 * create a split instance, null for cases that bring existing instaces to the 436 * foreground (quickswitch, launching previous pairs from overview) 437 */ launchTasks(@ullable Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId)438 public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList, 439 @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) { 440 TestLogging.recordEvent( 441 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks"); 442 final ActivityOptions options1 = ActivityOptions.makeBasic(); 443 if (freezeTaskList) { 444 options1.setFreezeRecentTasksReordering(); 445 } 446 447 SplitSelectDataHolder.SplitLaunchData launchData = 448 mSplitSelectDataHolder.getSplitLaunchData(); 449 int firstTaskId = launchData.getInitialTaskId(); 450 int secondTaskId = launchData.getSecondTaskId(); 451 ShortcutInfo firstShortcut = launchData.getInitialShortcut(); 452 ShortcutInfo secondShortcut = launchData.getSecondShortcut(); 453 PendingIntent firstPI = launchData.getInitialPendingIntent(); 454 PendingIntent secondPI = launchData.getSecondPendingIntent(); 455 Intent widgetIntent = launchData.getWidgetSecondIntent(); 456 int firstUserId = launchData.getInitialUserId(); 457 int secondUserId = launchData.getSecondUserId(); 458 int initialStagePosition = launchData.getInitialStagePosition(); 459 Bundle optionsBundle = options1.toBundle(); 460 Bundle extrasBundle = new Bundle(1); 461 extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent); 462 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 463 final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, 464 secondTaskId, callback, "LaunchSplitPair"); 465 switch (launchData.getSplitLaunchType()) { 466 case SPLIT_TASK_TASK -> 467 mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, 468 null /* options2 */, initialStagePosition, snapPosition, 469 remoteTransition, shellInstanceId); 470 471 case SPLIT_TASK_PENDINGINTENT -> 472 mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle, 473 firstTaskId, extrasBundle, initialStagePosition, snapPosition, 474 remoteTransition, shellInstanceId); 475 476 case SPLIT_TASK_SHORTCUT -> 477 mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle, 478 firstTaskId, null /*options2*/, initialStagePosition, snapPosition, 479 remoteTransition, shellInstanceId); 480 481 case SPLIT_PENDINGINTENT_TASK -> 482 mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle, 483 secondTaskId, null /*options2*/, initialStagePosition, snapPosition, 484 remoteTransition, shellInstanceId); 485 486 case SPLIT_PENDINGINTENT_PENDINGINTENT -> 487 mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut, 488 optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle, 489 initialStagePosition, snapPosition, remoteTransition, 490 shellInstanceId); 491 492 case SPLIT_SHORTCUT_TASK -> 493 mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle, 494 secondTaskId, null /*options2*/, initialStagePosition, snapPosition, 495 remoteTransition, shellInstanceId); 496 } 497 } else { 498 final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId, 499 callback); 500 switch (launchData.getSplitLaunchType()) { 501 case SPLIT_TASK_TASK -> 502 mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, 503 secondTaskId, null /* options2 */, initialStagePosition, 504 snapPosition, adapter, shellInstanceId); 505 506 case SPLIT_TASK_PENDINGINTENT -> 507 mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI, 508 secondUserId, optionsBundle, firstTaskId, null /*options2*/, 509 initialStagePosition, snapPosition, adapter, shellInstanceId); 510 511 case SPLIT_TASK_SHORTCUT -> 512 mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut, 513 optionsBundle, firstTaskId, null /*options2*/, initialStagePosition, 514 snapPosition, adapter, shellInstanceId); 515 516 case SPLIT_PENDINGINTENT_TASK -> 517 mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, 518 optionsBundle, secondTaskId, null /*options2*/, 519 initialStagePosition, snapPosition, adapter, shellInstanceId); 520 521 case SPLIT_PENDINGINTENT_PENDINGINTENT -> 522 mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId, 523 firstShortcut, optionsBundle, secondPI, secondUserId, 524 secondShortcut, null /*options2*/, initialStagePosition, 525 snapPosition, adapter, shellInstanceId); 526 527 case SPLIT_SHORTCUT_TASK -> 528 mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut, 529 optionsBundle, secondTaskId, null /*options2*/, 530 initialStagePosition, snapPosition, adapter, shellInstanceId); 531 } 532 } 533 } 534 535 /** 536 * Used to launch split screen from a split pair that already exists, optionally with a custom 537 * remote transition. 538 * <p> 539 * See {@link SplitSelectStateController#launchExistingSplitPair( 540 * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)} 541 */ launchExistingSplitPair(@ullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition)542 public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, 543 int firstTaskId, int secondTaskId, @StagePosition int stagePosition, 544 Consumer<Boolean> callback, boolean freezeTaskList, 545 @PersistentSnapPosition int snapPosition) { 546 launchExistingSplitPair( 547 groupedTaskView, 548 firstTaskId, 549 secondTaskId, 550 stagePosition, 551 callback, 552 freezeTaskList, 553 snapPosition, 554 /* remoteTransition= */ null); 555 } 556 557 558 /** 559 * Used to launch split screen from a split pair that already exists (usually accessible through 560 * Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)} 561 * in that this only launches split screen that are existing tasks. This doesn't determine which 562 * API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc). 563 * 564 * <p/> 565 * NOTE: This is not to be used to launch AppPairs. 566 */ launchExistingSplitPair(@ullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition)567 public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, 568 int firstTaskId, int secondTaskId, @StagePosition int stagePosition, 569 Consumer<Boolean> callback, boolean freezeTaskList, 570 @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { 571 mLaunchingTaskView = groupedTaskView; 572 final ActivityOptions options1 = ActivityOptions.makeBasic(); 573 if (freezeTaskList) { 574 options1.setFreezeRecentTasksReordering(); 575 } 576 Bundle optionsBundle = options1.toBundle(); 577 578 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 579 final RemoteTransition transition = remoteTransition == null 580 ? getShellRemoteTransition( 581 firstTaskId, secondTaskId, callback, "LaunchExistingPair") 582 : remoteTransition; 583 mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, 584 stagePosition, snapPosition, transition, null /*shellInstanceId*/); 585 } else { 586 final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, 587 secondTaskId, callback); 588 mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId, 589 null /* options2 */, stagePosition, snapPosition, adapter, 590 null /*shellInstanceId*/); 591 } 592 } 593 594 /** 595 * Launches the initially selected task/intent in fullscreen (note the same SystemUi APIs are 596 * used as {@link #launchSplitTasks(int, Consumer)} because they are overloaded to launch both 597 * split and fullscreen tasks) 598 */ launchInitialAppFullscreen(Consumer<Boolean> callback)599 public void launchInitialAppFullscreen(Consumer<Boolean> callback) { 600 final ActivityOptions options1 = ActivityOptions.makeBasic(); 601 SplitSelectDataHolder.SplitLaunchData launchData = 602 mSplitSelectDataHolder.getFullscreenLaunchData(); 603 int firstTaskId = launchData.getInitialTaskId(); 604 int secondTaskId = launchData.getSecondTaskId(); 605 PendingIntent firstPI = launchData.getInitialPendingIntent(); 606 int firstUserId = launchData.getInitialUserId(); 607 int initialStagePosition = launchData.getInitialStagePosition(); 608 ShortcutInfo initialShortcut = launchData.getInitialShortcut(); 609 Bundle optionsBundle = options1.toBundle(); 610 611 final RemoteSplitLaunchTransitionRunner animationRunner = 612 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); 613 final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, 614 ActivityThread.currentActivityThread().getApplicationThread(), 615 "LaunchAppFullscreen"); 616 InstanceId instanceId = mSessionInstanceIds.first; 617 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 618 switch (launchData.getSplitLaunchType()) { 619 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, 620 optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, 621 SNAP_TO_50_50, remoteTransition, instanceId); 622 case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, 623 firstUserId, optionsBundle, secondTaskId, null /*options2*/, 624 initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); 625 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask( 626 initialShortcut, optionsBundle, firstTaskId, null /* options2 */, 627 initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId); 628 } 629 } else { 630 final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, 631 secondTaskId, callback); 632 switch (launchData.getSplitLaunchType()) { 633 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition( 634 firstTaskId, optionsBundle, secondTaskId, null /* options2 */, 635 initialStagePosition, SNAP_TO_50_50, adapter, instanceId); 636 case SPLIT_SINGLE_INTENT_FULLSCREEN -> 637 mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, 638 optionsBundle, secondTaskId, null /*options2*/, 639 initialStagePosition, SNAP_TO_50_50, adapter, instanceId); 640 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> 641 mSystemUiProxy.startShortcutAndTaskWithLegacyTransition( 642 initialShortcut, optionsBundle, firstTaskId, null /* options2 */, 643 initialStagePosition, SNAP_TO_50_50, adapter, instanceId); 644 } 645 } 646 } 647 648 /** 649 * Init {@code SplitFromDesktopController} 650 */ initSplitFromDesktopController(QuickstepLauncher launcher, OverviewComponentObserver overviewComponentObserver)651 public void initSplitFromDesktopController(QuickstepLauncher launcher, 652 OverviewComponentObserver overviewComponentObserver) { 653 initSplitFromDesktopController( 654 new SplitFromDesktopController(launcher, overviewComponentObserver)); 655 } 656 657 @VisibleForTesting initSplitFromDesktopController(SplitFromDesktopController controller)658 void initSplitFromDesktopController(SplitFromDesktopController controller) { 659 mSplitFromDesktopController = controller; 660 } 661 getShellRemoteTransition(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback, String transitionName)662 private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId, 663 @Nullable Consumer<Boolean> callback, String transitionName) { 664 final RemoteSplitLaunchTransitionRunner animationRunner = 665 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); 666 return new RemoteTransition(animationRunner, 667 ActivityThread.currentActivityThread().getApplicationThread(), transitionName); 668 } 669 getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)670 private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, 671 @Nullable Consumer<Boolean> callback) { 672 final RemoteSplitLaunchAnimationRunner animationRunner = 673 new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); 674 return new RemoteAnimationAdapter(animationRunner, 300, 150, 675 ActivityThread.currentActivityThread().getApplicationThread()); 676 } 677 678 /** 679 * Will initialize {@link #mSessionInstanceIds} if null and log the first split event from 680 * {@link #mSplitSelectDataHolder} 681 */ createAndLogInstanceIdsForSession()682 private void createAndLogInstanceIdsForSession() { 683 if (mSessionInstanceIds != null) { 684 Log.w(TAG, "SessionIds should be null"); 685 } 686 // Log separately the start of the session and then the first app selected 687 mSessionInstanceIds = LogUtils.getShellShareableInstanceId(); 688 mStatsLogManager.logger() 689 .withInstanceId(mSessionInstanceIds.second) 690 .log(LAUNCHER_SPLIT_SELECTION_INITIATED); 691 692 mStatsLogManager.logger() 693 .withItemInfo(mSplitSelectDataHolder.getItemInfo()) 694 .withInstanceId(mSessionInstanceIds.second) 695 .log(mSplitSelectDataHolder.getSplitEvent()); 696 } 697 getActiveSplitStagePosition()698 public @StagePosition int getActiveSplitStagePosition() { 699 return mSplitSelectDataHolder.getInitialStagePosition(); 700 } 701 getSplitEvent()702 public StatsLogManager.EventEnum getSplitEvent() { 703 return mSplitSelectDataHolder.getSplitEvent(); 704 } 705 setRecentsAnimationRunning(boolean running)706 public void setRecentsAnimationRunning(boolean running) { 707 mRecentsAnimationRunning = running; 708 } 709 isAnimateCurrentTaskDismissal()710 public boolean isAnimateCurrentTaskDismissal() { 711 return mAnimateCurrentTaskDismissal; 712 } 713 setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal)714 public void setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal) { 715 mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal; 716 } 717 isDismissingFromSplitPair()718 public boolean isDismissingFromSplitPair() { 719 return mDismissingFromSplitPair; 720 } 721 setDismissingFromSplitPair(boolean dismissingFromSplitPair)722 public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) { 723 mDismissingFromSplitPair = dismissingFromSplitPair; 724 } 725 getSplitAnimationController()726 public SplitAnimationController getSplitAnimationController() { 727 return mSplitAnimationController; 728 } 729 setLaunchingCuj(int launchCuj)730 public void setLaunchingCuj(int launchCuj) { 731 mLaunchCuj = launchCuj; 732 } 733 734 /** 735 * Requires Shell Transitions 736 */ 737 private class RemoteSplitLaunchTransitionRunner extends RemoteTransitionStub { 738 739 private final int mInitialTaskId; 740 private final int mSecondTaskId; 741 private Consumer<Boolean> mFinishCallback; 742 RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)743 RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, 744 @Nullable Consumer<Boolean> callback) { 745 mInitialTaskId = initialTaskId; 746 mSecondTaskId = secondTaskId; 747 mFinishCallback = callback; 748 } 749 750 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback)751 public void startAnimation(IBinder transition, TransitionInfo info, 752 SurfaceControl.Transaction t, 753 IRemoteTransitionFinishedCallback finishedCallback) { 754 final Runnable finishAdapter = () -> { 755 try { 756 finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); 757 } catch (RemoteException e) { 758 Log.e(TAG, "Failed to call transition finished callback", e); 759 } 760 }; 761 762 MAIN_EXECUTOR.execute(() -> { 763 // Only animate from taskView if it's already visible 764 boolean shouldLaunchFromTaskView = mLaunchingTaskView != null 765 && mLaunchingTaskView.getRecentsView() != null 766 && mLaunchingTaskView.getRecentsView().isTaskViewVisible( 767 mLaunchingTaskView); 768 mSplitAnimationController.playSplitLaunchAnimation( 769 shouldLaunchFromTaskView ? mLaunchingTaskView : null, 770 mLaunchingIconView, 771 mInitialTaskId, 772 mSecondTaskId, 773 null /* apps */, 774 null /* wallpapers */, 775 null /* nonApps */, 776 mStateManager, 777 mDepthController, 778 info, t, () -> { 779 finishAdapter.run(); 780 cleanup(true /*success*/); 781 }); 782 }); 783 } 784 785 @Override onTransitionConsumed(IBinder transition, boolean aborted)786 public void onTransitionConsumed(IBinder transition, boolean aborted) 787 throws RemoteException { 788 MAIN_EXECUTOR.execute(() -> { 789 cleanup(false /*success*/); 790 }); 791 } 792 793 /** 794 * Must be called on UI thread. 795 * @param success if launching the split apps occurred successfully or not 796 */ 797 @UiThread cleanup(boolean success)798 private void cleanup(boolean success) { 799 if (mFinishCallback != null) { 800 mFinishCallback.accept(success); 801 mFinishCallback = null; 802 } 803 resetState(); 804 } 805 } 806 807 /** 808 * LEGACY 809 * Remote animation runner for animation to launch an app. 810 */ 811 private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat { 812 813 private final int mInitialTaskId; 814 private final int mSecondTaskId; 815 private final Consumer<Boolean> mSuccessCallback; 816 RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> successCallback)817 RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, 818 @Nullable Consumer<Boolean> successCallback) { 819 mInitialTaskId = initialTaskId; 820 mSecondTaskId = secondTaskId; 821 mSuccessCallback = successCallback; 822 } 823 824 @Override onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)825 public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, 826 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, 827 Runnable finishedCallback) { 828 postAsyncCallback(mHandler, 829 () -> mSplitAnimationController.playSplitLaunchAnimation(mLaunchingTaskView, 830 mLaunchingIconView, mInitialTaskId, mSecondTaskId, apps, wallpapers, 831 nonApps, mStateManager, mDepthController, null /* info */, null /* t */, 832 () -> { 833 finishedCallback.run(); 834 if (mSuccessCallback != null) { 835 mSuccessCallback.accept(true); 836 } 837 resetState(); 838 })); 839 } 840 841 @Override onAnimationCancelled()842 public void onAnimationCancelled() { 843 postAsyncCallback(mHandler, () -> { 844 if (mSuccessCallback != null) { 845 // Launching legacy tasks while recents animation is running will always cause 846 // onAnimationCancelled to be called (should be fixed w/ shell transitions?) 847 mSuccessCallback.accept(mRecentsAnimationRunning); 848 } 849 resetState(); 850 }); 851 } 852 } 853 854 /** 855 * To be called whenever we exit split selection state. If 856 * {@link FeatureFlags#enableSplitContextually()} is set, this should be the 857 * central way split is getting reset, which should then go through the callbacks to reset 858 * other state. 859 */ resetState()860 public void resetState() { 861 mSplitSelectDataHolder.resetState(); 862 dispatchOnSplitSelectionExit(); 863 mRecentsAnimationRunning = false; 864 mLaunchingTaskView = null; 865 mLaunchingIconView = null; 866 mAnimateCurrentTaskDismissal = false; 867 mDismissingFromSplitPair = false; 868 mFirstFloatingTaskView = null; 869 mSplitInstructionsView = null; 870 mLaunchingFirstAppFullscreen = false; 871 872 if (mLaunchCuj != -1) { 873 InteractionJankMonitorWrapper.end(mLaunchCuj); 874 } 875 mLaunchCuj = -1; 876 877 if (mSessionInstanceIds != null) { 878 mStatsLogManager.logger() 879 .withInstanceId(mSessionInstanceIds.second) 880 .log(LAUNCHER_SPLIT_SELECTION_COMPLETE); 881 } 882 mSessionInstanceIds = null; 883 } 884 885 /** 886 * @return {@code true} if first task has been selected and waiting for the second task to be 887 * chosen 888 */ isSplitSelectActive()889 public boolean isSplitSelectActive() { 890 return mSplitSelectDataHolder.isSplitSelectActive(); 891 } 892 893 /** 894 * @return {@code true} if the first and second task have been chosen and split is waiting to 895 * be launched 896 */ isBothSplitAppsConfirmed()897 public boolean isBothSplitAppsConfirmed() { 898 return mSplitSelectDataHolder.isBothSplitAppsConfirmed(); 899 } 900 isLaunchingFirstAppFullscreen()901 public boolean isLaunchingFirstAppFullscreen() { 902 return mLaunchingFirstAppFullscreen; 903 } 904 getInitialTaskId()905 public int getInitialTaskId() { 906 return mSplitSelectDataHolder.getInitialTaskId(); 907 } 908 getSecondTaskId()909 public int getSecondTaskId() { 910 return mSplitSelectDataHolder.getSecondTaskId(); 911 } 912 setLaunchingFirstAppFullscreen()913 public void setLaunchingFirstAppFullscreen() { 914 mLaunchingFirstAppFullscreen = true; 915 } setFirstFloatingTaskView(FloatingTaskView floatingTaskView)916 public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) { 917 mFirstFloatingTaskView = floatingTaskView; 918 } 919 setSplitInstructionsView(SplitInstructionsView splitInstructionsView)920 public void setSplitInstructionsView(SplitInstructionsView splitInstructionsView) { 921 mSplitInstructionsView = splitInstructionsView; 922 } 923 924 @Nullable getFirstFloatingTaskView()925 public FloatingTaskView getFirstFloatingTaskView() { 926 return mFirstFloatingTaskView; 927 } 928 929 @Nullable getSplitInstructionsView()930 public SplitInstructionsView getSplitInstructionsView() { 931 return mSplitInstructionsView; 932 } 933 getAppPairsController()934 public AppPairsController getAppPairsController() { 935 return mAppPairsController; 936 } 937 setLaunchingIconView(AppPairIcon launchingIconView)938 public void setLaunchingIconView(AppPairIcon launchingIconView) { 939 mLaunchingIconView = launchingIconView; 940 } 941 getSplitBackHandler()942 public BackPressHandler getSplitBackHandler() { 943 return mSplitBackHandler; 944 } 945 dump(String prefix, PrintWriter writer)946 public void dump(String prefix, PrintWriter writer) { 947 if (mSplitSelectDataHolder != null) { 948 mSplitSelectDataHolder.dump(prefix, writer); 949 } 950 } 951 952 public class SplitFromDesktopController { 953 private static final String TAG = "SplitFromDesktopController"; 954 955 private final QuickstepLauncher mLauncher; 956 private final OverviewComponentObserver mOverviewComponentObserver; 957 private final int mSplitPlaceholderSize; 958 private final int mSplitPlaceholderInset; 959 private ActivityManager.RunningTaskInfo mTaskInfo; 960 private ISplitSelectListener mSplitSelectListener; 961 private Drawable mAppIcon; 962 SplitFromDesktopController(QuickstepLauncher launcher, OverviewComponentObserver overviewComponentObserver)963 public SplitFromDesktopController(QuickstepLauncher launcher, 964 OverviewComponentObserver overviewComponentObserver) { 965 mLauncher = launcher; 966 mOverviewComponentObserver = overviewComponentObserver; 967 mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( 968 R.dimen.split_placeholder_size); 969 mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( 970 R.dimen.split_placeholder_inset); 971 mSplitSelectListener = new ISplitSelectListener.Stub() { 972 @Override 973 public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo, 974 int splitPosition, Rect taskBounds) { 975 MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition, 976 taskBounds)); 977 return true; 978 } 979 }; 980 SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener); 981 } 982 onDestroy()983 void onDestroy() { 984 SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener( 985 mSplitSelectListener); 986 mSplitSelectListener = null; 987 } 988 989 /** 990 * Enter split select from desktop mode. 991 * @param taskInfo the desktop task to move to split stage 992 * @param splitPosition the stage position used for this transition 993 * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation 994 */ enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, int splitPosition, Rect taskBounds)995 public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, 996 int splitPosition, Rect taskBounds) { 997 mTaskInfo = taskInfo; 998 String packageName = mTaskInfo.realActivity.getPackageName(); 999 PackageManager pm = mLauncher.getApplicationContext().getPackageManager(); 1000 IconProvider provider = new IconProvider(mLauncher.getApplicationContext()); 1001 try { 1002 mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, 1003 PackageManager.ComponentInfoFlags.of(0))); 1004 } catch (PackageManager.NameNotFoundException e) { 1005 Log.w(TAG, "Package not found: " + packageName, e); 1006 } 1007 RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( 1008 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), 1009 false /* allowMinimizeSplitScreen */); 1010 1011 DesktopSplitRecentsAnimationListener listener = 1012 new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds); 1013 1014 MAIN_EXECUTOR.execute(() -> { 1015 callbacks.addListener(listener); 1016 UI_HELPER_EXECUTOR.execute( 1017 // Transition from app to enter stage split in launcher with 1018 // recents animation. 1019 () -> ActivityManagerWrapper.getInstance().startRecentsActivity( 1020 mOverviewComponentObserver.getOverviewIntent(), 1021 SystemClock.uptimeMillis(), callbacks, null, null)); 1022 }); 1023 } 1024 1025 private class DesktopSplitRecentsAnimationListener implements 1026 RecentsAnimationCallbacks.RecentsAnimationListener { 1027 private final Rect mTempRect = new Rect(); 1028 private final RectF mTaskBounds = new RectF(); 1029 private final int mSplitPosition; 1030 DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds)1031 DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds) { 1032 mSplitPosition = splitPosition; 1033 mTaskBounds.set(taskBounds); 1034 } 1035 1036 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)1037 public void onRecentsAnimationStart(RecentsAnimationController controller, 1038 RecentsAnimationTargets targets) { 1039 StatsLogManager.LauncherEvent launcherDesktopSplitEvent = 1040 mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ? 1041 LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM : 1042 LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP; 1043 setInitialTaskSelect(mTaskInfo, mSplitPosition, 1044 null, launcherDesktopSplitEvent); 1045 1046 RecentsView recentsView = mLauncher.getOverviewPanel(); 1047 recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds( 1048 mSplitPlaceholderSize, mSplitPlaceholderInset, 1049 mLauncher.getDeviceProfile(), getActiveSplitStagePosition(), mTempRect); 1050 1051 PendingAnimation anim = new PendingAnimation( 1052 SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration()); 1053 final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView( 1054 mLauncher, mLauncher.getDragLayer(), 1055 null /* thumbnail */, 1056 mAppIcon, new RectF()); 1057 floatingTaskView.setAlpha(1); 1058 floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect, 1059 false /* fadeWithThumbnail */, true /* isStagedTask */); 1060 setFirstFloatingTaskView(floatingTaskView); 1061 1062 anim.addListener(new AnimatorListenerAdapter() { 1063 @Override 1064 public void onAnimationStart(Animator animation) { 1065 controller.finish(true /* toRecents */, null /* onFinishComplete */, 1066 false /* sendUserLeaveHint */); 1067 } 1068 @Override 1069 public void onAnimationEnd(Animator animation) { 1070 SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) 1071 .onDesktopSplitSelectAnimComplete(mTaskInfo); 1072 } 1073 }); 1074 anim.buildAnim().start(); 1075 } 1076 } 1077 } 1078 } 1079