Merge "Bug 2372180: pass multi-touch events from browser to webkit. Since the UI uses multi-touch for zooming, the event is passed to webkit only when the webpage doesn't allow zooming. 1. Updated the data structure TouchEventData in WebViewCore.java    to take multiple points and used it everywhere. 2. Added a passMultiTouchToWebkit() function to do what its name means. 3. Added the multi-point touch support to dumprendertree."
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bca9b36..73fd8ed 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -457,6 +457,9 @@
     // default is not set, the UI will continue handle them.
     private boolean mDeferTouchProcess;
 
+    // if true, multi-touch events will be passed to webkit directly before UI
+    private boolean mDeferMultitouch = false;
+
     // to avoid interfering with the current touch events, track them
     // separately. Currently no snapping or fling in the deferred process mode
     private int mDeferTouchMode = TOUCH_DONE_MODE;
@@ -4943,8 +4946,9 @@
         }
 
         if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
-                    + mTouchMode);
+            Log.v(LOGTAG, ev + " at " + ev.getEventTime()
+                + " mTouchMode=" + mTouchMode
+                + " numPointers=" + ev.getPointerCount());
         }
 
         int action = ev.getAction();
@@ -4991,16 +4995,18 @@
             }
         }
 
-        // FIXME: we may consider to give WebKit an option to handle multi-touch
-        // events later.
+        // If the page disallows zoom, pass multi-pointer events to webkit.
+        if (ev.getPointerCount() > 1
+            && (mZoomManager.isZoomScaleFixed() || mDeferMultitouch)) {
+            if (DebugFlags.WEB_VIEW) {
+                Log.v(LOGTAG, "passing " + ev.getPointerCount() + " points to webkit");
+            }
+            passMultiTouchToWebKit(ev);
+            return true;
+        }
+
         if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 &&
                 mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) {
-
-            // if the page disallows zoom, skip multi-pointer action
-            if (!mZoomManager.supportsPanDuringZoom() && mZoomManager.isZoomScaleFixed()) {
-                return true;
-            }
-
             if (!detector.isInProgress() &&
                     ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) {
                 // Insert a fake pointer down event in order to start
@@ -5151,8 +5157,8 @@
                     if (shouldForwardTouchEvent()) {
                         TouchEventData ted = new TouchEventData();
                         ted.mAction = action;
-                        ted.mX = contentX;
-                        ted.mY = contentY;
+                        ted.mPoints = new Point[1];
+                        ted.mPoints[0] = new Point(contentX, contentY);
                         ted.mMetaState = ev.getMetaState();
                         ted.mReprocess = mDeferTouchProcess;
                         mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
@@ -5193,8 +5199,8 @@
                         || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
                     TouchEventData ted = new TouchEventData();
                     ted.mAction = action;
-                    ted.mX = contentX;
-                    ted.mY = contentY;
+                    ted.mPoints = new Point[1];
+                    ted.mPoints[0] = new Point(contentX, contentY);
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
                     mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
@@ -5382,8 +5388,8 @@
                 if (shouldForwardTouchEvent()) {
                     TouchEventData ted = new TouchEventData();
                     ted.mAction = action;
-                    ted.mX = contentX;
-                    ted.mY = contentY;
+                    ted.mPoints = new Point[1];
+                    ted.mPoints[0] = new Point(contentX, contentY);
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
                     mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
@@ -5396,8 +5402,8 @@
                         if (inFullScreenMode() || mDeferTouchProcess) {
                             TouchEventData ted = new TouchEventData();
                             ted.mAction = WebViewCore.ACTION_DOUBLETAP;
-                            ted.mX = contentX;
-                            ted.mY = contentY;
+                            ted.mPoints = new Point[1];
+                            ted.mPoints[0] = new Point(contentX, contentY);
                             ted.mMetaState = ev.getMetaState();
                             ted.mReprocess = mDeferTouchProcess;
                             mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
@@ -5513,14 +5519,32 @@
         return true;
     }
 
+    private void passMultiTouchToWebKit(MotionEvent ev) {
+        TouchEventData ted = new TouchEventData();
+        ted.mAction = ev.getAction() & MotionEvent.ACTION_MASK;
+        final int count = ev.getPointerCount();
+        ted.mPoints = new Point[count];
+        for (int c = 0; c < count; c++) {
+            int x = viewToContentX((int) ev.getX(c) + mScrollX);
+            int y = viewToContentY((int) ev.getY(c) + mScrollY);
+            ted.mPoints[c] = new Point(x, y);
+        }
+        ted.mMetaState = ev.getMetaState();
+        ted.mReprocess = mDeferTouchProcess;
+        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+        cancelLongPress();
+        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+        mPreventDefault = PREVENT_DEFAULT_IGNORE;
+    }
+
     private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
         if (shouldForwardTouchEvent()) {
             if (removeEvents) {
                 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
             }
             TouchEventData ted = new TouchEventData();
-            ted.mX = x;
-            ted.mY = y;
+            ted.mPoints = new Point[1];
+            ted.mPoints[0] = new Point(x, y);
             ted.mAction = MotionEvent.ACTION_CANCEL;
             mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
             mPreventDefault = PREVENT_DEFAULT_IGNORE;
@@ -6517,8 +6541,9 @@
                     if (inFullScreenMode() || mDeferTouchProcess) {
                         TouchEventData ted = new TouchEventData();
                         ted.mAction = WebViewCore.ACTION_LONGPRESS;
-                        ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
-                        ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
+                        ted.mPoints = new Point[1];
+                        ted.mPoints[0] = new Point(viewToContentX((int) mLastTouchX + mScrollX),
+                                                   viewToContentY((int) mLastTouchY + mScrollY));
                         // metaState for long press is tricky. Should it be the
                         // state when the press started or when the press was
                         // released? Or some intermediary key state? For
@@ -6746,16 +6771,16 @@
                         TouchEventData ted = (TouchEventData) msg.obj;
                         switch (ted.mAction) {
                             case MotionEvent.ACTION_DOWN:
-                                mLastDeferTouchX = contentToViewX(ted.mX)
+                                mLastDeferTouchX = contentToViewX(ted.mPoints[0].x)
                                         - mScrollX;
-                                mLastDeferTouchY = contentToViewY(ted.mY)
+                                mLastDeferTouchY = contentToViewY(ted.mPoints[0].y)
                                         - mScrollY;
                                 mDeferTouchMode = TOUCH_INIT_MODE;
                                 break;
                             case MotionEvent.ACTION_MOVE: {
                                 // no snapping in defer process
-                                int x = contentToViewX(ted.mX) - mScrollX;
-                                int y = contentToViewY(ted.mY) - mScrollY;
+                                int x = contentToViewX(ted.mPoints[0].x) - mScrollX;
+                                int y = contentToViewY(ted.mPoints[0].y) - mScrollY;
                                 if (mDeferTouchMode != TOUCH_DRAG_MODE) {
                                     mDeferTouchMode = TOUCH_DRAG_MODE;
                                     mLastDeferTouchX = x;
@@ -6784,8 +6809,8 @@
                                 break;
                             case WebViewCore.ACTION_DOUBLETAP:
                                 // doDoubleTap() needs mLastTouchX/Y as anchor
-                                mLastTouchX = contentToViewX(ted.mX) - mScrollX;
-                                mLastTouchY = contentToViewY(ted.mY) - mScrollY;
+                                mLastTouchX = contentToViewX(ted.mPoints[0].x) - mScrollX;
+                                mLastTouchY = contentToViewY(ted.mPoints[0].y) - mScrollY;
                                 mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
@@ -7494,6 +7519,17 @@
     }
 
     /**
+     * Toggle whether multi touch events should be sent to webkit
+     * no matter if UI wants to handle it first.
+     *
+     * @hide This is only used by the webkit layout test.
+     */
+    public void setDeferMultiTouch(boolean value) {
+        mDeferMultitouch = value;
+        Log.v(LOGTAG, "set mDeferMultitouch to " + value);
+    }
+
+    /**
      *  Update our cache with updatedText.
      *  @param updatedText  The new text to put in our cache.
      */
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 122cf6a..a0c6974 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -506,8 +506,8 @@
     private native void nativeTouchUp(int touchGeneration,
             int framePtr, int nodePtr, int x, int y);
 
-    private native boolean nativeHandleTouchEvent(int action, int x, int y,
-            int metaState);
+    private native boolean nativeHandleTouchEvent(int action, int[] x, int[] y,
+            int count, int metaState);
 
     private native void nativeUpdateFrameCache();
 
@@ -711,8 +711,7 @@
 
     static class TouchEventData {
         int mAction;
-        int mX;
-        int mY;
+        Point[] mPoints;
         int mMetaState;
         boolean mReprocess;
     }
@@ -1180,12 +1179,19 @@
 
                         case TOUCH_EVENT: {
                             TouchEventData ted = (TouchEventData) msg.obj;
+                            final int count = ted.mPoints.length;
+                            int[] xArray = new int[count];
+                            int[] yArray = new int[count];
+                            for (int c = 0; c < count; c++) {
+                                xArray[c] = ted.mPoints[c].x;
+                                yArray[c] = ted.mPoints[c].y;
+                            }
                             Message.obtain(
                                     mWebView.mPrivateHandler,
                                     WebView.PREVENT_TOUCH_ID,
                                     ted.mAction,
-                                    nativeHandleTouchEvent(ted.mAction, ted.mX,
-                                            ted.mY, ted.mMetaState) ? 1 : 0,
+                                    nativeHandleTouchEvent(ted.mAction, xArray,
+                                            yArray, count, ted.mMetaState) ? 1 : 0,
                                     ted.mReprocess ? ted : null).sendToTarget();
                             break;
                         }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 40af8c0..19815fd 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -145,6 +145,10 @@
         // WebView::setJsFlags is noop in JSC build.
         mWebView.setJsFlags("--expose_gc");
 
+        // Always send multitouch events to Webkit since the layout test
+        // is only for the Webkit not the browser's UI.
+        mWebView.setDeferMultiTouch(true);
+
         mHandler = new AsyncHandler();
 
         Intent intent = getIntent();
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
index 716086b..383d782 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/WebViewEventSender.java
@@ -31,6 +31,7 @@
 	
     WebViewEventSender(WebView webView) {
         mWebView = webView;
+        mWebView.getSettings().setBuiltInZoomControls(true);
         mTouchPoints = new Vector<TouchPoint>();
     }
 	
@@ -170,70 +171,128 @@
 	}
 
     public void touchStart() {
-        // We only support single touch so examine the first touch point only.
-        // If multi touch is enabled in the future, we need to re-examine this to send
-        // all the touch points with the event.
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        tp.setDownTime(SystemClock.uptimeMillis());
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), tp.downTime(),
-                MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), mTouchMetaState);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        long downTime = SystemClock.uptimeMillis();
+
+        for (int i = 0; i < numPoints; ++i) {
+            pointerIds[i] = mTouchPoints.get(i).getId();
+            pointerCoords[i] = new MotionEvent.PointerCoords();
+            pointerCoords[i].x = mTouchPoints.get(i).getX();
+            pointerCoords[i].y = mTouchPoints.get(i).getY();
+            mTouchPoints.get(i).setDownTime(downTime);
+        }
+
+        MotionEvent event = MotionEvent.obtain(downTime, downTime,
+            MotionEvent.ACTION_DOWN, numPoints, pointerIds, pointerCoords,
+            mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
         mWebView.onTouchEvent(event);
     }
 
     public void touchMove() {
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        if (!tp.hasMoved()) {
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        int numMovedPoints = 0;
+        for (int i = 0; i < numPoints; ++i) {
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.hasMoved()) {
+                pointerIds[numMovedPoints] = mTouchPoints.get(i).getId();
+                pointerCoords[i] = new MotionEvent.PointerCoords();
+                pointerCoords[numMovedPoints].x = mTouchPoints.get(i).getX();
+                pointerCoords[numMovedPoints].y = mTouchPoints.get(i).getY();
+                ++numMovedPoints;
+                tp.setMoved(false);
+            }
+        }
+
+        if (numMovedPoints == 0) {
             return;
         }
 
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), mTouchMetaState);
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE,
+                numMovedPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
         mWebView.onTouchEvent(event);
-
-        tp.setMoved(false);
     }
 
     public void touchEnd() {
-        TouchPoint tp = mTouchPoints.get(0);
-
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                MotionEvent.ACTION_UP, tp.getX(), tp.getY(), mTouchMetaState);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+
+        for (int i = 0; i < numPoints; ++i) {
+            pointerIds[i] = mTouchPoints.get(i).getId();
+            pointerCoords[i] = new MotionEvent.PointerCoords();
+            pointerCoords[i].x = mTouchPoints.get(i).getX();
+            pointerCoords[i].y = mTouchPoints.get(i).getY();
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
+                numPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
         mWebView.onTouchEvent(event);
 
-        if (tp.isReleased()) {
-            mTouchPoints.remove(0);
+        for (int i = numPoints - 1; i >= 0; --i) {  // remove released points.
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.isReleased()) {
+              mTouchPoints.remove(i);
+            }
         }
     }
 
     public void touchCancel() {
-        TouchPoint tp = mTouchPoints.get(0);
-        if (tp == null) {
+        final int numPoints = mTouchPoints.size();
+        if (numPoints == 0) {
             return;
         }
 
-        if (tp.cancelled()) {
-            MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
-                    MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), mTouchMetaState);
-            mWebView.onTouchEvent(event);
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+        long cancelTime = SystemClock.uptimeMillis();
+        int numCanceledPoints = 0;
+
+        for (int i = 0; i < numPoints; ++i) {
+            TouchPoint tp = mTouchPoints.get(i);
+            if (tp.cancelled()) {
+                pointerIds[numCanceledPoints] = mTouchPoints.get(i).getId();
+                pointerCoords[numCanceledPoints] = new MotionEvent.PointerCoords();
+                pointerCoords[numCanceledPoints].x = mTouchPoints.get(i).getX();
+                pointerCoords[numCanceledPoints].y = mTouchPoints.get(i).getY();
+                ++numCanceledPoints;
+            }
         }
+
+        if (numCanceledPoints == 0) {
+            return;
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
+            SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL,
+            numCanceledPoints, pointerIds, pointerCoords,
+            mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
+        mWebView.onTouchEvent(event);
     }
 
     public void cancelTouchPoint(int id) {
-        TouchPoint tp = mTouchPoints.get(0);
+        TouchPoint tp = mTouchPoints.get(id);
         if (tp == null) {
             return;
         }
@@ -242,14 +301,19 @@
     }
 
     public void addTouchPoint(int x, int y) {
-        mTouchPoints.add(new TouchPoint(contentsToWindowX(x), contentsToWindowY(y)));
-        if (mTouchPoints.size() > 1) {
-            Log.w(LOGTAG, "Adding more than one touch point, but multi touch is not supported!");
+        final int numPoints = mTouchPoints.size();
+        int id;
+        if (numPoints == 0) {
+          id = 0;
+        } else {
+          id = mTouchPoints.get(numPoints - 1).getId() + 1;
         }
+
+        mTouchPoints.add(new TouchPoint(id, contentsToWindowX(x), contentsToWindowY(y)));
     }
 
-    public void updateTouchPoint(int id, int x, int y) {
-        TouchPoint tp = mTouchPoints.get(0);
+    public void updateTouchPoint(int i, int x, int y) {
+        TouchPoint tp = mTouchPoints.get(i);
         if (tp == null) {
             return;
         }
@@ -276,7 +340,7 @@
     }
 
     public void releaseTouchPoint(int id) {
-        TouchPoint tp = mTouchPoints.get(0);
+        TouchPoint tp = mTouchPoints.get(id);
         if (tp == null) {
             return;
         }
@@ -305,6 +369,7 @@
     private int mouseY;
 
     private class TouchPoint {
+        private int mId;
         private int mX;
         private int mY;
         private long mDownTime;
@@ -312,7 +377,8 @@
         private boolean mMoved;
         private boolean mCancelled;
 
-        public TouchPoint(int x, int y) {
+        public TouchPoint(int id, int x, int y) {
+            mId = id;
             mX = x;
             mY = y;
             mReleased = false;
@@ -332,6 +398,7 @@
         public void setMoved(boolean moved) { mMoved = moved; }
         public boolean hasMoved() { return mMoved; }
 
+        public int getId() { return mId; }
         public int getX() { return mX; }
         public int getY() { return mY; }
 
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
index 93e6137..8ac0436 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
@@ -58,6 +58,7 @@
 
     public static class TouchPoint {
         WebView mWebView;
+        private int mId;
         private int mX;
         private int mY;
         private long mDownTime;
@@ -65,12 +66,17 @@
         private boolean mMoved = false;
         private boolean mCancelled = false;
 
-        public TouchPoint(WebView webView, int x, int y) {
+        public TouchPoint(WebView webView, int id, int x, int y) {
             mWebView = webView;
+            mId = id;
             mX = scaleX(x);
             mY = scaleY(y);
         }
 
+        public int getId() {
+          return mId;
+        }
+
         public int getX() {
             return mX;
         }
@@ -215,38 +221,38 @@
                 /** TOUCH */
 
                 case MSG_ADD_TOUCH_POINT:
-                    getTouchPoints().add(new TouchPoint(mWebView,
-                            msg.arg1, msg.arg2));
-                    if (getTouchPoints().size() > 1) {
-                        Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point");
+                    int numPoints = getTouchPoints().size();
+                    int id;
+                    if (numPoints == 0) {
+                        id = 0;
+                    } else {
+                        id = getTouchPoints().get(numPoints - 1).getId() + 1;
                     }
+                    getTouchPoints().add(new TouchPoint(mWebView, id,
+                            msg.arg1, msg.arg2));
                     break;
 
                 case MSG_TOUCH_START:
-                    /**
-                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
-                     * the first touch point. In future this method will need rewriting.
-                     */
                     if (getTouchPoints().isEmpty()) {
                         return;
                     }
-                    touchPoint = getTouchPoints().get(0);
-
-                    touchPoint.setDownTime(SystemClock.uptimeMillis());
-                    executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN);
+                    for (int i = 0; i < getTouchPoints().size(); ++i) {
+                        getTouchPoints().get(i).setDownTime(SystemClock.uptimeMillis());
+                    }
+                    executeTouchEvent(MotionEvent.ACTION_DOWN);
                     break;
 
                 case MSG_UPDATE_TOUCH_POINT:
                     bundle = (Bundle)msg.obj;
 
-                    int id = bundle.getInt("id");
-                    if (id >= getTouchPoints().size()) {
+                    int index = bundle.getInt("id");
+                    if (index >= getTouchPoints().size()) {
                         Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: "
-                                + id);
+                                + index);
                         break;
                     }
 
-                    getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y"));
+                    getTouchPoints().get(index).move(bundle.getInt("x"), bundle.getInt("y"));
                     break;
 
                 case MSG_TOUCH_MOVE:
@@ -257,13 +263,10 @@
                     if (getTouchPoints().isEmpty()) {
                         return;
                     }
-                    touchPoint = getTouchPoints().get(0);
-
-                    if (!touchPoint.hasMoved()) {
-                        return;
+                    executeTouchEvent(MotionEvent.ACTION_MOVE);
+                    for (int i = 0; i < getTouchPoints().size(); ++i) {
+                        getTouchPoints().get(i).resetHasMoved();
                     }
-                    executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE);
-                    touchPoint.resetHasMoved();
                     break;
 
                 case MSG_CANCEL_TOUCH_POINT:
@@ -284,11 +287,7 @@
                     if (getTouchPoints().isEmpty()) {
                         return;
                     }
-                    touchPoint = getTouchPoints().get(0);
-
-                    if (touchPoint.isCancelled()) {
-                        executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL);
-                    }
+                    executeTouchEvent(MotionEvent.ACTION_CANCEL);
                     break;
 
                 case MSG_RELEASE_TOUCH_POINT:
@@ -309,12 +308,12 @@
                     if (getTouchPoints().isEmpty()) {
                         return;
                     }
-                    touchPoint = getTouchPoints().get(0);
-
-                    executeTouchEvent(touchPoint, MotionEvent.ACTION_UP);
-                    if (touchPoint.isReleased()) {
-                        getTouchPoints().remove(0);
-                        touchPoint = null;
+                    executeTouchEvent(MotionEvent.ACTION_UP);
+                    // remove released points.
+                    for (int i = getTouchPoints().size() - 1; i >= 0; --i) {
+                        if (getTouchPoints().get(i).isReleased()) {
+                            getTouchPoints().remove(i);
+                        }
                     }
                     break;
 
@@ -462,10 +461,48 @@
         return mTouchPoints;
     }
 
-    private void executeTouchEvent(TouchPoint touchPoint, int action) {
-        MotionEvent event =
-                MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(),
-                action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState);
+    private void executeTouchEvent(int action) {
+        int numPoints = getTouchPoints().size();
+        int[] pointerIds = new int[numPoints];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
+
+        for (int i = 0; i < numPoints; ++i) {
+            boolean isNeeded = false;
+            switch(action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_UP:
+                isNeeded = true;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                isNeeded = getTouchPoints().get(i).hasMoved();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                isNeeded = getTouchPoints().get(i).isCancelled();
+                break;
+            default:
+                Log.w(LOG_TAG + "::executeTouchEvent(),", "action not supported:" + action);
+                break;
+            }
+
+            numPoints = 0;
+            if (isNeeded) {
+                pointerIds[numPoints] = getTouchPoints().get(i).getId();
+                pointerCoords[numPoints] = new MotionEvent.PointerCoords();
+                pointerCoords[numPoints].x = getTouchPoints().get(i).getX();
+                pointerCoords[numPoints].y = getTouchPoints().get(i).getY();
+                ++numPoints;
+            }
+        }
+
+        if (numPoints == 0) {
+            return;
+        }
+
+        MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).getDownTime(),
+                SystemClock.uptimeMillis(), action,
+                numPoints, pointerIds, pointerCoords,
+                mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
+
         mWebView.onTouchEvent(event);
     }
 
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index 30d255a..089af0d 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -377,6 +377,7 @@
         webView.setTouchInterval(-1);
 
         webView.clearCache(true);
+        webView.setDeferMultiTouch(true);
 
         WebSettings webViewSettings = webView.getSettings();
         webViewSettings.setAppCacheEnabled(true);