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