Simplifying memory management, use Task Keys as resource cache keys.

- Attempts to load non-topmost task thumbnails from cache
- Ensuring that we release all references to the activity from the bg loader
- Removes background loading debug flag
- Moving callbacks into their respective classes
- cleaning up some callbacks when data is loaded in the bg

Change-Id: Ibb968349d08084922d5b28e432b76a165bf20d6b
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 57ebbc2..34dd726 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -28,10 +28,10 @@
         public static class App {
             public static final boolean EnableTaskFiltering = false;
             public static final boolean EnableTaskStackClipping = false;
-            public static final boolean EnableBackgroundTaskLoading = true;
-            public static final boolean ForceDisableBackgroundCache = false;
+            // This disables the bitmap and icon caches to
+            public static final boolean DisableBackgroundCache = false;
 
-            public static final boolean TaskDataLoader = false;
+            public static final boolean TaskDataLoader = true;
             public static final boolean SystemUIHandshake = false;
             public static final boolean TimeSystemCalls = false;
             public static final boolean Memory = false;
@@ -43,7 +43,7 @@
             public static final boolean TouchEvents = false;
             public static final boolean MeasureAndLayout = false;
             public static final boolean Clipping = false;
-            public static final boolean HwLayers = false;
+            public static final boolean HwLayers = true;
         }
 
         public static class TaskStack {
@@ -74,8 +74,6 @@
             public static class Animation {
                 public static final int TaskRemovedReshuffleDuration = 200;
                 public static final int SnapScrollBackDuration = 650;
-                public static final int SwipeDismissDuration = 350;
-                public static final int SwipeSnapBackDuration = 350;
             }
 
             // The padding will be applied to the smallest dimension, and then applied to all sides
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index fc4d819..e3908ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -45,7 +45,6 @@
         SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
         ArrayList<TaskStack> stacks = root.getStacks();
         if (!stacks.isEmpty()) {
-            // XXX: We just replace the root every time for now, we will change this in the future
             mRecentsView.setBSP(root);
         }
 
@@ -155,7 +154,7 @@
                 Console.AnsiRed);
         super.onPause();
 
-        // Stop the loader when we leave Recents
+        // Stop the loader immediately when we leave Recents
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         loader.stopLoader();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 96efed4..c8576b2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -36,7 +36,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 
@@ -84,7 +83,9 @@
     TaskResourceLoadQueue mLoadQueue;
     DrawableLruCache mIconCache;
     BitmapLruCache mThumbnailCache;
+
     boolean mCancelled;
+    boolean mWaitingOnLoadQueue;
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
     public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache iconCache,
@@ -116,6 +117,11 @@
         Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|stop]");
         // Mark as cancelled for the thread to pick up
         mCancelled = true;
+        // If we are waiting for the load queue for more tasks, then we can just reset the
+        // Context now, since nothing is using it
+        if (mWaitingOnLoadQueue) {
+            mContext = null;
+        }
     }
 
     @Override
