Bug 603008 - Android widget multitouch implementation. r=blassey,kats
authorWes Johnston <wjohnston@mozilla.com>
Wed, 25 Jan 2012 01:31:33 +0100
changeset 86512 0286995894b721ce49b392e0bc4d32eef9b46e1b
parent 86511 49c9b4e6325bcf30209daf1d5956017223fce744
child 86513 a63b9ee257e8414ae610b0c4dcda0c06fb5b901e
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, kats
bugs603008
milestone12.0a1
Bug 603008 - Android widget multitouch implementation. r=blassey,kats
config/autoconf.mk.in
embedding/android/GeckoAppShell.java
embedding/android/GeckoEvent.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoEvent.java
mobile/android/base/gfx/LayerController.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/ui/PanZoomController.java
mobile/android/confvars.sh
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
widget/android/Makefile.in
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -143,16 +143,17 @@ MOZ_UPDATER	= @MOZ_UPDATER@
 MOZ_UPDATE_CHANNEL	= @MOZ_UPDATE_CHANNEL@
 MOZ_UPDATE_PACKAGING	= @MOZ_UPDATE_PACKAGING@
 MOZ_DISABLE_PARENTAL_CONTROLS = @MOZ_DISABLE_PARENTAL_CONTROLS@
 NS_ENABLE_TSF = @NS_ENABLE_TSF@
 MOZ_SPELLCHECK = @MOZ_SPELLCHECK@
 MOZ_ANDROID_HISTORY = @MOZ_ANDROID_HISTORY@
 MOZ_WEBSMS_BACKEND = @MOZ_WEBSMS_BACKEND@
 MOZ_JAVA_COMPOSITOR = @MOZ_JAVA_COMPOSITOR@
+MOZ_ONLY_TOUCH_EVENTS = @MOZ_ONLY_TOUCH_EVENTS@
 MOZ_TOUCH = @MOZ_TOUCH@
 MOZ_PROFILELOCKING = @MOZ_PROFILELOCKING@
 MOZ_FEEDS = @MOZ_FEEDS@
 MOZ_TOOLKIT_SEARCH = @MOZ_TOOLKIT_SEARCH@
 MOZ_PLACES = @MOZ_PLACES@
 MOZ_SAFE_BROWSING = @MOZ_SAFE_BROWSING@
 MOZ_URL_CLASSIFIER = @MOZ_URL_CLASSIFIER@
 MOZ_ZIPWRITER = @MOZ_ZIPWRITER@
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1785,9 +1785,12 @@ public class GeckoAppShell
 
     public static void enableNetworkNotifications() {
         GeckoNetworkManager.getInstance().enableNotifications();
     }
 
     public static void disableNetworkNotifications() {
         GeckoNetworkManager.getInstance().disableNotifications();
     }
+
+    // This is only used in Native Fennec.
+    public static void preventPanning() { }
 }
--- a/embedding/android/GeckoEvent.java
+++ b/embedding/android/GeckoEvent.java
@@ -41,16 +41,17 @@ import android.os.*;
 import android.app.*;
 import android.view.*;
 import android.content.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 import android.location.*;
 import android.util.FloatMath;
+import android.util.DisplayMetrics;
 
 import android.util.Log;
 
 /* We're not allowed to hold on to most events given to us
  * so we save the parts of the events we want to use in GeckoEvent.
  * Fields have different meanings depending on the event type.
  */
 
