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