@@ -124,6 +130,8 @@
             Console.log(Constants.DebugFlags.App.TaskDataLoader,
                     "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
             if (mCancelled) {
+                Console.log(Constants.DebugFlags.App.TaskDataLoader,
+                        "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
                 // We have to unset the context here, since the background thread may be using it
                 // when we call stop()
                 mContext = null;
@@ -142,50 +150,52 @@
                 final Task t = mLoadQueue.nextTask();
                 if (t != null) {
                     try {
-                        Drawable cachedIcon = mIconCache.get(t);
-                        Bitmap cachedThumbnail = mThumbnailCache.get(t);
+                        Drawable loadIcon = mIconCache.get(t.key);
+                        Bitmap loadThumbnail = mThumbnailCache.get(t.key);
                         Console.log(Constants.DebugFlags.App.TaskDataLoader,
                                 "  [TaskResourceLoader|load]",
-                                t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
+                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail);
                         // Load the icon
-                        if (cachedIcon == null) {
+                        if (loadIcon == null) {
                             PackageManager pm = mContext.getPackageManager();
-                            ActivityInfo info = pm.getActivityInfo(t.intent.getComponent(),
+                            ActivityInfo info = pm.getActivityInfo(t.key.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);
+                                loadIcon = icon;
+                                mIconCache.put(t.key, icon);
                             }
                         }
                         // Load the thumbnail
-                        if (cachedThumbnail == null) {
+                        if (loadThumbnail == null) {
                             ActivityManager am = (ActivityManager)
                                     mContext.getSystemService(Context.ACTIVITY_SERVICE);
-                            Bitmap thumbnail = am.getTaskTopThumbnail(t.id);
+                            Bitmap thumbnail = am.getTaskTopThumbnail(t.key.id);
                             if (!mCancelled) {
                                 if (thumbnail != null) {
                                     Console.log(Constants.DebugFlags.App.TaskDataLoader,
                                             "    [TaskResourceLoader|loadThumbnail]",
                                             thumbnail);
-                                    t.thumbnail = thumbnail;
-                                    mThumbnailCache.put(t, thumbnail);
+                                    loadThumbnail = thumbnail;
+                                    mThumbnailCache.put(t.key, thumbnail);
                                 } else {
                                     Console.logError(mContext,
                                             "Failed to load task top thumbnail for: " +
-                                                    t.intent.getComponent().getPackageName());
+                                                    t.key.intent.getComponent().getPackageName());
                                 }
                             }
                         }
                         if (!mCancelled) {
                             // Notify that the task data has changed
+                            final Drawable newIcon = loadIcon;
+                            final Bitmap newThumbnail = loadThumbnail;
                             mMainThreadHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
-                                    t.notifyTaskDataChanged();
+                                    t.notifyTaskDataLoaded(newThumbnail, newIcon);
                                 }
                             });
                         }
@@ -200,7 +210,9 @@
                         try {
                             Console.log(Constants.DebugFlags.App.TaskDataLoader,
                                     "[TaskResourceLoader|waitOnLoadQueue]");
+                            mWaitingOnLoadQueue = true;
                             mLoadQueue.wait();
+                            mWaitingOnLoadQueue = false;
                         } catch (InterruptedException ie) {
                             ie.printStackTrace();
                         }
@@ -211,14 +223,17 @@
     }
 }
 
-/** The drawable cache */
-class DrawableLruCache extends LruCache<Task, Drawable> {
+/**
+ * The drawable cache.  By using the Task's key, we can prevent holding onto a reference to the Task
+ * resource data, while keeping the cache data in memory where necessary.
+ */
+class DrawableLruCache extends LruCache<Task.TaskKey, Drawable> {
     public DrawableLruCache(int cacheSize) {
         super(cacheSize);
     }
 
     @Override
-    protected int sizeOf(Task t, Drawable d) {
+    protected int sizeOf(Task.TaskKey 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);
@@ -226,14 +241,17 @@
     }
 }
 
-/** The bitmap cache */
-class BitmapLruCache extends LruCache<Task, Bitmap> {
+/**
+ * The bitmap cache.  By using the Task's key, we can prevent holding onto a reference to the Task
+ * resource data, while keeping the cache data in memory where necessary.
+ */
+class BitmapLruCache extends LruCache<Task.TaskKey, Bitmap> {
     public BitmapLruCache(int cacheSize) {
         super(cacheSize);
     }
 
     @Override
-    protected int sizeOf(Task t, Bitmap bitmap) {
+    protected int sizeOf(Task.TaskKey t, Bitmap bitmap) {
         // The cache size will be measured in kilobytes rather than number of items
         return bitmap.getAllocationByteCount() / 1024;
     }
@@ -257,16 +275,18 @@
 
     /** Private Constructor */
     private RecentsTaskLoader(Context context) {
-        // Calculate the cache sizes
+        // Calculate the cache sizes, we just use a reasonable number here similar to those
+        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
+        // for icons.
         int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
         mMaxThumbnailCacheSize = maxMemory / 8;
         mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
-        int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 :
+        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
-        int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 :
+        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
-        Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
+        Console.log(Constants.DebugFlags.App.TaskDataLoader,
                 "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
                 " iconCache: " + iconCacheSize);
 
@@ -351,36 +371,34 @@
                 ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(),
                         PackageManager.GET_META_DATA);
                 String title = info.loadLabel(pm).toString();
-                Task task;
+                boolean isForemostTask = (i == (taskCount - 1));
+
                 // Preload the specified number of apps
-                if (i >= (taskCount - preloadCount) ||
-                        !Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
+                if (i >= (taskCount - preloadCount)) {
                     Console.log(Constants.DebugFlags.App.TaskDataLoader,
                             "[RecentsTaskLoader|preloadTask]",
                             "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
 
-                    task = new Task(t.persistentId, t.baseIntent, title, null, null);
+                    Task task = new Task(t.persistentId, t.baseIntent, title);
 
-                    // Load the icon (if possible from the cache)
-                    if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-                        task.icon = mIconCache.get(task);
+                    // Load the icon (if possible and not the foremost task, from the cache)
+                    if (!isForemostTask) {
+                        task.icon = mIconCache.get(task.key);
                     }
                     if (task.icon == null) {
                         task.icon = info.loadIcon(pm);
-                        if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-                            mIconCache.put(task, task.icon);
-                        }
+                        mIconCache.put(task.key, task.icon);
                     }
 
-                    // Load the thumbnail (if possible from the cache)
-                    if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-                        task.thumbnail = mThumbnailCache.get(task);
+                    // Load the thumbnail (if possible and not the foremost task, from the cache)
+                    if (!isForemostTask) {
+                        task.thumbnail = mThumbnailCache.get(task.key);
                     }
                     if (task.thumbnail == null) {
+                        Console.log(Constants.DebugFlags.App.TaskDataLoader,
+                                "[RecentsTaskLoader|loadingTaskThumbnail]");
                         task.thumbnail = am.getTaskTopThumbnail(t.id);
-                        if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-                            mThumbnailCache.put(task, task.thumbnail);
-                        }
+                        mThumbnailCache.put(task.key, task.thumbnail);
                     }
 
                     // Create as many tasks a we want to multiply by
@@ -394,19 +412,9 @@
                     for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
                         Console.log(Constants.DebugFlags.App.TaskDataLoader,
                                 "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
-                        task = new Task(t.persistentId, t.baseIntent, title, null, null);
-                        stack.addTask(task);
+                        stack.addTask(new Task(t.persistentId, t.baseIntent, title));
                     }
                 }
-
-                /*
-                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]",
@@ -428,64 +436,59 @@
         } catch (Exception e) {
             e.printStackTrace();
         }
+
+        // Start the task loader
         mLoader.start(context);
+
         return root;
     }
 
     /** Acquires the task resource data from the pool. */
     public void loadTaskData(Task t) {
-        if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-            Drawable icon = mIconCache.get(t);
-            Bitmap thumbnail = mThumbnailCache.get(t);
+        Drawable icon = mIconCache.get(t.key);
+        Bitmap thumbnail = mThumbnailCache.get(t.key);
 
-            Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
-                    t + " icon: " + icon + " thumbnail: " + thumbnail +
-                            " thumbnailCacheSize: " + mThumbnailCache.size());
+        Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
+                t + " icon: " + icon + " thumbnail: " + thumbnail +
+                        " thumbnailCacheSize: " + mThumbnailCache.size());
 
-            boolean requiresLoad = false;
-            if (icon == null) {
-                icon = mDefaultIcon;
-                requiresLoad = true;
-            }
-            if (thumbnail == null) {
-                thumbnail = mDefaultThumbnail;
-                requiresLoad = true;
-            }
-            if (requiresLoad) {
-                mLoadQueue.addTask(t);
-            }
-            t.notifyTaskLoaded(thumbnail, icon);
+        boolean requiresLoad = false;
+        if (icon == null) {
+            icon = mDefaultIcon;
+            requiresLoad = true;
         }
+        if (thumbnail == null) {
+            thumbnail = mDefaultThumbnail;
+            requiresLoad = true;
+        }
+        if (requiresLoad) {
+            mLoadQueue.addTask(t);
+        }
+        t.notifyTaskDataLoaded(thumbnail, icon);
     }
 
     /** Releases the task resource data back into the pool. */
     public void unloadTaskData(Task t) {
-        if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-            Console.log(Constants.DebugFlags.App.TaskDataLoader,
-                    "[RecentsTaskLoader|unloadTask]", t +
-                    " thumbnailCacheSize: " + mThumbnailCache.size());
-            mLoadQueue.removeTask(t);
-            t.notifyTaskUnloaded(mDefaultThumbnail, mDefaultIcon);
-        } else {
-            t.notifyTaskUnloaded(null, null);
-        }
+        Console.log(Constants.DebugFlags.App.TaskDataLoader,
+                "[RecentsTaskLoader|unloadTask]", t +
+                " thumbnailCacheSize: " + mThumbnailCache.size());
+
+        mLoadQueue.removeTask(t);
+        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultIcon);
     }
 
     /** Completely removes the resource data from the pool. */
     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.notifyTaskUnloaded(mDefaultThumbnail, mDefaultIcon);
-        } else {
-            t.notifyTaskUnloaded(null, null);
-        }
+        Console.log(Constants.DebugFlags.App.TaskDataLoader,
+                "[RecentsTaskLoader|deleteTask]", t);
+
+        mLoadQueue.removeTask(t);
+        mThumbnailCache.remove(t.key);
+        mIconCache.remove(t.key);
+        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultIcon);
     }
 
