Bug 742019 - Rewrite how we handle touch events so we don't break panning, and don't introduce unnecessary latency. r=wesj

This commit is contained in:
Kartikaya Gupta 2012-04-07 03:09:26 -04:00
parent 218ff032bd
commit e685c4edc8
12 changed files with 362 additions and 181 deletions

View File

@ -1846,7 +1846,7 @@ public class GeckoAppShell
}
// This is only used in Native Fennec.
public static void setPreventPanning(final boolean aPreventPanning) { }
public static void notifyDefaultPrevented(boolean defaultPrevented) { }
public static short getScreenOrientation() {
return GeckoScreenOrientationListener.getInstance().getScreenOrientation();

View File

@ -991,7 +991,7 @@ abstract public class GeckoApp
if (Tabs.getInstance().isSelectedTab(tab)) {
mMainHandler.post(new Runnable() {
public void run() {
mLayerController.setWaitForTouchListeners(true);
mLayerController.getView().getTouchEventHandler().setWaitForTouchListeners(true);
}
});
}
@ -2805,7 +2805,7 @@ abstract public class GeckoApp
LayerController layerController = getLayerController();
layerController.setLayerClient(mLayerClient);
layerController.setOnTouchListener(new View.OnTouchListener() {
layerController.getView().getTouchEventHandler().setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
if (event == null)
return true;

View File

@ -1201,11 +1201,11 @@ public class GeckoAppShell
});
}
public static void setPreventPanning(final boolean aPreventPanning) {
public static void notifyDefaultPrevented(final boolean defaultPrevented) {
getMainHandler().post(new Runnable() {
public void run() {
LayerController layerController = GeckoApp.mAppContext.getLayerController();
layerController.preventPanning(aPreventPanning);
LayerView view = GeckoApp.mAppContext.getLayerController().getView();
view.getTouchEventHandler().handleEventListenerAction(!defaultPrevented);
}
});
}

View File

@ -144,6 +144,7 @@ FENNEC_JAVA_FILES = \
gfx/TextureGenerator.java \
gfx/TextureReaper.java \
gfx/TileLayer.java \
gfx/TouchEventHandler.java \
gfx/ViewTransform.java \
gfx/ViewportMetrics.java \
gfx/VirtualLayer.java \

View File

@ -38,33 +38,20 @@
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.ui.PanZoomController;
import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Tab;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
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 android.view.ViewConfiguration;
import java.lang.Math;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -75,7 +62,7 @@ import java.util.regex.Pattern;
*
* Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
*/
public class LayerController implements Tabs.OnTabsChangedListener {
public class LayerController {
private static final String LOGTAG = "GeckoLayerController";
private Layer mRootLayer; /* The root layer. */
@ -95,15 +82,12 @@ public class LayerController implements Tabs.OnTabsChangedListener {
* fields. */
private volatile ImmutableViewportMetrics mViewportMetrics; /* The current viewport metrics. */
private boolean mWaitForTouchListeners;
private PanZoomController mPanZoomController;
/*
* The panning and zooming controller, which interprets pan and zoom gestures for us and
* updates our visible rect appropriately.
*/
private PanZoomController mPanZoomController;
private OnTouchListener mOnTouchListener; /* The touch listener. */
private GeckoLayerClient mLayerClient; /* The layer client. */
/* The new color for the checkerboard. */
@ -112,14 +96,6 @@ public class LayerController implements Tabs.OnTabsChangedListener {
private boolean mForceRedraw;
/* The time limit for pages to respond with preventDefault on touchevents
* before we begin panning the page */
private int mTimeout = 200;
private boolean allowDefaultActions = true;
private Timer allowDefaultTimer = null;
private PointF initialTouchLocation = null;
private static Pattern sColorPattern;
public LayerController(Context context) {
@ -130,14 +106,6 @@ public class LayerController implements Tabs.OnTabsChangedListener {
mPanZoomController = new PanZoomController(this);
mView = new LayerView(context, this);
mCheckerboardShouldShowChecks = true;
Tabs.registerOnTabsChangedListener(this);
mTimeout = ViewConfiguration.getLongPressTimeout();
}
public void onDestroy() {
Tabs.unregisterOnTabsChangedListener(this);
}
public void setRoot(Layer layer) { mRootLayer = layer; }
@ -293,10 +261,6 @@ public class LayerController implements Tabs.OnTabsChangedListener {
public boolean post(Runnable action) { return mView.post(action); }
public void setOnTouchListener(OnTouchListener onTouchListener) {
mOnTouchListener = onTouchListener;
}
/**
* The view as well as the controller itself use this method to notify the layer client that
* the geometry changed.
@ -366,81 +330,6 @@ public class LayerController implements Tabs.OnTabsChangedListener {
return layerPoint;
}
/*
* 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) {
int action = event.getAction();
PointF point = new PointF(event.getX(), event.getY());
// this will only match the first touchstart in a series
if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
initialTouchLocation = point;
allowDefaultActions = !mWaitForTouchListeners;
// if we have a timer, this may be a double tap,
// cancel the current timer but don't clear the event queue
if (allowDefaultTimer != null) {
allowDefaultTimer.cancel();
} else {
// if we don't have a timer, make sure we remove any old events
mView.clearEventQueue();
}
allowDefaultTimer = new Timer();
allowDefaultTimer.schedule(new TimerTask() {
public void run() {
post(new Runnable() {
public void run() {
preventPanning(false);
}
});
}
}, mTimeout);
}
// After the initial touch, ignore touch moves until they exceed a minimum distance.
if (initialTouchLocation != null && (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE) {
if (PointUtils.subtract(point, initialTouchLocation).length() > PanZoomController.PAN_THRESHOLD) {
initialTouchLocation = null;
} else {
return !allowDefaultActions;
}
}
// send the event to content
if (mOnTouchListener != null)
mOnTouchListener.onTouch(mView, event);
return !allowDefaultActions;
}
public void preventPanning(boolean aValue) {
if (allowDefaultTimer != null) {
allowDefaultTimer.cancel();
allowDefaultTimer = null;
}
if (aValue == allowDefaultActions) {
allowDefaultActions = !aValue;
if (aValue) {
mView.clearEventQueue();
mPanZoomController.cancelTouch();
} else {
mView.processEventQueue();
}
}
}
public void onTabChanged(Tab tab, Tabs.TabEvents msg) {
if ((Tabs.getInstance().isSelectedTab(tab) && msg == Tabs.TabEvents.STOP) || msg == Tabs.TabEvents.SELECTED) {
mWaitForTouchListeners = tab.getHasTouchListeners();
}
}
public void setWaitForTouchListeners(boolean aValue) {
mWaitForTouchListeners = aValue;
}
/** Retrieves whether we should show checkerboard checks or not. */
public boolean checkerboardShouldShowChecks() {
return mCheckerboardShouldShowChecks;

View File

@ -43,20 +43,16 @@ import org.mozilla.gecko.GeckoInputConnection;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.InputConnectionHandler;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.View;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.ScaleGestureDetector;
import android.widget.RelativeLayout;
import android.util.Log;
import java.nio.IntBuffer;
import java.util.LinkedList;
import org.mozilla.gecko.GeckoApp;
import android.content.Context;
@ -77,18 +73,16 @@ import javax.microedition.khronos.opengles.GL10;
* Note that LayerView is accessed by Robocop via reflection.
*/
public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
private static String LOGTAG = "GeckoLayerView";
private Context mContext;
private LayerController mController;
private TouchEventHandler mTouchEventHandler;
private GLController mGLController;
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>();
/* Must be a PAINT_xxx constant */
private int mPaintState = PAINT_NONE;
@ -111,54 +105,21 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
mGLController = new GLController(this);
mContext = context;
mController = controller;
mTouchEventHandler = new TouchEventHandler(context, this, mController);
mRenderer = new LayerRenderer(this);
mGestureDetector = new GestureDetector(context, controller.getGestureListener());
mScaleGestureDetector =
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;
mController.getPanZoomController().onTouchEvent(event);
return true;
return mTouchEventHandler.handleEvent(event);
}
public LayerController getController() { return mController; }
public TouchEventHandler getTouchEventHandler() { return mTouchEventHandler; }
/** The LayerRenderer calls this to indicate that the window has changed size. */
public void setViewportSize(IntSize size) {

View File

@ -0,0 +1,294 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.gfx;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Context;
import android.graphics.PointF;
import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.View.OnTouchListener;
import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
/**
* This class handles incoming touch events from the user and sends them to
* listeners in Gecko and/or performs the "default action" (asynchronous pan/zoom
* behaviour. EVERYTHING IN THIS CLASS MUST RUN ON THE UI THREAD.
*
* In the following code/comments, a "block" of events refers to a contiguous
* sequence of events that starts with a DOWN or POINTER_DOWN and goes up to
* but not including the next DOWN or POINTER_DOWN event.
*
* "Dispatching" an event refers to performing the default actions for the event,
* which at our level of abstraction just means sending it off to the gesture
* detectors and the pan/zoom controller.
*
* If an event is "default-prevented" that means one or more listeners in Gecko
* has called preventDefault() on the event, which means that the default action
* for that event should not occur. Usually we care about a "block" of events being
* default-prevented, which means that the DOWN/POINTER_DOWN event that started
* the block, or the first MOVE event following that, were prevent-defaulted.
*
* A "default-prevented notification" is when we here in Java-land receive a notification
* from gecko as to whether or not a block of events was default-prevented. This happens
* at some point after the first or second event in the block is processed in Gecko.
* This code assumes we get EXACTLY ONE default-prevented notification for each block
* of events.
*/
public final class TouchEventHandler implements Tabs.OnTabsChangedListener {
private static final String LOGTAG = "GeckoTouchEventHandler";
// The time limit for listeners to respond with preventDefault on touchevents
// before we begin panning the page
private final int EVENT_LISTENER_TIMEOUT = ViewConfiguration.getLongPressTimeout();
private final LayerView mView;
private final LayerController mController;
private final GestureDetector mGestureDetector;
private final SimpleScaleGestureDetector mScaleGestureDetector;
// the queue of events that we are holding on to while waiting for a preventDefault
// notification
private final Queue<MotionEvent> mEventQueue;
private final ListenerTimeoutProcessor mListenerTimeoutProcessor;
// the listener we use to notify gecko of touch events
private OnTouchListener mOnTouchListener;
// whether or not we should wait for touch listeners to respond (this state is
// per-tab and is updated when we switch tabs).
private boolean mWaitForTouchListeners;
// true if we should hold incoming events in our queue. this is re-set for every
// block of events, this is cleared once we find out if the block has been
// default-prevented or not (or we time out waiting for that).
private boolean mHoldInQueue;
// true if we should dispatch incoming events to the gesture detector and the pan/zoom
// controller. if this is false, then the current block of events has been
// default-prevented, and we should not dispatch these events (although we'll still send
// them to gecko listeners).
private boolean mDispatchEvents;
// this next variable requires some explanation. strap yourself in.
//
// for each block of events, we do two things: (1) send the events to gecko and expect
// exactly one default-prevented notification in return, and (2) kick off a delayed
// ListenerTimeoutProcessor that triggers in case we don't hear from the listener in
// a timely fashion.
// since events are constantly coming in, we need to be able to handle more than one
// block of events in the queue.
//
// this means that there are ordering restrictions on these that we can take advantage of,
// and need to abide by. blocks of events in the queue will always be in the order that
// the user generated them. default-prevented notifications we get from gecko will be in
// the same order as the blocks of events in the queue. the ListenerTimeoutProcessors that
// have been posted will also fire in the same order as the blocks of events in the queue.
// HOWEVER, we may get multiple default-prevented notifications interleaved with multiple
// ListenerTimeoutProcessor firings, and that interleaving is not predictable.
//
// therefore, we need to make sure that for each block of events, we process the queued
// events exactly once, either when we get the default-prevented notification, or when the
// timeout expires (whichever happens first). there is no way to associate the
// default-prevented notification with a particular block of events other than via ordering,
//
// so what we do to accomplish this is to track a "processing balance", which is the number
// of default-prevented notifications that we have received, minus the number of ListenerTimeoutProcessors
// that have fired. (think "balance" as in teeter-totter balance). this value is:
// - zero when we are in a state where the next default-prevented notification we expect
// to receive and the next ListenerTimeoutProcessor we expect to fire both correspond to
// the next block of events in the queue.
// - positive when we are in a state where we have received more default-prevented notifications
// than ListenerTimeoutProcessors. This means that the next default-prevented notification
// does correspond to the block at the head of the queue, but the next n ListenerTimeoutProcessors
// need to be ignored as they are for blocks we have already processed. (n is the absolute value
// of the balance.)
// - negative when we are in a state where we have received more ListenerTimeoutProcessors than
// default-prevented notifications. This means that the next ListenerTimeoutProcessor that
// we receive does correspond to the block at the head of the queue, but the next n
// default-prevented notifications need to be ignored as they are for blocks we have already
// processed. (n is the absolute value of the balance.)
private int mProcessingBalance;
TouchEventHandler(Context context, LayerView view, LayerController controller) {
mView = view;
mController = controller;
mEventQueue = new LinkedList<MotionEvent>();
mGestureDetector = new GestureDetector(context, controller.getGestureListener());
mScaleGestureDetector = new SimpleScaleGestureDetector(controller.getScaleGestureListener());
mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
mDispatchEvents = true;
mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
Tabs.registerOnTabsChangedListener(this);
}
/* This function MUST be called on the UI thread */
public boolean handleEvent(MotionEvent event) {
// if we don't have gecko listeners, just dispatch the event
// and be done with it, no extra work needed.
if (mOnTouchListener == null) {
dispatchEvent(event);
return true;
}
if (isDownEvent(event)) {
// this is the start of a new block of events! whee!
mHoldInQueue = mWaitForTouchListeners;
if (mHoldInQueue) {
// if we're holding the events in the queue, set the timeout so that
// we dispatch these events if we don't get a default-prevented notification
mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
} else {
// if we're not holding these events, then we still need to pretend like
// we did and had a ListenerTimeoutProcessor fire so that when we get
// the default-prevented notification for this block, it doesn't accidentally
// act upon some other block
mProcessingBalance++;
}
}
// if we need to hold the events, add it to the queue. if we need to dispatch
// it directly, do that. it is possible that both mHoldInQueue and mDispatchEvents
// are false, in which case we are processing a block of events that we know
// has been default-prevented. in that case we don't keep the events as we don't
// need them (but we still pass them to the gecko listener).
if (mHoldInQueue) {
mEventQueue.add(MotionEvent.obtain(event));
} else if (mDispatchEvents) {
dispatchEvent(event);
}
// notify gecko of the event
mOnTouchListener.onTouch(mView, event);
return true;
}
/**
* This function is how gecko sends us a default-prevented notification. It is called
* once gecko knows definitively whether the block of events has had preventDefault
* called on it (either on the initial down event that starts the block, or on
* the first event following that down event).
*
* This function MUST be called on the UI thread.
*/
public void handleEventListenerAction(boolean allowDefaultAction) {
if (mProcessingBalance > 0) {
// this event listener that triggered this took too long, and the corresponding
// ListenerTimeoutProcessor runnable already ran for the event in question. the
// block of events this is for has already been processed, so we don't need to
// do anything here.
} else {
processEventBlock(allowDefaultAction);
}
mProcessingBalance--;
}
/* This function MUST be called on the UI thread. */
public void setWaitForTouchListeners(boolean aValue) {
mWaitForTouchListeners = aValue;
}
/* This function MUST be called on the UI thread. */
public void setOnTouchListener(OnTouchListener onTouchListener) {
mOnTouchListener = onTouchListener;
}
private boolean isDownEvent(MotionEvent event) {
int action = (event.getAction() & MotionEvent.ACTION_MASK);
return (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN);
}
/**
* Dispatch the event to the gesture detectors and the pan/zoom controller.
*/
private void dispatchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)) {
return;
}
mScaleGestureDetector.onTouchEvent(event);
if (mScaleGestureDetector.isInProgress()) {
return;
}
mController.getPanZoomController().onTouchEvent(event);
}
/**
* Process the block of events at the head of the queue now that we know
* whether it has been default-prevented or not.
*/
private void processEventBlock(boolean allowDefaultAction) {
if (!allowDefaultAction) {
// if the block has been default-prevented, cancel whatever stuff we had in
// progress in the gesture detector and pan zoom controller
long now = SystemClock.uptimeMillis();
dispatchEvent(MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0));
}
// the odd loop condition is because the first event in the queue will
// always be a DOWN or POINTER_DOWN event, and we want to process all
// the events in the queue starting at that one, up to but not including
// the next DOWN or POINTER_DOWN event.
MotionEvent event = mEventQueue.poll();
while (true) {
// for each event we process, only dispatch it if the block hasn't been
// default-prevented.
if (allowDefaultAction) {
dispatchEvent(event);
}
event = mEventQueue.peek();
if (event == null) {
// we have processed the backlog of events, and are all caught up.
// now we can set clear the hold flag and set the dispatch flag so
// that the handleEvent() function can do the right thing for all
// remaining events in this block (which is still ongoing) without
// having to put them in the queue.
mHoldInQueue = false;
mDispatchEvents = allowDefaultAction;
break;
}
if (isDownEvent(event)) {
// we have finished processing the block we were interested in.
// now we wait for the next call to processEventBlock
break;
}
// pop the event we peeked above, as it is still part of the block and
// we want to keep processing
mEventQueue.remove();
}
}
private class ListenerTimeoutProcessor implements Runnable {
/* This MUST be run on the UI thread */
public void run() {
if (mProcessingBalance < 0) {
// gecko already responded with default-prevented notification, and so
// the block of events this ListenerTimeoutProcessor corresponds to have
// already been removed from the queue.
} else {
processEventBlock(true);
}
mProcessingBalance++;
}
}
// Tabs.OnTabsChangedListener implementation
public void onTabChanged(Tab tab, Tabs.TabEvents msg) {
if ((Tabs.getInstance().isSelectedTab(tab) && msg == Tabs.TabEvents.STOP) || msg == Tabs.TabEvents.SELECTED) {
mWaitForTouchListeners = tab.getHasTouchListeners();
}
}
}

View File

@ -367,6 +367,7 @@ public class PanZoomController
private boolean onTouchCancel(MotionEvent event) {
mState = PanZoomState.NOTHING;
cancelTouch();
// ensure we snap back if we're overscrolled
bounce();
return false;
@ -901,7 +902,7 @@ public class PanZoomController
return true;
}
public void cancelTouch() {
private void cancelTouch() {
GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", "");
GeckoAppShell.sendEventToGecko(e);
}

View File

@ -131,11 +131,12 @@ public class SimpleScaleGestureDetector {
private void onTouchEnd(MotionEvent event) {
mLastEventTime = event.getEventTime();
boolean isCancel = (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_CANCEL;
int id = event.getPointerId(getActionIndex(event));
ListIterator<PointerInfo> iterator = mPointerInfo.listIterator();
while (iterator.hasNext()) {
PointerInfo pointerInfo = iterator.next();
if (pointerInfo.getId() != id) {
if (!(isCancel || pointerInfo.getId() == id)) {
continue;
}

View File

@ -142,7 +142,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");
jSetPreventPanning = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setPreventPanning", "(Z)V");
jNotifyDefaultPrevented = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyDefaultPrevented", "(Z)V");
jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
jVibrate1 = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "(J)V");
@ -1961,12 +1961,12 @@ NS_IMETHODIMP nsAndroidBridge::SetDrawMetadataProvider(nsIAndroidDrawMetadataPro
}
void
AndroidBridge::SetPreventPanning(bool aPreventPanning) {
AndroidBridge::NotifyDefaultPrevented(bool aDefaultPrevented) {
JNIEnv *env = GetJNIEnv();
if (!env)
return;
env->CallStaticVoidMethod(mGeckoAppShellClass, jSetPreventPanning, (jboolean)aPreventPanning);
env->CallStaticVoidMethod(mGeckoAppShellClass, jNotifyDefaultPrevented, (jboolean)aDefaultPrevented);
}

View File

@ -262,7 +262,7 @@ public:
void ShowInputMethodPicker();
void SetPreventPanning(bool aPreventPanning);
void NotifyDefaultPrevented(bool aDefaultPrevented);
void HideProgressDialogOnce();
@ -498,7 +498,7 @@ protected:
jmethodID jGetDpi;
jmethodID jSetFullScreen;
jmethodID jShowInputMethodPicker;
jmethodID jSetPreventPanning;
jmethodID jNotifyDefaultPrevented;
jmethodID jHideProgressDialog;
jmethodID jPerformHapticFeedback;
jmethodID jVibrate1;

View File

@ -1442,28 +1442,66 @@ getDistance(const nsIntPoint &p1, const nsIntPoint &p2)
bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)
{
// This is set to true once we have called SetPreventPanning() exactly
// once for a given sequence of touch events. It is reset on the start
// of the next sequence.
static bool sDefaultPreventedNotified = false;
static bool sLastWasDownEvent = false;
bool preventDefaultActions = false;
bool isDownEvent = false;
switch (ae->Action() & AndroidMotionEvent::ACTION_MASK) {
case AndroidMotionEvent::ACTION_DOWN:
case AndroidMotionEvent::ACTION_POINTER_DOWN: {
nsTouchEvent event(true, NS_TOUCH_START, this);
return DispatchMultitouchEvent(event, ae);
preventDefaultActions = DispatchMultitouchEvent(event, ae);
isDownEvent = true;
break;
}
case AndroidMotionEvent::ACTION_MOVE: {
nsTouchEvent event(true, NS_TOUCH_MOVE, this);
return DispatchMultitouchEvent(event, ae);
preventDefaultActions = DispatchMultitouchEvent(event, ae);
break;
}
case AndroidMotionEvent::ACTION_UP:
case AndroidMotionEvent::ACTION_POINTER_UP: {
nsTouchEvent event(true, NS_TOUCH_END, this);
return DispatchMultitouchEvent(event, ae);
preventDefaultActions = DispatchMultitouchEvent(event, ae);
break;
}
case AndroidMotionEvent::ACTION_OUTSIDE:
case AndroidMotionEvent::ACTION_CANCEL: {
nsTouchEvent event(true, NS_TOUCH_CANCEL, this);
return DispatchMultitouchEvent(event, ae);
preventDefaultActions = DispatchMultitouchEvent(event, ae);
break;
}
}
return false;
// if the last event we got was a down event, then by now we know for sure whether
// this block has been default-prevented or not. if we haven't already sent the
// notification for this block, do so now.
if (sLastWasDownEvent && !sDefaultPreventedNotified) {
// if this event is a down event, that means it's the start of a new block, and the
// previous block should not be default-prevented
bool defaultPrevented = isDownEvent ? false : preventDefaultActions;
AndroidBridge::Bridge()->NotifyDefaultPrevented(defaultPrevented);
sDefaultPreventedNotified = true;
}
// now, if this event is a down event, then we might already know that it has been
// default-prevented. if so, we send the notification right away; otherwise we wait
// for the next event.
if (isDownEvent) {
if (preventDefaultActions) {
AndroidBridge::Bridge()->NotifyDefaultPrevented(true);
sDefaultPreventedNotified = true;
} else {
sDefaultPreventedNotified = false;
}
}
sLastWasDownEvent = isDownEvent;
return preventDefaultActions;
}
bool
@ -1503,11 +1541,7 @@ nsWindow::DispatchMultitouchEvent(nsTouchEvent &event, AndroidGeckoEvent *ae)
nsEventStatus status;
DispatchEvent(&event, status);
bool preventPanning = (status == nsEventStatus_eConsumeNoDefault);
if (preventPanning || action == AndroidMotionEvent::ACTION_MOVE) {
AndroidBridge::Bridge()->SetPreventPanning(preventPanning);
}
return preventPanning;
return (status == nsEventStatus_eConsumeNoDefault);
}
void