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
17package com.android.systemui.recents.model;
18
19import android.app.ActivityManager;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Bitmap;
23import android.graphics.drawable.Drawable;
24import android.os.UserHandle;
25import android.util.Log;
26import com.android.systemui.recents.RecentsConfiguration;
27import com.android.systemui.recents.misc.SystemServicesProxy;
28
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import 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 */
43public 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 */
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     */
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     */
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     */
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     */
225    public TaskStack getTaskStack() {
226        return mStack;
227    }
228
229    /**
230     * Composes and returns a SpaceNode from the preloaded list of recent tasks.
231     */
232    public SpaceNode getSpaceNode() {
233        SpaceNode node = new SpaceNode();
234        node.setStack(mStack);
235        return node;
236    }
237}
238