Home | History | Annotate | Download | only in model
      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 }