1 /*
2  * Copyright (C) 2014 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.systemui.shared.recents.model;
18 
19 import android.content.ComponentName;
20 import android.util.ArrayMap;
21 import android.util.ArraySet;
22 
23 import com.android.systemui.shared.recents.model.Task.TaskKey;
24 import com.android.systemui.shared.recents.utilities.AnimationProps;
25 import com.android.systemui.shared.system.PackageManagerWrapper;
26 
27 import java.io.PrintWriter;
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 
32 /**
33  * The task stack contains a list of multiple tasks.
34  */
35 public class TaskStack {
36 
37     private static final String TAG = "TaskStack";
38 
39     /** Task stack callbacks */
40     public interface TaskStackCallbacks {
41         /**
42          * Notifies when a new task has been added to the stack.
43          */
onStackTaskAdded(TaskStack stack, Task newTask)44         void onStackTaskAdded(TaskStack stack, Task newTask);
45 
46         /**
47          * Notifies when a task has been removed from the stack.
48          */
onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved)49         void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
50                 AnimationProps animation, boolean fromDockGesture,
51                 boolean dismissRecentsIfAllRemoved);
52 
53         /**
54          * Notifies when all tasks have been removed from the stack.
55          */
onStackTasksRemoved(TaskStack stack)56         void onStackTasksRemoved(TaskStack stack);
57 
58         /**
59          * Notifies when tasks in the stack have been updated.
60          */
onStackTasksUpdated(TaskStack stack)61         void onStackTasksUpdated(TaskStack stack);
62     }
63 
64     private final ArrayList<Task> mRawTaskList = new ArrayList<>();
65     private final FilteredTaskList mStackTaskList = new FilteredTaskList();
66     private TaskStackCallbacks mCb;
67 
TaskStack()68     public TaskStack() {
69         // Ensure that we only show stack tasks
70         mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask);
71     }
72 
73     /** Sets the callbacks for this task stack. */
setCallbacks(TaskStackCallbacks cb)74     public void setCallbacks(TaskStackCallbacks cb) {
75         mCb = cb;
76     }
77 
78     /**
79      * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
80      * how they should update themselves.
81      */
removeTask(Task t, AnimationProps animation, boolean fromDockGesture)82     public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
83         removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
84     }
85 
86     /**
87      * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
88      * how they should update themselves.
89      */
removeTask(Task t, AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved)90     public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
91             boolean dismissRecentsIfAllRemoved) {
92         if (mStackTaskList.contains(t)) {
93             mStackTaskList.remove(t);
94             Task newFrontMostTask = getFrontMostTask();
95             if (mCb != null) {
96                 // Notify that a task has been removed
97                 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
98                         fromDockGesture, dismissRecentsIfAllRemoved);
99             }
100         }
101         mRawTaskList.remove(t);
102     }
103 
104     /**
105      * Removes all tasks from the stack.
106      */
removeAllTasks(boolean notifyStackChanges)107     public void removeAllTasks(boolean notifyStackChanges) {
108         ArrayList<Task> tasks = mStackTaskList.getTasks();
109         for (int i = tasks.size() - 1; i >= 0; i--) {
110             Task t = tasks.get(i);
111             mStackTaskList.remove(t);
112             mRawTaskList.remove(t);
113         }
114         if (mCb != null && notifyStackChanges) {
115             // Notify that all tasks have been removed
116             mCb.onStackTasksRemoved(this);
117         }
118     }
119 
120 
121     /**
122      * @see #setTasks(List, boolean)
123      */
setTasks(TaskStack stack, boolean notifyStackChanges)124     public void setTasks(TaskStack stack, boolean notifyStackChanges) {
125         setTasks(stack.mRawTaskList, notifyStackChanges);
126     }
127 
128     /**
129      * Sets a few tasks in one go, without calling any callbacks.
130      *
131      * @param tasks the new set of tasks to replace the current set.
132      * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
133      */
setTasks(List<Task> tasks, boolean notifyStackChanges)134     public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
135         // Compute a has set for each of the tasks
136         ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
137         ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
138         ArrayList<Task> addedTasks = new ArrayList<>();
139         ArrayList<Task> removedTasks = new ArrayList<>();
140         ArrayList<Task> allTasks = new ArrayList<>();
141 
142         // Disable notifications if there are no callbacks
143         if (mCb == null) {
144             notifyStackChanges = false;
145         }
146 
147         // Remove any tasks that no longer exist
148         int taskCount = mRawTaskList.size();
149         for (int i = taskCount - 1; i >= 0; i--) {
150             Task task = mRawTaskList.get(i);
151             if (!newTasksMap.containsKey(task.key)) {
152                 if (notifyStackChanges) {
153                     removedTasks.add(task);
154                 }
155             }
156         }
157 
158         // Add any new tasks
159         taskCount = tasks.size();
160         for (int i = 0; i < taskCount; i++) {
161             Task newTask = tasks.get(i);
162             Task currentTask = currentTasksMap.get(newTask.key);
163             if (currentTask == null && notifyStackChanges) {
164                 addedTasks.add(newTask);
165             } else if (currentTask != null) {
166                 // The current task has bound callbacks, so just copy the data from the new task
167                 // state and add it back into the list
168                 currentTask.copyFrom(newTask);
169                 newTask = currentTask;
170             }
171             allTasks.add(newTask);
172         }
173 
174         // Sort all the tasks to ensure they are ordered correctly
175         for (int i = allTasks.size() - 1; i >= 0; i--) {
176             allTasks.get(i).temporarySortIndexInStack = i;
177         }
178 
179         mStackTaskList.set(allTasks);
180         mRawTaskList.clear();
181         mRawTaskList.addAll(allTasks);
182 
183         // Only callback for the removed tasks after the stack has updated
184         int removedTaskCount = removedTasks.size();
185         Task newFrontMostTask = getFrontMostTask();
186         for (int i = 0; i < removedTaskCount; i++) {
187             mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
188                     AnimationProps.IMMEDIATE, false /* fromDockGesture */,
189                     true /* dismissRecentsIfAllRemoved */);
190         }
191 
192         // Only callback for the newly added tasks after this stack has been updated
193         int addedTaskCount = addedTasks.size();
194         for (int i = 0; i < addedTaskCount; i++) {
195             mCb.onStackTaskAdded(this, addedTasks.get(i));
196         }
197 
198         // Notify that the task stack has been updated
199         if (notifyStackChanges) {
200             mCb.onStackTasksUpdated(this);
201         }
202     }
203 
204     /**
205      * Gets the front-most task in the stack.
206      */
getFrontMostTask()207     public Task getFrontMostTask() {
208         ArrayList<Task> stackTasks = mStackTaskList.getTasks();
209         if (stackTasks.isEmpty()) {
210             return null;
211         }
212         return stackTasks.get(stackTasks.size() - 1);
213     }
214 
215     /** Gets the task keys */
getTaskKeys()216     public ArrayList<TaskKey> getTaskKeys() {
217         ArrayList<TaskKey> taskKeys = new ArrayList<>();
218         ArrayList<Task> tasks = computeAllTasksList();
219         int taskCount = tasks.size();
220         for (int i = 0; i < taskCount; i++) {
221             Task task = tasks.get(i);
222             taskKeys.add(task.key);
223         }
224         return taskKeys;
225     }
226 
227     /**
228      * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
229      */
getTasks()230     public ArrayList<Task> getTasks() {
231         return mStackTaskList.getTasks();
232     }
233 
234     /**
235      * Computes a set of all the active and historical tasks.
236      */
computeAllTasksList()237     public ArrayList<Task> computeAllTasksList() {
238         ArrayList<Task> tasks = new ArrayList<>();
239         tasks.addAll(mStackTaskList.getTasks());
240         return tasks;
241     }
242 
243     /**
244      * Returns the number of stack tasks.
245      */
getTaskCount()246     public int getTaskCount() {
247         return mStackTaskList.size();
248     }
249 
250     /**
251      * Returns the task in stack tasks which is the launch target.
252      */
getLaunchTarget()253     public Task getLaunchTarget() {
254         ArrayList<Task> tasks = mStackTaskList.getTasks();
255         int taskCount = tasks.size();
256         for (int i = 0; i < taskCount; i++) {
257             Task task = tasks.get(i);
258             if (task.isLaunchTarget) {
259                 return task;
260             }
261         }
262         return null;
263     }
264 
265     /**
266      * Returns whether the next launch target should actually be the PiP task.
267      */
isNextLaunchTargetPip(long lastPipTime)268     public boolean isNextLaunchTargetPip(long lastPipTime) {
269         Task launchTarget = getLaunchTarget();
270         Task nextLaunchTarget = getNextLaunchTargetRaw();
271         if (nextLaunchTarget != null && lastPipTime > 0) {
272             // If the PiP time is more recent than the next launch target, then launch the PiP task
273             return lastPipTime > nextLaunchTarget.key.lastActiveTime;
274         } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
275             // Otherwise, if there is no next launch target, but there is a PiP, then launch
276             // the PiP task
277             return true;
278         }
279         return false;
280     }
281 
282     /**
283      * Returns the task in stack tasks which should be launched next if Recents are toggled
284      * again, or null if there is no task to be launched. Callers should check
285      * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
286      * stack.
287      */
getNextLaunchTarget()288     public Task getNextLaunchTarget() {
289         Task nextLaunchTarget = getNextLaunchTargetRaw();
290         if (nextLaunchTarget != null) {
291             return nextLaunchTarget;
292         }
293         return getTasks().get(getTaskCount() - 1);
294     }
295 
getNextLaunchTargetRaw()296     private Task getNextLaunchTargetRaw() {
297         int taskCount = getTaskCount();
298         if (taskCount == 0) {
299             return null;
300         }
301         int launchTaskIndex = indexOfTask(getLaunchTarget());
302         if (launchTaskIndex != -1 && launchTaskIndex > 0) {
303             return getTasks().get(launchTaskIndex - 1);
304         }
305         return null;
306     }
307 
308     /** Returns the index of this task in this current task stack */
indexOfTask(Task t)309     public int indexOfTask(Task t) {
310         return mStackTaskList.indexOf(t);
311     }
312 
313     /** Finds the task with the specified task id. */
findTaskWithId(int taskId)314     public Task findTaskWithId(int taskId) {
315         ArrayList<Task> tasks = computeAllTasksList();
316         int taskCount = tasks.size();
317         for (int i = 0; i < taskCount; i++) {
318             Task task = tasks.get(i);
319             if (task.key.id == taskId) {
320                 return task;
321             }
322         }
323         return null;
324     }
325 
326     /**
327      * Computes the components of tasks in this stack that have been removed as a result of a change
328      * in the specified package.
329      */
computeComponentsRemoved(String packageName, int userId)330     public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
331         // Identify all the tasks that should be removed as a result of the package being removed.
332         // Using a set to ensure that we callback once per unique component.
333         ArraySet<ComponentName> existingComponents = new ArraySet<>();
334         ArraySet<ComponentName> removedComponents = new ArraySet<>();
335         ArrayList<TaskKey> taskKeys = getTaskKeys();
336         int taskKeyCount = taskKeys.size();
337         for (int i = 0; i < taskKeyCount; i++) {
338             TaskKey t = taskKeys.get(i);
339 
340             // Skip if this doesn't apply to the current user
341             if (t.userId != userId) continue;
342 
343             ComponentName cn = t.getComponent();
344             if (cn.getPackageName().equals(packageName)) {
345                 if (existingComponents.contains(cn)) {
346                     // If we know that the component still exists in the package, then skip
347                     continue;
348                 }
349                 if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
350                     existingComponents.add(cn);
351                 } else {
352                     removedComponents.add(cn);
353                 }
354             }
355         }
356         return removedComponents;
357     }
358 
359     @Override
toString()360     public String toString() {
361         String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
362         ArrayList<Task> tasks = mStackTaskList.getTasks();
363         int taskCount = tasks.size();
364         for (int i = 0; i < taskCount; i++) {
365             str += "    " + tasks.get(i).toString() + "\n";
366         }
367         return str;
368     }
369 
370     /**
371      * Given a list of tasks, returns a map of each task's key to the task.
372      */
createTaskKeyMapFromList(List<Task> tasks)373     private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
374         ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
375         int taskCount = tasks.size();
376         for (int i = 0; i < taskCount; i++) {
377             Task task = tasks.get(i);
378             map.put(task.key, task);
379         }
380         return map;
381     }
382 
dump(String prefix, PrintWriter writer)383     public void dump(String prefix, PrintWriter writer) {
384         String innerPrefix = prefix + "  ";
385 
386         writer.print(prefix); writer.print(TAG);
387         writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
388         writer.println();
389         ArrayList<Task> tasks = mStackTaskList.getTasks();
390         int taskCount = tasks.size();
391         for (int i = 0; i < taskCount; i++) {
392             tasks.get(i).dump(innerPrefix, writer);
393         }
394     }
395 }
396