• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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