RecentsTaskLoader.java revision f1fbd77cf057e43926f9a0347692611386d09f40
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.util.Pair;
31import com.android.systemui.recents.Console;
32import com.android.systemui.recents.Constants;
33import com.android.systemui.recents.SystemServicesProxy;
34
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.List;
38import java.util.concurrent.ConcurrentHashMap;
39import java.util.concurrent.ConcurrentLinkedQueue;
40
41
42/** A bitmap load queue */
43class TaskResourceLoadQueue {
44    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
45    ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
46            new ConcurrentHashMap<Task.TaskKey, Boolean>();
47
48    static final Boolean sFalse = new Boolean(false);
49
50    /** Adds a new task to the load queue */
51    void addTask(Task t, boolean forceLoad) {
52        if (Console.Enabled) {
53            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
54        }
55        if (!mQueue.contains(t)) {
56            mQueue.add(t);
57        }
58        if (forceLoad) {
59            mForceLoadSet.put(t.key, new Boolean(true));
60        }
61        synchronized(this) {
62            notifyAll();
63        }
64    }
65
66    /**
67     * Retrieves the next task from the load queue, as well as whether we want that task to be
68     * force reloaded.
69     */
70    Pair<Task, Boolean> nextTask() {
71        if (Console.Enabled) {
72            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
73        }
74        Task task = mQueue.poll();
75        Boolean forceLoadTask = null;
76        if (task != null) {
77            forceLoadTask = mForceLoadSet.remove(task.key);
78        }
79        if (forceLoadTask == null) {
80            forceLoadTask = sFalse;
81        }
82        return new Pair<Task, Boolean>(task, forceLoadTask);
83    }
84
85    /** Removes a task from the load queue */
86    void removeTask(Task t) {
87        if (Console.Enabled) {
88            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
89        }
90        mQueue.remove(t);
91        mForceLoadSet.remove(t.key);
92    }
93
94    /** Clears all the tasks from the load queue */
95    void clearTasks() {
96        if (Console.Enabled) {
97            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
98        }
99        mQueue.clear();
100        mForceLoadSet.clear();
101    }
102
103    /** Returns whether the load queue is empty */
104    boolean isEmpty() {
105        return mQueue.isEmpty();
106    }
107}
108
109/* Task resource loader */
110class TaskResourceLoader implements Runnable {
111    Context mContext;
112    HandlerThread mLoadThread;
113    Handler mLoadThreadHandler;
114    Handler mMainThreadHandler;
115
116    SystemServicesProxy mSystemServicesProxy;
117    TaskResourceLoadQueue mLoadQueue;
118    DrawableLruCache mApplicationIconCache;
119    BitmapLruCache mThumbnailCache;
120    Bitmap mDefaultThumbnail;
121
122    boolean mCancelled;
123    boolean mWaitingOnLoadQueue;
124
125    /** Constructor, creates a new loading thread that loads task resources in the background */
126    public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
127                              DrawableLruCache applicationIconCache,
128                              BitmapLruCache thumbnailCache,
129                              Bitmap defaultThumbnail) {
130        mLoadQueue = loadQueue;
131        mApplicationIconCache = applicationIconCache;
132        mThumbnailCache = thumbnailCache;
133        mDefaultThumbnail = defaultThumbnail;
134        mMainThreadHandler = new Handler();
135        mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
136        mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
137        mLoadThread.start();
138        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
139        mLoadThreadHandler.post(this);
140    }
141
142    /** Restarts the loader thread */
143    void start(Context context) {
144        if (Console.Enabled) {
145            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
146        }
147        mContext = context;
148        mCancelled = false;
149        mSystemServicesProxy = new SystemServicesProxy(context);
150        // Notify the load thread to start loading
151        synchronized(mLoadThread) {
152            mLoadThread.notifyAll();
153        }
154    }
155
156    /** Requests the loader thread to stop after the current iteration */
157    void stop() {
158        if (Console.Enabled) {
159            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
160        }
161        // Mark as cancelled for the thread to pick up
162        mCancelled = true;
163        mSystemServicesProxy = null;
164        // If we are waiting for the load queue for more tasks, then we can just reset the
165        // Context now, since nothing is using it
166        if (mWaitingOnLoadQueue) {
167            mContext = null;
168        }
169    }
170
171    @Override
172    public void run() {
173        while (true) {
174            if (Console.Enabled) {
175                Console.log(Constants.Log.App.TaskDataLoader,
176                        "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
177            }
178            if (mCancelled) {
179                if (Console.Enabled) {
180                    Console.log(Constants.Log.App.TaskDataLoader,
181                            "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
182                }
183                // We have to unset the context here, since the background thread may be using it
184                // when we call stop()
185                mContext = null;
186                // If we are cancelled, then wait until we are started again
187                synchronized(mLoadThread) {
188                    try {
189                        if (Console.Enabled) {
190                            Console.log(Constants.Log.App.TaskDataLoader,
191                                    "[TaskResourceLoader|waitOnLoadThreadCancelled]");
192                        }
193                        mLoadThread.wait();
194                    } catch (InterruptedException ie) {
195                        ie.printStackTrace();
196                    }
197                }
198            } else {
199                SystemServicesProxy ssp = mSystemServicesProxy;
200
201                // Load the next item from the queue
202                Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
203                final Task t = nextTaskData.first;
204                final boolean forceLoadTask = nextTaskData.second;
205                if (t != null) {
206                    Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
207                    Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
208                    if (Console.Enabled) {
209                        Console.log(Constants.Log.App.TaskDataLoader,
210                                "  [TaskResourceLoader|load]",
211                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
212                                        " forceLoad: " + forceLoadTask);
213                    }
214                    // Load the application icon
215                    if (loadIcon == null || forceLoadTask) {
216                        ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
217                                t.userId);
218                        Drawable icon = ssp.getActivityIcon(info, t.userId);
219                        if (!mCancelled) {
220                            if (icon != null) {
221                                if (Console.Enabled) {
222                                    Console.log(Constants.Log.App.TaskDataLoader,
223                                            "    [TaskResourceLoader|loadIcon]", icon);
224                                }
225                                loadIcon = icon;
226                                mApplicationIconCache.put(t.key, icon);
227                            }
228                        }
229                    }
230                    // Load the thumbnail
231                    if (loadThumbnail == null || forceLoadTask) {
232                        Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
233                        if (!mCancelled) {
234                            if (thumbnail != null) {
235                                if (Console.Enabled) {
236                                    Console.log(Constants.Log.App.TaskDataLoader,
237                                            "    [TaskResourceLoader|loadThumbnail]", thumbnail);
238                                }
239                                thumbnail.setHasAlpha(false);
240                                loadThumbnail = thumbnail;
241                                mThumbnailCache.put(t.key, thumbnail);
242                            } else {
243                                loadThumbnail = mDefaultThumbnail;
244                                Console.logError(mContext,
245                                        "Failed to load task top thumbnail for: " +
246                                                t.key.baseIntent.getComponent().getPackageName());
247                            }
248                        }
249                    }
250                    if (!mCancelled) {
251                        // Notify that the task data has changed
252                        final Drawable newIcon = loadIcon;
253                        final Bitmap newThumbnail = loadThumbnail;
254                        mMainThreadHandler.post(new Runnable() {
255                            @Override
256                            public void run() {
257                                t.notifyTaskDataLoaded(newThumbnail, newIcon, forceLoadTask);
258                            }
259                        });
260                    }
261                }
262
263                // If there are no other items in the list, then just wait until something is added
264                if (!mCancelled && mLoadQueue.isEmpty()) {
265                    synchronized(mLoadQueue) {
266                        try {
267                            if (Console.Enabled) {
268                                Console.log(Constants.Log.App.TaskDataLoader,
269                                        "[TaskResourceLoader|waitOnLoadQueue]");
270                            }
271                            mWaitingOnLoadQueue = true;
272                            mLoadQueue.wait();
273                            mWaitingOnLoadQueue = false;
274                        } catch (InterruptedException ie) {
275                            ie.printStackTrace();
276                        }
277                    }
278                }
279            }
280        }
281    }
282}
283
284/* Recents task loader
285 * NOTE: We should not hold any references to a Context from a static instance */
286public class RecentsTaskLoader {
287    static RecentsTaskLoader sInstance;
288
289    SystemServicesProxy mSystemServicesProxy;
290    DrawableLruCache mApplicationIconCache;
291    BitmapLruCache mThumbnailCache;
292    TaskResourceLoadQueue mLoadQueue;
293    TaskResourceLoader mLoader;
294
295    RecentsPackageMonitor mPackageMonitor;
296
297    int mMaxThumbnailCacheSize;
298    int mMaxIconCacheSize;
299
300    BitmapDrawable mDefaultApplicationIcon;
301    Bitmap mDefaultThumbnail;
302    Bitmap mLoadingThumbnail;
303
304    /** Private Constructor */
305    private RecentsTaskLoader(Context context) {
306        // Calculate the cache sizes, we just use a reasonable number here similar to those
307        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
308        // for icons.
309        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
310        mMaxThumbnailCacheSize = maxMemory / 8;
311        mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
312        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
313                mMaxIconCacheSize;
314        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
315                mMaxThumbnailCacheSize;
316
317        if (Console.Enabled) {
318            Console.log(Constants.Log.App.TaskDataLoader,
319                    "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
320                    " iconCache: " + iconCacheSize);
321        }
322
323        // Initialize the proxy, cache and loaders
324        mSystemServicesProxy = new SystemServicesProxy(context);
325        mPackageMonitor = new RecentsPackageMonitor();
326        mLoadQueue = new TaskResourceLoadQueue();
327        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
328        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
329        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
330                mDefaultThumbnail);
331
332        // Create the default assets
333        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
334        icon.eraseColor(0x00000000);
335        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
336        mDefaultThumbnail.eraseColor(0xFFffffff);
337        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
338        mLoadingThumbnail.eraseColor(0x00000000);
339        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
340        if (Console.Enabled) {
341            Console.log(Constants.Log.App.TaskDataLoader,
342                    "[RecentsTaskLoader|defaultBitmaps]",
343                    "icon: " + mDefaultApplicationIcon +
344                    " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
345        }
346    }
347
348    /** Initializes the recents task loader */
349    public static RecentsTaskLoader initialize(Context context) {
350        if (sInstance == null) {
351            sInstance = new RecentsTaskLoader(context);
352        }
353        return sInstance;
354    }
355
356    /** Returns the current recents task loader */
357    public static RecentsTaskLoader getInstance() {
358        return sInstance;
359    }
360
361    /** Returns the system services proxy */
362    public SystemServicesProxy getSystemServicesProxy() {
363        return mSystemServicesProxy;
364    }
365
366    private List<ActivityManager.RecentTaskInfo> getRecentTasks() {
367        long t1 = System.currentTimeMillis();
368
369        SystemServicesProxy ssp = mSystemServicesProxy;
370        List<ActivityManager.RecentTaskInfo> tasks =
371                ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
372        Collections.reverse(tasks);
373        if (Console.Enabled) {
374            Console.log(Constants.Log.App.TimeSystemCalls,
375                    "[RecentsTaskLoader|getRecentTasks]",
376                    "" + (System.currentTimeMillis() - t1) + "ms");
377            Console.log(Constants.Log.App.TaskDataLoader,
378                    "[RecentsTaskLoader|tasks]", "" + tasks.size());
379        }
380
381        return tasks;
382    }
383
384    /** Reload the set of recent tasks */
385    public SpaceNode reload(Context context, int preloadCount) {
386        long t1 = System.currentTimeMillis();
387
388        if (Console.Enabled) {
389            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
390        }
391        Resources res = context.getResources();
392        ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
393        TaskStack stack = new TaskStack(context);
394        SpaceNode root = new SpaceNode(context);
395        root.setStack(stack);
396
397        // Get the recent tasks
398        SystemServicesProxy ssp = mSystemServicesProxy;
399        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks();
400
401        // Add each task to the task stack
402        t1 = System.currentTimeMillis();
403        int taskCount = tasks.size();
404        for (int i = 0; i < taskCount; i++) {
405            ActivityManager.RecentTaskInfo t = tasks.get(i);
406            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
407            if (info == null) continue;
408
409            ActivityManager.TaskDescription av = t.taskDescription;
410            String activityLabel = null;
411            BitmapDrawable activityIcon = null;
412            int activityColor = 0;
413            if (av != null) {
414                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
415                activityIcon = (av.getIcon() != null) ? new BitmapDrawable(res, av.getIcon()) : null;
416                activityColor = av.getPrimaryColor();
417            } else {
418                activityLabel = ssp.getActivityLabel(info);
419            }
420            boolean isForemostTask = (i == (taskCount - 1));
421
422            // Create a new task
423            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
424                    activityIcon, activityColor, t.userId, t.lastActiveTime);
425
426            // Preload the specified number of apps
427            if (i >= (taskCount - preloadCount)) {
428                if (Console.Enabled) {
429                    Console.log(Constants.Log.App.TaskDataLoader,
430                            "[RecentsTaskLoader|preloadTask]",
431                            "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
432                }
433
434                // Load the icon from the cache if possible
435                task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
436                if (task.applicationIcon == null) {
437                    if (isForemostTask) {
438                        // We force loading the application icon for the foremost task
439                        task.applicationIcon = ssp.getActivityIcon(info, task.userId);
440                        if (task.applicationIcon != null) {
441                            mApplicationIconCache.put(task.key, task.applicationIcon);
442                        } else {
443                            task.applicationIcon = mDefaultApplicationIcon;
444                        }
445                    } else {
446                        // Either the task has updated, or we haven't cached any information for the
447                        // task, so reload it
448                        tasksToForceLoad.add(task);
449                    }
450                }
451
452                // Load the thumbnail (if possible and not the foremost task, from the cache)
453                task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
454                if (task.thumbnail == null) {
455                    if (Console.Enabled) {
456                        Console.log(Constants.Log.App.TaskDataLoader,
457                                "[RecentsTaskLoader|loadingTaskThumbnail]");
458                    }
459                    if (isForemostTask) {
460                        // We force loading the thumbnail icon for the foremost task
461                        task.thumbnail = ssp.getTaskThumbnail(task.key.id);
462                        if (task.thumbnail != null) {
463                            task.thumbnail.setHasAlpha(false);
464                            mThumbnailCache.put(task.key, task.thumbnail);
465                        } else {
466                            task.thumbnail = mDefaultThumbnail;
467                        }
468                    } else {
469                        // Either the task has updated, or we haven't cached any information for the
470                        // task, so reload it
471                        tasksToForceLoad.add(task);
472                    }
473                }
474            }
475
476            // Add the task to the stack
477            if (Console.Enabled) {
478                Console.log(Constants.Log.App.TaskDataLoader,
479                        "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
480            }
481            stack.addTask(task);
482        }
483        if (Console.Enabled) {
484            Console.log(Constants.Log.App.TimeSystemCalls,
485                    "[RecentsTaskLoader|getAllTaskTopThumbnail]",
486                    "" + (System.currentTimeMillis() - t1) + "ms");
487        }
488
489        // Start the task loader
490        mLoader.start(context);
491
492        // Add all the tasks that we are force/re-loading
493        for (Task t : tasksToForceLoad) {
494            mLoadQueue.addTask(t, true);
495        }
496
497        // Update the package monitor with the list of packages to listen for
498        mPackageMonitor.setTasks(tasks);
499
500        return root;
501    }
502
503    /** Acquires the task resource data from the pool. */
504    public void loadTaskData(Task t) {
505        Drawable applicationIcon = mApplicationIconCache.get(t.key);
506        Bitmap thumbnail = mThumbnailCache.get(t.key);
507
508        if (Console.Enabled) {
509            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
510                    t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
511                            " thumbnailCacheSize: " + mThumbnailCache.size());
512        }
513
514        boolean requiresLoad = false;
515        if (applicationIcon == null) {
516            applicationIcon = mDefaultApplicationIcon;
517            requiresLoad = true;
518        }
519        if (thumbnail == null) {
520            thumbnail = mLoadingThumbnail;
521            requiresLoad = true;
522        }
523        if (requiresLoad) {
524            mLoadQueue.addTask(t, false);
525        }
526        t.notifyTaskDataLoaded(thumbnail, applicationIcon, false);
527    }
528
529    /** Releases the task resource data back into the pool. */
530    public void unloadTaskData(Task t) {
531        if (Console.Enabled) {
532            Console.log(Constants.Log.App.TaskDataLoader,
533                    "[RecentsTaskLoader|unloadTask]", t +
534                    " thumbnailCacheSize: " + mThumbnailCache.size());
535        }
536
537        mLoadQueue.removeTask(t);
538        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
539    }
540
541    /** Completely removes the resource data from the pool. */
542    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
543        if (Console.Enabled) {
544            Console.log(Constants.Log.App.TaskDataLoader,
545                    "[RecentsTaskLoader|deleteTask]", t);
546        }
547
548        mLoadQueue.removeTask(t);
549        mThumbnailCache.remove(t.key);
550        mApplicationIconCache.remove(t.key);
551        if (notifyTaskDataUnloaded) {
552            t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
553        }
554    }
555
556    /** Stops the task loader and clears all pending tasks */
557    void stopLoader() {
558        if (Console.Enabled) {
559            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
560        }
561        mLoader.stop();
562        mLoadQueue.clearTasks();
563    }
564
565    /** Registers any broadcast receivers. */
566    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
567        // Register the broadcast receiver to handle messages related to packages being added/removed
568        mPackageMonitor.register(context, cb);
569    }
570
571    /** Unregisters any broadcast receivers. */
572    public void unregisterReceivers() {
573        mPackageMonitor.unregister();
574    }
575
576    /**
577     * Handles signals from the system, trimming memory when requested to prevent us from running
578     * out of memory.
579     */
580    public void onTrimMemory(int level) {
581        if (Console.Enabled) {
582            Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
583                    Console.trimMemoryLevelToString(level));
584        }
585
586        switch (level) {
587            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
588                // Stop the loader immediately when the UI is no longer visible
589                stopLoader();
590                break;
591            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
592            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
593                // We are leaving recents, so trim the data a bit
594                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
595                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
596                break;
597            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
598            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
599                // We are going to be low on memory
600                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
601                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
602                break;
603            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
604            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
605                // We are low on memory, so release everything
606                mThumbnailCache.evictAll();
607                mApplicationIconCache.evictAll();
608                break;
609            default:
610                break;
611        }
612    }
613}
614