Enforce serial ordering of MotionEvents.

This change modifies ViewRoot to wait until the MotionEvent has been
delivered before telling the input dispatcher that it is finished.
The serial ordering guarantee will be required by new features that
may need to transfer touch focus from one window to another.

Change-Id: I0a64d1f0c8bc3f08846f213d6e28ce48b1d48e58
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 59980ef..0608e72 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -1876,6 +1876,9 @@
                 deliverPointerEvent(event);
             } finally {
                 event.recycle();
+                if (msg.arg1 != 0) {
+                    finishInputEvent();
+                }
                 if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
             }
         } break;
@@ -1885,6 +1888,9 @@
                 deliverTrackballEvent(event);
             } finally {
                 event.recycle();
+                if (msg.arg1 != 0) {
+                    finishInputEvent();
+                }
             }
         } break;
         case DISPATCH_APP_VISIBILITY:
@@ -2019,15 +2025,24 @@
         }
     }
     
-    private void finishKeyEvent(KeyEvent event) {
-        if (LOCAL_LOGV) Log.v(TAG, "Telling window manager key is finished");
+    private void startInputEvent(Runnable finishedCallback) {
+        if (mFinishedCallback != null) {
+            Slog.w(TAG, "Received a new input event from the input queue but there is "
+                    + "already an unfinished input event in progress.");
+        }
+
+        mFinishedCallback = finishedCallback;
+    }
+
+    private void finishInputEvent() {
+        if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
 
         if (mFinishedCallback != null) {
             mFinishedCallback.run();
             mFinishedCallback = null;
         } else {
-            Slog.w(TAG, "Attempted to tell the input queue that the current key event "
-                    + "is finished but there is no key event actually in progress.");
+            Slog.w(TAG, "Attempted to tell the input queue that the current input event "
+                    + "is finished but there is no input event actually in progress.");
         }
     }
     
@@ -2487,7 +2502,7 @@
                 ? mView.dispatchKeyEventPreIme(event) : true;
         if (handled) {
             if (sendDone) {
-                finishKeyEvent(event);
+                finishInputEvent();
             }
             return;
         }
@@ -2518,7 +2533,7 @@
                 deliverKeyEventToViewHierarchy(event, sendDone);
                 return;
             } else if (sendDone) {
-                finishKeyEvent(event);
+                finishInputEvent();
             } else {
                 Log.w(TAG, "handleFinishedEvent(seq=" + seq
                         + " handled=" + handled + " ev=" + event
@@ -2591,7 +2606,7 @@
 
         } finally {
             if (sendDone) {
-                finishKeyEvent(event);
+                finishInputEvent();
             }
             // Let the exception fall through -- the looper will catch
             // it and take care of the bad app for us.
@@ -2774,20 +2789,13 @@
     
     private final InputHandler mInputHandler = new InputHandler() {
         public void handleKey(KeyEvent event, Runnable finishedCallback) {
-            if (mFinishedCallback != null) {
-                Slog.w(TAG, "Received a new key event from the input queue but there is "
-                        + "already an unfinished key event in progress.");
-            }
-
-            mFinishedCallback = finishedCallback;
-
+            startInputEvent(finishedCallback);
             dispatchKey(event, true);
         }
 
         public void handleMotion(MotionEvent event, Runnable finishedCallback) {
-            finishedCallback.run();
-            
-            dispatchMotion(event);
+            startInputEvent(finishedCallback);
+            dispatchMotion(event, true);
         }
     };
 
@@ -2820,26 +2828,43 @@
     }
     
     public void dispatchMotion(MotionEvent event) {
+        dispatchMotion(event, false);
+    }
+
+    private void dispatchMotion(MotionEvent event, boolean sendDone) {
         int source = event.getSource();
         if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-            dispatchPointer(event);
+            dispatchPointer(event, sendDone);
         } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-            dispatchTrackball(event);
+            dispatchTrackball(event, sendDone);
         } else {
             // TODO
             Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
+            if (sendDone) {
+                finishInputEvent();
+            }
         }
     }
 
     public void dispatchPointer(MotionEvent event) {
+        dispatchPointer(event, false);
+    }
+
+    private void dispatchPointer(MotionEvent event, boolean sendDone) {
         Message msg = obtainMessage(DISPATCH_POINTER);
         msg.obj = event;
+        msg.arg1 = sendDone ? 1 : 0;
         sendMessageAtTime(msg, event.getEventTime());
     }
 
     public void dispatchTrackball(MotionEvent event) {
+        dispatchTrackball(event, false);
+    }
+
+    private void dispatchTrackball(MotionEvent event, boolean sendDone) {
         Message msg = obtainMessage(DISPATCH_TRACKBALL);
         msg.obj = event;
+        msg.arg1 = sendDone ? 1 : 0;
         sendMessageAtTime(msg, event.getEventTime());
     }
     
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 274124b..567b270 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -232,8 +232,11 @@
         @Override
         public void handleMotion(MotionEvent event, Runnable finishedCallback) {
             finishedCallback.run();
+            
             synchronized (mLock) {
-                mPointerLocationView.addTouchEvent(event);
+                if (mPointerLocationView != null) {
+                    mPointerLocationView.addTouchEvent(event);
+                }
             }
         }
     };