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.drawable.Drawable;
24 import android.os.UserHandle;
25 import android.util.Log;
26 import com.android.systemui.recents.RecentsConfiguration;
27 import com.android.systemui.recents.misc.SystemServicesProxy;
28 
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 
34 
35 /**
36  * This class stores the loading state as it goes through multiple stages of loading:
37  *   - preloadRawTasks() will load the raw set of recents tasks from the system
38  *   - preloadPlan() will construct a new task stack with all metadata and only icons and thumbnails
39  *     that are currently in the cache
40  *   - executePlan() will actually load and fill in the icons and thumbnails according to the load
41  *     options specified, such that we can transition into the Recents activity seamlessly
42  */
43 public class RecentsTaskLoadPlan {
44     static String TAG = "RecentsTaskLoadPlan";
45     static boolean DEBUG = false;
46 
47     /** The set of conditions to load tasks. */
48     public static class Options {
49         public int runningTaskId = -1;
50         public boolean loadIcons = true;
51         public boolean loadThumbnails = true;
52         public boolean onlyLoadForCache = false;
53         public boolean onlyLoadPausedActivities = false;
54         public int numVisibleTasks = 0;
55         public int numVisibleTaskThumbnails = 0;
56     }
57 
58     Context mContext;
59     RecentsConfiguration mConfig;
60     SystemServicesProxy mSystemServicesProxy;
61 
62     List<ActivityManager.RecentTaskInfo> mRawTasks;
63     TaskStack mStack;
64     HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
65             new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
66 
67     /** Package level ctor */
RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp)68     RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
69         mContext = context;
70         mConfig = config;
71         mSystemServicesProxy = ssp;
72     }
73 
74     /**
75      * An optimization to preload the raw list of tasks.
76      */
preloadRawTasks(boolean isTopTaskHome)77     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
78         mRawTasks = mSystemServicesProxy.getRecentTasks(mConfig.maxNumTasksToLoad,
79                 UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
80         Collections.reverse(mRawTasks);
81 
82         if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
83     }
84 
85     /**
86      * Preloads the list of recent tasks from the system.  After this call, the TaskStack will
87      * have a list of all the recent tasks with their metadata, not including icons or
88      * thumbnails which were not cached and have to be loaded.
89      */
preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome)90     synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
91         if (DEBUG) Log.d(TAG, "preloadPlan");
92 
93         mActivityInfoCache.clear();
94         mStack = new TaskStack();
95 
96         Resources res = mContext.getResources();
97         ArrayList<Task> loadedTasks = new ArrayList<Task>();
98         if (mRawTasks == null) {
99             preloadRawTasks(isTopTaskHome);
100         }
101         int taskCount = mRawTasks.size();
102         for (int i = 0; i < taskCount; i++) {
103             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
104 
105             // Compose the task key
106             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
107                     t.firstActiveTime, t.lastActiveTime);
108 
109             // Get an existing activity info handle if possible
110             Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
111             ActivityInfoHandle infoHandle;
112             boolean hadCachedActivityInfo = false;
113             if (mActivityInfoCache.containsKey(cnKey)) {
114                 infoHandle = mActivityInfoCache.get(cnKey);
115                 hadCachedActivityInfo = true;
116             } else {
117                 infoHandle = new ActivityInfoHandle();
118             }
119 
120             // Load the label, icon, and color
121             String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
122                     mSystemServicesProxy, infoHandle);
123             Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
124                     mSystemServicesProxy, res, infoHandle, false);
125             int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
126 
127             // Update the activity info cache
128             if (!hadCachedActivityInfo && infoHandle.info != null) {
129                 mActivityInfoCache.put(cnKey, infoHandle);
130             }
131 
132             Bitmap icon = t.taskDescription != null
133                     ? t.taskDescription.getInMemoryIcon()
134                     : null;
135             String iconFilename = t.taskDescription != null
136                     ? t.taskDescription.getIconFilename()
137                     : null;
138 
139             // Add the task to the stack
140             Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
141                     t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, activityIcon,
142                     activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
143                     iconFilename);
144             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
145             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
146             loadedTasks.add(task);
147         }
148         mStack.setTasks(loadedTasks);
149         mStack.createAffiliatedGroupings(mConfig);
150 
151         // Assertion
152         if (mStack.getTaskCount() != mRawTasks.size()) {
153             throw new RuntimeException("Loading failed");
154         }
155     }
156 
157     /**
158      * Called to apply the actual loading based on the specified conditions.
159      */
executePlan(Options opts, RecentsTaskLoader loader, TaskResourceLoadQueue loadQueue)160     synchronized void executePlan(Options opts, RecentsTaskLoader loader,
161             TaskResourceLoadQueue loadQueue) {
162         if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
163                 ", # thumbnails: " + opts.numVisibleTaskThumbnails +
164                 ", running task id: " + opts.runningTaskId);
165 
166         Resources res = mContext.getResources();
167 
168         // Iterate through each of the tasks and load them according to the load conditions.
169         ArrayList<Task> tasks = mStack.getTasks();
170         int taskCount = tasks.size();
171         for (int i = 0; i < taskCount; i++) {
172             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
173             Task task = tasks.get(i);
174             Task.TaskKey taskKey = task.key;
175 
176             // Get an existing activity info handle if possible
177             Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
178             ActivityInfoHandle infoHandle;
179             boolean hadCachedActivityInfo = false;
180             if (mActivityInfoCache.containsKey(cnKey)) {
181                 infoHandle = mActivityInfoCache.get(cnKey);
182                 hadCachedActivityInfo = true;
183             } else {
184                 infoHandle = new ActivityInfoHandle();
185             }
186 
187             boolean isRunningTask = (task.key.id == opts.runningTaskId);
188             boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
189             boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
190 
191             // If requested, skip the running task
192             if (opts.onlyLoadPausedActivities && isRunningTask) {
193                 continue;
194             }
195 
196             if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
197                 if (task.activityIcon == null) {
198                     if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
199                     task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
200                             mSystemServicesProxy, res, infoHandle, true);
201                 }
202             }
203             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
204                 if (task.thumbnail == null || isRunningTask) {
205                     if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
206                     if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
207                         task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy,
208                                 true);
209                     } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
210                         loadQueue.addTask(task);
211                     }
212                 }
213             }
214 
215             // Update the activity info cache
216             if (!hadCachedActivityInfo && infoHandle.info != null) {
217                 mActivityInfoCache.put(cnKey, infoHandle);
218             }
219         }
220     }
221 
222     /**
223      * Composes and returns a TaskStack from the preloaded list of recent tasks.
224      */
getTaskStack()225     public TaskStack getTaskStack() {
226         return mStack;
227     }
228 
229     /**
230      * Composes and returns a SpaceNode from the preloaded list of recent tasks.
231      */
getSpaceNode()232     public SpaceNode getSpaceNode() {
233         SpaceNode node = new SpaceNode();
234         node.setStack(mStack);
235         return node;
236     }
237 }
238