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.recents.model; 18 19 import android.graphics.Color; 20 import android.graphics.Rect; 21 import com.android.systemui.recents.Constants; 22 import com.android.systemui.recents.RecentsConfiguration; 23 import com.android.systemui.recents.misc.NamedCounter; 24 import com.android.systemui.recents.misc.Utilities; 25 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.Comparator; 29 import java.util.HashMap; 30 import java.util.List; 31 import 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 */ 37 interface 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 */ 45 class 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 */ 161 public 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 }