diff options
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java new file mode 100644 index 0000000..c303ca7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.UserHandle; +import android.util.LruCache; +import com.android.systemui.recents.model.SpaceNode; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + + +/** A bitmap load queue */ +class TaskResourceLoadQueue { + ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); + + Task nextTask() { + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]"); + return mQueue.poll(); + } + + void addTask(Task t) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]"); + if (!mQueue.contains(t)) { + mQueue.add(t); + } + synchronized(this) { + notifyAll(); + } + } + + void removeTask(Task t) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]"); + mQueue.remove(t); + } + + void clearTasks() { + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]"); + mQueue.clear(); + } + + boolean isEmpty() { + return mQueue.isEmpty(); + } +} + +/* Task resource loader */ +class TaskResourceLoader implements Runnable { + Context mContext; + HandlerThread mLoadThread; + Handler mLoadThreadHandler; + Handler mMainThreadHandler; + + TaskResourceLoadQueue mLoadQueue; + DrawableLruCache mIconCache; + BitmapLruCache mThumbnailCache; + boolean mCancelled; + + /** Constructor, creates a new loading thread that loads task resources in the background */ + public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache iconCache, + BitmapLruCache thumbnailCache) { + mLoadQueue = loadQueue; + mIconCache = iconCache; + mThumbnailCache = thumbnailCache; + mMainThreadHandler = new Handler(); + mLoadThread = new HandlerThread("Recents-TaskResourceLoader"); + mLoadThread.setPriority(Thread.NORM_PRIORITY - 1); + mLoadThread.start(); + mLoadThreadHandler = new Handler(mLoadThread.getLooper()); + mLoadThreadHandler.post(this); + } + + /** Restarts the loader thread */ + void start(Context context) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|start]"); + mContext = context; + mCancelled = false; + // Notify the load thread to start loading + synchronized(mLoadThread) { + mLoadThread.notifyAll(); + } + } + + /** Requests the loader thread to stop after the current iteration */ + void stop() { + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|stop]"); + // Mark as cancelled for the thread to pick up + mCancelled = true; + } + + @Override + public void run() { + while (true) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]"); + if (mCancelled) { + // We have to unset the context here, since the background thread may be using it + // when we call stop() + mContext = null; + // If we are cancelled, then wait until we are started again + synchronized(mLoadThread) { + try { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[TaskResourceLoader|waitOnLoadThreadCancelled]"); + mLoadThread.wait(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } else { + // Load the next item from the queue + final Task t = mLoadQueue.nextTask(); + if (t != null) { + try { + Drawable cachedIcon = mIconCache.get(t); + Bitmap cachedThumbnail = mThumbnailCache.get(t); + Console.log(Constants.DebugFlags.App.TaskDataLoader, + " [TaskResourceLoader|load]", + t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail); + // Load the icon + if (cachedIcon == null) { + PackageManager pm = mContext.getPackageManager(); + ActivityInfo info = pm.getActivityInfo(t.intent.getComponent(), + PackageManager.GET_META_DATA); + Drawable icon = info.loadIcon(pm); + if (!mCancelled) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + " [TaskResourceLoader|loadIcon]", + icon); + t.icon = icon; + mIconCache.put(t, icon); + } + } + // Load the thumbnail + if (cachedThumbnail == null) { + ActivityManager am = (ActivityManager) + mContext.getSystemService(Context.ACTIVITY_SERVICE); + Bitmap thumbnail = am.getTaskTopThumbnail(t.id); + if (!mCancelled) { + if (thumbnail != null) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + " [TaskResourceLoader|loadThumbnail]", + thumbnail); + t.thumbnail = thumbnail; + mThumbnailCache.put(t, thumbnail); + } else { + Console.logError(mContext, + "Failed to load task top thumbnail for: " + + t.intent.getComponent().getPackageName()); + } + } + } + if (!mCancelled) { + // Notify that the task data has changed + mMainThreadHandler.post(new Runnable() { + @Override + public void run() { + t.notifyTaskDataChanged(); + } + }); + } + } catch (PackageManager.NameNotFoundException ne) { + ne.printStackTrace(); + } + } + + // If there are no other items in the list, then just wait until something is added + if (!mCancelled && mLoadQueue.isEmpty()) { + synchronized(mLoadQueue) { + try { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[TaskResourceLoader|waitOnLoadQueue]"); + mLoadQueue.wait(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + } + } +} + +/** The drawable cache */ +class DrawableLruCache extends LruCache<Task, Drawable> { + public DrawableLruCache(int cacheSize) { + super(cacheSize); + } + + @Override + protected int sizeOf(Task t, Drawable d) { + // The cache size will be measured in kilobytes rather than number of items + // NOTE: this isn't actually correct, as the icon may be smaller + int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4); + return maxBytes / 1024; + } +} + +/** The bitmap cache */ +class BitmapLruCache extends LruCache<Task, Bitmap> { + public BitmapLruCache(int cacheSize) { + super(cacheSize); + } + + @Override + protected int sizeOf(Task t, Bitmap bitmap) { + // The cache size will be measured in kilobytes rather than number of items + return bitmap.getByteCount() / 1024; + } +} + +/* Recents task loader + * NOTE: We should not hold any references to a Context from a static instance */ +public class RecentsTaskLoader { + static RecentsTaskLoader sInstance; + + DrawableLruCache mIconCache; + BitmapLruCache mThumbnailCache; + TaskResourceLoadQueue mLoadQueue; + TaskResourceLoader mLoader; + + BitmapDrawable mDefaultIcon; + Bitmap mDefaultThumbnail; + + /** Private Constructor */ + private RecentsTaskLoader(Context context) { + int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 16; + int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 8; + Console.log(Constants.DebugFlags.App.SystemUIHandshake, + "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize + + " iconCache: " + iconCacheSize); + mLoadQueue = new TaskResourceLoadQueue(); + mIconCache = new DrawableLruCache(iconCacheSize); + mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); + mLoader = new TaskResourceLoader(mLoadQueue, mIconCache, mThumbnailCache); + + // Create the default assets + Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(); + c.setBitmap(icon); + c.drawColor(0x00000000); + c.setBitmap(mDefaultThumbnail); + c.drawColor(0x00000000); + c.setBitmap(null); + mDefaultIcon = new BitmapDrawable(context.getResources(), icon); + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[RecentsTaskLoader|defaultBitmaps]", + "icon: " + mDefaultIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed); + } + + /** Initializes the recents task loader */ + public static RecentsTaskLoader initialize(Context context) { + if (sInstance == null) { + sInstance = new RecentsTaskLoader(context); + } + return sInstance; + } + + /** Returns the current recents task loader */ + public static RecentsTaskLoader getInstance() { + return sInstance; + } + + /** Reload the set of recent tasks */ + SpaceNode reload(Context context, int preloadCount) { + Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|reload]"); + TaskStack stack = new TaskStack(context); + SpaceNode root = new SpaceNode(context); + root.setStack(stack); + try { + long t1 = System.currentTimeMillis(); + + PackageManager pm = context.getPackageManager(); + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + // Get the recent tasks + List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(25, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier()); + Collections.reverse(tasks); + Console.log(Constants.DebugFlags.App.TimeSystemCalls, + "[RecentsTaskLoader|getRecentTasks]", + "" + (System.currentTimeMillis() - t1) + "ms"); + Console.log(Constants.DebugFlags.App.SystemUIHandshake, + "[RecentsTaskLoader|tasks]", "" + tasks.size()); + + // Remove home/recents tasks + Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); + while (iter.hasNext()) { + ActivityManager.RecentTaskInfo t = iter.next(); + + // Skip tasks in the home stack + if (am.isInHomeStack(t.persistentId)) { + iter.remove(); + continue; + } + // Skip tasks from this Recents package + if (t.baseIntent.getComponent().getPackageName().equals(context.getPackageName())) { + iter.remove(); + continue; + } + } + + // Add each task to the task stack + t1 = System.currentTimeMillis(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = tasks.get(i); + + // Load the label, icon and thumbnail + ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(), + PackageManager.GET_META_DATA); + String title = info.loadLabel(pm).toString(); + Drawable icon = null; + Bitmap thumbnail = null; + Task task; + if (i >= (taskCount - preloadCount) || !Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + Console.log(Constants.DebugFlags.App.SystemUIHandshake, + "[RecentsTaskLoader|preloadTask]", + "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName()); + icon = info.loadIcon(pm); + thumbnail = am.getTaskTopThumbnail(t.id); + for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) { + Console.log(Constants.DebugFlags.App.SystemUIHandshake, + " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName()); + task = new Task(t.persistentId, t.baseIntent, title, icon, thumbnail); + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + if (thumbnail != null) mThumbnailCache.put(task, thumbnail); + if (icon != null) { + mIconCache.put(task, icon); + } + } + stack.addTask(task); + } + } else { + for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) { + Console.log(Constants.DebugFlags.App.SystemUIHandshake, + " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName()); + task = new Task(t.persistentId, t.baseIntent, title, null, null); + stack.addTask(task); + } + } + + /* + if (stacks.containsKey(t.stackId)) { + builder = stacks.get(t.stackId); + } else { + builder = new TaskStackBuilder(); + stacks.put(t.stackId, builder); + } + */ + } + Console.log(Constants.DebugFlags.App.TimeSystemCalls, + "[RecentsTaskLoader|getAllTaskTopThumbnail]", + "" + (System.currentTimeMillis() - t1) + "ms"); + + /* + // Get all the stacks + t1 = System.currentTimeMillis(); + List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos(); + Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms"); + Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|stacks]", "" + tasks.size()); + for (ActivityManager.StackInfo s : stackInfos) { + Console.log(Constants.DebugFlags.App.SystemUIHandshake, " [RecentsTaskLoader|stack]", s.toString()); + if (stacks.containsKey(s.stackId)) { + stacks.get(s.stackId).setRect(s.bounds); + } + } + */ + } catch (Exception e) { + e.printStackTrace(); + } + mLoader.start(context); + return root; + } + + /** Acquires the task resource data from the pool. + * XXX: Move this into Task? */ + public void loadTaskData(Task t) { + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + t.icon = mIconCache.get(t); + t.thumbnail = mThumbnailCache.get(t); + + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]", + t + " icon: " + t.icon + " thumbnail: " + t.thumbnail); + + boolean requiresLoad = false; + if (t.icon == null) { + t.icon = mDefaultIcon; + requiresLoad = true; + } + if (t.thumbnail == null) { + t.thumbnail = mDefaultThumbnail; + requiresLoad = true; + } + if (requiresLoad) { + mLoadQueue.addTask(t); + } + } + } + + /** Releases the task resource data back into the pool. + * XXX: Move this into Task? */ + public void unloadTaskData(Task t) { + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[RecentsTaskLoader|unloadTask]", t); + mLoadQueue.removeTask(t); + t.icon = mDefaultIcon; + t.thumbnail = mDefaultThumbnail; + } + } + + /** Completely removes the resource data from the pool. + * XXX: Move this into Task? */ + public void deleteTaskData(Task t) { + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + "[RecentsTaskLoader|deleteTask]", t); + mLoadQueue.removeTask(t); + mThumbnailCache.remove(t); + mIconCache.remove(t); + } + t.icon = mDefaultIcon; + t.thumbnail = mDefaultThumbnail; + } + + /** Stops the task loader */ + void stopLoader() { + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]"); + mLoader.stop(); + mLoadQueue.clearTasks(); + } +} |