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.pm.ActivityInfo;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.UserInfo;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.drawable.Drawable;
27import android.os.UserHandle;
28import android.os.UserManager;
29import android.util.ArraySet;
30import android.util.SparseArray;
31import android.util.SparseIntArray;
32
33import com.android.systemui.Prefs;
34import com.android.systemui.R;
35import com.android.systemui.recents.Recents;
36import com.android.systemui.recents.RecentsConfiguration;
37import com.android.systemui.recents.RecentsDebugFlags;
38import com.android.systemui.recents.misc.SystemServicesProxy;
39
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.List;
43
44
45/**
46 * This class stores the loading state as it goes through multiple stages of loading:
47 *   1) preloadRawTasks() will load the raw set of recents tasks from the system
48 *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
49 *      thumbnails that are currently in the cache
50 *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
51 *      options specified, such that we can transition into the Recents activity seamlessly
52 */
53public class RecentsTaskLoadPlan {
54
55    private static int MIN_NUM_TASKS = 5;
56    private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
57            6 /* hrs */;
58
59    /** The set of conditions to load tasks. */
60    public static class Options {
61        public int runningTaskId = -1;
62        public boolean loadIcons = true;
63        public boolean loadThumbnails = true;
64        public boolean onlyLoadForCache = false;
65        public boolean onlyLoadPausedActivities = false;
66        public int numVisibleTasks = 0;
67        public int numVisibleTaskThumbnails = 0;
68    }
69
70    Context mContext;
71
72    List<ActivityManager.RecentTaskInfo> mRawTasks;
73    TaskStack mStack;
74    ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
75
76    /** Package level ctor */
77    RecentsTaskLoadPlan(Context context) {
78        mContext = context;
79    }
80
81    private void updateCurrentQuietProfilesCache(int currentUserId) {
82        mCurrentQuietProfiles.clear();
83
84        if (currentUserId == UserHandle.USER_CURRENT) {
85            currentUserId = ActivityManager.getCurrentUser();
86        }
87        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
88        List<UserInfo> profiles = userManager.getProfiles(currentUserId);
89        if (profiles != null) {
90            for (int i = 0; i < profiles.size(); i++) {
91                UserInfo user  = profiles.get(i);
92                if (user.isManagedProfile() && user.isQuietModeEnabled()) {
93                    mCurrentQuietProfiles.add(user.id);
94                }
95            }
96        }
97    }
98
99    /**
100     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
101     * to most-recent order.
102     */
103    public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
104        int currentUserId = UserHandle.USER_CURRENT;
105        updateCurrentQuietProfilesCache(currentUserId);
106        SystemServicesProxy ssp = Recents.getSystemServices();
107        mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
108                currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
109
110        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
111        Collections.reverse(mRawTasks);
112    }
113
114    /**
115     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
116     * have a list of all the recent tasks with their metadata, not including icons or
117     * thumbnails which were not cached and have to be loaded.
118     *
119     * The tasks will be ordered by:
120     * - least-recent to most-recent stack tasks
121     * - least-recent to most-recent freeform tasks
122     */
123    public synchronized void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
124            boolean includeFrontMostExcludedTask) {
125        Resources res = mContext.getResources();
126        ArrayList<Task> allTasks = new ArrayList<>();
127        if (mRawTasks == null) {
128            preloadRawTasks(includeFrontMostExcludedTask);
129        }
130
131        SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
132        SparseIntArray affiliatedTaskCounts = new SparseIntArray();
133        String dismissDescFormat = mContext.getString(
134                R.string.accessibility_recents_item_will_be_dismissed);
135        String appInfoDescFormat = mContext.getString(
136                R.string.accessibility_recents_item_open_app_info);
137        long lastStackActiveTime = Prefs.getLong(mContext,
138                Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
139        if (RecentsDebugFlags.Static.EnableMockTasks) {
140            lastStackActiveTime = 0;
141        }
142        long newLastStackActiveTime = -1;
143        int taskCount = mRawTasks.size();
144        for (int i = 0; i < taskCount; i++) {
145            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
146
147            // Compose the task key
148            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
149                    t.userId, t.firstActiveTime, t.lastActiveTime);
150
151            // This task is only shown in the stack if it statisfies the historical time or min
152            // number of tasks constraints. Freeform tasks are also always shown.
153            boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
154            boolean isStackTask = isFreeformTask || !isHistoricalTask(t) ||
155                    (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
156            boolean isLaunchTarget = taskKey.id == runningTaskId;
157
158            // The last stack active time is the baseline for which we show visible tasks.  Since
159            // the system will store all the tasks, we don't want to show the tasks prior to the
160            // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
161            // the other stack-task constraints.
162            if (isStackTask && newLastStackActiveTime < 0) {
163                newLastStackActiveTime = t.lastActiveTime;
164            }
165
166            // Load the title, icon, and color
167            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
168            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
169            String titleDescription = loader.getAndUpdateContentDescription(taskKey, res);
170            String dismissDescription = String.format(dismissDescFormat, titleDescription);
171            String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
172            Drawable icon = isStackTask
173                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
174                    : null;
175            Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
176            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
177            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
178            boolean isSystemApp = (info != null) &&
179                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
180
181            // Add the task to the stack
182            Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
183                    thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
184                    activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
185                    t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity);
186
187            allTasks.add(task);
188            affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
189            affiliatedTasks.put(taskKey.id, taskKey);
190        }
191        if (newLastStackActiveTime != -1) {
192            Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
193                    newLastStackActiveTime);
194        }
195
196        // Initialize the stacks
197        mStack = new TaskStack();
198        mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
199    }
200
201    /**
202     * Called to apply the actual loading based on the specified conditions.
203     */
204    public synchronized void executePlan(Options opts, RecentsTaskLoader loader,
205            TaskResourceLoadQueue loadQueue) {
206        RecentsConfiguration config = Recents.getConfiguration();
207        Resources res = mContext.getResources();
208
209        // Iterate through each of the tasks and load them according to the load conditions.
210        ArrayList<Task> tasks = mStack.getStackTasks();
211        int taskCount = tasks.size();
212        for (int i = 0; i < taskCount; i++) {
213            Task task = tasks.get(i);
214            Task.TaskKey taskKey = task.key;
215
216            boolean isRunningTask = (task.key.id == opts.runningTaskId);
217            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
218            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
219
220            // If requested, skip the running task
221            if (opts.onlyLoadPausedActivities && isRunningTask) {
222                continue;
223            }
224
225            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
226                if (task.icon == null) {
227                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
228                            true);
229                }
230            }
231            if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
232                if (task.thumbnail == null || isRunningTask) {
233                    if (config.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
234                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
235                                true /* loadIfNotCached */);
236                    } else if (config.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
237                        loadQueue.addTask(task);
238                    }
239                }
240            }
241        }
242    }
243
244    /**
245     * Returns the TaskStack from the preloaded list of recent tasks.
246     */
247    public TaskStack getTaskStack() {
248        return mStack;
249    }
250
251    /** Returns whether there are any tasks in any stacks. */
252    public boolean hasTasks() {
253        if (mStack != null) {
254            return mStack.getTaskCount() > 0;
255        }
256        return false;
257    }
258
259    /**
260     * Returns whether this task is too old to be shown.
261     */
262    private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
263        return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
264    }
265}
266