TaskStack.java revision 04400672962d2e12132f9465928cbf7615c147c4
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
17package com.android.systemui.recents.model;
18
19import android.graphics.Color;
20import com.android.systemui.recents.Constants;
21import com.android.systemui.recents.RecentsConfiguration;
22import com.android.systemui.recents.misc.NamedCounter;
23import com.android.systemui.recents.misc.Utilities;
24
25import java.util.ArrayList;
26import java.util.Collections;
27import java.util.Comparator;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Random;
31
32
33/**
34 * An interface for a task filter to query whether a particular task should show in a stack.
35 */
36interface TaskFilter {
37    /** Returns whether the filter accepts the specified task */
38    public boolean acceptTask(Task t, int index);
39}
40
41/**
42 * A list of filtered tasks.
43 */
44class FilteredTaskList {
45    ArrayList<Task> mTasks = new ArrayList<Task>();
46    ArrayList<Task> mFilteredTasks = new ArrayList<Task>();
47    HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<Task.TaskKey, Integer>();
48    TaskFilter mFilter;
49
50    /** Sets the task filter, saving the current touch state */
51    boolean setFilter(TaskFilter filter) {
52        ArrayList<Task> prevFilteredTasks = new ArrayList<Task>(mFilteredTasks);
53        mFilter = filter;
54        updateFilteredTasks();
55        if (!prevFilteredTasks.equals(mFilteredTasks)) {
56            return true;
57        } else {
58            // If the tasks are exactly the same pre/post filter, then just reset it
59            mFilter = null;
60            return false;
61        }
62    }
63
64    /** Removes the task filter and returns the previous touch state */
65    void removeFilter() {
66        mFilter = null;
67        updateFilteredTasks();
68    }
69
70    /** Adds a new task to the task list */
71    void add(Task t) {
72        mTasks.add(t);
73        updateFilteredTasks();
74    }
75
76    /** Sets the list of tasks */
77    void set(List<Task> tasks) {
78        mTasks.clear();
79        mTasks.addAll(tasks);
80        updateFilteredTasks();
81    }
82
83    /** Removes a task from the base list only if it is in the filtered list */
84    boolean remove(Task t) {
85        if (mFilteredTasks.contains(t)) {
86            boolean removed = mTasks.remove(t);
87            updateFilteredTasks();
88            return removed;
89        }
90        return false;
91    }
92
93    /** Returns the index of this task in the list of filtered tasks */
94    int indexOf(Task t) {
95        if (mTaskIndices.containsKey(t.key)) {
96            return mTaskIndices.get(t.key);
97        }
98        return -1;
99    }
100
101    /** Returns the size of the list of filtered tasks */
102    int size() {
103        return mFilteredTasks.size();
104    }
105
106    /** Returns whether the filtered list contains this task */
107    boolean contains(Task t) {
108        return mTaskIndices.containsKey(t.key);
109    }
110
111    /** Updates the list of filtered tasks whenever the base task list changes */
112    private void updateFilteredTasks() {
113        mFilteredTasks.clear();
114        if (mFilter != null) {
115            int taskCount = mTasks.size();
116            for (int i = 0; i < taskCount; i++) {
117                Task t = mTasks.get(i);
118                if (mFilter.acceptTask(t, i)) {
119                    mFilteredTasks.add(t);
120                }
121            }
122        } else {
123            mFilteredTasks.addAll(mTasks);
124        }
125        updateFilteredTaskIndices();
126    }
127
128    /** Updates the mapping of tasks to indices. */
129    private void updateFilteredTaskIndices() {
130        mTaskIndices.clear();
131        int taskCount = mFilteredTasks.size();
132        for (int i = 0; i < taskCount; i++) {
133            Task t = mFilteredTasks.get(i);
134            mTaskIndices.put(t.key, i);
135        }
136    }
137
138    /** Returns whether this task list is filtered */
139    boolean hasFilter() {
140        return (mFilter != null);
141    }
142
143    /** Returns the list of filtered tasks */
144    ArrayList<Task> getTasks() {
145        return mFilteredTasks;
146    }
147}
148
149/**
150 * The task stack contains a list of multiple tasks.
151 */
152public class TaskStack {
153
154    /** Task stack callbacks */
155    public interface TaskStackCallbacks {
156        /* Notifies when a task has been added to the stack */
157        public void onStackTaskAdded(TaskStack stack, Task t);
158        /* Notifies when a task has been removed from the stack */
159        public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
160        /** Notifies when the stack was filtered */
161        public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
162        /** Notifies when the stack was un-filtered */
163        public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
164    }
165
166    /** A pair of indices representing the group and task positions in the stack and group. */
167    public static class GroupTaskIndex {
168        public int groupIndex; // Index in the stack
169        public int taskIndex;  // Index in the group
170
171        public GroupTaskIndex() {}
172
173        public GroupTaskIndex(int gi, int ti) {
174            groupIndex = gi;
175            taskIndex = ti;
176        }
177    }
178
179    // The task offset to apply to a task id as a group affiliation
180    static final int IndividualTaskIdOffset = 1 << 16;
181
182    FilteredTaskList mTaskList = new FilteredTaskList();
183    TaskStackCallbacks mCb;
184
185    ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
186    HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
187
188    /** Sets the callbacks for this task stack */
189    public void setCallbacks(TaskStackCallbacks cb) {
190        mCb = cb;
191    }
192
193    /** Adds a new task */
194    public void addTask(Task t) {
195        mTaskList.add(t);
196        if (mCb != null) {
197            mCb.onStackTaskAdded(this, t);
198        }
199    }
200
201    /** Removes a task */
202    public void removeTask(Task t) {
203        if (mTaskList.contains(t)) {
204            // Remove the task from the list
205            mTaskList.remove(t);
206            // Remove it from the group as well, and if it is empty, remove the group
207            TaskGrouping group = t.group;
208            group.removeTask(t);
209            if (group.getTaskCount() == 0) {
210                removeGroup(group);
211            }
212            // Update the lock-to-app state
213            t.lockToThisTask = false;
214            Task newFrontMostTask = getFrontMostTask();
215            if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
216                newFrontMostTask.lockToThisTask = true;
217            }
218            if (mCb != null) {
219                // Notify that a task has been removed
220                mCb.onStackTaskRemoved(this, t, newFrontMostTask);
221            }
222        }
223    }
224
225    /** Sets a few tasks in one go */
226    public void setTasks(List<Task> tasks) {
227        ArrayList<Task> taskList = mTaskList.getTasks();
228        int taskCount = taskList.size();
229        for (int i = 0; i < taskCount; i++) {
230            Task t = taskList.get(i);
231            // Remove the task from the list
232            mTaskList.remove(t);
233            // Remove it from the group as well, and if it is empty, remove the group
234            TaskGrouping group = t.group;
235            group.removeTask(t);
236            if (group.getTaskCount() == 0) {
237                removeGroup(group);
238            }
239            if (mCb != null) {
240                // Notify that a task has been removed
241                mCb.onStackTaskRemoved(this, t, null);
242            }
243        }
244        mTaskList.set(tasks);
245        for (Task t : tasks) {
246            if (mCb != null) {
247                mCb.onStackTaskAdded(this, t);
248            }
249        }
250    }
251
252    /** Gets the front task */
253    public Task getFrontMostTask() {
254        if (mTaskList.size() == 0) return null;
255        return mTaskList.getTasks().get(mTaskList.size() - 1);
256    }
257
258    /** Gets the task keys */
259    public ArrayList<Task.TaskKey> getTaskKeys() {
260        ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
261        ArrayList<Task> tasks = mTaskList.getTasks();
262        int taskCount = tasks.size();
263        for (int i = 0; i < taskCount; i++) {
264            taskKeys.add(tasks.get(i).key);
265        }
266        return taskKeys;
267    }
268
269    /** Gets the tasks */
270    public ArrayList<Task> getTasks() {
271        return mTaskList.getTasks();
272    }
273
274    /** Gets the number of tasks */
275    public int getTaskCount() {
276        return mTaskList.size();
277    }
278
279    /** Returns the index of this task in this current task stack */
280    public int indexOfTask(Task t) {
281        return mTaskList.indexOf(t);
282    }
283
284    /** Finds the task with the specified task id. */
285    public Task findTaskWithId(int taskId) {
286        ArrayList<Task> tasks = mTaskList.getTasks();
287        int taskCount = tasks.size();
288        for (int i = 0; i < taskCount; i++) {
289            Task task = tasks.get(i);
290            if (task.key.id == taskId) {
291                return task;
292            }
293        }
294        return null;
295    }
296
297    /******** Filtering ********/
298
299    /** Filters the stack into tasks similar to the one specified */
300    public void filterTasks(final Task t) {
301        ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
302
303        // Set the task list filter
304        boolean filtered = mTaskList.setFilter(new TaskFilter() {
305            @Override
306            public boolean acceptTask(Task at, int i) {
307                return t.key.baseIntent.getComponent().getPackageName().equals(
308                        at.key.baseIntent.getComponent().getPackageName());
309            }
310        });
311        if (filtered && mCb != null) {
312            mCb.onStackFiltered(this, oldStack, t);
313        }
314    }
315
316    /** Unfilters the current stack */
317    public void unfilterTasks() {
318        ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
319
320        // Unset the filter, then update the virtual scroll
321        mTaskList.removeFilter();
322        if (mCb != null) {
323            mCb.onStackUnfiltered(this, oldStack);
324        }
325    }
326
327    /** Returns whether tasks are currently filtered */
328    public boolean hasFilteredTasks() {
329        return mTaskList.hasFilter();
330    }
331
332    /******** Grouping ********/
333
334    /** Adds a group to the set */
335    public void addGroup(TaskGrouping group) {
336        mGroups.add(group);
337        mAffinitiesGroups.put(group.affiliation, group);
338    }
339
340    public void removeGroup(TaskGrouping group) {
341        mGroups.remove(group);
342        mAffinitiesGroups.remove(group.affiliation);
343    }
344
345    /** Returns the group with the specified affiliation. */
346    public TaskGrouping getGroupWithAffiliation(int affiliation) {
347        return mAffinitiesGroups.get(affiliation);
348    }
349
350    /**
351     * Temporary: This method will simulate affiliation groups by
352     */
353    public void createAffiliatedGroupings(RecentsConfiguration config) {
354        if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
355            HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
356            // Sort all tasks by increasing firstActiveTime of the task
357            ArrayList<Task> tasks = mTaskList.getTasks();
358            Collections.sort(tasks, new Comparator<Task>() {
359                @Override
360                public int compare(Task task, Task task2) {
361                    return (int) (task.key.firstActiveTime - task2.key.firstActiveTime);
362                }
363            });
364            // Create groups when sequential packages are the same
365            NamedCounter counter = new NamedCounter("task-group", "");
366            int taskCount = tasks.size();
367            String prevPackage = "";
368            int prevAffiliation = -1;
369            Random r = new Random();
370            int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
371            for (int i = 0; i < taskCount; i++) {
372                Task t = tasks.get(i);
373                String packageName = t.key.baseIntent.getComponent().getPackageName();
374                packageName = "pkg";
375                TaskGrouping group;
376                if (packageName.equals(prevPackage) && groupCountDown > 0) {
377                    group = getGroupWithAffiliation(prevAffiliation);
378                    groupCountDown--;
379                } else {
380                    int affiliation = IndividualTaskIdOffset + t.key.id;
381                    group = new TaskGrouping(affiliation);
382                    addGroup(group);
383                    prevAffiliation = affiliation;
384                    prevPackage = packageName;
385                    groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
386                }
387                group.addTask(t);
388                taskMap.put(t.key, t);
389            }
390            // Sort groups by increasing latestActiveTime of the group
391            Collections.sort(mGroups, new Comparator<TaskGrouping>() {
392                @Override
393                public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
394                    return (int) (taskGrouping.latestActiveTimeInGroup -
395                            taskGrouping2.latestActiveTimeInGroup);
396                }
397            });
398            // Sort group tasks by increasing firstActiveTime of the task, and also build a new list of
399            // tasks
400            int taskIndex = 0;
401            int groupCount = mGroups.size();
402            for (int i = 0; i < groupCount; i++) {
403                TaskGrouping group = mGroups.get(i);
404                Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
405                    @Override
406                    public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
407                        return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime);
408                    }
409                });
410                ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
411                int groupTaskCount = groupTasks.size();
412                for (int j = 0; j < groupTaskCount; j++) {
413                    tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
414                    taskIndex++;
415                }
416            }
417            mTaskList.set(tasks);
418        } else {
419            // Create the task groups
420            HashMap<Task.TaskKey, Task> tasksMap = new HashMap<Task.TaskKey, Task>();
421            ArrayList<Task> tasks = mTaskList.getTasks();
422            int taskCount = tasks.size();
423            for (int i = 0; i < taskCount; i++) {
424                Task t = tasks.get(i);
425                TaskGrouping group;
426                int affiliation = t.taskAffiliation > 0 ? t.taskAffiliation :
427                        IndividualTaskIdOffset + t.key.id;
428                if (mAffinitiesGroups.containsKey(affiliation)) {
429                    group = getGroupWithAffiliation(affiliation);
430                } else {
431                    group = new TaskGrouping(affiliation);
432                    addGroup(group);
433                }
434                group.addTask(t);
435                tasksMap.put(t.key, t);
436            }
437            // Update the task colors for each of the groups
438            float minAlpha = config.taskBarViewAffiliationColorMinAlpha;
439            int taskGroupCount = mGroups.size();
440            for (int i = 0; i < taskGroupCount; i++) {
441                TaskGrouping group = mGroups.get(i);
442                taskCount = group.getTaskCount();
443                // Ignore the groups that only have one task
444                if (taskCount <= 1) continue;
445                // Calculate the group color distribution
446                int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor;
447                float alphaStep = (1f - minAlpha) / taskCount;
448                float alpha = 1f;
449                for (int j = 0; j < taskCount; j++) {
450                    Task t = tasksMap.get(group.mTaskKeys.get(j));
451                    t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
452                            alpha);
453                    alpha -= alphaStep;
454                }
455            }
456        }
457    }
458
459    @Override
460    public String toString() {
461        String str = "Tasks:\n";
462        for (Task t : mTaskList.getTasks()) {
463            str += "  " + t.toString() + "\n";
464        }
465        return str;
466    }
467}