RecentsTaskLoader.java revision 56e09b42a0f1670970872bef611a8036904ad6bf
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.ComponentCallbacks2;
21import android.content.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.Resources;
24import android.graphics.Bitmap;
25import android.graphics.drawable.BitmapDrawable;
26import android.graphics.drawable.Drawable;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.provider.Settings.SettingNotFoundException;
32
33import com.android.systemui.recents.Constants;
34import com.android.systemui.recents.RecentsConfiguration;
35import com.android.systemui.recents.misc.SystemServicesProxy;
36
37import java.util.Collection;
38import java.util.Collections;
39import java.util.LinkedHashSet;
40import java.util.List;
41import java.util.concurrent.ConcurrentLinkedQueue;
42
43
44/** A bitmap load queue */
45class TaskResourceLoadQueue {
46    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
47
48    /** Adds a new task to the load queue */
49    void addTasks(Collection<Task> tasks) {
50        for (Task t : tasks) {
51            if (!mQueue.contains(t)) {
52                mQueue.add(t);
53            }
54        }
55        synchronized(this) {
56            notifyAll();
57        }
58    }
59
60    /** Adds a new task to the load queue */
61    void addTask(Task t) {
62        if (!mQueue.contains(t)) {
63            mQueue.add(t);
64        }
65        synchronized(this) {
66            notifyAll();
67        }
68    }
69
70    /**
71     * Retrieves the next task from the load queue, as well as whether we want that task to be
72     * force reloaded.
73     */
74    Task nextTask() {
75        return mQueue.poll();
76    }
77
78    /** Removes a task from the load queue */
79    void removeTask(Task t) {
80        mQueue.remove(t);
81    }
82
83    /** Clears all the tasks from the load queue */
84    void clearTasks() {
85        mQueue.clear();
86    }
87
88    /** Returns whether the load queue is empty */
89    boolean isEmpty() {
90        return mQueue.isEmpty();
91    }
92}
93
94/* Task resource loader */
95class TaskResourceLoader implements Runnable {
96    Context mContext;
97    HandlerThread mLoadThread;
98    Handler mLoadThreadHandler;
99    Handler mMainThreadHandler;
100
101    SystemServicesProxy mSystemServicesProxy;
102    TaskResourceLoadQueue mLoadQueue;
103    DrawableLruCache mApplicationIconCache;
104    BitmapLruCache mThumbnailCache;
105    Bitmap mDefaultThumbnail;
106    BitmapDrawable mDefaultApplicationIcon;
107
108    boolean mCancelled;
109    boolean mWaitingOnLoadQueue;
110
111    /** Constructor, creates a new loading thread that loads task resources in the background */
112    public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
113                              BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
114                              BitmapDrawable defaultApplicationIcon) {
115        mLoadQueue = loadQueue;
116        mApplicationIconCache = applicationIconCache;
117        mThumbnailCache = thumbnailCache;
118        mDefaultThumbnail = defaultThumbnail;
119        mDefaultApplicationIcon = defaultApplicationIcon;
120        mMainThreadHandler = new Handler();
121        mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
122        mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
123        mLoadThread.start();
124        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
125        mLoadThreadHandler.post(this);
126    }
127
128    /** Restarts the loader thread */
129    void start(Context context) {
130        mContext = context;
131        mCancelled = false;
132        mSystemServicesProxy = new SystemServicesProxy(context);
133        // Notify the load thread to start loading
134        synchronized(mLoadThread) {
135            mLoadThread.notifyAll();
136        }
137    }
138
139    /** Requests the loader thread to stop after the current iteration */
140    void stop() {
141        // Mark as cancelled for the thread to pick up
142        mCancelled = true;
143        mSystemServicesProxy = null;
144        // If we are waiting for the load queue for more tasks, then we can just reset the
145        // Context now, since nothing is using it
146        if (mWaitingOnLoadQueue) {
147            mContext = null;
148        }
149    }
150
151    @Override
152    public void run() {
153        while (true) {
154            if (mCancelled) {
155                // We have to unset the context here, since the background thread may be using it
156                // when we call stop()
157                mContext = null;
158                // If we are cancelled, then wait until we are started again
159                synchronized(mLoadThread) {
160                    try {
161                        mLoadThread.wait();
162                    } catch (InterruptedException ie) {
163                        ie.printStackTrace();
164                    }
165                }
166            } else {
167                SystemServicesProxy ssp = mSystemServicesProxy;
168
169                // Load the next item from the queue
170                final Task t = mLoadQueue.nextTask();
171                if (t != null) {
172                    Drawable cachedIcon = mApplicationIconCache.get(t.key);
173                    Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
174                    // Load the application icon if it is stale or we haven't cached one yet
175                    if (cachedIcon == null) {
176                        ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
177                                t.userId);
178                        if (info != null) {
179                            cachedIcon = ssp.getActivityIcon(info, t.userId);
180                        }
181                        if (cachedIcon == null) {
182                            cachedIcon = mDefaultApplicationIcon;
183                        }
184                        // At this point, even if we can't load the icon, we will set the default
185                        // icon.
186                        mApplicationIconCache.put(t.key, cachedIcon);
187                    }
188                    // Load the thumbnail if it is stale or we haven't cached one yet
189                    if (cachedThumbnail == null) {
190                        cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
191                        if (cachedThumbnail != null) {
192                            cachedThumbnail.setHasAlpha(false);
193                        } else {
194                            cachedThumbnail = mDefaultThumbnail;
195                        }
196                        mThumbnailCache.put(t.key, cachedThumbnail);
197                    }
198                    if (!mCancelled) {
199                        // Notify that the task data has changed
200                        final Drawable newIcon = cachedIcon;
201                        final Bitmap newThumbnail = cachedThumbnail;
202                        mMainThreadHandler.post(new Runnable() {
203                            @Override
204                            public void run() {
205                                t.notifyTaskDataLoaded(newThumbnail, newIcon);
206                            }
207                        });
208                    }
209                }
210
211                // If there are no other items in the list, then just wait until something is added
212                if (!mCancelled && mLoadQueue.isEmpty()) {
213                    synchronized(mLoadQueue) {
214                        try {
215                            mWaitingOnLoadQueue = true;
216                            mLoadQueue.wait();
217                            mWaitingOnLoadQueue = false;
218                        } catch (InterruptedException ie) {
219                            ie.printStackTrace();
220                        }
221                    }
222                }
223            }
224        }
225    }
226}
227
228/* Recents task loader
229 * NOTE: We should not hold any references to a Context from a static instance */
230public class RecentsTaskLoader {
231    static RecentsTaskLoader sInstance;
232
233    SystemServicesProxy mSystemServicesProxy;
234    DrawableLruCache mApplicationIconCache;
235    BitmapLruCache mThumbnailCache;
236    TaskResourceLoadQueue mLoadQueue;
237    TaskResourceLoader mLoader;
238
239    RecentsPackageMonitor mPackageMonitor;
240
241    int mMaxThumbnailCacheSize;
242    int mMaxIconCacheSize;
243
244    BitmapDrawable mDefaultApplicationIcon;
245    Bitmap mDefaultThumbnail;
246    Bitmap mLoadingThumbnail;
247
248    /** Private Constructor */
249    private RecentsTaskLoader(Context context) {
250        // Calculate the cache sizes, we just use a reasonable number here similar to those
251        // suggested in the Android docs, 1/6th for the thumbnail cache and 1/30 of the max memory
252        // for icons.
253        int maxMemory = (int) Runtime.getRuntime().maxMemory();
254        mMaxThumbnailCacheSize = maxMemory / 6;
255        mMaxIconCacheSize = mMaxThumbnailCacheSize / 5;
256        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
257                mMaxIconCacheSize;
258        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
259                mMaxThumbnailCacheSize;
260
261        // Create the default assets
262        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
263        icon.eraseColor(0x00000000);
264        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
265        mDefaultThumbnail.setHasAlpha(false);
266        mDefaultThumbnail.eraseColor(0xFFffffff);
267        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
268        mLoadingThumbnail.setHasAlpha(false);
269        mLoadingThumbnail.eraseColor(0xFFffffff);
270        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
271
272        // Initialize the proxy, cache and loaders
273        mSystemServicesProxy = new SystemServicesProxy(context);
274        mPackageMonitor = new RecentsPackageMonitor();
275        mLoadQueue = new TaskResourceLoadQueue();
276        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
277        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
278        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
279                mDefaultThumbnail, mDefaultApplicationIcon);
280    }
281
282    /** Initializes the recents task loader */
283    public static RecentsTaskLoader initialize(Context context) {
284        if (sInstance == null) {
285            sInstance = new RecentsTaskLoader(context);
286        }
287        return sInstance;
288    }
289
290    /** Returns the current recents task loader */
291    public static RecentsTaskLoader getInstance() {
292        return sInstance;
293    }
294
295    /** Returns the system services proxy */
296    public SystemServicesProxy getSystemServicesProxy() {
297        return mSystemServicesProxy;
298    }
299
300    /** Gets the list of recent tasks, ordered from back to front. */
301    private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
302        List<ActivityManager.RecentTaskInfo> tasks =
303                ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
304        Collections.reverse(tasks);
305        return tasks;
306    }
307
308    /** Reload the set of recent tasks */
309    public SpaceNode reload(Context context, int preloadCount) {
310        RecentsConfiguration config = RecentsConfiguration.getInstance();
311        Resources res = context.getResources();
312        LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
313        TaskStack stack = new TaskStack();
314        SpaceNode root = new SpaceNode();
315        root.setStack(stack);
316
317        // Get the recent tasks
318        SystemServicesProxy ssp = mSystemServicesProxy;
319        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
320
321        // From back to front, add each task to the task stack
322        int taskCount = tasks.size();
323        for (int i = 0; i < taskCount; i++) {
324            ActivityManager.RecentTaskInfo t = tasks.get(i);
325            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
326            if (info == null) continue;
327
328            ActivityManager.TaskDescription av = t.taskDescription;
329            String activityLabel = null;
330            Drawable activityIcon = mDefaultApplicationIcon;
331            int activityColor = config.taskBarViewDefaultBackgroundColor;
332            if (av != null) {
333                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
334                activityIcon = (av.getIcon() != null) ?
335                        ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
336                if (av.getPrimaryColor() != 0) {
337                    activityColor = av.getPrimaryColor();
338                }
339            } else {
340                activityLabel = ssp.getActivityLabel(info);
341            }
342
343            // Create a new task
344            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId,
345                    activityLabel, activityIcon, activityColor, t.userId, t.firstActiveTime,
346                    t.lastActiveTime, (i == (taskCount - 1)), config.lockToAppEnabled);
347
348            // Preload the specified number of apps
349            if (i >= (taskCount - preloadCount)) {
350                // Load the icon from the cache if possible
351                task.applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(task.key);
352                if (task.applicationIcon == null) {
353                    // Load the icon from the system
354                    task.applicationIcon = ssp.getActivityIcon(info, task.userId);
355                    if (task.applicationIcon != null) {
356                        mApplicationIconCache.put(task.key, task.applicationIcon);
357                    }
358                }
359                if (task.applicationIcon == null) {
360                    // Either the task has changed since the last active time, or it was not
361                    // previously cached, so try and load the task anew.
362                    tasksToLoad.add(task);
363                }
364
365                // Load the thumbnail from the cache if possible
366                task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(task.key);
367                if (task.thumbnail == null) {
368                    // Load the thumbnail from the system
369                    task.thumbnail = ssp.getTaskThumbnail(task.key.id);
370                    if (task.thumbnail != null) {
371                        task.thumbnail.setHasAlpha(false);
372                        mThumbnailCache.put(task.key, task.thumbnail);
373                    }
374                }
375                if (task.thumbnail == null) {
376                    // Either the task has changed since the last active time, or it was not
377                    // previously cached, so try and load the task anew.
378                    tasksToLoad.add(task);
379                }
380            }
381
382            // Add the task to the stack
383            stack.addTask(task);
384        }
385
386        // Simulate the groupings that we describe
387        stack.createAffiliatedGroupings();
388
389        // Start the task loader and add all the tasks we need to load
390        mLoader.start(context);
391        mLoadQueue.addTasks(tasksToLoad);
392
393        // Update the package monitor with the list of packages to listen for
394        mPackageMonitor.setTasks(tasks);
395
396        return root;
397    }
398
399    /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
400    public static TaskStack getShallowTaskStack(SystemServicesProxy ssp) {
401        RecentsConfiguration config = RecentsConfiguration.getInstance();
402        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
403        TaskStack stack = new TaskStack();
404
405        int taskCount = tasks.size();
406        for (int i = 0; i < taskCount; i++) {
407            ActivityManager.RecentTaskInfo t = tasks.get(i);
408            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
409            if (info == null) continue;
410
411            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
412                    null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)),
413                    config.lockToAppEnabled));
414        }
415        stack.createAffiliatedGroupings();
416        return stack;
417    }
418
419    /** Acquires the task resource data directly from the pool. */
420    public void loadTaskData(Task t) {
421        Drawable applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(t.key);
422        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key);
423
424        // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
425        // use the default assets in their place until they load
426        boolean requiresLoad = (applicationIcon == null) || (thumbnail == null);
427        applicationIcon = applicationIcon != null ? applicationIcon : mDefaultApplicationIcon;
428        thumbnail = thumbnail != null ? thumbnail : mDefaultThumbnail;
429        if (requiresLoad) {
430            mLoadQueue.addTask(t);
431        }
432        t.notifyTaskDataLoaded(thumbnail, applicationIcon);
433    }
434
435    /** Releases the task resource data back into the pool. */
436    public void unloadTaskData(Task t) {
437        mLoadQueue.removeTask(t);
438        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
439    }
440
441    /** Completely removes the resource data from the pool. */
442    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
443        mLoadQueue.removeTask(t);
444        mThumbnailCache.remove(t.key);
445        mApplicationIconCache.remove(t.key);
446        if (notifyTaskDataUnloaded) {
447            t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
448        }
449    }
450
451    /** Stops the task loader and clears all pending tasks */
452    void stopLoader() {
453        mLoader.stop();
454        mLoadQueue.clearTasks();
455    }
456
457    /** Registers any broadcast receivers. */
458    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
459        // Register the broadcast receiver to handle messages related to packages being added/removed
460        mPackageMonitor.register(context, cb);
461    }
462
463    /** Unregisters any broadcast receivers. */
464    public void unregisterReceivers() {
465        mPackageMonitor.unregister();
466    }
467
468    /**
469     * Handles signals from the system, trimming memory when requested to prevent us from running
470     * out of memory.
471     */
472    public void onTrimMemory(int level) {
473        switch (level) {
474            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
475                // Stop the loader immediately when the UI is no longer visible
476                stopLoader();
477                break;
478            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
479            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
480                // We are leaving recents, so trim the data a bit
481                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
482                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
483                break;
484            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
485            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
486                // We are going to be low on memory
487                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
488                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
489                break;
490            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
491            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
492                // We are low on memory, so release everything
493                mThumbnailCache.evictAll();
494                mApplicationIconCache.evictAll();
495                break;
496            default:
497                break;
498        }
499    }
500}
501