mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
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:
parent
218ff032bd
commit
e685c4edc8
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
294
mobile/android/base/gfx/TouchEventHandler.java
Normal file
294
mobile/android/base/gfx/TouchEventHandler.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user