@@ -96,17 +97,22 @@ public class GeckoEvent {
 
     public static final int IME_RANGE_UNDERLINE = 1;
     public static final int IME_RANGE_FORECOLOR = 2;
     public static final int IME_RANGE_BACKCOLOR = 4;
 
     public int mType;
     public int mAction;
     public long mTime;
-    public Point mP0, mP1, mP2;
+    public Point[] mPoints;
+    public int[] mPointIndicies;
+    public int mPointerIndex;
+    public float[] mOrientations;
+    public float[] mPressures;
+    public Point[] mPointRadii;
     public Rect mRect;
     public double mX, mY, mZ;
     public double mAlpha, mBeta, mGamma;
 
     public int mMetaState, mFlags;
     public int mKeyCode, mUnicodeChar;
     public int mOffset, mCount;
     public String mCharacters, mCharactersExtra;
@@ -139,20 +145,86 @@ public class GeckoEvent {
         mCharacters = k.getCharacters();
     }
 
     public GeckoEvent(MotionEvent m) {
         mType = MOTION_EVENT;
         mAction = m.getAction();
         mTime = m.getEventTime();
         mMetaState = m.getMetaState();
-        mP0 = new Point((int)m.getX(0), (int)m.getY(0));
-        mCount = m.getPointerCount();
-        if (mCount > 1)
-            mP1 = new Point((int)m.getX(1), (int)m.getY(1));
+
+        switch (mAction & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE: {
+                mCount = m.getPointerCount();
+                mPoints = new Point[mCount];
+                mPointIndicies = new int[mCount];
+                mOrientations = new float[mCount];
+                mPressures = new float[mCount];
+                mPointRadii = new Point[mCount];
+                mPointerIndex = (mAction & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+                for (int i = 0; i < mCount; i++) {
+                    addMotionPoint(i, i, m);
+                }
+                break;
+            }
+            default: {
+                mCount = 0;
+                mPointerIndex = -1;
+                mPoints = new Point[mCount];
+                mPointIndicies = new int[mCount];
+                mOrientations = new float[mCount];
+                mPressures = new float[mCount];
+                mPointRadii = new Point[mCount];
+            }
+        }
+    }
+
+    public void addMotionPoint(int index, int eventIndex, MotionEvent event) {
+        PointF geckoPoint = new PointF(event.getX(eventIndex), event.getY(eventIndex));
+    
+        mPoints[index] = new Point((int)Math.round(geckoPoint.x), (int)Math.round(geckoPoint.y));
+        mPointIndicies[index] = event.getPointerId(eventIndex);
+        // getToolMajor, getToolMinor and getOrientation are API Level 9 features
+        if (Build.VERSION.SDK_INT >= 9) {
+            double radians = event.getOrientation(eventIndex);
+            mOrientations[index] = (float) Math.toDegrees(radians);
+            // w3c touchevents spec does not allow orientations == 90
+            // this shifts it to -90, which will be shifted to zero below
+            if (mOrientations[index] == 90)
+                mOrientations[index] = -90;
+
+            // w3c touchevent radius are given by an orientation between 0 and 90
+            // the radius is found by removing the orientation and measuring the x and y
+            // radius of the resulting ellipse
+            // for android orientations >= 0 and < 90, the major axis should correspond to
+            // just reporting the y radius as the major one, and x as minor
+            // however, for a radius < 0, we have to shift the orientation by adding 90, and
+            // reverse which radius is major and minor
+            if (mOrientations[index] < 0) {
+                mOrientations[index] += 90;
+                mPointRadii[index] = new Point((int)event.getToolMajor(eventIndex)/2,
+                                               (int)event.getToolMinor(eventIndex)/2);
+            } else {
+                mPointRadii[index] = new Point((int)event.getToolMinor(eventIndex)/2,
+                                               (int)event.getToolMajor(eventIndex)/2);
+            }
+        } else {
+            float size = event.getSize(eventIndex);
+            DisplayMetrics displaymetrics = new DisplayMetrics();
+            GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+            size = size*Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels);
+            mPointRadii[index] = new Point((int)size,(int)size);
+            mOrientations[index] = 0;
+        }
+        mPressures[index] = event.getPressure(eventIndex);
     }
 
     public GeckoEvent(SensorEvent s) {
 
         if (s.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
             mType = ACCELERATION_EVENT;
             mX = s.values[0];
             mY = s.values[1];
@@ -222,19 +294,20 @@ public class GeckoEvent {
     public GeckoEvent(int etype, int w, int h, int screenw, int screenh) {
         if (etype != SIZE_CHANGED) {
             mType = INVALID;
             return;
         }
 
         mType = etype;
 
-        mP0 = new Point(w, h);
-        mP1 = new Point(screenw, screenh);
-        mP2 = new Point(0, 0);
+        mPoints = new Point[3];
+        mPoints[0] = new Point(w, h);
+        mPoints[1] = new Point(screenw, screenh);
+        mPoints[2] = new Point(0, 0);
     }
 
     public GeckoEvent(String subject, String data) {
         mType = BROADCAST;
         mCharacters = subject;
         mCharactersExtra = data;
     }
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -498,16 +498,19 @@ public class GeckoAppShell
     private static void geckoLoaded() {
         final LayerController layerController = GeckoApp.mAppContext.getLayerController();
         LayerView v = layerController.getView();
         mInputConnection = GeckoInputConnection.create(v);
         v.setInputConnectionHandler(mInputConnection);
 
         layerController.setOnTouchListener(new View.OnTouchListener() {
             public boolean onTouch(View view, MotionEvent event) {
+                if (event == null)
+                    return true;
+                GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
                 return true;
             }
         });
 
         layerController.notifyLayerClientOfGeometryChange();
     }
 
     static void sendPendingEventsToGecko() {
@@ -1112,16 +1115,25 @@ public class GeckoAppShell
     public static void setKeepScreenOn(final boolean on) {
         GeckoApp.mAppContext.runOnUiThread(new Runnable() {
             public void run() {
                 // TODO
             }
         });
     }
 
+    public static void preventPanning() {
+        getMainHandler().post(new Runnable() {
+            public void run() {
+                LayerController layerController = GeckoApp.mAppContext.getLayerController();
+                layerController.preventPanning(true);
+            }
+        });
+    }
+
     public static boolean isNetworkLinkUp() {
         ConnectivityManager cm = (ConnectivityManager)
             GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo info = cm.getActiveNetworkInfo();
         if (info == null || !info.isConnected())
             return false;
         return true;
     }
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -43,16 +43,21 @@ import android.os.*;
 import android.app.*;
 import android.view.*;
 import android.content.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 import android.location.*;
 import android.util.FloatMath;
+import android.util.DisplayMetrics;
+import android.graphics.PointF;
+import android.text.format.Time;
+import android.os.SystemClock;
+import java.lang.System;
 
 import android.util.Log;
 
 /* We're not allowed to hold on to most events given to us
  * so we save the parts of the events we want to use in GeckoEvent.
  * Fields have different meanings depending on the event type.
  */
 
@@ -99,17 +104,22 @@ public class GeckoEvent {
 
     public static final int IME_RANGE_UNDERLINE = 1;
     public static final int IME_RANGE_FORECOLOR = 2;
     public static final int IME_RANGE_BACKCOLOR = 4;
 
     public int mType;
     public int mAction;
     public long mTime;
-    public Point mP0, mP1, mP2;
+    public Point[] mPoints;
+    public int[] mPointIndicies;
+    public int mPointerIndex; // index of the point that has changed
+    public float[] mOrientations;
+    public float[] mPressures;
+    public Point[] mPointRadii;
     public Rect mRect;
     public double mX, mY, mZ;
     public double mAlpha, mBeta, mGamma;
 
     public int mMetaState, mFlags;
     public int mKeyCode, mUnicodeChar;
     public int mOffset, mCount;
     public String mCharacters, mCharactersExtra;
@@ -140,22 +150,89 @@ public class GeckoEvent {
         mKeyCode = k.getKeyCode();
         mUnicodeChar = k.getUnicodeChar();
         mCharacters = k.getCharacters();
     }
 
     public GeckoEvent(MotionEvent m) {
         mType = MOTION_EVENT;
         mAction = m.getAction();
-        mTime = m.getEventTime();
+        mTime = (System.currentTimeMillis() - SystemClock.elapsedRealtime()) + m.getEventTime();
         mMetaState = m.getMetaState();
-        mP0 = new Point((int)m.getX(0), (int)m.getY(0));
-        mCount = m.getPointerCount();
-        if (mCount > 1)
-            mP1 = new Point((int)m.getX(1), (int)m.getY(1));
+
+        switch (mAction & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE: {
+                mCount = m.getPointerCount();
+                mPoints = new Point[mCount];
+                mPointIndicies = new int[mCount];
+                mOrientations = new float[mCount];
+                mPressures = new float[mCount];
+                mPointRadii = new Point[mCount];
+                mPointerIndex = (mAction & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+                for (int i = 0; i < mCount; i++) {
+                    addMotionPoint(i, i, m);
+                }
+                break;
+            }
+            default: {
+                mCount = 0;
+                mPointerIndex = -1;
+                mPoints = new Point[mCount];
+                mPointIndicies = new int[mCount];
+                mOrientations = new float[mCount];
+                mPressures = new float[mCount];
+                mPointRadii = new Point[mCount];
+            }
+        }
+    }
+
+    public void addMotionPoint(int index, int eventIndex, MotionEvent event) {
+        PointF geckoPoint = new PointF(event.getX(eventIndex), event.getY(eventIndex));
+        geckoPoint = GeckoApp.mAppContext.getLayerController().convertViewPointToLayerPoint(geckoPoint);
+
+        mPoints[index] = new Point((int)Math.round(geckoPoint.x), (int)Math.round(geckoPoint.y));
+        mPointIndicies[index] = event.getPointerId(eventIndex);
+        // getToolMajor, getToolMinor and getOrientation are API Level 9 features
+        if (Build.VERSION.SDK_INT >= 9) {
+            double radians = event.getOrientation(eventIndex);
+            mOrientations[index] = (float) Math.toDegrees(radians);
+            // w3c touchevents spec does not allow orientations == 90
+            // this shifts it to -90, which will be shifted to zero below
+            if (mOrientations[index] == 90)
+                mOrientations[index] = -90;
+
+            // w3c touchevent radius are given by an orientation between 0 and 90
+            // the radius is found by removing the orientation and measuring the x and y
+            // radius of the resulting ellipse
+            // for android orientations >= 0 and < 90, the major axis should correspond to
+            // just reporting the y radius as the major one, and x as minor
+            // however, for a radius < 0, we have to shift the orientation by adding 90, and
+            // reverse which radius is major and minor
+            if (mOrientations[index] < 0) {
+                mOrientations[index] += 90;
+                mPointRadii[index] = new Point((int)event.getToolMajor(eventIndex)/2,
+                                               (int)event.getToolMinor(eventIndex)/2);
+            } else {
+                mPointRadii[index] = new Point((int)event.getToolMinor(eventIndex)/2,
+                                               (int)event.getToolMajor(eventIndex)/2);
+            }
+        } else {
+            float size = event.getSize(eventIndex);
+            DisplayMetrics displaymetrics = new DisplayMetrics();
+            GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+            size = size*Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels);
+            mPointRadii[index] = new Point((int)size,(int)size);
+            mOrientations[index] = 0;
+        }
+        mPressures[index] = event.getPressure(eventIndex);
     }
 
     public GeckoEvent(SensorEvent s) {
 
         if (s.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
             mType = ACCELERATION_EVENT;
             mX = s.values[0];
             mY = s.values[1];
@@ -225,19 +302,20 @@ public class GeckoEvent {
     public GeckoEvent(int etype, int w, int h, int screenw, int screenh, int tilew, int tileh) {
         if (etype != SIZE_CHANGED) {
             mType = INVALID;
             return;
         }
 
         mType = etype;
 
-        mP0 = new Point(w, h);
-        mP1 = new Point(screenw, screenh);
-        mP2 = new Point(tilew, tileh);
+        mPoints = new Point[3];
+        mPoints[0] = new Point(w, h);
+        mPoints[1] = new Point(screenw, screenh);
+        mPoints[2] = new Point(tilew, tileh);
     }
 
     public GeckoEvent(String subject, String data) {
         mType = BROADCAST;
         mCharacters = subject;
         mCharactersExtra = data;
     }
 
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -40,30 +40,34 @@ package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.LayerClient;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoEvent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.GestureDetector;
 import android.view.ScaleGestureDetector;
 import android.view.View.OnTouchListener;
 import java.lang.Math;
+import java.util.Timer;
+import java.util.TimerTask;
 
 /**
  * The layer controller manages a tile that represents the visible page. It does panning and
  * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
  * to a higher-level view.
  *
  * Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
  */
@@ -95,16 +99,24 @@ public class LayerController {
      */
     public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
 
     /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
      * we start aggressively redrawing to minimize checkerboarding. */
     private static final int DANGER_ZONE_X = 75;
     private static final int DANGER_ZONE_Y = 150;
 
+    /* The time limit for pages to respond with preventDefault on touchevents
+     * before we begin panning the page */
+    private static final int PREVENT_DEFAULT_TIMEOUT = 200;
+
+    private boolean allowDefaultActions = true;
+    private Timer allowDefaultTimer =  null;
+    private boolean inTouchSession = false;
+
     public LayerController(Context context) {
         mContext = context;
 
         mForceRedraw = true;
         mViewportMetrics = new ViewportMetrics();
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
     }
@@ -144,16 +156,17 @@ public class LayerController {
 
     public float getZoomFactor() {
         return mViewportMetrics.getZoomFactor();
     }
 
     public Bitmap getBackgroundPattern()    { return getDrawable("background"); }
     public Bitmap getShadowPattern()        { return getDrawable("shadow"); }
 
+    public PanZoomController getPanZoomController()                                 { return mPanZoomController; }
     public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
     public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
         return mPanZoomController;
     }
     public GestureDetector.OnDoubleTapListener getDoubleTapListener()               { return mPanZoomController; }
 
     private Bitmap getDrawable(String name) {
         Resources resources = mContext.getResources();
@@ -342,21 +355,68 @@ public class LayerController {
         return newPoint;
     }
 
     /*
      * Gesture detection. This is handled only at a high level in this class; we dispatch to the
      * pan/zoom controller to do the dirty work.
      */
     public boolean onTouchEvent(MotionEvent event) {
-        if (mPanZoomController.onTouchEvent(event))
-            return true;
+        int action = event.getAction();
+        if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+            post(new Runnable() {
+                public void run() {
+                    mView.clearEventQueue();
+                    preventPanning(true);
+                }
+            });
+        }
+
         if (mOnTouchListener != null)
-            return mOnTouchListener.onTouch(mView, event);
-        return false;
+            mOnTouchListener.onTouch(mView, event);
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
+                if (!inTouchSession && allowDefaultTimer == null) {
+                    inTouchSession = true;
+                    allowDefaultTimer = new Timer();
+                    allowDefaultTimer.schedule(new TimerTask() {
+                        public void run() {
+                            post(new Runnable() {
+                                public void run() {
+                                    preventPanning(false);
+                                }
+                            });
+                        }
+                    }, PREVENT_DEFAULT_TIMEOUT);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP: {
+                inTouchSession = false;
+            }
+        }
+        return !allowDefaultActions;
+    }
+
+    public void preventPanning(boolean aValue) {
+        if (allowDefaultTimer != null) {
+            allowDefaultTimer.cancel();
+            allowDefaultTimer.purge();
+            allowDefaultTimer = null;
+        }
+        allowDefaultActions = !aValue;
+
+        if (aValue) {
+            mView.clearEventQueue();
+            mPanZoomController.cancelTouch();
+        } else {
+            mView.processEventQueue();
+        }
     }
 
     /** Retrieves the color that the checkerboard should be. */
     public int getCheckerboardColor() {
         return mCheckerboardColor;
     }
 
     /** Sets a new color for the checkerboard. */
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -43,32 +43,38 @@ import org.mozilla.gecko.gfx.LayerContro
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 import android.content.Context;
 import android.opengl.GLSurfaceView;
 import android.view.GestureDetector;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.util.Log;
+import java.util.LinkedList;
 
 /**
  * A view rendered by the layer compositor.
  *
  * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
  * mediator between the LayerRenderer and the LayerController.
  */
 public class LayerView extends GLSurfaceView {
     private Context mContext;
     private LayerController mController;
     private InputConnectionHandler mInputConnectionHandler;
     private LayerRenderer mRenderer;
     private GestureDetector mGestureDetector;
     private SimpleScaleGestureDetector mScaleGestureDetector;
     private long mRenderTime;
     private boolean mRenderTimeReset;
+    private static String LOGTAG = "GeckoLayerView";
+    /* List of events to be processed if the page does not prevent them. Should only be touched on the main thread */
+    private LinkedList<MotionEvent> mEventQueue = new LinkedList<MotionEvent>();
+
 
     public LayerView(Context context, LayerController controller) {
         super(context);
 
         mContext = context;
         mController = controller;
         mRenderer = new LayerRenderer(this);
         setRenderer(mRenderer);
@@ -78,24 +84,50 @@ public class LayerView extends GLSurface
             new SimpleScaleGestureDetector(controller.getScaleGestureListener());
         mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
     }
 
+    private void addToEventQueue(MotionEvent event) {
+        MotionEvent copy = MotionEvent.obtain(event);
+        mEventQueue.add(copy);
+    }
+
+    public void processEventQueue() {
+        MotionEvent event = mEventQueue.poll();
+        while(event != null) {
+            processEvent(event);
+            event = mEventQueue.poll();
+        }
+    }
+
+    public void clearEventQueue() {
+        mEventQueue.clear();
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (mController.onTouchEvent(event)) {
+            addToEventQueue(event);
+            return true;
+        }
+        return processEvent(event);
+    }
+
+    private boolean processEvent(MotionEvent event) {
         if (mGestureDetector.onTouchEvent(event))
             return true;
         mScaleGestureDetector.onTouchEvent(event);
         if (mScaleGestureDetector.isInProgress())
             return true;
-        return mController.onTouchEvent(event);
+        mController.getPanZoomController().onTouchEvent(event);
+        return true;
     }
 
     public LayerController getController() { return mController; }
 
     /** The LayerRenderer calls this to indicate that the window has changed size. */
     public void setViewportSize(IntSize size) {
         mController.setViewportSize(new FloatSize(size));
     }
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -838,17 +838,17 @@ public class PanZoomController
     }
 
     @Override
     public boolean onDoubleTap(MotionEvent motionEvent) {
         sendPointToGecko("Gesture:DoubleTap", motionEvent);
         return true;
     }
 
-    private void cancelTouch() {
+    public void cancelTouch() {
         GeckoEvent e = new GeckoEvent("Gesture:CancelTouch", "");
         GeckoAppShell.sendEventToGecko(e);
     }
 
     private boolean animatedZoomTo(RectF zoomToRect) {
         GeckoApp.mAppContext.hidePluginViews();
         GeckoApp.mAppContext.mAutoCompletePopup.hide();
 
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -62,14 +62,17 @@ MOZ_ANDROID_HISTORY=1
 
 # Needed for building our components as part of libxul
 MOZ_APP_COMPONENT_LIBS="browsercomps"
 MOZ_APP_COMPONENT_INCLUDE=nsBrowserComponents.h
 
 # use custom widget for html:select
 MOZ_USE_NATIVE_POPUP_WINDOWS=1
 
+# dispatch only touch events (no mouse events)
+MOZ_ONLY_TOUCH_EVENTS=1
+
 MOZ_APP_ID={aa3c5121-dab2-40e2-81ca-7ea25febc110}
 
 MOZ_JAVA_COMPOSITOR=1
 MOZ_EXTENSION_MANAGER=1
 MOZ_APP_STATIC_INI=1
 
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -134,16 +134,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jSetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setClipboardText", "(Ljava/lang/String;)V");
     jShowAlertNotification = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jShowFilePicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePicker", "(Ljava/lang/String;)Ljava/lang/String;");
     jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jAlertsProgressListener_OnCancel = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnCancel", "(Ljava/lang/String;)V");
     jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I");
     jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V");
     jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V");
+    jPreventPanning = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "preventPanning", "()V");
     jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
     jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
     jVibrate1 = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "(J)V");
     jVibrateA = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "([JI)V");
     jCancelVibrate = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "cancelVibrate", "()V");
     jSetKeepScreenOn = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setKeepScreenOn", "(Z)V");
     jIsNetworkLinkUp = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkUp", "()Z");
     jIsNetworkLinkKnown = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkKnown", "()Z");
@@ -1607,8 +1608,14 @@ NS_IMETHODIMP nsAndroidBridge::HandleGec
 
 /* void SetDrawMetadataProvider (in nsIAndroidDrawMetadataProvider message); */
 NS_IMETHODIMP nsAndroidBridge::SetDrawMetadataProvider(nsIAndroidDrawMetadataProvider *aProvider)
 {
     gDrawMetadataProvider = aProvider;
     return NS_OK;
 }
 
+void
+AndroidBridge::PreventPanning() {
+    ALOG_BRIDGE("AndroidBridge::PreventPanning");
+    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jPreventPanning);
+}
+
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -213,16 +213,18 @@ public:
 
     void Vibrate(const nsTArray<PRUint32>& aPattern);
     void CancelVibrate();
 
     void SetFullScreen(bool aFullScreen);
 
     void ShowInputMethodPicker();
 
+    void PreventPanning();
+
     void HideProgressDialogOnce();
 
     bool IsNetworkLinkUp();
 
     bool IsNetworkLinkKnown();
 
     void SetSelectedLocale(const nsAString&);
 
@@ -408,16 +410,17 @@ protected:
     jmethodID jSetClipboardText;
     jmethodID jShowAlertNotification;
     jmethodID jShowFilePicker;
     jmethodID jAlertsProgressListener_OnProgress;
     jmethodID jAlertsProgressListener_OnCancel;
     jmethodID jGetDpi;
     jmethodID jSetFullScreen;
     jmethodID jShowInputMethodPicker;
+    jmethodID jPreventPanning;
     jmethodID jHideProgressDialog;
     jmethodID jPerformHapticFeedback;
     jmethodID jVibrate1;
     jmethodID jVibrateA;
     jmethodID jCancelVibrate;
     jmethodID jSetKeepScreenOn;
     jmethodID jIsNetworkLinkUp;
     jmethodID jIsNetworkLinkKnown;
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -39,19 +39,21 @@
 #include "AndroidBridge.h"
 
 using namespace mozilla;
 
 jclass AndroidGeckoEvent::jGeckoEventClass = 0;
 jfieldID AndroidGeckoEvent::jActionField = 0;
 jfieldID AndroidGeckoEvent::jTypeField = 0;
 jfieldID AndroidGeckoEvent::jTimeField = 0;
-jfieldID AndroidGeckoEvent::jP0Field = 0;
-jfieldID AndroidGeckoEvent::jP1Field = 0;
-jfieldID AndroidGeckoEvent::jP2Field = 0;
+jfieldID AndroidGeckoEvent::jPoints = 0;
+jfieldID AndroidGeckoEvent::jPointIndicies = 0;
+jfieldID AndroidGeckoEvent::jPressures = 0;
+jfieldID AndroidGeckoEvent::jPointRadii = 0;
+jfieldID AndroidGeckoEvent::jOrientations = 0;
 jfieldID AndroidGeckoEvent::jAlphaField = 0;
 jfieldID AndroidGeckoEvent::jBetaField = 0;
 jfieldID AndroidGeckoEvent::jGammaField = 0;
 jfieldID AndroidGeckoEvent::jXField = 0;
 jfieldID AndroidGeckoEvent::jYField = 0;
 jfieldID AndroidGeckoEvent::jZField = 0;
 jfieldID AndroidGeckoEvent::jRectField = 0;
 jfieldID AndroidGeckoEvent::jNativeWindowField = 0;
@@ -59,16 +61,17 @@ jfieldID AndroidGeckoEvent::jNativeWindo
 jfieldID AndroidGeckoEvent::jCharactersField = 0;
 jfieldID AndroidGeckoEvent::jCharactersExtraField = 0;
 jfieldID AndroidGeckoEvent::jKeyCodeField = 0;
 jfieldID AndroidGeckoEvent::jMetaStateField = 0;
 jfieldID AndroidGeckoEvent::jFlagsField = 0;
 jfieldID AndroidGeckoEvent::jUnicodeCharField = 0;
 jfieldID AndroidGeckoEvent::jOffsetField = 0;
 jfieldID AndroidGeckoEvent::jCountField = 0;
+jfieldID AndroidGeckoEvent::jPointerIndexField = 0;
 jfieldID AndroidGeckoEvent::jRangeTypeField = 0;
 jfieldID AndroidGeckoEvent::jRangeStylesField = 0;
 jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
 jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 jfieldID AndroidGeckoEvent::jAddressField = 0;
 jfieldID AndroidGeckoEvent::jBandwidthField = 0;
 jfieldID AndroidGeckoEvent::jCanBeMeteredField = 0;
@@ -152,35 +155,38 @@ AndroidGeckoEvent::InitGeckoEventClass(J
 {
     initInit();
 
     jGeckoEventClass = getClassGlobalRef("org/mozilla/gecko/GeckoEvent");
 
     jActionField = getField("mAction", "I");
     jTypeField = getField("mType", "I");
     jTimeField = getField("mTime", "J");
-    jP0Field = getField("mP0", "Landroid/graphics/Point;");
-    jP1Field = getField("mP1", "Landroid/graphics/Point;");
-    jP2Field = getField("mP2", "Landroid/graphics/Point;");
+    jPoints = getField("mPoints", "[Landroid/graphics/Point;");
+    jPointIndicies = getField("mPointIndicies", "[I");
+    jOrientations = getField("mOrientations", "[F");
+    jPressures = getField("mPressures", "[F");
+    jPointRadii = getField("mPointRadii", "[Landroid/graphics/Point;");
     jAlphaField = getField("mAlpha", "D");
     jBetaField = getField("mBeta", "D");
     jGammaField = getField("mGamma", "D");
     jXField = getField("mX", "D");
     jYField = getField("mY", "D");
     jZField = getField("mZ", "D");
     jRectField = getField("mRect", "Landroid/graphics/Rect;");
 
     jCharactersField = getField("mCharacters", "Ljava/lang/String;");
     jCharactersExtraField = getField("mCharactersExtra", "Ljava/lang/String;");
     jKeyCodeField = getField("mKeyCode", "I");
     jMetaStateField = getField("mMetaState", "I");
     jFlagsField = getField("mFlags", "I");
     jUnicodeCharField = getField("mUnicodeChar", "I");
     jOffsetField = getField("mOffset", "I");
     jCountField = getField("mCount", "I");
+    jPointerIndexField = getField("mPointerIndex", "I");
     jRangeTypeField = getField("mRangeType", "I");
     jRangeStylesField = getField("mRangeStyles", "I");
     jRangeForeColorField = getField("mRangeForeColor", "I");
     jRangeBackColorField = getField("mRangeBackColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
     jAddressField = getField("mAddress", "Landroid/location/Address;");
     jBandwidthField = getField("mBandwidth", "D");
     jCanBeMeteredField = getField("mCanBeMetered", "Z");
@@ -333,37 +339,57 @@ AndroidGeckoSoftwareLayerClient::InitGec
 }
 
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
 void
-AndroidGeckoEvent::ReadP0Field(JNIEnv *jenv)
+AndroidGeckoEvent::ReadPointArray(nsTArray<nsIntPoint> &points,
+                                  JNIEnv *jenv,
+                                  jfieldID field,
+                                  PRUint32 count)
 {
-    AndroidPoint p0(jenv, jenv->GetObjectField(wrappedObject(), jP0Field));
-    mP0.x = p0.X();
-    mP0.y = p0.Y();
+    jobjectArray jObjArray = (jobjectArray)jenv->GetObjectField(wrapped_obj, field);
+    for (PRInt32 i = 0; i < count; i++) {
+        jobject jObj = jenv->GetObjectArrayElement(jObjArray, i);
+        AndroidPoint jpoint(jenv, jObj);
+
+        nsIntPoint p(jpoint.X(), jpoint.Y());
+        points.AppendElement(p);
+    }
 }
 
 void
-AndroidGeckoEvent::ReadP1Field(JNIEnv *jenv)
+AndroidGeckoEvent::ReadIntArray(nsTArray<int> &aVals,
+                                JNIEnv *jenv,
+                                jfieldID field,
+                                PRUint32 count)
 {
-    AndroidPoint p1(jenv, jenv->GetObjectField(wrappedObject(), jP1Field));
-    mP1.x = p1.X();
-    mP1.y = p1.Y();
+    jintArray jIntArray = (jintArray)jenv->GetObjectField(wrapped_obj, field);
+    jint *vals = jenv->GetIntArrayElements(jIntArray, false);
+    for (PRInt32 i = 0; i < count; i++) {
+        aVals.AppendElement(vals[i]);
+    }
+    jenv->ReleaseIntArrayElements(jIntArray, vals, JNI_ABORT);
 }
 
 void
-AndroidGeckoEvent::ReadP2Field(JNIEnv *jenv)
+AndroidGeckoEvent::ReadFloatArray(nsTArray<float> &aVals,
+                                  JNIEnv *jenv,
+                                  jfieldID field,
+                                  PRUint32 count)
 {
-    AndroidPoint p2(jenv, jenv->GetObjectField(wrappedObject(), jP2Field));
-    mP2.x = p2.X();
-    mP2.y = p2.Y();
+    jfloatArray jFloatArray = (jfloatArray)jenv->GetObjectField(wrapped_obj, field);
+    jfloat *vals = jenv->GetFloatArrayElements(jFloatArray, false);
+    for (PRInt32 i = 0; i < count; i++) {
+        aVals.AppendElement(vals[i]);
+    }
+    jenv->ReleaseFloatArrayElements(jFloatArray, vals, JNI_ABORT);
 }
 
 void
 AndroidGeckoEvent::ReadRectField(JNIEnv *jenv)
 {
     AndroidRect r(jenv, jenv->GetObjectField(wrappedObject(), jRectField));
     if (!r.isNull()) {
         mRect.SetRect(r.Left(),
@@ -420,37 +446,40 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
     if (!jobj)
         return;
 
     mAction = jenv->GetIntField(jobj, jActionField);
     mType = jenv->GetIntField(jobj, jTypeField);
 
     switch (mType) {
         case SIZE_CHANGED:
-            ReadP0Field(jenv);
-            ReadP1Field(jenv);
-            ReadP2Field(jenv);
+            ReadPointArray(mPoints, jenv, jPoints, 3);
             break;
 
         case KEY_EVENT:
             mTime = jenv->GetLongField(jobj, jTimeField);
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             mFlags = jenv->GetIntField(jobj, jFlagsField);
             mKeyCode = jenv->GetIntField(jobj, jKeyCodeField);
             mUnicodeChar = jenv->GetIntField(jobj, jUnicodeCharField);
             ReadCharactersField(jenv);
             break;
 
         case MOTION_EVENT:
             mTime = jenv->GetLongField(jobj, jTimeField);
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             mCount = jenv->GetIntField(jobj, jCountField);
-            ReadP0Field(jenv);
-            if (mCount > 1)
-                ReadP1Field(jenv);
+            mPointerIndex = jenv->GetIntField(jobj, jPointerIndexField);
+
+            ReadPointArray(mPointRadii, jenv, jPointRadii, mCount);
+            ReadFloatArray(mOrientations, jenv, jOrientations, mCount);
+            ReadFloatArray(mPressures, jenv, jPressures, mCount);
+            ReadPointArray(mPoints, jenv, jPoints, mCount);
+            ReadIntArray(mPointIndicies, jenv, jPointIndicies, mCount);
+
             break;
 
         case IME_EVENT:
             if (mAction == IME_GET_TEXT || mAction == IME_SET_SELECTION) {
                 mOffset = jenv->GetIntField(jobj, jOffsetField);
                 mCount = jenv->GetIntField(jobj, jCountField);
             } else if (mAction == IME_SET_TEXT || mAction == IME_ADD_RANGE) {
                 if (mAction == IME_SET_TEXT)
@@ -539,20 +568,17 @@ AndroidGeckoEvent::Init(int x1, int y1, 
 
 void
 AndroidGeckoEvent::Init(AndroidGeckoEvent *aResizeEvent)
 {
     NS_ASSERTION(aResizeEvent->Type() == SIZE_CHANGED, "Init called on non-SIZE_CHANGED event");
 
     mType = FORCED_RESIZE;
     mTime = aResizeEvent->mTime;
-    mP0.x = aResizeEvent->mP0.x;
-    mP0.y = aResizeEvent->mP0.y;
-    mP1.x = aResizeEvent->mP1.x;
-    mP1.y = aResizeEvent->mP1.y;
+    mPoints = aResizeEvent->mPoints; // x,y coordinates
 }
 
 void
 AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
 {
     if (jobj) {
         mX = jenv->GetIntField(jobj, jXField);
         mY = jenv->GetIntField(jobj, jYField);
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -429,94 +429,112 @@ public:
     void Init(int aType);
     void Init(int x1, int y1, int x2, int y2);
     void Init(int aType, const nsIntRect &aRect);
     void Init(AndroidGeckoEvent *aResizeEvent);
 
     int Action() { return mAction; }
     int Type() { return mType; }
     int64_t Time() { return mTime; }
-    const nsIntPoint& P0() { return mP0; }
-    const nsIntPoint& P1() { return mP1; }
-    const nsIntPoint& P2() { return mP2; }
+    nsTArray<nsIntPoint> Points() { return mPoints; }
+    nsTArray<int> PointIndicies() { return mPointIndicies; }
+    nsTArray<float> Pressures() { return mPressures; }
+    nsTArray<float> Orientations() { return mOrientations; }
+    nsTArray<nsIntPoint> PointRadii() { return mPointRadii; }
     double Alpha() { return mAlpha; }
     double Beta() { return mBeta; }
     double Gamma() { return mGamma; }
     double X() { return mX; }
     double Y() { return mY; }
     double Z() { return mZ; }
     const nsIntRect& Rect() { return mRect; }
     nsAString& Characters() { return mCharacters; }
     nsAString& CharactersExtra() { return mCharactersExtra; }
     int KeyCode() { return mKeyCode; }
     int MetaState() { return mMetaState; }
     int Flags() { return mFlags; }
     int UnicodeChar() { return mUnicodeChar; }
     int Offset() { return mOffset; }
     int Count() { return mCount; }
+    int PointerIndex() { return mPointerIndex; }
     int RangeType() { return mRangeType; }
     int RangeStyles() { return mRangeStyles; }
     int RangeForeColor() { return mRangeForeColor; }
     int RangeBackColor() { return mRangeBackColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     nsGeoPositionAddress* GeoAddress() { return mGeoAddress; }
     double Bandwidth() { return mBandwidth; }
     bool CanBeMetered() { return mCanBeMetered; }
 
 protected:
     int mAction;
     int mType;
     int64_t mTime;
-    nsIntPoint mP0;
-    nsIntPoint mP1;
-    nsIntPoint mP2;
+    nsTArray<nsIntPoint> mPoints;
+    nsTArray<nsIntPoint> mPointRadii;
+    nsTArray<int> mPointIndicies;
+    nsTArray<float> mOrientations;
+    nsTArray<float> mPressures;
     nsIntRect mRect;
     int mFlags, mMetaState;
     int mKeyCode, mUnicodeChar;
     int mOffset, mCount;
     int mRangeType, mRangeStyles;
     int mRangeForeColor, mRangeBackColor;
     double mAlpha, mBeta, mGamma;
     double mX, mY, mZ;
+    int mPointerIndex;
     nsString mCharacters, mCharactersExtra;
     nsRefPtr<nsGeoPosition> mGeoPosition;
     nsRefPtr<nsGeoPositionAddress> mGeoAddress;
     double mBandwidth;
     bool mCanBeMetered;
 
-    void ReadP0Field(JNIEnv *jenv);
-    void ReadP1Field(JNIEnv *jenv);
-    void ReadP2Field(JNIEnv *jenv);
+    void ReadIntArray(nsTArray<int> &aVals,
+                      JNIEnv *jenv,
+                      jfieldID field,
+                      PRUint32 count);
+    void ReadFloatArray(nsTArray<float> &aVals,
+                        JNIEnv *jenv,
+                        jfieldID field,
+                        PRUint32 count);
+    void ReadPointArray(nsTArray<nsIntPoint> &mPoints,
+                        JNIEnv *jenv,
+                        jfieldID field,
+                        PRUint32 count);
     void ReadRectField(JNIEnv *jenv);
     void ReadCharactersField(JNIEnv *jenv);
     void ReadCharactersExtraField(JNIEnv *jenv);
 
     static jclass jGeckoEventClass;
     static jfieldID jActionField;
     static jfieldID jTypeField;
     static jfieldID jTimeField;
-    static jfieldID jP0Field;
-    static jfieldID jP1Field;
-    static jfieldID jP2Field;
+    static jfieldID jPoints;
+    static jfieldID jPointIndicies;
+    static jfieldID jOrientations;
+    static jfieldID jPressures;
+    static jfieldID jPointRadii;
     static jfieldID jAlphaField;
     static jfieldID jBetaField;
     static jfieldID jGammaField;
     static jfieldID jXField;
     static jfieldID jYField;
     static jfieldID jZField;
     static jfieldID jRectField;
     static jfieldID jNativeWindowField;
 
     static jfieldID jCharactersField;
     static jfieldID jCharactersExtraField;
     static jfieldID jKeyCodeField;
     static jfieldID jMetaStateField;
     static jfieldID jFlagsField;
     static jfieldID jOffsetField;
     static jfieldID jCountField;
+    static jfieldID jPointerIndexField;
     static jfieldID jUnicodeCharField;
     static jfieldID jRangeTypeField;
     static jfieldID jRangeStylesField;
     static jfieldID jRangeForeColorField;
     static jfieldID jRangeBackColorField;
     static jfieldID jLocationField;
     static jfieldID jAddressField;
 
--- a/widget/android/Makefile.in
+++ b/widget/android/Makefile.in
@@ -98,12 +98,13 @@ DEFINES += -D_IMPL_NS_WIDGET
 #DEFINES += -DDEBUG_WIDGETS
 
 LOCAL_INCLUDES += \
 	-I$(topsrcdir)/widget/xpwidgets \
 	-I$(topsrcdir)/widget/shared \
 	-I$(topsrcdir)/dom/system/android \
 	-I$(topsrcdir)/toolkit/components/places \
 	-I$(topsrcdir)/docshell/base \
+	-I$(topsrcdir)/content/events/src \
 	-I$(srcdir) \
 	$(NULL)
 
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -53,16 +53,17 @@ using mozilla::unused;
 #include "nsAppShell.h"
 #include "nsIdleService.h"
 #include "nsWindow.h"
 #include "nsIObserverService.h"
 #include "nsFocusManager.h"
 
 #include "nsRenderingContext.h"
 #include "nsIDOMSimpleGestureEvent.h"
+#include "nsDOMTouchEvent.h"
 
 #include "nsGkAtoms.h"
 #include "nsWidgetsCID.h"
 #include "nsGfxCIID.h"
 
 #include "gfxImageSurface.h"
 #include "gfxContext.h"
 
@@ -890,39 +891,42 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             win->mBounds.width = 0;
             win->mBounds.height = 0;
             // also resize the children
             for (PRUint32 i = 0; i < win->mChildren.Length(); i++) {
                 win->mChildren[i]->mBounds.width = 0;
                 win->mChildren[i]->mBounds.height = 0;
             }
         case AndroidGeckoEvent::SIZE_CHANGED: {
-            int nw = ae->P0().x;
-            int nh = ae->P0().y;
+            nsTArray<nsIntPoint> points = ae->Points();
+            NS_ASSERTION(points.Length() != 3, "Size changed does not have enough coordinates");
+
+            int nw = points[0].x;
+            int nh = points[0].y;
 
             if (ae->Type() == AndroidGeckoEvent::FORCED_RESIZE || nw != gAndroidBounds.width ||
                 nh != gAndroidBounds.height) {
 
                 gAndroidBounds.width = nw;
                 gAndroidBounds.height = nh;
 
                 // tell all the windows about the new size
                 for (size_t i = 0; i < gTopLevelWindows.Length(); ++i) {
                     if (gTopLevelWindows[i]->mIsVisible)
                         gTopLevelWindows[i]->Resize(gAndroidBounds.width,
                                                     gAndroidBounds.height,
                                                     true);
                 }
             }
 
-            gAndroidTileSize.width = ae->P2().x;
-            gAndroidTileSize.height = ae->P2().y;
+            gAndroidTileSize.width = points[2].x;
+            gAndroidTileSize.height = points[2].y;
 
-            int newScreenWidth = ae->P1().x;
-            int newScreenHeight = ae->P1().y;
+            int newScreenWidth = points[1].x;
+            int newScreenHeight = points[1].y;
 
             if (newScreenWidth == gAndroidScreenBounds.width &&
                 newScreenHeight == gAndroidScreenBounds.height)
                 break;
 
             gAndroidScreenBounds.width = newScreenWidth;
             gAndroidScreenBounds.height = newScreenHeight;
 
@@ -946,39 +950,46 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
 
             nsCOMPtr<ContentCreationNotifier> notifier = new ContentCreationNotifier;
             if (NS_SUCCEEDED(obs->AddObserver(notifier, "ipc:content-created", false))) {
                 if (NS_SUCCEEDED(obs->AddObserver(notifier, "xpcom-shutdown", false)))
                     gContentCreationNotifier = notifier;
                 else
                     obs->RemoveObserver(notifier, "ipc:content-created");
             }
+            break;
         }
 
         case AndroidGeckoEvent::MOTION_EVENT: {
             win->UserActivity();
             if (!gTopLevelWindows.IsEmpty()) {
-                nsIntPoint pt(ae->P0());
+                nsIntPoint pt(0,0);
+                nsTArray<nsIntPoint> points = ae->Points();
+                if (points.Length() > 0) {
+                    pt = points[0];
+                }
                 pt.x = clamped(pt.x, 0, gAndroidBounds.width - 1);
                 pt.y = clamped(pt.y, 0, gAndroidBounds.height - 1);
                 nsWindow *target = win->FindWindowForPoint(pt);
-
 #if 0
-                ALOG("MOTION_EVENT %f,%f -> %p (visible: %d children: %d)", ae->P0().x, ae->P0().y, (void*)target,
+                ALOG("MOTION_EVENT %f,%f -> %p (visible: %d children: %d)", pt.x, pt.y, (void*)target,
                      target ? target->mIsVisible : 0,
                      target ? target->mChildren.Length() : 0);
 
                 DumpWindows();
 #endif
-
                 if (target) {
-                    if (ae->Count() > 1)
-                        target->OnMultitouchEvent(ae);
-                    else
+                    bool preventDefaultActions = target->OnMultitouchEvent(ae);
+                    if (!preventDefaultActions && ae->Count() == 2) {
+                        target->OnGestureEvent(ae);
+                    }
+#ifndef MOZ_ONLY_TOUCH_EVENTS
+                    if (!preventDefaultActions && ae->Count() < 2)
                         target->OnMotionEvent(ae);
+#endif
                 }
             }
             break;
         }
 
         case AndroidGeckoEvent::KEY_EVENT:
             win->UserActivity();
             if (win->mFocus)
@@ -1465,49 +1476,30 @@ nsWindow::OnMotionEvent(AndroidGeckoEven
         case AndroidMotionEvent::ACTION_CANCEL:
             msg = NS_MOUSE_BUTTON_UP;
             break;
 
         default:
             return;
     }
 
-    nsRefPtr<nsWindow> kungFuDeathGrip(this);
-    nsIntPoint pt(ae->P0());
-    nsIntPoint offset = WidgetToScreenOffset();
-
-    //ALOG("#### motion pt: %d %d offset: %d %d", pt.x, pt.y, offset.x, offset.y);
-
-    pt.x -= offset.x;
-    pt.y -= offset.y;
-
-    // XXX possibly bound the range of pt here. some code may get confused.
-
 send_again:
 
     nsMouseEvent event(true,
                        msg, this,
                        nsMouseEvent::eReal, nsMouseEvent::eNormal);
-    InitEvent(event, &pt);
-
-    event.time = ae->Time();
-    event.isShift = !!(ae->MetaState() & AndroidKeyEvent::META_SHIFT_ON);
-    event.isControl = false;
-    event.isMeta = false;
-    event.isAlt = !!(ae->MetaState() & AndroidKeyEvent::META_ALT_ON);
-
     // XXX can we synthesize different buttons?
     event.button = nsMouseEvent::eLeftButton;
 
     if (msg != NS_MOUSE_MOVE)
         event.clickCount = 1;
 
     // XXX add the double-click handling logic here
-
-    DispatchEvent(&event);
+    if (ae->Points().Length() > 0)
+        DispatchMotionEvent(event, ae, ae->Points()[0]);
     if (Destroyed())
         return;
 
     if (msg == NS_MOUSE_BUTTON_DOWN) {
         msg = NS_MOUSE_MOVE;
         goto send_again;
     }
 }
@@ -1515,26 +1507,97 @@ send_again:
 static double
 getDistance(const nsIntPoint &p1, const nsIntPoint &p2)
 {
     double deltaX = p2.x - p1.x;
     double deltaY = p2.y - p1.y;
     return sqrt(deltaX*deltaX + deltaY*deltaY);
 }
 
-void nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)
+bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)
+{
+    switch (ae->Action() & AndroidMotionEvent::ACTION_MASK) {
+        case AndroidMotionEvent::ACTION_DOWN:
+        case AndroidMotionEvent::ACTION_POINTER_DOWN: {
+            nsTouchEvent event(PR_TRUE, NS_TOUCH_START, this);
+            return DispatchMultitouchEvent(event, ae);
+        }
+        case AndroidMotionEvent::ACTION_MOVE: {
+            nsTouchEvent event(PR_TRUE, NS_TOUCH_MOVE, this);
+            return DispatchMultitouchEvent(event, ae);
+        }
+        case AndroidMotionEvent::ACTION_UP:
+        case AndroidMotionEvent::ACTION_POINTER_UP: {
+            nsTouchEvent event(PR_TRUE, NS_TOUCH_END, this);
+            return DispatchMultitouchEvent(event, ae);
+        }
+        case AndroidMotionEvent::ACTION_OUTSIDE:
+        case AndroidMotionEvent::ACTION_CANCEL: {
+            nsTouchEvent event(PR_TRUE, NS_TOUCH_CANCEL, this);
+            return DispatchMultitouchEvent(event, ae);
+        }
+    }
+    return false;
+}
+
+bool
+nsWindow::DispatchMultitouchEvent(nsTouchEvent &event, AndroidGeckoEvent *ae)
+{
+    nsIntPoint offset = WidgetToScreenOffset();
+
+    event.isShift = false;
+    event.isControl = false;
+    event.isMeta = false;
+    event.isAlt = false;
+    event.time = ae->Time();
+
+    int action = ae->Action() & AndroidMotionEvent::ACTION_MASK;
+    if (action == AndroidMotionEvent::ACTION_UP ||
+        action == AndroidMotionEvent::ACTION_POINTER_UP) {
+        event.touches.SetCapacity(1);
+        int pointerIndex = ae->PointerIndex();
+        nsCOMPtr<nsIDOMTouch> t(new nsDOMTouch(ae->PointIndicies()[pointerIndex],
+                                               ae->Points()[pointerIndex] - offset,
+                                               ae->PointRadii()[pointerIndex],
+                                               ae->Orientations()[pointerIndex],
+                                               ae->Pressures()[pointerIndex]));
+        event.touches.AppendElement(t);
+    } else {
+        int count = ae->Count();
+        event.touches.SetCapacity(count);
+        for (int i = 0; i < count; i++) {
+            nsCOMPtr<nsIDOMTouch> t(new nsDOMTouch(ae->PointIndicies()[i],
+                                                   ae->Points()[i] - offset,
+                                                   ae->PointRadii()[i],
+                                                   ae->Orientations()[i],
+                                                   ae->Pressures()[i]));
+            event.touches.AppendElement(t);
+        }
+    }
+
+    nsEventStatus status;
+    DispatchEvent(&event, status);
+    if (status == nsEventStatus_eConsumeNoDefault) {
+        AndroidBridge::Bridge()->PreventPanning();
+        return true;
+    }
+    return false;
+}
+
+void
+nsWindow::OnGestureEvent(AndroidGeckoEvent *ae)
 {
     PRUint32 msg = 0;
 
     nsIntPoint midPoint;
-    midPoint.x = ((ae->P0().x + ae->P1().x) / 2);
-    midPoint.y = ((ae->P0().y + ae->P1().y) / 2);
+    midPoint.x = ((ae->Points()[0].x + ae->Points()[1].x) / 2);
+    midPoint.y = ((ae->Points()[0].y + ae->Points()[1].y) / 2);
     nsIntPoint refPoint = midPoint - WidgetToScreenOffset();
 
-    double pinchDist = getDistance(ae->P0(), ae->P1());
+    double pinchDist = getDistance(ae->Points()[0], ae->Points()[1]);
     double pinchDelta = 0;
 
     switch (ae->Action() & AndroidMotionEvent::ACTION_MASK) {
         case AndroidMotionEvent::ACTION_POINTER_DOWN:
             msg = NS_SIMPLE_GESTURE_MAGNIFY_START;
             mStartPoint = new nsIntPoint(midPoint);
             mStartDist = mLastDist = pinchDist;
             mGestureFinished = false;
@@ -1607,16 +1670,36 @@ nsWindow::DispatchGestureEvent(PRUint32 
     event.isMeta = false;
     event.isAlt = false;
     event.time = time;
     event.refPoint = refPoint;
 
     DispatchEvent(&event);
 }
 
+
+void
+nsWindow::DispatchMotionEvent(nsInputEvent &event, AndroidGeckoEvent *ae,
+                              const nsIntPoint &refPoint)
+{
+    nsIntPoint offset = WidgetToScreenOffset();
+
+    event.isShift = PR_FALSE;
+    event.isControl = PR_FALSE;
+    event.isMeta = PR_FALSE;
+    event.isAlt = PR_FALSE;
+    event.time = ae->Time();
+
+    // XXX possibly bound the range of event.refPoint here.
+    //     some code may get confused.
+    event.refPoint = refPoint - offset;
+
+    DispatchEvent(&event);
+}
+
 void
 nsWindow::InitKeyEvent(nsKeyEvent& event, AndroidGeckoEvent& key)
 {
     switch (key.KeyCode()) {
     case AndroidKeyEvent::KEYCODE_UNKNOWN:
     case AndroidKeyEvent::KEYCODE_HOME:
         break;
     case AndroidKeyEvent::KEYCODE_BACK:
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -68,18 +68,19 @@ public:
 
     static void OnGlobalAndroidEvent(mozilla::AndroidGeckoEvent *ae);
     static gfxIntSize GetAndroidScreenBounds();
 
     nsWindow* FindWindowForPoint(const nsIntPoint& pt);
 
     void OnAndroidEvent(mozilla::AndroidGeckoEvent *ae);
     void OnDraw(mozilla::AndroidGeckoEvent *ae);
+    bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
+    void OnGestureEvent(mozilla::AndroidGeckoEvent *ae);
     void OnMotionEvent(mozilla::AndroidGeckoEvent *ae);
-    void OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
     void OnKeyEvent(mozilla::AndroidGeckoEvent *ae);
     void OnIMEEvent(mozilla::AndroidGeckoEvent *ae);
 
     void OnSizeChanged(const gfxIntSize& aSize);
 
     void InitEvent(nsGUIEvent& event, nsIntPoint* aPoint = 0);
 
     //
@@ -216,19 +217,23 @@ protected:
     InputContext mInputContext;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private:
     void InitKeyEvent(nsKeyEvent& event, mozilla::AndroidGeckoEvent& key);
-    void DispatchGestureEvent(mozilla::AndroidGeckoEvent *ae);
+    bool DispatchMultitouchEvent(nsTouchEvent &event,
+                             mozilla::AndroidGeckoEvent *ae);
+    void DispatchMotionEvent(nsInputEvent &event,
+                             mozilla::AndroidGeckoEvent *ae,
+                             const nsIntPoint &refPoint);
     void DispatchGestureEvent(PRUint32 msg, PRUint32 direction, double delta,
-                               const nsIntPoint &refPoint, PRUint64 time);
+                              const nsIntPoint &refPoint, PRUint64 time);
     void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);
     void RedrawAll();
 
 #ifdef ACCESSIBILITY
     nsRefPtr<nsAccessible> mRootAccessible;
 
     /**
      * Request to create the accessible for this window if it is top level.