• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.quickstep;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
22 import static android.content.Intent.ACTION_CHOOSER;
23 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
24 import static android.view.Display.DEFAULT_DISPLAY;
25 
26 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
27 
28 import android.annotation.UserIdInt;
29 import android.app.ActivityManager.RunningTaskInfo;
30 import android.content.Context;
31 
32 import androidx.annotation.Nullable;
33 import androidx.annotation.UiThread;
34 
35 import com.android.launcher3.util.MainThreadInitializedObject;
36 import com.android.launcher3.util.SplitConfigurationOptions;
37 import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
38 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
39 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
40 import com.android.launcher3.util.TraceHelper;
41 import com.android.systemui.shared.recents.model.Task;
42 import com.android.systemui.shared.recents.model.Task.TaskKey;
43 import com.android.systemui.shared.system.ActivityManagerWrapper;
44 import com.android.systemui.shared.system.TaskStackChangeListener;
45 import com.android.systemui.shared.system.TaskStackChangeListeners;
46 import com.android.wm.shell.splitscreen.ISplitScreenListener;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.Iterator;
52 import java.util.LinkedList;
53 import java.util.List;
54 
55 /**
56  * This class tracked the top-most task and  some 'approximate' task history to allow faster
57  * system state estimation during touch interaction
58  */
59 public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
60 
61     public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
62             new MainThreadInitializedObject<>(TopTaskTracker::new);
63 
64     private static final int HISTORY_SIZE = 5;
65 
66     // Ordered list with first item being the most recent task.
67     private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
68 
69 
70     private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
71     private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
72     private int mPinnedTaskId = INVALID_TASK_ID;
73 
TopTaskTracker(Context context)74     private TopTaskTracker(Context context) {
75         mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
76         mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
77 
78         TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
79         SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
80     }
81 
82     @Override
onTaskRemoved(int taskId)83     public void onTaskRemoved(int taskId) {
84         mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
85     }
86 
87     @Override
onTaskMovedToFront(RunningTaskInfo taskInfo)88     public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
89         mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
90         mOrderedTaskList.addFirst(taskInfo);
91 
92         // Keep the home display's top running task in the first while adding a non-home
93         // display's task to the list, to avoid showing non-home display's task upon going to
94         // Recents animation.
95         if (taskInfo.displayId != DEFAULT_DISPLAY) {
96             final RunningTaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream()
97                     .filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null);
98             if (topTaskOnHomeDisplay != null) {
99                 mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId);
100                 mOrderedTaskList.addFirst(topTaskOnHomeDisplay);
101             }
102         }
103 
104         if (mOrderedTaskList.size() >= HISTORY_SIZE) {
105             // If we grow in size, remove the last taskInfo which is not part of the split task.
106             Iterator<RunningTaskInfo> itr = mOrderedTaskList.descendingIterator();
107             while (itr.hasNext()) {
108                 RunningTaskInfo info = itr.next();
109                 if (info.taskId != taskInfo.taskId
110                         && info.taskId != mMainStagePosition.taskId
111                         && info.taskId != mSideStagePosition.taskId) {
112                     itr.remove();
113                     return;
114                 }
115             }
116         }
117     }
118 
119     @Override
onStagePositionChanged(@tageType int stage, @StagePosition int position)120     public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
121         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
122             mMainStagePosition.stagePosition = position;
123         } else {
124             mSideStagePosition.stagePosition = position;
125         }
126     }
127 
128     @Override
onTaskStageChanged(int taskId, @StageType int stage, boolean visible)129     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
130         // If task is not visible but we are tracking it, stop tracking it
131         if (!visible) {
132             if (mMainStagePosition.taskId == taskId) {
133                 resetTaskId(mMainStagePosition);
134             } else if (mSideStagePosition.taskId == taskId) {
135                 resetTaskId(mSideStagePosition);
136             } // else it's an un-tracked child
137             return;
138         }
139 
140         // If stage has moved to undefined, stop tracking the task
141         if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
142             resetTaskId(taskId == mMainStagePosition.taskId
143                     ? mMainStagePosition : mSideStagePosition);
144             return;
145         }
146 
147         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
148             mMainStagePosition.taskId = taskId;
149         } else {
150             mSideStagePosition.taskId = taskId;
151         }
152     }
153 
154     @Override
onActivityPinned(String packageName, int userId, int taskId, int stackId)155     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
156         mPinnedTaskId = taskId;
157     }
158 
159     @Override
onActivityUnpinned()160     public void onActivityUnpinned() {
161         mPinnedTaskId = INVALID_TASK_ID;
162     }
163 
resetTaskId(SplitStageInfo taskPosition)164     private void resetTaskId(SplitStageInfo taskPosition) {
165         taskPosition.taskId = -1;
166     }
167 
168     /**
169      * @return index 0 will be task in left/top position, index 1 in right/bottom position.
170      *         Will return empty array if device is not in staged split
171      */
getRunningSplitTaskIds()172     public int[] getRunningSplitTaskIds() {
173         if (mMainStagePosition.taskId == INVALID_TASK_ID
174                 || mSideStagePosition.taskId == INVALID_TASK_ID) {
175             return new int[]{};
176         }
177         int[] out = new int[2];
178         if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
179             out[0] = mMainStagePosition.taskId;
180             out[1] = mSideStagePosition.taskId;
181         } else {
182             out[1] = mMainStagePosition.taskId;
183             out[0] = mSideStagePosition.taskId;
184         }
185         return out;
186     }
187 
188 
189     /**
190      * Returns the CachedTaskInfo for the top most task
191      */
192     @UiThread
getCachedTopTask(boolean filterOnlyVisibleRecents)193     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
194         if (filterOnlyVisibleRecents) {
195             // Since we only know about the top most task, any filtering may not be applied on the
196             // cache. The second to top task may change while the top task is still the same.
197             RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
198                     ActivityManagerWrapper.getInstance().getRunningTasks(true));
199             return new CachedTaskInfo(Arrays.asList(tasks));
200         }
201 
202         if (mOrderedTaskList.isEmpty()) {
203             RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
204                     ActivityManagerWrapper.getInstance().getRunningTasks(
205                             false /* filterOnlyVisibleRecents */));
206             Collections.addAll(mOrderedTaskList, tasks);
207         }
208 
209         // Strip the pinned task
210         ArrayList<RunningTaskInfo> tasks = new ArrayList<>(mOrderedTaskList);
211         tasks.removeIf(t -> t.taskId == mPinnedTaskId);
212         return new CachedTaskInfo(tasks);
213     }
214 
215     /**
216      * Class to provide information about a task which can be safely cached and do not change
217      * during the lifecycle of the task.
218      */
219     public static class CachedTaskInfo {
220 
221         @Nullable
222         private final RunningTaskInfo mTopTask;
223         private final List<RunningTaskInfo> mAllCachedTasks;
224 
CachedTaskInfo(List<RunningTaskInfo> allCachedTasks)225         CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
226             mAllCachedTasks = allCachedTasks;
227             mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
228         }
229 
getTaskId()230         public int getTaskId() {
231             return mTopTask == null ? INVALID_TASK_ID : mTopTask.taskId;
232         }
233 
234         /**
235          * Returns true if the root of the task chooser activity
236          */
isRootChooseActivity()237         public boolean isRootChooseActivity() {
238             return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
239         }
240 
241         /**
242          * Returns true if the given task holds an Assistant activity that is excluded from recents
243          */
isExcludedAssistant()244         public boolean isExcludedAssistant() {
245             return mTopTask != null && mTopTask.configuration.windowConfiguration
246                     .getActivityType() == ACTIVITY_TYPE_ASSISTANT
247                     && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
248         }
249 
250         /**
251          * Returns true if this represents the HOME task
252          */
isHomeTask()253         public boolean isHomeTask() {
254             return mTopTask != null && mTopTask.configuration.windowConfiguration
255                     .getActivityType() == ACTIVITY_TYPE_HOME;
256         }
257 
258         /**
259          * Returns {@code true} if this task windowing mode is set to {@link
260          * android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
261          */
isFreeformTask()262         public boolean isFreeformTask() {
263             return mTopTask != null && mTopTask.configuration.windowConfiguration.getWindowingMode()
264                     == WINDOWING_MODE_FREEFORM;
265         }
266 
267         /**
268          * Returns {@link Task} array which can be used as a placeholder until the true object
269          * is loaded by the model
270          */
getPlaceholderTasks()271         public Task[] getPlaceholderTasks() {
272             return mTopTask == null ? new Task[0]
273                     : new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
274         }
275 
276         /**
277          * Returns {@link Task} array corresponding to the provided task ids which can be used as a
278          * placeholder until the true object is loaded by the model
279          */
getPlaceholderTasks(int[] taskIds)280         public Task[] getPlaceholderTasks(int[] taskIds) {
281             if (mTopTask == null) {
282                 return new Task[0];
283             }
284             Task[] result = new Task[taskIds.length];
285             for (int i = 0; i < taskIds.length; i++) {
286                 final int index = i;
287                 int taskId = taskIds[i];
288                 mAllCachedTasks.forEach(rti -> {
289                     if (rti.taskId == taskId) {
290                         result[index] = Task.from(new TaskKey(rti), rti, false);
291                     }
292                 });
293             }
294             return result;
295         }
296 
297         @UserIdInt
298         @Nullable
getUserId()299         public Integer getUserId() {
300             return mTopTask == null ? null : mTopTask.userId;
301         }
302 
303         @Nullable
getPackageName()304         public String getPackageName() {
305             if (mTopTask == null || mTopTask.baseActivity == null) {
306                 return null;
307             }
308             return mTopTask.baseActivity.getPackageName();
309         }
310     }
311 }
312