• 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 com.android.launcher3.Utilities.postAsyncCallback;
20 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
23 import static com.android.launcher3.testing.shared.TestProtocol.LAUNCH_SPLIT_PAIR;
24 import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
25 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
26 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
27 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
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 
39 import android.animation.Animator;
40 import android.animation.AnimatorListenerAdapter;
41 import android.annotation.NonNull;
42 import android.app.ActivityManager;
43 import android.app.ActivityOptions;
44 import android.app.ActivityThread;
45 import android.app.PendingIntent;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.pm.PackageManager;
49 import android.content.pm.ShortcutInfo;
50 import android.graphics.Rect;
51 import android.graphics.RectF;
52 import android.graphics.drawable.Drawable;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.RemoteException;
57 import android.os.SystemClock;
58 import android.os.UserHandle;
59 import android.util.Log;
60 import android.util.Pair;
61 import android.view.RemoteAnimationAdapter;
62 import android.view.RemoteAnimationTarget;
63 import android.view.SurfaceControl;
64 import android.window.IRemoteTransition;
65 import android.window.IRemoteTransitionFinishedCallback;
66 import android.window.RemoteTransition;
67 import android.window.TransitionInfo;
68 
69 import androidx.annotation.Nullable;
70 
71 import com.android.internal.logging.InstanceId;
72 import com.android.launcher3.Launcher;
73 import com.android.launcher3.R;
74 import com.android.launcher3.anim.PendingAnimation;
75 import com.android.launcher3.config.FeatureFlags;
76 import com.android.launcher3.icons.IconProvider;
77 import com.android.launcher3.logging.StatsLogManager;
78 import com.android.launcher3.model.data.ItemInfo;
79 import com.android.launcher3.statehandlers.DepthController;
80 import com.android.launcher3.statemanager.StateManager;
81 import com.android.launcher3.testing.TestLogging;
82 import com.android.launcher3.testing.shared.TestProtocol;
83 import com.android.launcher3.util.ComponentKey;
84 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
85 import com.android.quickstep.OverviewComponentObserver;
86 import com.android.quickstep.RecentsAnimationCallbacks;
87 import com.android.quickstep.RecentsAnimationController;
88 import com.android.quickstep.RecentsAnimationDeviceState;
89 import com.android.quickstep.RecentsAnimationTargets;
90 import com.android.quickstep.RecentsModel;
91 import com.android.quickstep.SplitSelectionListener;
92 import com.android.quickstep.SystemUiProxy;
93 import com.android.quickstep.TaskAnimationManager;
94 import com.android.quickstep.TaskViewUtils;
95 import com.android.quickstep.views.FloatingTaskView;
96 import com.android.quickstep.views.GroupedTaskView;
97 import com.android.quickstep.views.SplitInstructionsView;
98 import com.android.quickstep.views.RecentsView;
99 import com.android.systemui.shared.recents.model.Task;
100 import com.android.systemui.shared.system.ActivityManagerWrapper;
101 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
102 import com.android.wm.shell.splitscreen.ISplitSelectListener;
103 
104 import java.io.PrintWriter;
105 import java.util.ArrayList;
106 import java.util.Collections;
107 import java.util.List;
108 import java.util.function.Consumer;
109 
110 /**
111  * Represent data needed for the transient state when user has selected one app for split screen
112  * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
113  */
114 public class SplitSelectStateController {
115     private static final String TAG = "SplitSelectStateCtor";
116 
117     private Context mContext;
118     private final Handler mHandler;
119     private final RecentsModel mRecentTasksModel;
120     private final SplitAnimationController mSplitAnimationController;
121     private final AppPairsController mAppPairsController;
122     private final SplitSelectDataHolder mSplitSelectDataHolder;
123     private final StatsLogManager mStatsLogManager;
124     private final SystemUiProxy mSystemUiProxy;
125     private final StateManager mStateManager;
126     private SplitFromDesktopController mSplitFromDesktopController;
127     @Nullable
128     private DepthController mDepthController;
129     private boolean mRecentsAnimationRunning;
130     /** If {@code true}, animates the existing task view split placeholder view */
131     private boolean mAnimateCurrentTaskDismissal;
132     /**
133      * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
134      * split pair task view without wanting to animate current task dismissal overall
135      */
136     private boolean mDismissingFromSplitPair;
137     /** If not null, this is the TaskView we want to launch from */
138     @Nullable
139     private GroupedTaskView mLaunchingTaskView;
140 
141     private FloatingTaskView mFirstFloatingTaskView;
142     private SplitInstructionsView mSplitInstructionsView;
143 
144     private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>();
145 
SplitSelectStateController(Context context, Handler handler, StateManager stateManager, DepthController depthController, StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel)146     public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
147             DepthController depthController, StatsLogManager statsLogManager,
148             SystemUiProxy systemUiProxy, RecentsModel recentsModel) {
149         mContext = context;
150         mHandler = handler;
151         mStatsLogManager = statsLogManager;
152         mSystemUiProxy = systemUiProxy;
153         mStateManager = stateManager;
154         mDepthController = depthController;
155         mRecentTasksModel = recentsModel;
156         mSplitAnimationController = new SplitAnimationController(this);
157         mAppPairsController = new AppPairsController(context, this, statsLogManager);
158         mSplitSelectDataHolder = new SplitSelectDataHolder(mContext);
159     }
160 
onDestroy()161     public void onDestroy() {
162         mContext = null;
163     }
164 
165     /**
166      * @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID}
167      *                           then @param intent will be used to launch the initial task
168      * @param intent will be ignored if @param alreadyRunningTask is set
169      */
setInitialTaskSelect(@ullable Intent intent, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent, int alreadyRunningTask)170     public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition,
171             @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
172             int alreadyRunningTask) {
173         mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
174                 alreadyRunningTask);
175     }
176 
177     /**
178      * To be called after first task selected from using a split shortcut from the fullscreen
179      * running app.
180      */
setInitialTaskSelect(ActivityManager.RunningTaskInfo info, @StagePosition int stagePosition, @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent)181     public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info,
182             @StagePosition int stagePosition, @NonNull ItemInfo itemInfo,
183             StatsLogManager.EventEnum splitEvent) {
184         mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
185     }
186 
187     /**
188      * Maps a List<ComponentKey> to List<@Nullable Task>, searching through active Tasks in
189      * RecentsModel. If found, the Task will be the most recently-interacted-with instance of that
190      * Task. Then runs the given callback on that List.
191      * <p>
192      * Used in various task-switching or splitscreen operations when we need to check if there is a
193      * currently running Task of a certain type and use the most recent one.
194      */
findLastActiveTasksAndRunCallback( @ullable List<ComponentKey> componentKeys, Consumer<List<Task>> callback)195     public void findLastActiveTasksAndRunCallback(
196             @Nullable List<ComponentKey> componentKeys, Consumer<List<Task>> callback) {
197         mRecentTasksModel.getTasks(taskGroups -> {
198             if (componentKeys == null || componentKeys.isEmpty()) {
199                 callback.accept(Collections.emptyList());
200                 return;
201             }
202 
203             List<Task> lastActiveTasks = new ArrayList<>();
204             // For each key we are looking for, add to lastActiveTasks with the corresponding Task
205             // (or null if not found).
206             for (ComponentKey key : componentKeys) {
207                 Task lastActiveTask = null;
208                 // Loop through tasks in reverse, since they are ordered with most-recent tasks last
209                 for (int i = taskGroups.size() - 1; i >= 0; i--) {
210                     GroupTask groupTask = taskGroups.get(i);
211                     Task task1 = groupTask.task1;
212                     // Don't add duplicate Tasks
213                     if (isInstanceOfComponent(task1, key) && !lastActiveTasks.contains(task1)) {
214                         lastActiveTask = task1;
215                         break;
216                     }
217                     Task task2 = groupTask.task2;
218                     if (isInstanceOfComponent(task2, key) && !lastActiveTasks.contains(task2)) {
219                         lastActiveTask = task2;
220                         break;
221                     }
222                 }
223 
224                 lastActiveTasks.add(lastActiveTask);
225             }
226 
227             callback.accept(lastActiveTasks);
228         });
229     }
230 
231     /**
232      * Checks if a given Task is the most recently-active Task of type componentName. Used for
233      * selecting already-running Tasks for splitscreen.
234      */
isInstanceOfComponent(@ullable Task task, @NonNull ComponentKey componentKey)235     public boolean isInstanceOfComponent(@Nullable Task task, @NonNull ComponentKey componentKey) {
236         // Exclude the task that is already staged
237         if (task == null || task.key.id == mSplitSelectDataHolder.getInitialTaskId()) {
238             return false;
239         }
240 
241         return task.key.baseIntent.getComponent().equals(componentKey.componentName)
242                 && task.key.userId == componentKey.user.getIdentifier();
243     }
244 
245     /**
246      * Listener will only get callbacks going forward from the point of registration. No
247      * methods will be fired upon registering.
248      */
registerSplitListener(@onNull SplitSelectionListener listener)249     public void registerSplitListener(@NonNull SplitSelectionListener listener) {
250         if (mSplitSelectionListeners.contains(listener)) {
251             return;
252         }
253         mSplitSelectionListeners.add(listener);
254     }
255 
unregisterSplitListener(@onNull SplitSelectionListener listener)256     public void unregisterSplitListener(@NonNull SplitSelectionListener listener) {
257         mSplitSelectionListeners.remove(listener);
258     }
259 
dispatchOnSplitSelectionExit()260     private void dispatchOnSplitSelectionExit() {
261         for (SplitSelectionListener listener : mSplitSelectionListeners) {
262             listener.onSplitSelectionExit(false);
263         }
264     }
265 
266     /**
267      * To be called when the both split tasks are ready to be launched. Call after launcher side
268      * animations are complete.
269      */
launchSplitTasks(@ullable Consumer<Boolean> callback)270     public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
271         Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
272                 LogUtils.getShellShareableInstanceId();
273         launchTasks(callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
274                 instanceIds.first);
275 
276         mStatsLogManager.logger()
277                 .withItemInfo(mSplitSelectDataHolder.getItemInfo())
278                 .withInstanceId(instanceIds.second)
279                 .log(mSplitSelectDataHolder.getSplitEvent());
280     }
281 
282     /**
283      * A version of {@link #launchTasks(Consumer, boolean, float, InstanceId)} with no success
284      * callback.
285      */
launchSplitTasks()286     public void launchSplitTasks() {
287         launchSplitTasks(null);
288     }
289 
290     /**
291      * To be called as soon as user selects the second task (even if animations aren't complete)
292      * @param task The second task that will be launched.
293      */
setSecondTask(Task task)294     public void setSecondTask(Task task) {
295         mSplitSelectDataHolder.setSecondTask(task.key.id);
296     }
297 
298     /**
299      * To be called as soon as user selects the second app (even if animations aren't complete)
300      * @param intent The second intent that will be launched.
301      * @param user The user of that intent.
302      */
setSecondTask(Intent intent, UserHandle user)303     public void setSecondTask(Intent intent, UserHandle user) {
304         mSplitSelectDataHolder.setSecondTask(intent, user);
305     }
306 
307     /**
308      * To be called as soon as user selects the second app (even if animations aren't complete)
309      * @param pendingIntent The second PendingIntent that will be launched.
310      */
setSecondTask(PendingIntent pendingIntent)311     public void setSecondTask(PendingIntent pendingIntent) {
312         mSplitSelectDataHolder.setSecondTask(pendingIntent);
313     }
314 
315     /**
316      * To be called when we want to launch split pairs from Overview. Split can be initiated from
317      * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
318      * fill in intent with a taskId2 are set.
319      * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that
320      *                   create a split instance, null for cases that bring existing instaces to the
321      *                   foreground (quickswitch, launching previous pairs from overview)
322      */
launchTasks(@ullable Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio, @Nullable InstanceId shellInstanceId)323     public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
324             float splitRatio, @Nullable InstanceId shellInstanceId) {
325         TestLogging.recordEvent(
326                 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
327         final ActivityOptions options1 = ActivityOptions.makeBasic();
328         if (freezeTaskList) {
329             options1.setFreezeRecentTasksReordering();
330         }
331 
332         SplitSelectDataHolder.SplitLaunchData launchData =
333                 mSplitSelectDataHolder.getSplitLaunchData();
334         int firstTaskId = launchData.getInitialTaskId();
335         int secondTaskId = launchData.getSecondTaskId();
336         ShortcutInfo firstShortcut = launchData.getInitialShortcut();
337         ShortcutInfo secondShortcut = launchData.getSecondShortcut();
338         PendingIntent firstPI = launchData.getInitialPendingIntent();
339         PendingIntent secondPI = launchData.getSecondPendingIntent();
340         int firstUserId = launchData.getInitialUserId();
341         int secondUserId = launchData.getSecondUserId();
342         int initialStagePosition = launchData.getInitialStagePosition();
343         Bundle optionsBundle = options1.toBundle();
344 
345         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
346             final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
347                     secondTaskId, callback, "LaunchSplitPair");
348             switch (launchData.getSplitLaunchType()) {
349                 case SPLIT_TASK_TASK ->
350                         mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
351                                 null /* options2 */, initialStagePosition, splitRatio,
352                                 remoteTransition, shellInstanceId);
353 
354                 case SPLIT_TASK_PENDINGINTENT ->
355                         mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
356                                 firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
357                                 remoteTransition, shellInstanceId);
358 
359                 case SPLIT_TASK_SHORTCUT ->
360                         mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
361                                 firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
362                                 remoteTransition, shellInstanceId);
363 
364                 case SPLIT_PENDINGINTENT_TASK ->
365                         mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
366                                 secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
367                                 remoteTransition, shellInstanceId);
368 
369                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
370                         mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
371                                 optionsBundle, secondPI, secondUserId, secondShortcut,
372                                 null /*options2*/, initialStagePosition, splitRatio,
373                                 remoteTransition, shellInstanceId);
374 
375                 case SPLIT_SHORTCUT_TASK ->
376                         mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
377                                 secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
378                                 remoteTransition, shellInstanceId);
379             }
380         } else {
381             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId,
382                     callback);
383             switch (launchData.getSplitLaunchType()) {
384                 case SPLIT_TASK_TASK ->
385                         mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
386                                 secondTaskId, null /* options2 */, initialStagePosition,
387                                 splitRatio, adapter, shellInstanceId);
388 
389                 case SPLIT_TASK_PENDINGINTENT ->
390                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
391                                 secondUserId, optionsBundle, firstTaskId, null /*options2*/,
392                                 initialStagePosition, splitRatio, adapter, shellInstanceId);
393 
394                 case SPLIT_TASK_SHORTCUT ->
395                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
396                                 optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
397                                 splitRatio, adapter, shellInstanceId);
398 
399                 case SPLIT_PENDINGINTENT_TASK ->
400                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
401                                 optionsBundle, secondTaskId, null /*options2*/,
402                                 initialStagePosition, splitRatio, adapter, shellInstanceId);
403 
404                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
405                         mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
406                                 firstShortcut, optionsBundle, secondPI, secondUserId,
407                                 secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
408                                 adapter, shellInstanceId);
409 
410                 case SPLIT_SHORTCUT_TASK ->
411                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
412                                 optionsBundle, secondTaskId, null /*options2*/,
413                                 initialStagePosition, splitRatio, adapter, shellInstanceId);
414             }
415         }
416     }
417 
418     /**
419      * Used to launch split screen from a split pair that already exists (usually accessible through
420      * Overview). This is different than {@link #launchTasks(Consumer, boolean, float, InstanceId)}
421      * in that this only launches split screen that are existing tasks. This doesn't determine which
422      * API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc).
423      *
424      * <p/>
425      * NOTE: This is not to be used to launch AppPairs.
426      */
launchExistingSplitPair(@ullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio)427     public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
428             int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
429             Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
430         mLaunchingTaskView = groupedTaskView;
431         final ActivityOptions options1 = ActivityOptions.makeBasic();
432         if (freezeTaskList) {
433             options1.setFreezeRecentTasksReordering();
434         }
435         Bundle optionsBundle = options1.toBundle();
436 
437         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
438             final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
439                     secondTaskId, callback, "LaunchExistingPair");
440             mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
441                     null /* options2 */, stagePosition, splitRatio,
442                     remoteTransition, null /*shellInstanceId*/);
443         } else {
444             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
445                     secondTaskId, callback);
446             mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
447                     secondTaskId, null /* options2 */, stagePosition,
448                     splitRatio, adapter, null /*shellInstanceId*/);
449         }
450     }
451 
452     /**
453      * Launches the initially selected task/intent in fullscreen (note the same SystemUi APIs are
454      * used as {@link #launchSplitTasks(Consumer)} because they are overloaded to launch both
455      * split and fullscreen tasks)
456      */
launchInitialAppFullscreen(Consumer<Boolean> callback)457     public void launchInitialAppFullscreen(Consumer<Boolean> callback) {
458         final ActivityOptions options1 = ActivityOptions.makeBasic();
459         SplitSelectDataHolder.SplitLaunchData launchData =
460                 mSplitSelectDataHolder.getFullscreenLaunchData();
461         int firstTaskId = launchData.getInitialTaskId();
462         int secondTaskId = launchData.getSecondTaskId();
463         PendingIntent firstPI = launchData.getInitialPendingIntent();
464         int firstUserId = launchData.getInitialUserId();
465         int initialStagePosition = launchData.getInitialStagePosition();
466         ShortcutInfo initialShortcut = launchData.getInitialShortcut();
467         Bundle optionsBundle = options1.toBundle();
468 
469         final RemoteSplitLaunchTransitionRunner animationRunner =
470                 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
471         final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
472                 ActivityThread.currentActivityThread().getApplicationThread(),
473                 "LaunchAppFullscreen");
474         InstanceId instanceId = LogUtils.getShellShareableInstanceId().first;
475         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
476             switch (launchData.getSplitLaunchType()) {
477                 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
478                         optionsBundle, secondTaskId, null /* options2 */, initialStagePosition,
479                         DEFAULT_SPLIT_RATIO, remoteTransition, instanceId);
480                 case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI,
481                         firstUserId, optionsBundle, secondTaskId, null /*options2*/,
482                         initialStagePosition, DEFAULT_SPLIT_RATIO, remoteTransition,
483                         instanceId);
484                 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask(
485                         initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
486                         initialStagePosition, DEFAULT_SPLIT_RATIO, remoteTransition, instanceId);
487             }
488         } else {
489             final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
490                     secondTaskId, callback);
491             switch (launchData.getSplitLaunchType()) {
492                 case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition(
493                         firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
494                         initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, instanceId);
495                 case SPLIT_SINGLE_INTENT_FULLSCREEN ->
496                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
497                                 optionsBundle, secondTaskId, null /*options2*/,
498                                 initialStagePosition, DEFAULT_SPLIT_RATIO, adapter,
499                                 instanceId);
500                 case SPLIT_SINGLE_SHORTCUT_FULLSCREEN ->
501                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(
502                                 initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
503                                 initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, instanceId);
504             }
505         }
506     }
507 
initSplitFromDesktopController(Launcher launcher)508     public void initSplitFromDesktopController(Launcher launcher) {
509         mSplitFromDesktopController = new SplitFromDesktopController(launcher);
510     }
511 
getShellRemoteTransition(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback, String transitionName)512     private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
513             @Nullable Consumer<Boolean> callback, String transitionName) {
514         final RemoteSplitLaunchTransitionRunner animationRunner =
515                 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
516         return new RemoteTransition(animationRunner,
517                 ActivityThread.currentActivityThread().getApplicationThread(), transitionName);
518     }
519 
getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)520     private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId,
521             @Nullable Consumer<Boolean> callback) {
522         final RemoteSplitLaunchAnimationRunner animationRunner =
523                 new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
524         return new RemoteAnimationAdapter(animationRunner, 300, 150,
525                 ActivityThread.currentActivityThread().getApplicationThread());
526     }
527 
getActiveSplitStagePosition()528     public @StagePosition int getActiveSplitStagePosition() {
529         return mSplitSelectDataHolder.getInitialStagePosition();
530     }
531 
getSplitEvent()532     public StatsLogManager.EventEnum getSplitEvent() {
533         return mSplitSelectDataHolder.getSplitEvent();
534     }
535 
setRecentsAnimationRunning(boolean running)536     public void setRecentsAnimationRunning(boolean running) {
537         mRecentsAnimationRunning = running;
538     }
539 
isAnimateCurrentTaskDismissal()540     public boolean isAnimateCurrentTaskDismissal() {
541         return mAnimateCurrentTaskDismissal;
542     }
543 
setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal)544     public void setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal) {
545         mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
546     }
547 
isDismissingFromSplitPair()548     public boolean isDismissingFromSplitPair() {
549         return mDismissingFromSplitPair;
550     }
551 
setDismissingFromSplitPair(boolean dismissingFromSplitPair)552     public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
553         mDismissingFromSplitPair = dismissingFromSplitPair;
554     }
555 
getSplitAnimationController()556     public SplitAnimationController getSplitAnimationController() {
557         return mSplitAnimationController;
558     }
559 
560     /**
561      * Requires Shell Transitions
562      */
563     private class RemoteSplitLaunchTransitionRunner extends IRemoteTransition.Stub {
564 
565         private final int mInitialTaskId;
566         private final int mSecondTaskId;
567         private final Consumer<Boolean> mSuccessCallback;
568 
RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> callback)569         RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
570                 @Nullable Consumer<Boolean> callback) {
571             mInitialTaskId = initialTaskId;
572             mSecondTaskId = secondTaskId;
573             mSuccessCallback = callback;
574         }
575 
576         @Override
startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback)577         public void startAnimation(IBinder transition, TransitionInfo info,
578                 SurfaceControl.Transaction t,
579                 IRemoteTransitionFinishedCallback finishedCallback) {
580             testLogD(LAUNCH_SPLIT_PAIR, "Received split startAnimation");
581             final Runnable finishAdapter = () ->  {
582                 try {
583                     finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
584                 } catch (RemoteException e) {
585                     Log.e(TAG, "Failed to call transition finished callback", e);
586                 }
587             };
588 
589             MAIN_EXECUTOR.execute(() -> {
590                 TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager,
591                         mDepthController, mInitialTaskId, mSecondTaskId, info, t, () -> {
592                             finishAdapter.run();
593                             if (mSuccessCallback != null) {
594                                 mSuccessCallback.accept(true);
595                             }
596                             resetState();
597                         });
598             });
599         }
600 
601         @Override
mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishedCallback)602         public void mergeAnimation(IBinder transition, TransitionInfo info,
603                 SurfaceControl.Transaction t, IBinder mergeTarget,
604                 IRemoteTransitionFinishedCallback finishedCallback) { }
605     }
606 
607     /**
608      * LEGACY
609      * Remote animation runner for animation to launch an app.
610      */
611     private class RemoteSplitLaunchAnimationRunner extends RemoteAnimationRunnerCompat {
612 
613         private final int mInitialTaskId;
614         private final int mSecondTaskId;
615         private final Consumer<Boolean> mSuccessCallback;
616 
RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId, @Nullable Consumer<Boolean> successCallback)617         RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId,
618                 @Nullable Consumer<Boolean> successCallback) {
619             mInitialTaskId = initialTaskId;
620             mSecondTaskId = secondTaskId;
621             mSuccessCallback = successCallback;
622         }
623 
624         @Override
onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)625         public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
626                 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
627                 Runnable finishedCallback) {
628             postAsyncCallback(mHandler,
629                     () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
630                             mLaunchingTaskView, mInitialTaskId, mSecondTaskId, apps, wallpapers,
631                             nonApps, mStateManager, mDepthController, () -> {
632                                 finishedCallback.run();
633                                 if (mSuccessCallback != null) {
634                                     mSuccessCallback.accept(true);
635                                 }
636                                 resetState();
637                             }));
638         }
639 
640         @Override
onAnimationCancelled()641         public void onAnimationCancelled() {
642             postAsyncCallback(mHandler, () -> {
643                 if (mSuccessCallback != null) {
644                     // Launching legacy tasks while recents animation is running will always cause
645                     // onAnimationCancelled to be called (should be fixed w/ shell transitions?)
646                     mSuccessCallback.accept(mRecentsAnimationRunning);
647                 }
648                 resetState();
649             });
650         }
651     }
652 
653     /**
654      * To be called whenever we exit split selection state. If
655      * {@link FeatureFlags#ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE} is set, this should be the
656      * central way split is getting reset, which should then go through the callbacks to reset
657      * other state.
658      */
resetState()659     public void resetState() {
660         mSplitSelectDataHolder.resetState();
661         dispatchOnSplitSelectionExit();
662         mRecentsAnimationRunning = false;
663         mLaunchingTaskView = null;
664         mAnimateCurrentTaskDismissal = false;
665         mDismissingFromSplitPair = false;
666         mFirstFloatingTaskView = null;
667         mSplitInstructionsView = null;
668     }
669 
670     /**
671      * @return {@code true} if first task has been selected and waiting for the second task to be
672      *         chosen
673      */
isSplitSelectActive()674     public boolean isSplitSelectActive() {
675         return mSplitSelectDataHolder.isSplitSelectActive();
676     }
677 
678     /**
679      * @return {@code true} if the first and second task have been chosen and split is waiting to
680      *          be launched
681      */
isBothSplitAppsConfirmed()682     public boolean isBothSplitAppsConfirmed() {
683         return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
684     }
685 
getInitialTaskId()686     public int getInitialTaskId() {
687         return mSplitSelectDataHolder.getInitialTaskId();
688     }
689 
getSecondTaskId()690     public int getSecondTaskId() {
691         return mSplitSelectDataHolder.getSecondTaskId();
692     }
693 
setFirstFloatingTaskView(FloatingTaskView floatingTaskView)694     public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
695         mFirstFloatingTaskView = floatingTaskView;
696     }
697 
setSplitInstructionsView(SplitInstructionsView splitInstructionsView)698     public void setSplitInstructionsView(SplitInstructionsView splitInstructionsView) {
699         mSplitInstructionsView = splitInstructionsView;
700     }
701 
702     @Nullable
getFirstFloatingTaskView()703     public FloatingTaskView getFirstFloatingTaskView() {
704         return mFirstFloatingTaskView;
705     }
706 
707     @Nullable
getSplitInstructionsView()708     public SplitInstructionsView getSplitInstructionsView() {
709         return mSplitInstructionsView;
710     }
711 
getAppPairsController()712     public AppPairsController getAppPairsController() {
713         return mAppPairsController;
714     }
715 
dump(String prefix, PrintWriter writer)716     public void dump(String prefix, PrintWriter writer) {
717         if (mSplitSelectDataHolder != null) {
718             mSplitSelectDataHolder.dump(prefix, writer);
719         }
720     }
721 
722     public class SplitFromDesktopController {
723         private static final String TAG = "SplitFromDesktopController";
724 
725         private final Launcher mLauncher;
726         private final OverviewComponentObserver mOverviewComponentObserver;
727         private final int mSplitPlaceholderSize;
728         private final int mSplitPlaceholderInset;
729         private ActivityManager.RunningTaskInfo mTaskInfo;
730         private ISplitSelectListener mSplitSelectListener;
731         private Drawable mAppIcon;
732 
SplitFromDesktopController(Launcher launcher)733         public SplitFromDesktopController(Launcher launcher) {
734             mLauncher = launcher;
735             RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
736                     launcher.getApplicationContext());
737             mOverviewComponentObserver =
738                     new OverviewComponentObserver(launcher.getApplicationContext(), deviceState);
739             mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
740                     R.dimen.split_placeholder_size);
741             mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
742                     R.dimen.split_placeholder_inset);
743             mSplitSelectListener = new ISplitSelectListener.Stub() {
744                 @Override
745                 public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
746                         int splitPosition, Rect taskBounds) {
747                     if (!ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) return false;
748                     MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
749                             taskBounds));
750                     return true;
751                 }
752             };
753             SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
754         }
755 
756         /**
757          * Enter split select from desktop mode.
758          * @param taskInfo the desktop task to move to split stage
759          * @param splitPosition the stage position used for this transition
760          * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation
761          */
enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, int splitPosition, Rect taskBounds)762         public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
763                 int splitPosition, Rect taskBounds) {
764             mTaskInfo = taskInfo;
765             String packageName = mTaskInfo.realActivity.getPackageName();
766             PackageManager pm = mLauncher.getApplicationContext().getPackageManager();
767             IconProvider provider = new IconProvider(mLauncher.getApplicationContext());
768             try {
769                 mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
770                      PackageManager.ComponentInfoFlags.of(0)));
771             } catch (PackageManager.NameNotFoundException e) {
772                 Log.w(TAG, "Package not found: " + packageName, e);
773             }
774             RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
775                     SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
776                     false /* allowMinimizeSplitScreen */);
777 
778             DesktopSplitRecentsAnimationListener listener =
779                     new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds);
780 
781             MAIN_EXECUTOR.execute(() -> {
782                 callbacks.addListener(listener);
783                 UI_HELPER_EXECUTOR.execute(
784                         // Transition from app to enter stage split in launcher with
785                         // recents animation.
786                         () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
787                                 mOverviewComponentObserver.getOverviewIntent(),
788                                 SystemClock.uptimeMillis(), callbacks, null, null));
789             });
790         }
791 
792         private class DesktopSplitRecentsAnimationListener implements
793                 RecentsAnimationCallbacks.RecentsAnimationListener {
794             private final Rect mTempRect = new Rect();
795             private final RectF mTaskBounds = new RectF();
796             private final int mSplitPosition;
797 
DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds)798             DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds) {
799                 mSplitPosition = splitPosition;
800                 mTaskBounds.set(taskBounds);
801             }
802 
803             @Override
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)804             public void onRecentsAnimationStart(RecentsAnimationController controller,
805                     RecentsAnimationTargets targets) {
806                 StatsLogManager.LauncherEvent launcherDesktopSplitEvent =
807                         mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ?
808                         LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM :
809                         LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
810                 setInitialTaskSelect(mTaskInfo, mSplitPosition,
811                         null, launcherDesktopSplitEvent);
812 
813                 RecentsView recentsView = mLauncher.getOverviewPanel();
814                 recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
815                         mSplitPlaceholderSize, mSplitPlaceholderInset,
816                         mLauncher.getDeviceProfile(), getActiveSplitStagePosition(), mTempRect);
817 
818                 PendingAnimation anim = new PendingAnimation(
819                         SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration());
820                 final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
821                         mLauncher, mLauncher.getDragLayer(),
822                         null /* thumbnail */,
823                         mAppIcon, new RectF());
824                 floatingTaskView.setAlpha(1);
825                 floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect,
826                         false /* fadeWithThumbnail */, true /* isStagedTask */);
827                 setFirstFloatingTaskView(floatingTaskView);
828 
829                 anim.addListener(new AnimatorListenerAdapter() {
830                     @Override
831                     public void onAnimationStart(Animator animation) {
832                         controller.finish(true /* toRecents */, null /* onFinishComplete */,
833                                 false /* sendUserLeaveHint */);
834                     }
835                     @Override
836                     public void onAnimationEnd(Animator animation) {
837                         SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
838                                 .onDesktopSplitSelectAnimComplete(mTaskInfo);
839                     }
840                 });
841                 anim.buildAnim().start();
842             }
843         }
844     }
845 }
846