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.app.ActivityManager;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.os.UserHandle;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import com.android.systemui.recents.Constants;
29 import com.android.systemui.recents.RecentsConfiguration;
30 import com.android.systemui.recents.misc.SystemServicesProxy;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.List;
36 
37 
38 /**
39  * This class stores the loading state as it goes through multiple stages of loading:
40  *   1) preloadRawTasks() will load the raw set of recents tasks from the system
41  *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
42  *      thumbnails that are currently in the cache
43  *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
44  *      options specified, such that we can transition into the Recents activity seamlessly
45  */
46 public class RecentsTaskLoadPlan {
47     static String TAG = "RecentsTaskLoadPlan";
48     static boolean DEBUG = false;
49 
50     /** The set of conditions to load tasks. */
51     public static class Options {
52         public int runningTaskId = -1;
53         public boolean loadIcons = true;
54         public boolean loadThumbnails = true;
55         public boolean onlyLoadForCache = false;
56         public boolean onlyLoadPausedActivities = false;
57         public int numVisibleTasks = 0;
58         public int numVisibleTaskThumbnails = 0;
59     }
60 
61     Context mContext;
62     RecentsConfiguration mConfig;
63     SystemServicesProxy mSystemServicesProxy;
64 
65     List<ActivityManager.RecentTaskInfo> mRawTasks;
66     SparseArray<TaskStack> mStacks = new SparseArray<>();
67     HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
68             new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
69 
70     /** Package level ctor */
RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp)71     RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
72         mContext = context;
73         mConfig = config;
74         mSystemServicesProxy = ssp;
75     }
76 
77     /**
78      * An optimization to preload the raw list of tasks.
79      */
preloadRawTasks(boolean isTopTaskHome)80     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
81         mRawTasks = mSystemServicesProxy.getRecentTasks(mConfig.maxNumTasksToLoad,
82                 UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
83         Collections.reverse(mRawTasks);
84 
85         if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
86     }
87 
88     /**
89      * Preloads the list of recent tasks from the system.  After this call, the TaskStack will
90      * have a list of all the recent tasks with their metadata, not including icons or
91      * thumbnails which were not cached and have to be loaded.
92      */
preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome)93     synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
94         if (DEBUG) Log.d(TAG, "preloadPlan");
95 
96         // This activity info cache will be used for both preloadPlan() and executePlan()
97         mActivityInfoCache.clear();
98 
99         // TODO (multi-display): Currently assume the primary display
100         Rect displayBounds = mSystemServicesProxy.getWindowRect();
101 
102         Resources res = mContext.getResources();
103         SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>();
104         if (mRawTasks == null) {
105             preloadRawTasks(isTopTaskHome);
106         }
107         int taskCount = mRawTasks.size();
108         for (int i = 0; i < taskCount; i++) {
109             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
110 
111             // Compose the task key
112             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
113                     t.userId, t.firstActiveTime, t.lastActiveTime);
114 
115             // Get an existing activity info handle if possible
116             Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
117             ActivityInfoHandle infoHandle;
118             boolean hadCachedActivityInfo = false;
119             if (mActivityInfoCache.containsKey(cnKey)) {
120                 infoHandle = mActivityInfoCache.get(cnKey);
121                 hadCachedActivityInfo = true;
122             } else {
123                 infoHandle = new ActivityInfoHandle();
124             }
125 
126             // Load the label, icon, and color
127             String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
128                     mSystemServicesProxy, infoHandle);
129             String contentDescription = loader.getAndUpdateContentDescription(taskKey,
130                     activityLabel, mSystemServicesProxy, res);
131             Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
132                     mSystemServicesProxy, res, infoHandle, false);
133             int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
134 
135             // Update the activity info cache
136             if (!hadCachedActivityInfo && infoHandle.info != null) {
137                 mActivityInfoCache.put(cnKey, infoHandle);
138             }
139 
140             Bitmap icon = t.taskDescription != null
141                     ? t.taskDescription.getInMemoryIcon()
142                     : null;
143             String iconFilename = t.taskDescription != null
144                     ? t.taskDescription.getIconFilename()
145                     : null;
146 
147             // Add the task to the stack
148             Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
149                     t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
150                     activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
151                     icon, iconFilename);
152             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
153             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
154 
155             if (!mConfig.multiStackEnabled ||
156                     Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
157                 int firstStackId = 0;
158                 ArrayList<Task> stackTasks = stacksTasks.get(firstStackId);
159                 if (stackTasks == null) {
160                     stackTasks = new ArrayList<>();
161                     stacksTasks.put(firstStackId, stackTasks);
162                 }
163                 stackTasks.add(task);
164             } else {
165                 ArrayList<Task> stackTasks = stacksTasks.get(t.stackId);
166                 if (stackTasks == null) {
167                     stackTasks = new ArrayList<>();
168                     stacksTasks.put(t.stackId, stackTasks);
169                 }
170                 stackTasks.add(task);
171             }
172         }
173 
174         // Initialize the stacks
175         SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos();
176         mStacks.clear();
177         int stackCount = stacksTasks.size();
178         for (int i = 0; i < stackCount; i++) {
179             int stackId = stacksTasks.keyAt(i);
180             ActivityManager.StackInfo info = stackInfos.get(stackId);
181             ArrayList<Task> stackTasks = stacksTasks.valueAt(i);
182             TaskStack stack = new TaskStack(stackId);
183             if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) {
184                 stack.setBounds(displayBounds, displayBounds);
185             } else {
186                 stack.setBounds(info.bounds, displayBounds);
187             }
188             stack.setTasks(stackTasks);
189             stack.createAffiliatedGroupings(mConfig);
190             mStacks.put(stackId, stack);
191         }
192     }
193 
194     /**
195      * Called to apply the actual loading based on the specified conditions.
196      */
executePlan(Options opts, RecentsTaskLoader loader, TaskResourceLoadQueue loadQueue)197     synchronized void executePlan(Options opts, RecentsTaskLoader loader,
198             TaskResourceLoadQueue loadQueue) {
199         if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
200                 ", # thumbnails: " + opts.numVisibleTaskThumbnails +
201                 ", running task id: " + opts.runningTaskId);
202 
203         Resources res = mContext.getResources();
204 
205         // Iterate through each of the tasks and load them according to the load conditions.
206         int stackCount = mStacks.size();
207         for (int j = 0; j < stackCount; j++) {
208             ArrayList<Task> tasks = mStacks.valueAt(j).getTasks();
209             int taskCount = tasks.size();
210             for (int i = 0; i < taskCount; i++) {
211                 ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
212                 Task task = tasks.get(i);
213                 Task.TaskKey taskKey = task.key;
214 
215                 // Get an existing activity info handle if possible
216                 Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
217                 ActivityInfoHandle infoHandle;
218                 boolean hadCachedActivityInfo = false;
219                 if (mActivityInfoCache.containsKey(cnKey)) {
220                     infoHandle = mActivityInfoCache.get(cnKey);
221                     hadCachedActivityInfo = true;
222                 } else {
223                     infoHandle = new ActivityInfoHandle();
224                 }
225 
226                 boolean isRunningTask = (task.key.id == opts.runningTaskId);
227                 boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
228                 boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
229 
230                 // If requested, skip the running task
231                 if (opts.onlyLoadPausedActivities && isRunningTask) {
232                     continue;
233                 }
234 
235                 if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
236                     if (task.activityIcon == null) {
237                         if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
238                         task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
239                                 t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
240                     }
241                 }
242                 if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
243                     if (task.thumbnail == null || isRunningTask) {
244                         if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
245                         if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
246                             task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
247                                     mSystemServicesProxy, true);
248                         } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
249                             loadQueue.addTask(task);
250                         }
251                     }
252                 }
253 
254                 // Update the activity info cache
255                 if (!hadCachedActivityInfo && infoHandle.info != null) {
256                     mActivityInfoCache.put(cnKey, infoHandle);
257                 }
258             }
259         }
260     }
261 
262     /**
263      * Returns all TaskStacks from the preloaded list of recent tasks.
264      */
getAllTaskStacks()265     public ArrayList<TaskStack> getAllTaskStacks() {
266         ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
267         int stackCount = mStacks.size();
268         for (int i = 0; i < stackCount; i++) {
269             stacks.add(mStacks.valueAt(i));
270         }
271         // Ensure that we have at least one stack
272         if (stacks.isEmpty()) {
273             stacks.add(new TaskStack());
274         }
275         return stacks;
276     }
277 
278     /**
279      * Returns a specific TaskStack from the preloaded list of recent tasks.
280      */
getTaskStack(int stackId)281     public TaskStack getTaskStack(int stackId) {
282         return mStacks.get(stackId);
283     }
284 
285     /** Returns whether there are any tasks in any stacks. */
hasTasks()286     public boolean hasTasks() {
287         int stackCount = mStacks.size();
288         for (int i = 0; i < stackCount; i++) {
289             if (mStacks.valueAt(i).getTaskCount() > 0) {
290                 return true;
291             }
292         }
293         return false;
294     }
295 }
296