Detect invalid uses of Message.

Throw an exception when an application attempts to recycle or
resend messages that are still in-use.

Change-Id: I7096e8b7bd5bec41b7b8ef0c798c55ce3db6827e
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index ff31130..6d7c9cf 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -149,7 +149,7 @@
                         + msg.callback + " what=" + msg.what);
             }
 
-            msg.recycle();
+            msg.recycleUnchecked();
         }
     }
 
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 51203a48..d30bbc1 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -71,7 +71,14 @@
      */
     public Messenger replyTo;
 
-    /** If set message is in use */
+    /** If set message is in use.
+     * This flag is set when the message is enqueued and remains set while it
+     * is delivered and afterwards when it is recycled.  The flag is only cleared
+     * when a new message is created or obtained since that is the only time that
+     * applications are allowed to modify the contents of the message.
+     *
+     * It is an error to attempt to enqueue or recycle a message that is already in use.
+     */
     /*package*/ static final int FLAG_IN_USE = 1 << 0;
 
     /** If set message is asynchronous */
@@ -86,9 +93,9 @@
     
     /*package*/ Bundle data;
     
-    /*package*/ Handler target;     
+    /*package*/ Handler target;
     
-    /*package*/ Runnable callback;   
+    /*package*/ Runnable callback;
     
     // sometimes we store linked lists of these things
     /*package*/ Message next;
@@ -109,6 +116,7 @@
                 Message m = sPool;
                 sPool = m.next;
                 m.next = null;
+                m.flags = 0; // clear in-use flag
                 sPoolSize--;
                 return m;
             }
@@ -241,12 +249,38 @@
     }
 
     /**
-     * Return a Message instance to the global pool.  You MUST NOT touch
-     * the Message after calling this function -- it has effectively been
-     * freed.
+     * Return a Message instance to the global pool.
+     * <p>
+     * You MUST NOT touch the Message after calling this function because it has
+     * effectively been freed.  It is an error to recycle a message that is currently
+     * enqueued or that is in the process of being delivered to a Handler.
+     * </p>
      */
     public void recycle() {
-        clearForRecycle();
+        if (isInUse()) {
+            throw new IllegalStateException("This message cannot be recycled because it "
+                    + "is still in use.");
+        }
+        recycleUnchecked();
+    }
+
+    /**
+     * Recycles a Message that may be in-use.
+     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
+     */
+    void recycleUnchecked() {
+        // Mark the message as in use while it remains in the recycled object pool.
+        // Clear out all other details.
+        flags = FLAG_IN_USE;
+        what = 0;
+        arg1 = 0;
+        arg2 = 0;
+        obj = null;
+        replyTo = null;
+        when = 0;
+        target = null;
+        callback = null;
+        data = null;
 
         synchronized (sPoolSync) {
             if (sPoolSize < MAX_POOL_SIZE) {
@@ -402,19 +436,6 @@
         }
     }
 
-    /*package*/ void clearForRecycle() {
-        flags = 0;
-        what = 0;
-        arg1 = 0;
-        arg2 = 0;
-        obj = null;
-        replyTo = null;
-        when = 0;
-        target = null;
-        callback = null;
-        data = null;
-    }
-
     /*package*/ boolean isInUse() {
         return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
     }
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 7ca5d49..01a23ce 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.util.AndroidRuntimeException;
 import android.util.Log;
 import android.util.Printer;
 
@@ -169,7 +168,6 @@
                         }
                         msg.next = null;
                         if (false) Log.v("MessageQueue", "Returning message: " + msg);
-                        msg.markInUse();
                         return msg;
                     }
                 } else {
@@ -233,7 +231,7 @@
 
     void quit(boolean safe) {
         if (!mQuitAllowed) {
-            throw new RuntimeException("Main thread not allowed to quit.");
+            throw new IllegalStateException("Main thread not allowed to quit.");
         }
 
         synchronized (this) {
@@ -259,6 +257,7 @@
         synchronized (this) {
             final int token = mNextBarrierToken++;
             final Message msg = Message.obtain();
+            msg.markInUse();
             msg.when = when;
             msg.arg1 = token;
 
@@ -303,7 +302,7 @@
                 mMessages = p.next;
                 needWake = mMessages == null || mMessages.target != null;
             }
-            p.recycle();
+            p.recycleUnchecked();
 
             // If the loop is quitting then it is already awake.
             // We can assume mPtr != 0 when mQuitting is false.
@@ -314,21 +313,23 @@
     }
 
     boolean enqueueMessage(Message msg, long when) {
-        if (msg.isInUse()) {
-            throw new AndroidRuntimeException(msg + " This message is already in use.");
-        }
         if (msg.target == null) {
-            throw new AndroidRuntimeException("Message must have a target.");
+            throw new IllegalArgumentException("Message must have a target.");
+        }
+        if (msg.isInUse()) {
+            throw new IllegalStateException(msg + " This message is already in use.");
         }
 
         synchronized (this) {
             if (mQuitting) {
-                RuntimeException e = new RuntimeException(
+                IllegalStateException e = new IllegalStateException(
                         msg.target + " sending message to a Handler on a dead thread");
                 Log.w("MessageQueue", e.getMessage(), e);
+                msg.recycle();
                 return false;
             }
 
+            msg.markInUse();
             msg.when = when;
             Message p = mMessages;
             boolean needWake;
@@ -424,7 +425,7 @@
                    && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
-                p.recycle();
+                p.recycleUnchecked();
                 p = n;
             }
 
@@ -435,7 +436,7 @@
                     if (n.target == h && n.what == what
                         && (object == null || n.obj == object)) {
                         Message nn = n.next;
-                        n.recycle();
+                        n.recycleUnchecked();
                         p.next = nn;
                         continue;
                     }
@@ -458,7 +459,7 @@
                    && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
-                p.recycle();
+                p.recycleUnchecked();
                 p = n;
             }
 
@@ -469,7 +470,7 @@
                     if (n.target == h && n.callback == r
                         && (object == null || n.obj == object)) {
                         Message nn = n.next;
-                        n.recycle();
+                        n.recycleUnchecked();
                         p.next = nn;
                         continue;
                     }
@@ -492,7 +493,7 @@
                     && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
-                p.recycle();
+                p.recycleUnchecked();
                 p = n;
             }
 
@@ -502,7 +503,7 @@
                 if (n != null) {
                     if (n.target == h && (object == null || n.obj == object)) {
                         Message nn = n.next;
-                        n.recycle();
+                        n.recycleUnchecked();
                         p.next = nn;
                         continue;
                     }
@@ -516,7 +517,7 @@
         Message p = mMessages;
         while (p != null) {
             Message n = p.next;
-            p.recycle();
+            p.recycleUnchecked();
             p = n;
         }
         mMessages = null;
@@ -544,7 +545,7 @@
                 do {
                     p = n;
                     n = p.next;
-                    p.recycle();
+                    p.recycleUnchecked();
                 } while (n != null);
             }
         }