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}