1 package com.android.quickstep.util;
2 
3 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
4 
5 import android.content.Context;
6 import android.os.IBinder;
7 
8 import com.android.launcher3.util.MainThreadInitializedObject;
9 import com.android.launcher3.util.SplitConfigurationOptions;
10 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
11 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
12 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
13 import com.android.quickstep.SystemUiProxy;
14 import com.android.systemui.shared.system.TaskStackChangeListener;
15 import com.android.systemui.shared.system.TaskStackChangeListeners;
16 import com.android.wm.shell.splitscreen.ISplitScreenListener;
17 
18 /**
19  * Listeners for system wide split screen position and stage changes.
20  *
21  * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in
22  * staged split.
23  *
24  * Use {@link #getPersistentSplitIds()} to know if tasks were in split screen before a quickswitch
25  * gesture happened.
26  */
27 public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
28 
29     public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
30             new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
31 
32     private static final int[] EMPTY_ARRAY = {};
33 
34     private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
35     private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
36 
37     private boolean mIsRecentsListFrozen = false;
38     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
39         @Override
40         public void onRecentTaskListFrozenChanged(boolean frozen) {
41             super.onRecentTaskListFrozenChanged(frozen);
42             mIsRecentsListFrozen = frozen;
43 
44             if (frozen) {
45                 mPersistentGroupedIds = getRunningSplitTaskIds();
46             } else {
47                 mPersistentGroupedIds = EMPTY_ARRAY;
48             }
49         }
50     };
51 
52     /**
53      * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
54      * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
55      * home - in that case the task list does not unfreeze immediately after the gesture, so it's
56      * done via {@link #notifySwipingToHome()}.
57      *
58      * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
59      * page, so user can quickswitch back to a grouped task.
60      */
61     private int[] mPersistentGroupedIds;
62 
LauncherSplitScreenListener(Context context)63     public LauncherSplitScreenListener(Context context) {
64         mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
65         mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
66     }
67 
68     /** Also call {@link #destroy()} when done. */
init()69     public void init() {
70         SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
71         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
72     }
73 
destroy()74     public void destroy() {
75         SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
76         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
77     }
78 
79     /**
80      * This method returns the active split taskIDs that were active if a user quickswitched from
81      * split screen to a fullscreen app as long as the recents task list remains frozen.
82      */
getPersistentSplitIds()83     public int[] getPersistentSplitIds() {
84         if (mIsRecentsListFrozen) {
85             return mPersistentGroupedIds;
86         } else {
87             return getRunningSplitTaskIds();
88         }
89     }
90     /**
91      * @return index 0 will be task in left/top position, index 1 in right/bottom position.
92      *         Will return empty array if device is not in staged split
93      */
getRunningSplitTaskIds()94     public int[] getRunningSplitTaskIds() {
95         if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
96             return new int[]{};
97         }
98         int[] out = new int[2];
99         if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
100             out[0] = mMainStagePosition.taskId;
101             out[1] = mSideStagePosition.taskId;
102         } else {
103             out[1] = mMainStagePosition.taskId;
104             out[0] = mSideStagePosition.taskId;
105         }
106         return out;
107     }
108 
109     @Override
onStagePositionChanged(@tageType int stage, @StagePosition int position)110     public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
111         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
112             mMainStagePosition.stagePosition = position;
113         } else {
114             mSideStagePosition.stagePosition = position;
115         }
116     }
117 
118     @Override
onTaskStageChanged(int taskId, @StageType int stage, boolean visible)119     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
120         // If task is not visible but we are tracking it, stop tracking it
121         if (!visible) {
122             if (mMainStagePosition.taskId == taskId) {
123                 resetTaskId(mMainStagePosition);
124             } else if (mSideStagePosition.taskId == taskId) {
125                 resetTaskId(mSideStagePosition);
126             } // else it's an un-tracked child
127             return;
128         }
129 
130         // If stage has moved to undefined, stop tracking the task
131         if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
132             resetTaskId(taskId == mMainStagePosition.taskId ?
133                     mMainStagePosition : mSideStagePosition);
134             return;
135         }
136 
137         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
138             mMainStagePosition.taskId = taskId;
139         } else {
140             mSideStagePosition.taskId = taskId;
141         }
142     }
143 
144     /** Notifies SystemUi to remove any split screen state */
notifySwipingToHome()145     public void notifySwipingToHome() {
146         boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate()
147                 .getPersistentSplitIds().length > 0;
148         if (!hasSplitTasks) {
149             return;
150         }
151 
152         mPersistentGroupedIds = EMPTY_ARRAY;
153     }
154 
resetTaskId(StagedSplitTaskPosition taskPosition)155     private void resetTaskId(StagedSplitTaskPosition taskPosition) {
156         taskPosition.taskId = -1;
157     }
158 
159     @Override
asBinder()160     public IBinder asBinder() {
161         return this;
162     }
163 }
164