• 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_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
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 a task is not visible anymore or has been moved to undefined, stop tracking it.
131         if (!visible || stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
132             if (mMainStagePosition.taskId == taskId) {
133                 mMainStagePosition.taskId = INVALID_TASK_ID;
134             } else if (mSideStagePosition.taskId == taskId) {
135                 mSideStagePosition.taskId = INVALID_TASK_ID;
136             } // else it's an un-tracked child
137             return;
138         }
139 
140         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
141             mMainStagePosition.taskId = taskId;
142         } else {
143             mSideStagePosition.taskId = taskId;
144         }
145     }
146 
147     @Override
onActivityPinned(String packageName, int userId, int taskId, int stackId)148     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
149         mPinnedTaskId = taskId;
150     }
151 
152     @Override
onActivityUnpinned()153     public void onActivityUnpinned() {
154         mPinnedTaskId = INVALID_TASK_ID;
155     }
156 
157     /**
158      * @return index 0 will be task in left/top position, index 1 in right/bottom position.
159      *         Will return empty array if device is not in staged split
160      */
getRunningSplitTaskIds()161     public int[] getRunningSplitTaskIds() {
162         if (mMainStagePosition.taskId == INVALID_TASK_ID
163                 || mSideStagePosition.taskId == INVALID_TASK_ID) {
164             return new int[]{};
165         }
166         int[] out = new int[2];
167         if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
168             out[0] = mMainStagePosition.taskId;
169             out[1] = mSideStagePosition.taskId;
170         } else {
171             out[1] = mMainStagePosition.taskId;
172             out[0] = mSideStagePosition.taskId;
173         }
174         return out;
175     }
176 
177 
178     /**
179      * Returns the CachedTaskInfo for the top most task
180      */
181     @UiThread
getCachedTopTask(boolean filterOnlyVisibleRecents)182     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
183         if (filterOnlyVisibleRecents) {
184             // Since we only know about the top most task, any filtering may not be applied on the
185             // cache. The second to top task may change while the top task is still the same.
186             RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
187                     ActivityManagerWrapper.getInstance().getRunningTasks(true));
188             return new CachedTaskInfo(Arrays.asList(tasks));
189         }
190 
191         if (mOrderedTaskList.isEmpty()) {
192             RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
193                     ActivityManagerWrapper.getInstance().getRunningTasks(
194                             false /* filterOnlyVisibleRecents */));
195             Collections.addAll(mOrderedTaskList, tasks);
196         }
197 
198         // Strip the pinned task
199         ArrayList<RunningTaskInfo> tasks = new ArrayList<>(mOrderedTaskList);
200         tasks.removeIf(t -> t.taskId == mPinnedTaskId);
201         return new CachedTaskInfo(tasks);
202     }
203 
204     /**
205      * Class to provide information about a task which can be safely cached and do not change
206      * during the lifecycle of the task.
207      */
208     public static class CachedTaskInfo {
209 
210         @Nullable
211         private final RunningTaskInfo mTopTask;
212         public final List<RunningTaskInfo> mAllCachedTasks;
213 
CachedTaskInfo(List<RunningTaskInfo> allCachedTasks)214         CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
215             mAllCachedTasks = allCachedTasks;
216             mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
217         }
218 
getTaskId()219         public int getTaskId() {
220             return mTopTask == null ? INVALID_TASK_ID : mTopTask.taskId;
221         }
222 
223         /**
224          * Returns true if the root of the task chooser activity
225          */
isRootChooseActivity()226         public boolean isRootChooseActivity() {
227             return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
228         }
229 
230         /**
231          * If the given task holds an activity that is excluded from recents, and there
232          * is another running task that is not excluded from recents, returns that underlying task.
233          */
otherVisibleTaskThisIsExcludedOver()234         public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
235             if (mTopTask == null
236                     || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
237                 // Not an excluded task.
238                 return null;
239             }
240             List<RunningTaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
241                     .filter(t -> t.isVisible
242                             && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
243                     .toList();
244             return visibleNonExcludedTasks.isEmpty() ? null
245                     : new CachedTaskInfo(visibleNonExcludedTasks);
246         }
247 
248         /**
249          * Returns true if this represents the HOME task
250          */
isHomeTask()251         public boolean isHomeTask() {
252             return mTopTask != null && mTopTask.configuration.windowConfiguration
253                     .getActivityType() == ACTIVITY_TYPE_HOME;
254         }
255 
isRecentsTask()256         public boolean isRecentsTask() {
257             return mTopTask != null && mTopTask.configuration.windowConfiguration
258                     .getActivityType() == ACTIVITY_TYPE_RECENTS;
259         }
260 
261         /**
262          * Returns {@code true} if this task windowing mode is set to {@link
263          * android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
264          */
isFreeformTask()265         public boolean isFreeformTask() {
266             return mTopTask != null && mTopTask.configuration.windowConfiguration.getWindowingMode()
267                     == WINDOWING_MODE_FREEFORM;
268         }
269 
270         /**
271          * Returns {@link Task} array which can be used as a placeholder until the true object
272          * is loaded by the model
273          */
getPlaceholderTasks()274         public Task[] getPlaceholderTasks() {
275             return mTopTask == null ? new Task[0]
276                     : new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
277         }
278 
279         /**
280          * Returns {@link Task} array corresponding to the provided task ids which can be used as a
281          * placeholder until the true object is loaded by the model
282          */
getPlaceholderTasks(int[] taskIds)283         public Task[] getPlaceholderTasks(int[] taskIds) {
284             if (mTopTask == null) {
285                 return new Task[0];
286             }
287             Task[] result = new Task[taskIds.length];
288             for (int i = 0; i < taskIds.length; i++) {
289                 final int index = i;
290                 int taskId = taskIds[i];
291                 mAllCachedTasks.forEach(rti -> {
292                     if (rti.taskId == taskId) {
293                         result[index] = Task.from(new TaskKey(rti), rti, false);
294                     }
295                 });
296             }
297             return result;
298         }
299 
300         @UserIdInt
301         @Nullable
getUserId()302         public Integer getUserId() {
303             return mTopTask == null ? null : mTopTask.userId;
304         }
305 
306         @Nullable
getPackageName()307         public String getPackageName() {
308             if (mTopTask == null || mTopTask.baseActivity == null) {
309                 return null;
310             }
311             return mTopTask.baseActivity.getPackageName();
312         }
313     }
314 }
315