TaskStack.java revision a433fa9c17772f563163ff7db177d091d6aebd5b
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 t); 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 FilteredTaskList mTaskList = new FilteredTaskList(); 174 TaskStackCallbacks mCb; 175 176 ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>(); 177 HashMap<String, TaskGrouping> mAffinitiesGroups = new HashMap<String, TaskGrouping>(); 178 179 /** Sets the callbacks for this task stack */ 180 public void setCallbacks(TaskStackCallbacks cb) { 181 mCb = cb; 182 } 183 184 /** Adds a new task */ 185 public void addTask(Task t) { 186 mTaskList.add(t); 187 if (mCb != null) { 188 mCb.onStackTaskAdded(this, t); 189 } 190 } 191 192 /** Removes a task */ 193 public void removeTask(Task t) { 194 if (mTaskList.contains(t)) { 195 // Remove the task from the list 196 mTaskList.remove(t); 197 // Remove it from the group as well, and if it is empty, remove the group 198 TaskGrouping group = t.group; 199 group.removeTask(t); 200 if (group.getTaskCount() == 0) { 201 removeGroup(group); 202 } 203 if (mCb != null) { 204 // Notify that a task has been removed 205 mCb.onStackTaskRemoved(this, t); 206 } 207 } 208 } 209 210 /** Sets a few tasks in one go */ 211 public void setTasks(List<Task> tasks) { 212 ArrayList<Task> taskList = mTaskList.getTasks(); 213 int taskCount = taskList.size(); 214 for (int i = 0; i < taskCount; i++) { 215 Task t = taskList.get(i); 216 // Remove the task from the list 217 mTaskList.remove(t); 218 // Remove it from the group as well, and if it is empty, remove the group 219 TaskGrouping group = t.group; 220 group.removeTask(t); 221 if (group.getTaskCount() == 0) { 222 removeGroup(group); 223 } 224 if (mCb != null) { 225 // Notify that a task has been removed 226 mCb.onStackTaskRemoved(this, t); 227 } 228 } 229 mTaskList.set(tasks); 230 for (Task t : tasks) { 231 if (mCb != null) { 232 mCb.onStackTaskAdded(this, t); 233 } 234 } 235 } 236 237 /** Gets the front task */ 238 public Task getFrontMostTask() { 239 return mTaskList.getTasks().get(mTaskList.size() - 1); 240 } 241 242 /** Gets the tasks */ 243 public ArrayList<Task> getTasks() { 244 return mTaskList.getTasks(); 245 } 246 247 /** Gets the number of tasks */ 248 public int getTaskCount() { 249 return mTaskList.size(); 250 } 251 252 /** Returns the index of this task in this current task stack */ 253 public int indexOfTask(Task t) { 254 return mTaskList.indexOf(t); 255 } 256 257 /******** Filtering ********/ 258 259 /** Filters the stack into tasks similar to the one specified */ 260 public void filterTasks(final Task t) { 261 ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks()); 262 263 // Set the task list filter 264 boolean filtered = mTaskList.setFilter(new TaskFilter() { 265 @Override 266 public boolean acceptTask(Task at, int i) { 267 return t.key.baseIntent.getComponent().getPackageName().equals( 268 at.key.baseIntent.getComponent().getPackageName()); 269 } 270 }); 271 if (filtered && mCb != null) { 272 mCb.onStackFiltered(this, oldStack, t); 273 } 274 } 275 276 /** Unfilters the current stack */ 277 public void unfilterTasks() { 278 ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks()); 279 280 // Unset the filter, then update the virtual scroll 281 mTaskList.removeFilter(); 282 if (mCb != null) { 283 mCb.onStackUnfiltered(this, oldStack); 284 } 285 } 286 287 /** Returns whether tasks are currently filtered */ 288 public boolean hasFilteredTasks() { 289 return mTaskList.hasFilter(); 290 } 291 292 /******** Grouping ********/ 293 294 /** Adds a group to the set */ 295 public void addGroup(TaskGrouping group) { 296 mGroups.add(group); 297 mAffinitiesGroups.put(group.affiliation, group); 298 } 299 300 public void removeGroup(TaskGrouping group) { 301 mGroups.remove(group); 302 mAffinitiesGroups.remove(group.affiliation); 303 } 304 305 /** Returns the group with the specified affiliation. */ 306 public TaskGrouping getGroupWithAffiliation(String affiliation) { 307 return mAffinitiesGroups.get(affiliation); 308 } 309 310 /** 311 * Temporary: This method will simulate affiliation groups by 312 */ 313 public void createSimulatedAffiliatedGroupings() { 314 if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) { 315 HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>(); 316 // Sort all tasks by increasing firstActiveTime of the task 317 ArrayList<Task> tasks = mTaskList.getTasks(); 318 Collections.sort(tasks, new Comparator<Task>() { 319 @Override 320 public int compare(Task task, Task task2) { 321 return (int) (task.key.firstActiveTime - task2.key.firstActiveTime); 322 } 323 }); 324 // Create groups when sequential packages are the same 325 NamedCounter counter = new NamedCounter("task-group", ""); 326 int taskCount = tasks.size(); 327 String prevPackage = ""; 328 String prevAffiliation = ""; 329 Random r = new Random(); 330 int groupCountDown = 1000; 331 for (int i = 0; i < taskCount; i++) { 332 Task t = tasks.get(i); 333 String packageName = t.key.baseIntent.getComponent().getPackageName(); 334 packageName = "pkg"; 335 TaskGrouping group; 336 if (packageName.equals(prevPackage) && groupCountDown > 0) { 337 group = getGroupWithAffiliation(prevAffiliation); 338 groupCountDown--; 339 } else { 340 String affiliation = counter.nextName(); 341 group = new TaskGrouping(affiliation); 342 addGroup(group); 343 prevAffiliation = affiliation; 344 prevPackage = packageName; 345 groupCountDown = 1000; 346 } 347 group.addTask(t); 348 taskMap.put(t.key, t); 349 } 350 // Sort groups by increasing latestActiveTime of the group 351 Collections.sort(mGroups, new Comparator<TaskGrouping>() { 352 @Override 353 public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) { 354 return (int) (taskGrouping.latestActiveTimeInGroup - 355 taskGrouping2.latestActiveTimeInGroup); 356 } 357 }); 358 // Sort group tasks by increasing firstActiveTime of the task, and also build a new list of 359 // tasks 360 int taskIndex = 0; 361 int groupCount = mGroups.size(); 362 for (int i = 0; i < groupCount; i++) { 363 TaskGrouping group = mGroups.get(i); 364 Collections.sort(group.mTasks, new Comparator<Task.TaskKey>() { 365 @Override 366 public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) { 367 return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime); 368 } 369 }); 370 ArrayList<Task.TaskKey> groupTasks = group.mTasks; 371 int groupTaskCount = groupTasks.size(); 372 for (int j = 0; j < groupTaskCount; j++) { 373 tasks.set(taskIndex, taskMap.get(groupTasks.get(j))); 374 taskIndex++; 375 } 376 } 377 mTaskList.set(tasks); 378 } else { 379 // Create a group per task 380 NamedCounter counter = new NamedCounter("task-group", ""); 381 ArrayList<Task> tasks = mTaskList.getTasks(); 382 int taskCount = tasks.size(); 383 for (int i = 0; i < taskCount; i++) { 384 Task t = tasks.get(i); 385 TaskGrouping group = new TaskGrouping(counter.nextName()); 386 addGroup(group); 387 group.addTask(t); 388 } 389 } 390 } 391 392 @Override 393 public String toString() { 394 String str = "Tasks:\n"; 395 for (Task t : mTaskList.getTasks()) { 396 str += " " + t.toString() + "\n"; 397 } 398 return str; 399 } 400}