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.shared.recents.model;
     18 
     19 import android.content.ComponentName;
     20 import android.util.ArrayMap;
     21 import android.util.ArraySet;
     22 
     23 import com.android.systemui.shared.recents.model.Task.TaskKey;
     24 import com.android.systemui.shared.recents.utilities.AnimationProps;
     25 import com.android.systemui.shared.system.PackageManagerWrapper;
     26 
     27 import java.io.PrintWriter;
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 
     31 
     32 /**
     33  * The task stack contains a list of multiple tasks.
     34  */
     35 public class TaskStack {
     36 
     37     private static final String TAG = "TaskStack";
     38 
     39     /** Task stack callbacks */
     40     public interface TaskStackCallbacks {
     41         /**
     42          * Notifies when a new task has been added to the stack.
     43          */
     44         void onStackTaskAdded(TaskStack stack, Task newTask);
     45 
     46         /**
     47          * Notifies when a task has been removed from the stack.
     48          */
     49         void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
     50                 AnimationProps animation, boolean fromDockGesture,
     51                 boolean dismissRecentsIfAllRemoved);
     52 
     53         /**
     54          * Notifies when all tasks have been removed from the stack.
     55          */
     56         void onStackTasksRemoved(TaskStack stack);
     57 
     58         /**
     59          * Notifies when tasks in the stack have been updated.
     60          */
     61         void onStackTasksUpdated(TaskStack stack);
     62     }
     63 
     64     private final ArrayList<Task> mRawTaskList = new ArrayList<>();
     65     private final FilteredTaskList mStackTaskList = new FilteredTaskList();
     66     private TaskStackCallbacks mCb;
     67 
     68     public TaskStack() {
     69         // Ensure that we only show stack tasks
     70         mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask);
     71     }
     72 
     73     /** Sets the callbacks for this task stack. */
     74     public void setCallbacks(TaskStackCallbacks cb) {
     75         mCb = cb;
     76     }
     77 
     78     /**
     79      * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
     80      * how they should update themselves.
     81      */
     82     public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
     83         removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
     84     }
     85 
     86     /**
     87      * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
     88      * how they should update themselves.
     89      */
     90     public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
     91             boolean dismissRecentsIfAllRemoved) {
     92         if (mStackTaskList.contains(t)) {
     93             mStackTaskList.remove(t);
     94             Task newFrontMostTask = getFrontMostTask();
     95             if (mCb != null) {
     96                 // Notify that a task has been removed
     97                 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
     98                         fromDockGesture, dismissRecentsIfAllRemoved);
     99             }
    100         }
    101         mRawTaskList.remove(t);
    102     }
    103 
    104     /**
    105      * Removes all tasks from the stack.
    106      */
    107     public void removeAllTasks(boolean notifyStackChanges) {
    108         ArrayList<Task> tasks = mStackTaskList.getTasks();
    109         for (int i = tasks.size() - 1; i >= 0; i--) {
    110             Task t = tasks.get(i);
    111             mStackTaskList.remove(t);
    112             mRawTaskList.remove(t);
    113         }
    114         if (mCb != null && notifyStackChanges) {
    115             // Notify that all tasks have been removed
    116             mCb.onStackTasksRemoved(this);
    117         }
    118     }
    119 
    120 
    121     /**
    122      * @see #setTasks(List, boolean)
    123      */
    124     public void setTasks(TaskStack stack, boolean notifyStackChanges) {
    125         setTasks(stack.mRawTaskList, notifyStackChanges);
    126     }
    127 
    128     /**
    129      * Sets a few tasks in one go, without calling any callbacks.
    130      *
    131      * @param tasks the new set of tasks to replace the current set.
    132      * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
    133      */
    134     public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
    135         // Compute a has set for each of the tasks
    136         ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
    137         ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
    138         ArrayList<Task> addedTasks = new ArrayList<>();
    139         ArrayList<Task> removedTasks = new ArrayList<>();
    140         ArrayList<Task> allTasks = new ArrayList<>();
    141 
    142         // Disable notifications if there are no callbacks
    143         if (mCb == null) {
    144             notifyStackChanges = false;
    145         }
    146 
    147         // Remove any tasks that no longer exist
    148         int taskCount = mRawTaskList.size();
    149         for (int i = taskCount - 1; i >= 0; i--) {
    150             Task task = mRawTaskList.get(i);
    151             if (!newTasksMap.containsKey(task.key)) {
    152                 if (notifyStackChanges) {
    153                     removedTasks.add(task);
    154                 }
    155             }
    156         }
    157 
    158         // Add any new tasks
    159         taskCount = tasks.size();
    160         for (int i = 0; i < taskCount; i++) {
    161             Task newTask = tasks.get(i);
    162             Task currentTask = currentTasksMap.get(newTask.key);
    163             if (currentTask == null && notifyStackChanges) {
    164                 addedTasks.add(newTask);
    165             } else if (currentTask != null) {
    166                 // The current task has bound callbacks, so just copy the data from the new task
    167                 // state and add it back into the list
    168                 currentTask.copyFrom(newTask);
    169                 newTask = currentTask;
    170             }
    171             allTasks.add(newTask);
    172         }
    173 
    174         // Sort all the tasks to ensure they are ordered correctly
    175         for (int i = allTasks.size() - 1; i >= 0; i--) {
    176             allTasks.get(i).temporarySortIndexInStack = i;
    177         }
    178 
    179         mStackTaskList.set(allTasks);
    180         mRawTaskList.clear();
    181         mRawTaskList.addAll(allTasks);
    182 
    183         // Only callback for the removed tasks after the stack has updated
    184         int removedTaskCount = removedTasks.size();
    185         Task newFrontMostTask = getFrontMostTask();
    186         for (int i = 0; i < removedTaskCount; i++) {
    187             mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
    188                     AnimationProps.IMMEDIATE, false /* fromDockGesture */,
    189                     true /* dismissRecentsIfAllRemoved */);
    190         }
    191 
    192         // Only callback for the newly added tasks after this stack has been updated
    193         int addedTaskCount = addedTasks.size();
    194         for (int i = 0; i < addedTaskCount; i++) {
    195             mCb.onStackTaskAdded(this, addedTasks.get(i));
    196         }
    197 
    198         // Notify that the task stack has been updated
    199         if (notifyStackChanges) {
    200             mCb.onStackTasksUpdated(this);
    201         }
    202     }
    203 
    204     /**
    205      * Gets the front-most task in the stack.
    206      */
    207     public Task getFrontMostTask() {
    208         ArrayList<Task> stackTasks = mStackTaskList.getTasks();
    209         if (stackTasks.isEmpty()) {
    210             return null;
    211         }
    212         return stackTasks.get(stackTasks.size() - 1);
    213     }
    214 
    215     /** Gets the task keys */
    216     public ArrayList<TaskKey> getTaskKeys() {
    217         ArrayList<TaskKey> taskKeys = new ArrayList<>();
    218         ArrayList<Task> tasks = computeAllTasksList();
    219         int taskCount = tasks.size();
    220         for (int i = 0; i < taskCount; i++) {
    221             Task task = tasks.get(i);
    222             taskKeys.add(task.key);
    223         }
    224         return taskKeys;
    225     }
    226 
    227     /**
    228      * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
    229      */
    230     public ArrayList<Task> getTasks() {
    231         return mStackTaskList.getTasks();
    232     }
    233 
    234     /**
    235      * Computes a set of all the active and historical tasks.
    236      */
    237     public ArrayList<Task> computeAllTasksList() {
    238         ArrayList<Task> tasks = new ArrayList<>();
    239         tasks.addAll(mStackTaskList.getTasks());
    240         return tasks;
    241     }
    242 
    243     /**
    244      * Returns the number of stack tasks.
    245      */
    246     public int getTaskCount() {
    247         return mStackTaskList.size();
    248     }
    249 
    250     /**
    251      * Returns the task in stack tasks which is the launch target.
    252      */
    253     public Task getLaunchTarget() {
    254         ArrayList<Task> tasks = mStackTaskList.getTasks();
    255         int taskCount = tasks.size();
    256         for (int i = 0; i < taskCount; i++) {
    257             Task task = tasks.get(i);
    258             if (task.isLaunchTarget) {
    259                 return task;
    260             }
    261         }
    262         return null;
    263     }
    264 
    265     /**
    266      * Returns whether the next launch target should actually be the PiP task.
    267      */
    268     public boolean isNextLaunchTargetPip(long lastPipTime) {
    269         Task launchTarget = getLaunchTarget();
    270         Task nextLaunchTarget = getNextLaunchTargetRaw();
    271         if (nextLaunchTarget != null && lastPipTime > 0) {
    272             // If the PiP time is more recent than the next launch target, then launch the PiP task
    273             return lastPipTime > nextLaunchTarget.key.lastActiveTime;
    274         } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
    275             // Otherwise, if there is no next launch target, but there is a PiP, then launch
    276             // the PiP task
    277             return true;
    278         }
    279         return false;
    280     }
    281 
    282     /**
    283      * Returns the task in stack tasks which should be launched next if Recents are toggled
    284      * again, or null if there is no task to be launched. Callers should check
    285      * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
    286      * stack.
    287      */
    288     public Task getNextLaunchTarget() {
    289         Task nextLaunchTarget = getNextLaunchTargetRaw();
    290         if (nextLaunchTarget != null) {
    291             return nextLaunchTarget;
    292         }
    293         return getTasks().get(getTaskCount() - 1);
    294     }
    295 
    296     private Task getNextLaunchTargetRaw() {
    297         int taskCount = getTaskCount();
    298         if (taskCount == 0) {
    299             return null;
    300         }
    301         int launchTaskIndex = indexOfTask(getLaunchTarget());
    302         if (launchTaskIndex != -1 && launchTaskIndex > 0) {
    303             return getTasks().get(launchTaskIndex - 1);
    304         }
    305         return null;
    306     }
    307 
    308     /** Returns the index of this task in this current task stack */
    309     public int indexOfTask(Task t) {
    310         return mStackTaskList.indexOf(t);
    311     }
    312 
    313     /** Finds the task with the specified task id. */
    314     public Task findTaskWithId(int taskId) {
    315         ArrayList<Task> tasks = computeAllTasksList();
    316         int taskCount = tasks.size();
    317         for (int i = 0; i < taskCount; i++) {
    318             Task task = tasks.get(i);
    319             if (task.key.id == taskId) {
    320                 return task;
    321             }
    322         }
    323         return null;
    324     }
    325 
    326     /**
    327      * Computes the components of tasks in this stack that have been removed as a result of a change
    328      * in the specified package.
    329      */
    330     public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
    331         // Identify all the tasks that should be removed as a result of the package being removed.
    332         // Using a set to ensure that we callback once per unique component.
    333         ArraySet<ComponentName> existingComponents = new ArraySet<>();
    334         ArraySet<ComponentName> removedComponents = new ArraySet<>();
    335         ArrayList<TaskKey> taskKeys = getTaskKeys();
    336         int taskKeyCount = taskKeys.size();
    337         for (int i = 0; i < taskKeyCount; i++) {
    338             TaskKey t = taskKeys.get(i);
    339 
    340             // Skip if this doesn't apply to the current user
    341             if (t.userId != userId) continue;
    342 
    343             ComponentName cn = t.getComponent();
    344             if (cn.getPackageName().equals(packageName)) {
    345                 if (existingComponents.contains(cn)) {
    346                     // If we know that the component still exists in the package, then skip
    347                     continue;
    348                 }
    349                 if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
    350                     existingComponents.add(cn);
    351                 } else {
    352                     removedComponents.add(cn);
    353                 }
    354             }
    355         }
    356         return removedComponents;
    357     }
    358 
    359     @Override
    360     public String toString() {
    361         String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
    362         ArrayList<Task> tasks = mStackTaskList.getTasks();
    363         int taskCount = tasks.size();
    364         for (int i = 0; i < taskCount; i++) {
    365             str += "    " + tasks.get(i).toString() + "\n";
    366         }
    367         return str;
    368     }
    369 
    370     /**
    371      * Given a list of tasks, returns a map of each task's key to the task.
    372      */
    373     private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
    374         ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
    375         int taskCount = tasks.size();
    376         for (int i = 0; i < taskCount; i++) {
    377             Task task = tasks.get(i);
    378             map.put(task.key, task);
    379         }
    380         return map;
    381     }
    382 
    383     public void dump(String prefix, PrintWriter writer) {
    384         String innerPrefix = prefix + "  ";
    385 
    386         writer.print(prefix); writer.print(TAG);
    387         writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
    388         writer.println();
    389         ArrayList<Task> tasks = mStackTaskList.getTasks();
    390         int taskCount = tasks.size();
    391         for (int i = 0; i < taskCount; i++) {
    392             tasks.get(i).dump(innerPrefix, writer);
    393         }
    394     }
    395 }
    396