Ensuring that unbind calls are not queued in the worker thread and executed subsequently on the main thread.  (Bug 7001531)

Change-Id: I677242c0952d6385ca229ba300fdd5a2a7908a8a
diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java
index b7e48b1..cee27df 100644
--- a/src/com/android/launcher2/DeferredHandler.java
+++ b/src/com/android/launcher2/DeferredHandler.java
@@ -16,12 +16,13 @@
 
 package com.android.launcher2;
 
-import java.util.LinkedList;
-
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
+import android.util.Pair;
+import java.util.LinkedList;
+import java.util.ListIterator;
 
 /**
  * Queue of things to run on a looper thread.  Items posted with {@link #post} will not
@@ -31,18 +32,20 @@
  * This class is fifo.
  */
 public class DeferredHandler {
-    private LinkedList<Runnable> mQueue = new LinkedList<Runnable>();
+    private LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
     private MessageQueue mMessageQueue = Looper.myQueue();
     private Impl mHandler = new Impl();
 
     private class Impl extends Handler implements MessageQueue.IdleHandler {
         public void handleMessage(Message msg) {
+            Pair<Runnable, Integer> p;
             Runnable r;
             synchronized (mQueue) {
                 if (mQueue.size() == 0) {
                     return;
                 }
-                r = mQueue.removeFirst();
+                p = mQueue.removeFirst();
+                r = p.first;
             }
             r.run();
             synchronized (mQueue) {
@@ -73,8 +76,11 @@
 
     /** Schedule runnable to run after everything that's on the queue right now. */
     public void post(Runnable runnable) {
+        post(runnable, 0);
+    }
+    public void post(Runnable runnable, int type) {
         synchronized (mQueue) {
-            mQueue.add(runnable);
+            mQueue.add(new Pair<Runnable, Integer>(runnable, type));
             if (mQueue.size() == 1) {
                 scheduleNextLocked();
             }
@@ -83,7 +89,10 @@
 
     /** Schedule runnable to run when the queue goes idle. */
     public void postIdle(final Runnable runnable) {
-        post(new IdleRunnable(runnable));
+        postIdle(runnable, 0);
+    }
+    public void postIdle(final Runnable runnable, int type) {
+        post(new IdleRunnable(runnable), type);
     }
 
     public void cancelRunnable(Runnable runnable) {
@@ -91,6 +100,18 @@
             while (mQueue.remove(runnable)) { }
         }
     }
+    public void cancelAllRunnablesOfType(int type) {
+        synchronized (mQueue) {
+            ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator();
+            Pair<Runnable, Integer> p;
+            while (iter.hasNext()) {
+                p = iter.next();
+                if (p.second == type) {
+                    iter.remove();
+                }
+            }
+        }
+    }
 
     public void cancel() {
         synchronized (mQueue) {
@@ -100,19 +121,20 @@
 
     /** Runs all queued Runnables from the calling thread. */
     public void flush() {
-        LinkedList<Runnable> queue = new LinkedList<Runnable>();
+        LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>();
         synchronized (mQueue) {
             queue.addAll(mQueue);
             mQueue.clear();
         }
-        for (Runnable r : queue) {
-            r.run();
+        for (Pair<Runnable, Integer> p : queue) {
+            p.first.run();
         }
     }
 
     void scheduleNextLocked() {
         if (mQueue.size() > 0) {
-            Runnable peek = mQueue.getFirst();
+            Pair<Runnable, Integer> p = mQueue.getFirst();
+            Runnable peek = p.first;
             if (peek instanceof IdleRunnable) {
                 mMessageQueue.addIdleHandler(mHandler);
             } else {
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 1e138f7..2fcf869 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1486,8 +1486,11 @@
 
         TextKeyListener.getInstance().release();
 
-
-        unbindWorkspaceAndHotseatItems();
+        // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
+        // to prevent leaking Launcher activities on orientation change.
+        if (mModel != null) {
+            mModel.unbindItemInfosAndClearQueuedBindRunnables();
+        }
 
         getContentResolver().unregisterContentObserver(mWidgetObserver);
         unregisterReceiver(mCloseSystemDialogsReceiver);
@@ -1878,16 +1881,6 @@
     }
 
     /**
-     * Go through the and disconnect any of the callbacks in the drawables and the views or we
-     * leak the previous Home screen on orientation change.
-     */
-    private void unbindWorkspaceAndHotseatItems() {
-        if (mModel != null) {
-            mModel.unbindWorkspaceItems();
-        }
-    }
-
-    /**
      * Launches the intent referred by the clicked shortcut.
      *
      * @param v The view representing the clicked shortcut.
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 7927593..269a0fd 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -82,6 +82,12 @@
     private LoaderTask mLoaderTask;
     private boolean mIsLoaderTaskRunning;
 
+    // Specific runnable types that are run on the main thread deferred handler, this allows us to
+    // clear all queued binding runnables when the Launcher activity is destroyed.
+    private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
+    private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
+
+
     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
     static {
         sWorkerThread.start();
@@ -177,6 +183,9 @@
     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
      * posted on the main thread handler. */
     private void runOnMainThread(Runnable r) {
+        runOnMainThread(r, 0);
+    }
+    private void runOnMainThread(Runnable r, int type) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             // If we are on the worker thread, post onto the main handler
             mHandler.post(r);
@@ -200,17 +209,22 @@
         return Bitmap.createBitmap(mDefaultIcon);
     }
 
-    public void unbindWorkspaceItems() {
-        sWorker.post(new Runnable() {
-            @Override
-            public void run() {
-                unbindWorkspaceItemsOnMainThread();
-            }
-        });
+    public void unbindItemInfosAndClearQueuedBindRunnables() {
+        if (sWorkerThread.getThreadId() == Process.myTid()) {
+            throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
+                    "main thread");
+        }
+
+        // Clear any deferred bind runnables
+        mDeferredBindRunnables.clear();
+        // Remove any queued bind runnables
+        mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
+        // Unbind all the workspace items
+        unbindWorkspaceItemsOnMainThread();
     }
 
     /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
-    private void unbindWorkspaceItemsOnMainThread() {
+    void unbindWorkspaceItemsOnMainThread() {
         // Ensure that we don't use the same workspace items data structure on the main thread
         // by making a copy of workspace items first.
         final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
@@ -880,7 +894,7 @@
         // Post the remaining side pages to be loaded
         if (!mDeferredBindRunnables.isEmpty()) {
             for (final Runnable r : mDeferredBindRunnables) {
-                mHandler.post(r);
+                mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
             }
             mDeferredBindRunnables.clear();
         }
@@ -1602,7 +1616,7 @@
                 if (postOnMainThread) {
                     deferredBindRunnables.add(r);
                 } else {
-                    runOnMainThread(r);
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                 }
             }
 
@@ -1619,7 +1633,7 @@
                 if (postOnMainThread) {
                     deferredBindRunnables.add(r);
                 } else {
-                    runOnMainThread(r);
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                 }
             }
 
@@ -1638,7 +1652,7 @@
                 if (postOnMainThread) {
                     deferredBindRunnables.add(r);
                 } else {
-                    runOnMainThread(r);
+                    runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                 }
             }
         }
@@ -1706,7 +1720,7 @@
                     }
                 }
             };
-            runOnMainThread(r);
+            runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
 
             // Load items on the current page
             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
@@ -1720,7 +1734,7 @@
                         }
                     }
                 };
-                runOnMainThread(r);
+                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
             }
 
             // Load all the remaining pages (if we are loading synchronously, we want to defer this
@@ -1749,7 +1763,7 @@
             if (isLoadingSynchronously) {
                 mDeferredBindRunnables.add(r);
             } else {
-                runOnMainThread(r);
+                runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
             }
         }