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