-    /** Stops the task loader */
+    /** Stops the task loader and clears all pending tasks */
     void stopLoader() {
         Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
         mLoader.stop();
@@ -496,46 +499,30 @@
         Console.log(Constants.DebugFlags.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
                 Console.trimMemoryLevelToString(level));
 
-        if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
-            // If we are hidden, then we should unload each of the task keys
-            if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
-                Console.log(Constants.DebugFlags.App.Memory, "[RecentsTaskLoader|unloadTasks]"
-                );
-                // Unload each of the keys in the thumbnail cache
-                Map<Task, Bitmap> thumbnailCache = mThumbnailCache.snapshot();
-                for (Task t : thumbnailCache.keySet()) {
-                    unloadTaskData(t);
-                }
-                // As well as the keys in the icon cache
-                Map<Task, Drawable> iconCache = mIconCache.snapshot();
-                for (Task t : iconCache.keySet()) {
-                    unloadTaskData(t);
-                }
-            }
-
-            switch (level) {
-                case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
-                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-                case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
-                    // We are leaving recents, so trim the data a bit
-                    mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
-                    mIconCache.trimToSize(mMaxIconCacheSize / 2);
-                    break;
-                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-                case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                    // We are going to be low on memory
-                    mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
-                    mIconCache.trimToSize(mMaxIconCacheSize / 4);
-                    break;
-                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-                case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                    // We are low on memory, so release everything
-                    mThumbnailCache.evictAll();
-                    mIconCache.evictAll();
-                    break;
-                default:
-                    break;
-            }
+        switch (level) {
+            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+                // Do nothing
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+                // We are leaving recents, so trim the data a bit
+                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
+                mIconCache.trimToSize(mMaxIconCacheSize / 2);
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+                // We are going to be low on memory
+                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
+                mIconCache.trimToSize(mMaxIconCacheSize / 4);
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+                // We are low on memory, so release everything
+                mThumbnailCache.evictAll();
+                mIconCache.evictAll();
+                break;
+            default:
+                break;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
index 5893abc..1dd1be6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.model;
 
 import android.content.Context;
+import android.graphics.Rect;
 
 import java.util.ArrayList;
 
@@ -26,6 +27,14 @@
  * stacks should be placed.
  */
 public class SpaceNode {
+    /* BSP node callbacks */
+    public interface SpaceNodeCallbacks {
+        /** Notifies when a node is added */
+        public void onSpaceNodeAdded(SpaceNode node);
+        /** Notifies when a node is measured */
+        public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
+    }
+
     Context mContext;
 
     SpaceNode mStartNode;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java
deleted file mode 100644
index 31b02e7..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNodeCallbacks.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.model;
-
-import android.graphics.Rect;
-
-
-/* BSP node callbacks */
-public interface SpaceNodeCallbacks {
-    /** Notifies when a node is added */
-    public void onSpaceNodeAdded(SpaceNode node);
-    /** Notifies when a node is measured */
-    public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 378984c..0c3c528 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -26,17 +26,53 @@
  * A task represents the top most task in the system's task stack.
  */
 public class Task {
-    public final int id;
-    public final Intent intent;
+    /* Task callbacks */
+    public interface TaskCallbacks {
+        /* Notifies when a task has been bound */
+        public void onTaskDataLoaded();
+        /* Notifies when a task has been unbound */
+        public void onTaskDataUnloaded();
+    }
+
+    /* The Task Key represents the unique primary key for the task */
+    public static class TaskKey {
+        public final int id;
+        public final Intent intent;
+
+        public TaskKey(int id, Intent intent) {
+            this.id = id;
+            this.intent = intent;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return hashCode() == o.hashCode();
+        }
+
+        @Override
+        public int hashCode() {
+            return id;
+        }
+
+        @Override
+        public String toString() {
+            return "Task.Key: " + id + ", " + intent.getComponent().getPackageName();
+        }
+    }
+
+    public TaskKey key;
     public String title;
     public Drawable icon;
     public Bitmap thumbnail;
 
     TaskCallbacks mCb;
 
+    public Task(int id, Intent intent, String activityTitle) {
+        this(id, intent, activityTitle, null, null);
+    }
+
     public Task(int id, Intent intent, String activityTitle, Drawable icon, Bitmap thumbnail) {
-        this.id = id;
-        this.intent = intent;
+        this.key = new TaskKey(id, intent);
         this.title = activityTitle;
         this.icon = icon;
         this.thumbnail = thumbnail;
@@ -47,28 +83,21 @@
         mCb = cb;
     }
 
-    /** Notifies the callback listeners that this task's data has changed */
-    public void notifyTaskDataChanged() {
-        if (mCb != null) {
-            mCb.onTaskDataChanged(this);
-        }
-    }
-
     /** Notifies the callback listeners that this task has been loaded */
-    public void notifyTaskLoaded(Bitmap thumbnail, Drawable icon) {
+    public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable icon) {
         this.icon = icon;
         this.thumbnail = thumbnail;
         if (mCb != null) {
-            mCb.onTaskBound();
+            mCb.onTaskDataLoaded();
         }
     }
 
     /** Notifies the callback listeners that this task has been unloaded */
-    public void notifyTaskUnloaded(Bitmap defaultThumbnail, Drawable defaultIcon) {
+    public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultIcon) {
         icon = defaultIcon;
         thumbnail = defaultThumbnail;
         if (mCb != null) {
-            mCb.onTaskUnbound();
+            mCb.onTaskDataUnloaded();
         }
     }
 
@@ -83,12 +112,11 @@
         // Otherwise, check that the id and intent match (the other fields can be asynchronously
         // loaded and is unsuitable to testing the identity of this Task)
         Task t = (Task) o;
-        return (id == t.id) &&
-                (intent.equals(t.intent));
+        return key.equals(t.key);
     }
 
     @Override
     public String toString() {
-        return "Task: " + intent.getComponent().getPackageName() + " [" + super.toString() + "]";
+        return "Task: " + key.intent.getComponent().getPackageName() + " [" + super.toString() + "]";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java
deleted file mode 100644
index 712580d..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.model;
-
-/* Task callbacks */
-public interface TaskCallbacks {
-    /* Notifies when a task's data has been updated */
-    public void onTaskDataChanged(Task task);
-    /* Notifies when a task has been bound */
-    public void onTaskBound();
-    /* Notifies when a task has been unbound */
-    public void onTaskUnbound();
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index a5aa387..f2f89ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -119,6 +119,18 @@
  * The task stack contains a list of multiple tasks.
  */
 public class TaskStack {
+    /* Task stack callbacks */
+    public interface TaskStackCallbacks {
+        /* Notifies when a task has been added to the stack */
+        public void onStackTaskAdded(TaskStack stack, Task t);
+        /* Notifies when a task has been removed from the stack */
+        public void onStackTaskRemoved(TaskStack stack, Task t);
+        /** Notifies when the stack was filtered */
+        public void onStackFiltered(TaskStack stack);
+        /** Notifies when the stack was un-filtered */
+        public void onStackUnfiltered(TaskStack stack);
+    }
+
     Context mContext;
 
     FilteredTaskList mTaskList = new FilteredTaskList();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java
deleted file mode 100644
index 4bec655..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStackCallbacks.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.model;
-
-/* Task stack callbacks */
-public interface TaskStackCallbacks {
-    /* Notifies when a task has been added to the stack */
-    public void onStackTaskAdded(TaskStack stack, Task t);
-    /* Notifies when a task has been removed from the stack */
-    public void onStackTaskRemoved(TaskStack stack, Task t);
-    /** Notifies when the stack was filtered */
-    public void onStackFiltered(TaskStack stack);
-    /** Notifies when the stack was un-filtered */
-    public void onStackUnfiltered(TaskStack stack);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index c92041c..32110cf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -39,7 +39,7 @@
  * This view is the the top level layout that contains TaskStacks (which are laid out according
  * to their SpaceNode bounds.
  */
-public class RecentsView extends FrameLayout implements TaskStackViewCallbacks {
+public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
     // The space partitioning root of this container
     SpaceNode mBSP;
 
@@ -52,8 +52,7 @@
     public void setBSP(SpaceNode n) {
         mBSP = n;
 
-        // XXX: We shouldn't be recereating new stacks every time, but for now, that is OK
-        // Add all the stacks for this partition
+        // Create and add all the stacks for this partition of space.
         removeAllViews();
         ArrayList<TaskStack> stacks = mBSP.getStacks();
         for (TaskStack stack : stacks) {
@@ -206,7 +205,7 @@
                 }
 
                 // Launch the activity with the desired animation
-                Intent i = new Intent(task.intent);
+                Intent i = new Intent(task.key.intent);
                 i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
                         | Intent.FLAG_ACTIVITY_TASK_ON_HOME
                         | Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7753d69..a3ed535 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -33,7 +33,6 @@
 import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.OverScroller;
-import android.widget.Toast;
 import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
@@ -41,18 +40,20 @@
 import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.TaskStackCallbacks;
 
 import java.util.ArrayList;
 
-/** The TaskView callbacks */
-interface TaskStackViewCallbacks {
-    public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
-}
 
 /* The visual representation of a task stack view */
-public class TaskStackView extends FrameLayout implements TaskStackCallbacks, TaskViewCallbacks,
-        ViewPoolConsumer<TaskView, Task>, View.OnClickListener {
+public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
+        TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
+        View.OnClickListener {
+
+    /** The TaskView callbacks */
+    interface TaskStackViewCallbacks {
+        public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+    }
+
     TaskStack mStack;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
@@ -348,7 +349,8 @@
                 tv.disableHwLayers();
             }
         } else if (mHwLayersRefCount < 0) {
-            throw new RuntimeException("Invalid hw layers ref count");
+            new Throwable("Invalid hw layers ref count").printStackTrace();
+            Console.logError(getContext(), "Invalid HW layers ref count");
         }
     }
 
@@ -598,7 +600,7 @@
         // Setup and attach the view to the window
         Task task = prepareData;
         // We try and rebind the task (this MUST be done before the task filled)
-        tv.bindToTask(task, this);
+        tv.onTaskBound(task);
         // Request that this tasks's data be filled
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         loader.loadTaskData(task);
@@ -619,7 +621,10 @@
                 "" + insertIndex);
         if (isNewView) {
             addView(tv, insertIndex);
+
+            // Set the callbacks and listeners for this new view
             tv.setOnClickListener(this);
+            tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
         }
@@ -649,7 +654,7 @@
                 mStack.filterTasks(tv.getTask());
             }
         } else {
-            Toast.makeText(getContext(), "Task Filtering TBD", Toast.LENGTH_SHORT).show();
+            Console.logError(getContext(), "Task Filtering TBD");
         }
     }
 
@@ -995,7 +1000,7 @@
         final ActivityManager am = (ActivityManager)
                 activity.getSystemService(Context.ACTIVITY_SERVICE);
         if (am != null) {
-            am.removeTask(tv.getTask().id,
+            am.removeTask(tv.getTask().key.id,
                     ActivityManager.REMOVE_TASK_KILL_PROCESS);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9ef74ca..ace2428 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -39,13 +39,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskCallbacks;
 
-/** The TaskView callbacks */
-interface TaskViewCallbacks {
-    public void onTaskIconClicked(TaskView tv);
-    // public void onTaskViewReboundToTask(TaskView tv, Task t);
-}
 
 /** The task thumbnail view */
 class TaskThumbnailView extends ImageView {
@@ -66,7 +60,7 @@
             // Update the bar color
             if (Constants.Values.TaskView.DrawColoredTaskBars) {
                 int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
-                mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length];
+                mBarColor = colors[mTask.key.intent.getComponent().getPackageName().length() % colors.length];
             }
 
             setImageBitmap(t.thumbnail);
@@ -213,7 +207,13 @@
 }
 
 /* A task view */
-public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks {
+public class TaskView extends FrameLayout implements View.OnClickListener, Task.TaskCallbacks {
+    /** The TaskView callbacks */
+    interface TaskViewCallbacks {
+        public void onTaskIconClicked(TaskView tv);
+        // public void onTaskViewReboundToTask(TaskView tv, Task t);
+    }
+
     Task mTask;
     TaskThumbnailView mThumbnailView;
     TaskIconView mIconView;
@@ -247,26 +247,11 @@
         ((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
     }
 
-    /** Set the task and callback */
-    void bindToTask(Task t, TaskViewCallbacks cb) {
-        mTask = t;
-        mTask.setCallbacks(this);
+    /** Set callback */
+    void setCallbacks(TaskViewCallbacks cb) {
         mCb = cb;
     }
 
-    /** Actually synchronizes the model data into the views */
-    private void syncToTask() {
-        mThumbnailView.rebindToTask(mTask, false);
-        mIconView.rebindToTask(mTask, false);
-    }
-
-    /** Unset the task and callback */
-    private void unbindFromTask() {
-        mTask.setCallbacks(null);
-        mThumbnailView.unbindFromTask();
-        mIconView.unbindFromTask();
-    }
-
     /** Gets the task */
     Task getTask() {
         return mTask;
@@ -367,26 +352,25 @@
 
     /**** TaskCallbacks Implementation ****/
 
-    @Override
-    public void onTaskDataChanged(Task task) {
-        Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
-                "[TaskView|onTaskDataChanged]", task);
-
-        // Only update this task view if the changed task is the same as the task for this view
-        if (mTask == task) {
-            mThumbnailView.rebindToTask(mTask, true);
-            mIconView.rebindToTask(mTask, true);
-        }
+    /** Binds this task view to the task */
+    public void onTaskBound(Task t) {
+        mTask = t;
+        mTask.setCallbacks(this);
     }
 
     @Override
-    public void onTaskBound() {
-        syncToTask();
+    public void onTaskDataLoaded() {
+        // Bind each of the views to the new task data
+        mThumbnailView.rebindToTask(mTask, false);
+        mIconView.rebindToTask(mTask, false);
     }
 
     @Override
-    public void onTaskUnbound() {
-        unbindFromTask();
+    public void onTaskDataUnloaded() {
+        // Unbind each of the views from the task data and remove the task callback
+        mTask.setCallbacks(null);
+        mThumbnailView.unbindFromTask();
+        mIconView.unbindFromTask();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index f7d7095..af0094e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -24,6 +24,15 @@
 
 /* A view pool to manage more views than we can visibly handle */
 public class ViewPool<V, T> {
+
+    /* An interface to the consumer of a view pool */
+    public interface ViewPoolConsumer<V, T> {
+        public V createView(Context context);
+        public void prepareViewToEnterPool(V v);
+        public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
+        public boolean hasPreferredData(V v, T preferredData);
+    }
+
     Context mContext;
     ViewPoolConsumer<V, T> mViewCreator;
     LinkedList<V> mPool = new LinkedList<V>();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java
deleted file mode 100644
index 50f45bf..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPoolConsumer.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.views;
-
-import android.content.Context;
-
-
-/* An interface to the consumer of a view pool */
-public interface ViewPoolConsumer<V, T> {
-    public V createView(Context context);
-    public void prepareViewToEnterPool(V v);
-    public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
-    public boolean hasPreferredData(V v, T preferredData);
-}