2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recents.model;
19 import android.graphics.Color;
20 import com.android.systemui.recents.Constants;
21 import com.android.systemui.recents.RecentsConfiguration;
22 import com.android.systemui.recents.misc.NamedCounter;
23 import com.android.systemui.recents.misc.Utilities;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Random;
34 * An interface for a task filter to query whether a particular task should show in a stack.
36 interface TaskFilter {
37 /** Returns whether the filter accepts the specified task */
38 public boolean acceptTask(Task t, int index);
42 * A list of filtered tasks.
44 class 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>();
50 /** Sets the task filter, saving the current touch state */
51 boolean setFilter(TaskFilter filter) {
52 ArrayList<Task> prevFilteredTasks = new ArrayList<Task>(mFilteredTasks);
54 updateFilteredTasks();
55 if (!prevFilteredTasks.equals(mFilteredTasks)) {
58 // If the tasks are exactly the same pre/post filter, then just reset it
64 /** Resets this FilteredTaskList. */
67 mFilteredTasks.clear();
72 /** Removes the task filter and returns the previous touch state */
75 updateFilteredTasks();
78 /** Adds a new task to the task list */
81 updateFilteredTasks();
84 /** Sets the list of tasks */
85 void set(List<Task> tasks) {
88 updateFilteredTasks();
91 /** Removes a task from the base list only if it is in the filtered list */
92 boolean remove(Task t) {
93 if (mFilteredTasks.contains(t)) {
94 boolean removed = mTasks.remove(t);
95 updateFilteredTasks();
101 /** Returns the index of this task in the list of filtered tasks */
102 int indexOf(Task t) {
103 if (mTaskIndices.containsKey(t.key)) {
104 return mTaskIndices.get(t.key);
109 /** Returns the size of the list of filtered tasks */
111 return mFilteredTasks.size();
114 /** Returns whether the filtered list contains this task */
115 boolean contains(Task t) {
116 return mTaskIndices.containsKey(t.key);
119 /** Updates the list of filtered tasks whenever the base task list changes */
120 private void updateFilteredTasks() {
121 mFilteredTasks.clear();
122 if (mFilter != null) {
123 int taskCount = mTasks.size();
124 for (int i = 0; i < taskCount; i++) {
125 Task t = mTasks.get(i);
126 if (mFilter.acceptTask(t, i)) {
127 mFilteredTasks.add(t);
131 mFilteredTasks.addAll(mTasks);
133 updateFilteredTaskIndices();
136 /** Updates the mapping of tasks to indices. */
137 private void updateFilteredTaskIndices() {
138 mTaskIndices.clear();
139 int taskCount = mFilteredTasks.size();
140 for (int i = 0; i < taskCount; i++) {
141 Task t = mFilteredTasks.get(i);
142 mTaskIndices.put(t.key, i);
146 /** Returns whether this task list is filtered */
147 boolean hasFilter() {
148 return (mFilter != null);
151 /** Returns the list of filtered tasks */
152 ArrayList<Task> getTasks() {
153 return mFilteredTasks;
158 * The task stack contains a list of multiple tasks.
160 public class TaskStack {
162 /** Task stack callbacks */
163 public interface TaskStackCallbacks {
164 /* Notifies when a task has been added to the stack */
165 public void onStackTaskAdded(TaskStack stack, Task t);
166 /* Notifies when a task has been removed from the stack */
167 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
168 /* Notifies when all task has been removed from the stack */
169 public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
170 /** Notifies when the stack was filtered */
171 public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
172 /** Notifies when the stack was un-filtered */
173 public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
176 /** A pair of indices representing the group and task positions in the stack and group. */
177 public static class GroupTaskIndex {
178 public int groupIndex; // Index in the stack
179 public int taskIndex; // Index in the group
181 public GroupTaskIndex() {}
183 public GroupTaskIndex(int gi, int ti) {
189 // The task offset to apply to a task id as a group affiliation
190 static final int IndividualTaskIdOffset = 1 << 16;
192 FilteredTaskList mTaskList = new FilteredTaskList();
193 TaskStackCallbacks mCb;
195 ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
196 HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
198 /** Sets the callbacks for this task stack */
199 public void setCallbacks(TaskStackCallbacks cb) {
203 /** Resets this TaskStack. */
204 public void reset() {
208 mAffinitiesGroups.clear();
211 /** Adds a new task */
212 public void addTask(Task t) {
215 mCb.onStackTaskAdded(this, t);
219 /** Does the actual work associated with removing the task. */
220 void removeTaskImpl(Task t) {
221 // Remove the task from the list
223 // Remove it from the group as well, and if it is empty, remove the group
224 TaskGrouping group = t.group;
226 if (group.getTaskCount() == 0) {
229 // Update the lock-to-app state
230 t.lockToThisTask = false;
233 /** Removes a task */
234 public void removeTask(Task t) {
235 if (mTaskList.contains(t)) {
237 Task newFrontMostTask = getFrontMostTask();
238 if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
239 newFrontMostTask.lockToThisTask = true;
242 // Notify that a task has been removed
243 mCb.onStackTaskRemoved(this, t, newFrontMostTask);
248 /** Removes all tasks */
249 public void removeAllTasks() {
250 ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
251 int taskCount = taskList.size();
252 for (int i = taskCount - 1; i >= 0; i--) {
253 Task t = taskList.get(i);
257 // Notify that all tasks have been removed
258 mCb.onStackAllTasksRemoved(this, taskList);
262 /** Sets a few tasks in one go */
263 public void setTasks(List<Task> tasks) {
264 ArrayList<Task> taskList = mTaskList.getTasks();
265 int taskCount = taskList.size();
266 for (int i = taskCount - 1; i >= 0; i--) {
267 Task t = taskList.get(i);
270 // Notify that a task has been removed
271 mCb.onStackTaskRemoved(this, t, null);
274 mTaskList.set(tasks);
275 for (Task t : tasks) {
277 mCb.onStackTaskAdded(this, t);
282 /** Gets the front task */
283 public Task getFrontMostTask() {
284 if (mTaskList.size() == 0) return null;
285 return mTaskList.getTasks().get(mTaskList.size() - 1);
288 /** Gets the task keys */
289 public ArrayList<Task.TaskKey> getTaskKeys() {
290 ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
291 ArrayList<Task> tasks = mTaskList.getTasks();
292 int taskCount = tasks.size();
293 for (int i = 0; i < taskCount; i++) {
294 taskKeys.add(tasks.get(i).key);
299 /** Gets the tasks */
300 public ArrayList<Task> getTasks() {
301 return mTaskList.getTasks();
304 /** Gets the number of tasks */
305 public int getTaskCount() {
306 return mTaskList.size();
309 /** Returns the index of this task in this current task stack */
310 public int indexOfTask(Task t) {
311 return mTaskList.indexOf(t);
314 /** Finds the task with the specified task id. */
315 public Task findTaskWithId(int taskId) {
316 ArrayList<Task> tasks = mTaskList.getTasks();
317 int taskCount = tasks.size();
318 for (int i = 0; i < taskCount; i++) {
319 Task task = tasks.get(i);
320 if (task.key.id == taskId) {
327 /******** Filtering ********/
329 /** Filters the stack into tasks similar to the one specified */
330 public void filterTasks(final Task t) {
331 ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
333 // Set the task list filter
334 boolean filtered = mTaskList.setFilter(new TaskFilter() {
336 public boolean acceptTask(Task at, int i) {
337 return t.key.baseIntent.getComponent().getPackageName().equals(
338 at.key.baseIntent.getComponent().getPackageName());
341 if (filtered && mCb != null) {
342 mCb.onStackFiltered(this, oldStack, t);
346 /** Unfilters the current stack */
347 public void unfilterTasks() {
348 ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
350 // Unset the filter, then update the virtual scroll
351 mTaskList.removeFilter();
353 mCb.onStackUnfiltered(this, oldStack);
357 /** Returns whether tasks are currently filtered */
358 public boolean hasFilteredTasks() {
359 return mTaskList.hasFilter();
362 /******** Grouping ********/
364 /** Adds a group to the set */
365 public void addGroup(TaskGrouping group) {
367 mAffinitiesGroups.put(group.affiliation, group);
370 public void removeGroup(TaskGrouping group) {
371 mGroups.remove(group);
372 mAffinitiesGroups.remove(group.affiliation);
375 /** Returns the group with the specified affiliation. */
376 public TaskGrouping getGroupWithAffiliation(int affiliation) {
377 return mAffinitiesGroups.get(affiliation);
381 * Temporary: This method will simulate affiliation groups by
383 public void createAffiliatedGroupings(RecentsConfiguration config) {
384 if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
385 HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
386 // Sort all tasks by increasing firstActiveTime of the task
387 ArrayList<Task> tasks = mTaskList.getTasks();
388 Collections.sort(tasks, new Comparator<Task>() {
390 public int compare(Task task, Task task2) {
391 return (int) (task.key.firstActiveTime - task2.key.firstActiveTime);
394 // Create groups when sequential packages are the same
395 NamedCounter counter = new NamedCounter("task-group", "");
396 int taskCount = tasks.size();
397 String prevPackage = "";
398 int prevAffiliation = -1;
399 Random r = new Random();
400 int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
401 for (int i = 0; i < taskCount; i++) {
402 Task t = tasks.get(i);
403 String packageName = t.key.baseIntent.getComponent().getPackageName();
406 if (packageName.equals(prevPackage) && groupCountDown > 0) {
407 group = getGroupWithAffiliation(prevAffiliation);
410 int affiliation = IndividualTaskIdOffset + t.key.id;
411 group = new TaskGrouping(affiliation);
413 prevAffiliation = affiliation;
414 prevPackage = packageName;
415 groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
418 taskMap.put(t.key, t);
420 // Sort groups by increasing latestActiveTime of the group
421 Collections.sort(mGroups, new Comparator<TaskGrouping>() {
423 public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
424 return (int) (taskGrouping.latestActiveTimeInGroup -
425 taskGrouping2.latestActiveTimeInGroup);
428 // Sort group tasks by increasing firstActiveTime of the task, and also build a new list of
431 int groupCount = mGroups.size();
432 for (int i = 0; i < groupCount; i++) {
433 TaskGrouping group = mGroups.get(i);
434 Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
436 public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
437 return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime);
440 ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
441 int groupTaskCount = groupTasks.size();
442 for (int j = 0; j < groupTaskCount; j++) {
443 tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
447 mTaskList.set(tasks);
449 // Create the task groups
450 HashMap<Task.TaskKey, Task> tasksMap = new HashMap<Task.TaskKey, Task>();
451 ArrayList<Task> tasks = mTaskList.getTasks();
452 int taskCount = tasks.size();
453 for (int i = 0; i < taskCount; i++) {
454 Task t = tasks.get(i);
456 int affiliation = t.taskAffiliation > 0 ? t.taskAffiliation :
457 IndividualTaskIdOffset + t.key.id;
458 if (mAffinitiesGroups.containsKey(affiliation)) {
459 group = getGroupWithAffiliation(affiliation);
461 group = new TaskGrouping(affiliation);
465 tasksMap.put(t.key, t);
467 // Update the task colors for each of the groups
468 float minAlpha = config.taskBarViewAffiliationColorMinAlpha;
469 int taskGroupCount = mGroups.size();
470 for (int i = 0; i < taskGroupCount; i++) {
471 TaskGrouping group = mGroups.get(i);
472 taskCount = group.getTaskCount();
473 // Ignore the groups that only have one task
474 if (taskCount <= 1) continue;
475 // Calculate the group color distribution
476 int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor;
477 float alphaStep = (1f - minAlpha) / taskCount;
479 for (int j = 0; j < taskCount; j++) {
480 Task t = tasksMap.get(group.mTaskKeys.get(j));
481 t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
490 public String toString() {
491 String str = "Tasks:\n";
492 for (Task t : mTaskList.getTasks()) {
493 str += " " + t.toString() + "\n";