From dd10ae14925d3567dd6c2004e7c07bcac1715842 Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Wed, 25 Jan 2012 01:31:33 +0100 Subject: [PATCH] Bug 603008 - Android widget multitouch implementation. r=blassey,kats --- config/autoconf.mk.in | 1 + embedding/android/GeckoAppShell.java | 3 + embedding/android/GeckoEvent.java | 89 +++++++++- mobile/android/base/GeckoAppShell.java | 12 ++ mobile/android/base/GeckoEvent.java | 96 ++++++++++- mobile/android/base/gfx/LayerController.java | 68 +++++++- mobile/android/base/gfx/LayerView.java | 34 +++- mobile/android/base/ui/PanZoomController.java | 2 +- mobile/android/confvars.sh | 3 + widget/android/AndroidBridge.cpp | 7 + widget/android/AndroidBridge.h | 3 + widget/android/AndroidJavaWrappers.cpp | 82 ++++++--- widget/android/AndroidJavaWrappers.h | 42 +++-- widget/android/Makefile.in | 1 + widget/android/nsWindow.cpp | 159 +++++++++++++----- widget/android/nsWindow.h | 11 +- 16 files changed, 509 insertions(+), 104 deletions(-) diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index c476a671cfe..d7bb9421a58 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -148,6 +148,7 @@ 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@ diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index e0efd993b18..11e97002bd8 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -1790,4 +1790,7 @@ public class GeckoAppShell public static void disableNetworkNotifications() { GeckoNetworkManager.getInstance().disableNotifications(); } + + // This is only used in Native Fennec. + public static void preventPanning() { } } diff --git a/embedding/android/GeckoEvent.java b/embedding/android/GeckoEvent.java index d2f4c4c2262..361018d499c 100644 --- a/embedding/android/GeckoEvent.java +++ b/embedding/android/GeckoEvent.java @@ -46,6 +46,7 @@ import android.widget.*; import android.hardware.*; import android.location.*; import android.util.FloatMath; +import android.util.DisplayMetrics; import android.util.Log; @@ -101,7 +102,12 @@ public class GeckoEvent { 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; @@ -144,10 +150,76 @@ public class GeckoEvent { 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) { @@ -227,9 +299,10 @@ public class GeckoEvent { 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) { diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 6ba6ed274d9..9df04212d02 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -503,6 +503,9 @@ public class GeckoAppShell layerController.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View view, MotionEvent event) { + if (event == null) + return true; + GeckoAppShell.sendEventToGecko(new GeckoEvent(event)); return true; } }); @@ -1117,6 +1120,15 @@ public class GeckoAppShell }); } + 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); diff --git a/mobile/android/base/GeckoEvent.java b/mobile/android/base/GeckoEvent.java index 2430c94f52b..631a071c6b8 100644 --- a/mobile/android/base/GeckoEvent.java +++ b/mobile/android/base/GeckoEvent.java @@ -48,6 +48,11 @@ 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; @@ -104,7 +109,12 @@ public class GeckoEvent { 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; @@ -145,12 +155,79 @@ public class GeckoEvent { 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) { @@ -230,9 +307,10 @@ public class GeckoEvent { 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) { diff --git a/mobile/android/base/gfx/LayerController.java b/mobile/android/base/gfx/LayerController.java index cc6d9d00221..33e7387ea36 100644 --- a/mobile/android/base/gfx/LayerController.java +++ b/mobile/android/base/gfx/LayerController.java @@ -45,10 +45,12 @@ 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; @@ -59,6 +61,8 @@ 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 @@ -100,6 +104,14 @@ public class LayerController { 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; @@ -149,6 +161,7 @@ public class LayerController { 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; @@ -347,11 +360,58 @@ public class LayerController { * 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. */ diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index 7856fd3e340..301ebdbb24d 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -48,6 +48,8 @@ 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. @@ -64,6 +66,10 @@ public class LayerView extends GLSurfaceView { 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 mEventQueue = new LinkedList(); + public LayerView(Context context, LayerController controller) { super(context); @@ -83,14 +89,40 @@ public class LayerView extends GLSurfaceView { 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; } diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 7b988e62d51..12abcce4d99 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -843,7 +843,7 @@ public class PanZoomController return true; } - private void cancelTouch() { + public void cancelTouch() { GeckoEvent e = new GeckoEvent("Gesture:CancelTouch", ""); GeckoAppShell.sendEventToGecko(e); } diff --git a/mobile/android/confvars.sh b/mobile/android/confvars.sh index dbeb6caaf7d..2f0426955ae 100644 --- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -67,6 +67,9 @@ 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 diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 733ac7de8a1..245a9365307 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -139,6 +139,7 @@ AndroidBridge::Init(JNIEnv *jEnv, 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"); @@ -1612,3 +1613,9 @@ NS_IMETHODIMP nsAndroidBridge::SetDrawMetadataProvider(nsIAndroidDrawMetadataPro return NS_OK; } +void +AndroidBridge::PreventPanning() { + ALOG_BRIDGE("AndroidBridge::PreventPanning"); + mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jPreventPanning); +} + diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index 02d29d3bf0e..69afeb3881f 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -218,6 +218,8 @@ public: void ShowInputMethodPicker(); + void PreventPanning(); + void HideProgressDialogOnce(); bool IsNetworkLinkUp(); @@ -413,6 +415,7 @@ protected: jmethodID jGetDpi; jmethodID jSetFullScreen; jmethodID jShowInputMethodPicker; + jmethodID jPreventPanning; jmethodID jHideProgressDialog; jmethodID jPerformHapticFeedback; jmethodID jVibrate1; diff --git a/widget/android/AndroidJavaWrappers.cpp b/widget/android/AndroidJavaWrappers.cpp index 82af51ec509..a0324452969 100644 --- a/widget/android/AndroidJavaWrappers.cpp +++ b/widget/android/AndroidJavaWrappers.cpp @@ -44,9 +44,11 @@ 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; @@ -64,6 +66,7 @@ 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; @@ -157,9 +160,11 @@ AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv) 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"); @@ -176,6 +181,7 @@ AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv) 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"); @@ -338,27 +344,47 @@ AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv) #undef getMethod void -AndroidGeckoEvent::ReadP0Field(JNIEnv *jenv) +AndroidGeckoEvent::ReadPointArray(nsTArray &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 &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 &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 @@ -425,9 +451,7 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj) switch (mType) { case SIZE_CHANGED: - ReadP0Field(jenv); - ReadP1Field(jenv); - ReadP2Field(jenv); + ReadPointArray(mPoints, jenv, jPoints, 3); break; case KEY_EVENT: @@ -443,9 +467,14 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj) 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: @@ -544,10 +573,7 @@ AndroidGeckoEvent::Init(AndroidGeckoEvent *aResizeEvent) 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 diff --git a/widget/android/AndroidJavaWrappers.h b/widget/android/AndroidJavaWrappers.h index c991c7a07f7..060910d18e5 100644 --- a/widget/android/AndroidJavaWrappers.h +++ b/widget/android/AndroidJavaWrappers.h @@ -434,9 +434,11 @@ public: 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 Points() { return mPoints; } + nsTArray PointIndicies() { return mPointIndicies; } + nsTArray Pressures() { return mPressures; } + nsTArray Orientations() { return mOrientations; } + nsTArray PointRadii() { return mPointRadii; } double Alpha() { return mAlpha; } double Beta() { return mBeta; } double Gamma() { return mGamma; } @@ -452,6 +454,7 @@ public: 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; } @@ -465,9 +468,11 @@ protected: int mAction; int mType; int64_t mTime; - nsIntPoint mP0; - nsIntPoint mP1; - nsIntPoint mP2; + nsTArray mPoints; + nsTArray mPointRadii; + nsTArray mPointIndicies; + nsTArray mOrientations; + nsTArray mPressures; nsIntRect mRect; int mFlags, mMetaState; int mKeyCode, mUnicodeChar; @@ -476,15 +481,25 @@ protected: int mRangeForeColor, mRangeBackColor; double mAlpha, mBeta, mGamma; double mX, mY, mZ; + int mPointerIndex; nsString mCharacters, mCharactersExtra; nsRefPtr mGeoPosition; nsRefPtr mGeoAddress; double mBandwidth; bool mCanBeMetered; - void ReadP0Field(JNIEnv *jenv); - void ReadP1Field(JNIEnv *jenv); - void ReadP2Field(JNIEnv *jenv); + void ReadIntArray(nsTArray &aVals, + JNIEnv *jenv, + jfieldID field, + PRUint32 count); + void ReadFloatArray(nsTArray &aVals, + JNIEnv *jenv, + jfieldID field, + PRUint32 count); + void ReadPointArray(nsTArray &mPoints, + JNIEnv *jenv, + jfieldID field, + PRUint32 count); void ReadRectField(JNIEnv *jenv); void ReadCharactersField(JNIEnv *jenv); void ReadCharactersExtraField(JNIEnv *jenv); @@ -493,9 +508,11 @@ protected: 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; @@ -512,6 +529,7 @@ protected: static jfieldID jFlagsField; static jfieldID jOffsetField; static jfieldID jCountField; + static jfieldID jPointerIndexField; static jfieldID jUnicodeCharField; static jfieldID jRangeTypeField; static jfieldID jRangeStylesField; diff --git a/widget/android/Makefile.in b/widget/android/Makefile.in index 0d2df2c72c9..0da1d283a99 100644 --- a/widget/android/Makefile.in +++ b/widget/android/Makefile.in @@ -103,6 +103,7 @@ LOCAL_INCLUDES += \ -I$(topsrcdir)/dom/system/android \ -I$(topsrcdir)/toolkit/components/places \ -I$(topsrcdir)/docshell/base \ + -I$(topsrcdir)/content/events/src \ -I$(srcdir) \ $(NULL) diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index c53f8593cd1..4a18b059ad9 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -58,6 +58,7 @@ using mozilla::unused; #include "nsRenderingContext.h" #include "nsIDOMSimpleGestureEvent.h" +#include "nsDOMTouchEvent.h" #include "nsGkAtoms.h" #include "nsWidgetsCID.h" @@ -895,8 +896,11 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae) win->mChildren[i]->mBounds.height = 0; } case AndroidGeckoEvent::SIZE_CHANGED: { - int nw = ae->P0().x; - int nh = ae->P0().y; + nsTArray 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) { @@ -913,11 +917,11 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae) } } - 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) @@ -951,29 +955,36 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae) 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 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; @@ -1470,30 +1481,11 @@ nsWindow::OnMotionEvent(AndroidGeckoEvent *ae) return; } - nsRefPtr 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; @@ -1501,8 +1493,8 @@ send_again: 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; @@ -1520,16 +1512,87 @@ getDistance(const nsIntPoint &p1, const nsIntPoint &p2) 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 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 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) { @@ -1612,6 +1675,26 @@ nsWindow::DispatchGestureEvent(PRUint32 msg, PRUint32 direction, double delta, 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) { diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h index 544f530a4b1..cb2fe287963 100644 --- a/widget/android/nsWindow.h +++ b/widget/android/nsWindow.h @@ -73,8 +73,9 @@ public: 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); @@ -221,9 +222,13 @@ protected: 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();