Commit 5b261952 authored by yusufo@chromium.org's avatar yusufo@chromium.org

Enable gesture events handling on Android.

This add gesture related functionality to the java side to ContentViewCore and also
adds some missing calls on the routing path of the gesture events to webkit. The WebKit side
for related parts of the WebCompositorInputHandler has already been upstreamed.

BUG=136680
TEST=

Review URL: https://chromiumcodereview.appspot.com/10790066

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148861 0039d316-1c4b-4281-b951-d872f2087c98
parent d1bbf2ea
......@@ -50,7 +50,7 @@ void SetContentCommandLineFlags(int max_render_process_count,
CommandLine* parsed_command_line = CommandLine::ForCurrentProcess();
#if !defined(ANDROID_UPSTREAM_BRINGUP)
// TODO(yfriedman): Upstream this when bringing up rendering code and
// rendering modes. b/6668088
// Set subflags for the --graphics-mode=XYZ omnibus flag.
......@@ -73,11 +73,11 @@ void SetContentCommandLineFlags(int max_render_process_count,
SetCommandLineSwitch(switches::kEnableCompositingForFixedPosition);
SetCommandLineSwitch(switches::kEnableThreadedCompositing);
// Per tile painting saves memory in background tabs (http://b/5669228).
SetCommandLineSwitch(switches::kEnablePerTilePainting);
// ...but it now fails an SkAssert(), so disable it. b/6819634
// SetCommandLineSwitch(switches::kEnablePerTilePainting);
} else {
LOG(FATAL) << "Invalid --graphics-mode flag: " << graphics_mode;
}
#endif
if (parsed_command_line->HasSwitch(switches::kRendererProcessLimit)) {
std::string limit = parsed_command_line->GetSwitchValueASCII(
......
......@@ -9,11 +9,17 @@
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "content/browser/android/content_view_client.h"
#include "content/browser/android/touch_point.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/navigation_controller_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/interstitial_page.h"
#include "content/public/browser/web_contents.h"
#include "jni/ContentViewCore_jni.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/android/WebInputEventFactory.h"
#include "webkit/glue/webmenuitem.h"
using base::android::AttachCurrentThread;
......@@ -23,6 +29,8 @@ using base::android::GetClass;
using base::android::HasField;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using WebKit::WebInputEvent;
using WebKit::WebInputEventFactory;
// Describes the type and enabled state of a select popup item.
// Keep in sync with the value defined in SelectPopupDialog.java
......@@ -96,6 +104,14 @@ void ContentViewCoreImpl::InitJNI(JNIEnv* env, jobject obj) {
java_object_->obj = env->NewWeakGlobalRef(obj);
}
RenderWidgetHostViewAndroid*
ContentViewCoreImpl::GetRenderWidgetHostViewAndroid() {
RenderWidgetHostView* rwhv = NULL;
if (web_contents_)
rwhv = web_contents_->GetRenderWidgetHostView();
return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
}
// ----------------------------------------------------------------------------
// Methods called from Java via JNI
// ----------------------------------------------------------------------------
......@@ -145,6 +161,103 @@ jboolean ContentViewCoreImpl::IsIncognito(JNIEnv* env, jobject obj) {
return web_contents()->GetBrowserContext()->IsOffTheRecord();
}
jboolean ContentViewCoreImpl::TouchEvent(JNIEnv* env,
jobject obj,
jlong time_ms,
jint type,
jobjectArray pts) {
RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
if (rwhv) {
using WebKit::WebTouchEvent;
WebKit::WebTouchEvent event;
TouchPoint::BuildWebTouchEvent(env, type, time_ms, pts, event);
rwhv->TouchEvent(event);
return true;
}
return false;
}
void ContentViewCoreImpl::SendGestureEvent(WebInputEvent::Type type,
long time_ms, int x, int y,
float dx, float dy,
bool link_preview_tap) {
WebKit::WebGestureEvent event = WebInputEventFactory::gestureEvent(
type, time_ms / 1000.0, x, y, dx, dy, 0);
if (GetRenderWidgetHostViewAndroid())
GetRenderWidgetHostViewAndroid()->GestureEvent(event);
}
void ContentViewCoreImpl::ScrollBegin(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y) {
SendGestureEvent(
WebInputEvent::GestureScrollBegin, time_ms, x, y, 0, 0, false);
}
void ContentViewCoreImpl::ScrollEnd(JNIEnv* env, jobject obj, jlong time_ms) {
SendGestureEvent(WebInputEvent::GestureScrollEnd, time_ms, 0, 0, 0, 0, false);
}
void ContentViewCoreImpl::ScrollBy(JNIEnv* env, jobject obj, jlong time_ms,
jint dx, jint dy) {
SendGestureEvent(
WebInputEvent::GestureScrollUpdate, time_ms, 0, 0, -dx, -dy, false);
}
void ContentViewCoreImpl::FlingStart(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y, jint vx, jint vy) {
SendGestureEvent(
WebInputEvent::GestureFlingStart, time_ms, x, y, vx, vy, false);
}
void ContentViewCoreImpl::FlingCancel(JNIEnv* env, jobject obj, jlong time_ms) {
SendGestureEvent(
WebInputEvent::GestureFlingCancel, time_ms, 0, 0, 0, 0, false);
}
void ContentViewCoreImpl::SingleTap(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y, jboolean link_preview_tap) {
SendGestureEvent(
WebInputEvent::GestureTap, time_ms, x, y, 0, 0, link_preview_tap);
}
void ContentViewCoreImpl::ShowPressState(JNIEnv* env, jobject obj,
jlong time_ms,
jint x, jint y) {
SendGestureEvent(WebInputEvent::GestureTapDown, time_ms, x, y, 0, 0, false);
}
void ContentViewCoreImpl::DoubleTap(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y) {
SendGestureEvent(WebInputEvent::GestureDoubleTap, time_ms, x, y, 0, 0, false);
}
void ContentViewCoreImpl::LongPress(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y, jboolean link_preview_tap) {
SendGestureEvent(
WebInputEvent::GestureLongPress, time_ms, x, y, 0, 0, link_preview_tap);
}
void ContentViewCoreImpl::PinchBegin(JNIEnv* env, jobject obj, jlong time_ms,
jint x, jint y) {
SendGestureEvent(
WebInputEvent::GesturePinchBegin, time_ms, x, y, 0, 0, false);
}
void ContentViewCoreImpl::PinchEnd(JNIEnv* env, jobject obj, jlong time_ms) {
SendGestureEvent(WebInputEvent::GesturePinchEnd, time_ms, 0, 0, 0, 0, false);
}
void ContentViewCoreImpl::PinchBy(JNIEnv* env, jobject obj, jlong time_ms,
jint anchor_x, jint anchor_y, jfloat delta) {
SendGestureEvent(WebInputEvent::GesturePinchUpdate,
time_ms,
anchor_x,
anchor_y,
delta,
delta,
false);
}
jboolean ContentViewCoreImpl::CanGoBack(JNIEnv* env, jobject obj) {
return web_contents_->GetController().CanGoBack();
}
......@@ -298,6 +411,22 @@ void ContentViewCoreImpl::ShowSelectPopupMenu(
multiple, selected_array.obj());
}
void ContentViewCoreImpl::ConfirmTouchEvent(bool handled) {
// TODO(yusufo): Upstream changes for http://crbug/139386 to match upstream
// to downstream.
JNIEnv* env = AttachCurrentThread();
Java_ContentViewCore_confirmTouchEvent(env,
java_object_->View(env).obj(),
handled);
}
void ContentViewCoreImpl::DidSetNeedTouchEvents(bool need_touch_events) {
JNIEnv* env = AttachCurrentThread();
Java_ContentViewCore_didSetNeedTouchEvents(env,
java_object_->View(env).obj(),
need_touch_events);
}
bool ContentViewCoreImpl::HasFocus() {
NOTIMPLEMENTED() << "not upstreamed yet";
return false;
......
......@@ -13,9 +13,11 @@
#include "base/i18n/rtl.h"
#include "base/memory/scoped_ptr.h"
#include "base/process.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/public/browser/android/content_view_core.h"
#include "content/public/browser/notification_observer.h"
#include "googleurl/src/gurl.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
#include "ui/gfx/rect.h"
struct WebMenuItem;
......@@ -31,7 +33,7 @@ class ContentViewCoreImpl : public ContentViewCore,
ContentViewCoreImpl(JNIEnv* env,
jobject obj,
WebContents* web_contents);
virtual void Destroy(JNIEnv* env, jobject obj);
virtual void Destroy(JNIEnv* env, jobject obj) OVERRIDE;
// --------------------------------------------------------------------------
// Methods called from Java via JNI
......@@ -56,6 +58,44 @@ class ContentViewCoreImpl : public ContentViewCore,
JNIEnv* env, jobject obj) const;
jboolean IsIncognito(JNIEnv* env, jobject obj);
jboolean Crashed(JNIEnv* env, jobject obj) const { return tab_crashed_; }
jboolean TouchEvent(JNIEnv* env,
jobject obj,
jlong time_ms,
jint type,
jobjectArray pts);
void ScrollBegin(JNIEnv* env, jobject obj, jlong time_ms, jint x, jint y);
void ScrollEnd(JNIEnv* env, jobject obj, jlong time_ms);
void ScrollBy(JNIEnv* env, jobject obj, jlong time_ms, jint dx, jint dy);
void FlingStart(JNIEnv* env,
jobject obj,
jlong time_ms,
jint x,
jint y,
jint vx,
jint vy);
void FlingCancel(JNIEnv* env, jobject obj, jlong time_ms);
void SingleTap(JNIEnv* env,
jobject obj,
jlong time_ms,
jint x,
jint y,
jboolean link_preview_tap);
void ShowPressState(JNIEnv* env, jobject obj, jlong time_ms, jint x, jint y);
void DoubleTap(JNIEnv* env, jobject obj, jlong time_ms, jint x, jint y) ;
void LongPress(JNIEnv* env,
jobject obj,
jlong time_ms,
jint x,
jint y,
jboolean link_preview_tap);
void PinchBegin(JNIEnv* env, jobject obj, jlong time_ms, jint x, jint y);
void PinchEnd(JNIEnv* env, jobject obj, jlong time_ms);
void PinchBy(JNIEnv* env,
jobject obj,
jlong time_ms,
jint x,
jint y,
jfloat delta);
jboolean CanGoBack(JNIEnv* env, jobject obj);
jboolean CanGoForward(JNIEnv* env, jobject obj);
jboolean CanGoToOffset(JNIEnv* env, jobject obj, jint offset);
......@@ -85,6 +125,8 @@ class ContentViewCoreImpl : public ContentViewCore,
void SetTitle(const string16& title);
bool HasFocus();
void ConfirmTouchEvent(bool handled);
void DidSetNeedTouchEvents(bool need_touch_events);
void OnSelectionChanged(const std::string& text);
void OnSelectionBoundsChanged(int startx,
int starty,
......@@ -131,6 +173,12 @@ class ContentViewCoreImpl : public ContentViewCore,
void InitJNI(JNIEnv* env, jobject obj);
RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid();
void SendGestureEvent(WebKit::WebInputEvent::Type type, long time_ms,
int x, int y,
float dx, float dy, bool link_preview_tap);
void PostLoadUrl(const GURL& url);
struct JavaObject;
......@@ -151,6 +199,6 @@ class ContentViewCoreImpl : public ContentViewCore,
bool RegisterContentViewCore(JNIEnv* env);
}; // namespace content
} // namespace content
#endif // CONTENT_BROWSER_ANDROID_CONTENT_VIEW_CORE_IMPL_H_
......@@ -330,6 +330,18 @@ void RenderWidgetHostViewAndroid::UnlockMouse() {
NOTIMPLEMENTED();
}
void RenderWidgetHostViewAndroid::TouchEvent(
const WebKit::WebTouchEvent& event) {
if (host_)
host_->ForwardTouchEvent(event);
}
void RenderWidgetHostViewAndroid::GestureEvent(
const WebKit::WebGestureEvent& event) {
if (host_)
host_->ForwardGestureEvent(event);
}
void RenderWidgetHostViewAndroid::SetContentViewCore(
ContentViewCoreImpl* content_view_core) {
content_view_core_ = content_view_core;
......@@ -340,6 +352,12 @@ void RenderWidgetHostViewAndroid::SetContentViewCore(
}
}
void RenderWidgetHostViewAndroid::DidSetNeedTouchEvents(
bool need_touch_events) {
if (content_view_core_)
content_view_core_->DidSetNeedTouchEvents(need_touch_events);
}
// static
void RenderWidgetHostViewPort::GetDefaultScreenInfo(
::WebKit::WebScreenInfo* results) {
......
......@@ -102,9 +102,13 @@ class RenderWidgetHostViewAndroid : public RenderWidgetHostViewBase {
virtual bool LockMouse() OVERRIDE;
virtual void UnlockMouse() OVERRIDE;
virtual void StartContentIntent(const GURL& content_url) OVERRIDE;
virtual void DidSetNeedTouchEvents(bool need_touch_events) OVERRIDE;
void SetContentViewCore(ContentViewCoreImpl* content_view_core);
void TouchEvent(const WebKit::WebTouchEvent& event);
void GestureEvent(const WebKit::WebGestureEvent& event);
private:
// The model object.
RenderWidgetHostImpl* host_;
......
......@@ -289,18 +289,18 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
* Start pinch zoom. You must call {@link #pinchEnd} to stop.
*/
void pinchBegin(long timeMs, int x, int y) {
mContentViewCore.pinchBegin(timeMs, x, y);
mContentViewCore.getContentViewGestureHandler().pinchBegin(timeMs, x, y);
}
/**
* Stop pinch zoom.
*/
void pinchEnd(long timeMs) {
mContentViewCore.pinchEnd(timeMs);
mContentViewCore.getContentViewGestureHandler().pinchEnd(timeMs);
}
void setIgnoreSingleTap(boolean value) {
mContentViewCore.setIgnoreSingleTap(value);
mContentViewCore.getContentViewGestureHandler().setIgnoreSingleTap(value);
}
/**
......@@ -316,7 +316,7 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
* coordinate.
*/
void pinchBy(long timeMs, int anchorX, int anchorY, float delta) {
mContentViewCore.pinchBy(timeMs, anchorX, anchorY, delta);
mContentViewCore.getContentViewGestureHandler().pinchBy(timeMs, anchorX, anchorY, delta);
}
/**
......@@ -347,6 +347,10 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mContentViewCore.onTouchEvent(event);
}
// End FrameLayout overrides.
@Override
......
......@@ -7,6 +7,7 @@ package org.chromium.content.browser;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
......@@ -17,14 +18,19 @@ import android.webkit.DownloadListener;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.WeakContext;
import org.chromium.content.browser.ContentViewGestureHandler;
import org.chromium.content.browser.TouchPoint;
import org.chromium.content.browser.ZoomManager;
import org.chromium.content.common.TraceEvent;
import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
/**
* Contains all the major functionality necessary to manage the lifecycle of a ContentView without
* being tied to the view system.
*/
@JNINamespace("content")
public class ContentViewCore {
public class ContentViewCore implements MotionEventDelegate {
private static final String TAG = ContentViewCore.class.getName();
// The following constants match the ones in chrome/common/page_transition_types.h.
......@@ -52,6 +58,10 @@ public class ContentViewCore {
// if resulting zooming will produce little visible difference.
private static float WEBVIEW_ZOOM_CONTROLS_EPSILON = 0.007f;
// To avoid checkerboard, we clamp the fling velocity based on the maximum number of tiles
// should be allowed to upload per 100ms.
private static int MAX_NUM_UPLOAD_TILES = 12;
/**
* Interface that consumers of {@link ContentViewCore} must implement to allow the proper
* dispatching of view methods through the containing view.
......@@ -123,6 +133,7 @@ public class ContentViewCore {
// Native pointer to C++ ContentView object which will be set by nativeInit()
private int mNativeContentViewCore = 0;
private ContentViewGestureHandler mContentViewGestureHandler;
private ZoomManager mZoomManager;
// Cached page scale factor from native
......@@ -223,7 +234,6 @@ public class ContentViewCore {
mPersonality = personality;
mContentSettings = new ContentSettings(this, mNativeContentViewCore);
mContainerView.setWillNotDraw(false);
mContainerView.setFocusable(true);
mContainerView.setFocusableInTouchMode(true);
if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
......@@ -231,7 +241,10 @@ public class ContentViewCore {
mContainerView.setVerticalScrollBarEnabled(false);
}
mContainerView.setClickable(true);
initGestureDetectors(context);
mZoomManager = new ZoomManager(context, this);
mZoomManager.updateMultiTouchSupport();
mContentViewGestureHandler = new ContentViewGestureHandler(context, this, mZoomManager);
Log.i(TAG, "mNativeContentView=0x"+ Integer.toHexString(mNativeContentViewCore));
}
......@@ -459,43 +472,93 @@ public class ContentViewCore {
if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
}
// End FrameLayout overrides.
/**
* Start pinch zoom. You must call {@link #pinchEnd} to stop.
* @see View#onTouchEvent(MotionEvent)
*/
void pinchBegin(long timeMs, int x, int y) {
if (mNativeContentViewCore != 0) {
// TODO(tedchoc): Pass pinch begin to native.
}
public boolean onTouchEvent(MotionEvent event) {
return mContentViewGestureHandler.onTouchEvent(event);
}
/**
* Stop pinch zoom.
* @return ContentViewGestureHandler for all MotionEvent and gesture related calls.
*/
void pinchEnd(long timeMs) {
ContentViewGestureHandler getContentViewGestureHandler() {
return mContentViewGestureHandler;
}
@Override
public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) {
if (mNativeContentViewCore != 0) {
// TODO(tedchoc): Pass pinch end to native.
return nativeTouchEvent(mNativeContentViewCore, timeMs, action, pts);
}
return false;
}
void setIgnoreSingleTap(boolean value) {
mIgnoreSingleTap = value;
@SuppressWarnings("unused")
@CalledByNative
private void didSetNeedTouchEvents(boolean needTouchEvents) {
mContentViewGestureHandler.didSetNeedTouchEvents(needTouchEvents);
}
/**
* Modify the ContentView magnification level. The effect of calling this
* method is exactly as after "pinch zoom".
*
* @param timeMs The event time in milliseconds.
* @param delta The ratio of the new magnification level over the current
* magnification level.
* @param anchorX The magnification anchor (X) in the current view
* coordinate.
* @param anchorY The magnification anchor (Y) in the current view
* coordinate.
*/
void pinchBy(long timeMs, int anchorX, int anchorY, float delta) {
if (mNativeContentViewCore != 0) {
// TODO(tedchoc): Pass pinch by to native.
@SuppressWarnings("unused")
@CalledByNative
private void confirmTouchEvent(boolean handled) {
mContentViewGestureHandler.confirmTouchEvent(handled);
}
@Override
public boolean sendGesture(int type, long timeMs, int x, int y, Bundle b) {
if (mNativeContentViewCore == 0) return false;
switch (type) {
case ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE:
nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UP:
nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
return true;
case ContentViewGestureHandler.GESTURE_SINGLE_TAP_CONFIRMED:
handleTapOrPress(timeMs, x, y, false,
b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false));
return true;
case ContentViewGestureHandler.GESTURE_LONG_PRESS:
handleTapOrPress(timeMs, x, y, true, false);
return true;
case ContentViewGestureHandler.GESTURE_SCROLL_START:
nativeScrollBegin(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SCROLL_BY:
nativeScrollBy(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SCROLL_END:
nativeScrollEnd(mNativeContentViewCore, timeMs);
return true;
case ContentViewGestureHandler.GESTURE_FLING_START:
nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
clampFlingVelocityX(b.getInt(ContentViewGestureHandler.VELOCITY_X, 0)),
clampFlingVelocityY(b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0)));
return true;
case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
nativeFlingCancel(mNativeContentViewCore, timeMs);
return true;
case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
nativePinchBegin(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_PINCH_BY:
nativePinchBy(mNativeContentViewCore, timeMs, x, y,
b.getFloat(ContentViewGestureHandler.DELTA, 0));
return true;
case ContentViewGestureHandler.GESTURE_PINCH_END:
nativePinchEnd(mNativeContentViewCore, timeMs);
return true;
default:
return false;
}
}
......@@ -533,6 +596,13 @@ public class ContentViewCore {
return mContentSettings;
}
@Override
public boolean didUIStealScroll(float x, float y) {
// TODO(yusufo): Stubbed out for now. Upstream when computeHorizontalScrollOffset is
// available.
return false;
}
private void hidePopupDialog() {
SelectPopupDialog.hide(this);
}
......@@ -554,14 +624,22 @@ public class ContentViewCore {
}
}
private void initGestureDetectors(final Context context) {
try {
TraceEvent.begin();
// TODO(tedchoc): Upstream the rest of the initialization.
mZoomManager = new ZoomManager(context, this);
mZoomManager.updateMultiTouchSupport();
} finally {
TraceEvent.end();
private void handleTapOrPress(
long timeMs, int x, int y, boolean isLongPress, boolean showPress) {
//TODO(yusufo):Upstream the rest of the bits about handlerControllers.
if (!mContainerView.isFocused()) mContainerView.requestFocus();
if (isLongPress) {
if (mNativeContentViewCore != 0) {
nativeLongPress(mNativeContentViewCore, timeMs, x, y, false);
}
} else {
if (!showPress && mNativeContentViewCore != 0) {
nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
}
if (mNativeContentViewCore != 0) {
nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
}
}
}
......@@ -579,6 +657,32 @@ public class ContentViewCore {
}
}
/*
* To avoid checkerboard, we clamp the fling velocity based on the maximum number of tiles
* allowed to be uploaded per 100ms. Calculation is limited to one direction. We assume the
* tile size is 256x256. The precise distance / velocity should be calculated based on the
* logic in Scroller.java. As it is almost linear for the first 100ms, we use a simple math.
*/
private int clampFlingVelocityX(int velocity) {
int cols = MAX_NUM_UPLOAD_TILES / (int) (Math.ceil((float) getHeight() / 256) + 1);
int maxVelocity = cols > 0 ? cols * 2560 : 1000;
if (Math.abs(velocity) > maxVelocity) {
return velocity > 0 ? maxVelocity : -maxVelocity;
} else {
return velocity;
}
}
private int clampFlingVelocityY(int velocity) {
int rows = MAX_NUM_UPLOAD_TILES / (int) (Math.ceil((float) getWidth() / 256) + 1);
int maxVelocity = rows > 0 ? rows * 2560 : 1000;
if (Math.abs(velocity) > maxVelocity) {
return velocity > 0 ? maxVelocity : -maxVelocity;
} else {
return velocity;
}
}
/**
* Register the listener to be used when content can not be handled by the
* rendering engine, and should be downloaded instead. This will replace the
......@@ -698,9 +802,9 @@ public class ContentViewCore {
int y = getHeight() / 2;
float delta = 1.25f;
pinchBegin(timeMs, x, y);
pinchBy(timeMs, x, y, delta);
pinchEnd(timeMs);
getContentViewGestureHandler().pinchBegin(timeMs, x, y);
getContentViewGestureHandler().pinchBy(timeMs, x, y, delta);
getContentViewGestureHandler().pinchEnd(timeMs);
return true;
}
......@@ -727,14 +831,17 @@ public class ContentViewCore {
int y = getHeight() / 2;
float delta = 0.8f;
pinchBegin(timeMs, x, y);
pinchBy(timeMs, x, y, delta);
pinchEnd(timeMs);
getContentViewGestureHandler().pinchBegin(timeMs, x, y);
getContentViewGestureHandler().pinchBy(timeMs, x, y, delta);
getContentViewGestureHandler().pinchEnd(timeMs);
return true;
}
// Invokes the graphical zoom picker widget for this ContentView.
/**
* Invokes the graphical zoom picker widget for this ContentView.
*/
@Override
public void invokeZoomPicker() {
if (mContentSettings.supportZoom()) {
mZoomManager.invokeZoomPicker();
......@@ -784,6 +891,40 @@ public class ContentViewCore {
// Returns true if the native side crashed so that java side can draw a sad tab.
private native boolean nativeCrashed(int nativeContentViewCoreImpl);
private native boolean nativeTouchEvent(int nativeContentViewCoreImpl,
long timeMs, int action,
TouchPoint[] pts);
private native void nativeScrollBegin(int nativeContentViewCoreImpl, long timeMs, int x, int y);
private native void nativeScrollEnd(int nativeContentViewCoreImpl, long timeMs);
private native void nativeScrollBy(
int nativeContentViewCoreImpl, long timeMs, int deltaX, int deltaY);
private native void nativeFlingStart(
int nativeContentViewCoreImpl, long timeMs, int x, int y, int vx, int vy);
private native void nativeFlingCancel(int nativeContentViewCoreImpl, long timeMs);
private native void nativeSingleTap(
int nativeContentViewCoreImpl, long timeMs, int x, int y, boolean linkPreviewTap);
private native void nativeShowPressState(
int nativeContentViewCoreImpl, long timeMs, int x, int y);
private native void nativeDoubleTap(int nativeContentViewCoreImpl, long timeMs, int x, int y);
private native void nativeLongPress(int nativeContentViewCoreImpl, long timeMs, int x, int y,
boolean linkPreviewTap);
private native void nativePinchBegin(int nativeContentViewCoreImpl, long timeMs, int x, int y);
private native void nativePinchEnd(int nativeContentViewCoreImpl, long timeMs);
private native void nativePinchBy(int nativeContentViewCoreImpl, long timeMs,
int anchorX, int anchorY, float deltaScale);
private native boolean nativeCanGoBack(int nativeContentViewCoreImpl);
private native boolean nativeCanGoForward(int nativeContentViewCoreImpl);
......
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import org.chromium.content.browser.LongPressDetector.LongPressDelegate;
import org.chromium.content.common.TraceEvent;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* This class handles all MotionEvent handling done in ContentViewCore including the gesture
* recognition. It sends all related native calls through the interface MotionEventDelegate.
*/
class ContentViewGestureHandler implements LongPressDelegate {
private static final String TAG = ContentViewGestureHandler.class.toString();
/**
* Used for GESTURE_FLING_START x velocity
*/
static final String VELOCITY_X = "Velocity X";
/**
* Used for GESTURE_FLING_START y velocity
*/
static final String VELOCITY_Y = "Velocity Y";
/**
* Used in GESTURE_SINGLE_TAP_CONFIRMED to check whether ShowPress has been called before.
*/
static final String SHOW_PRESS = "ShowPress";
/**
* Used for GESTURE_PINCH_BY delta
*/
static final String DELTA = "Delta";
private final Bundle mExtraParamBundle;
private GestureDetector mGestureDetector;
private final ZoomManager mZoomManager;
private LongPressDetector mLongPressDetector;
private OnGestureListener mListener;
private MotionEvent mCurrentDownEvent;
private final MotionEventDelegate mMotionEventDelegate;
// Queue of motion events. If the boolean value is true, it means
// that the event has been offered to the native side but not yet acknowledged. If the
// value is false, it means the touch event has not been offered
// to the native side and can be immediately processed.
private final Deque<Pair<MotionEvent, Boolean>> mPendingMotionEvents =
new ArrayDeque<Pair<MotionEvent, Boolean>>();
// Has WebKit told us the current page requires touch events.
private boolean mNeedTouchEvents = false;
// Remember whether onShowPress() is called. If it is not, in onSingleTapConfirmed()
// we will first show the press state, then trigger the click.
private boolean mShowPressIsCalled;
// TODO(klobag): this is to avoid a bug in GestureDetector. With multi-touch,
// mAlwaysInTapRegion is not reset. So when the last finger is up, onSingleTapUp()
// will be mistakenly fired.
private boolean mIgnoreSingleTap;
// Does native think we are scrolling? True from right before we
// send the first scroll event until the last finger is raised, or
// until after the follow-up fling has finished. Call
// nativeScrollBegin() when setting this to true, and use
// tellNativeScrollingHasEnded() to set it to false.
private boolean mNativeScrolling;
private boolean mPinchInProgress = false;
// Tracks whether a touch cancel event has been sent as a result of switching
// into scrolling or pinching mode.
private boolean mTouchCancelEventSent = false;
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
//On single tap this will store the x, y coordinates of the touch.
private int mSingleTapX;
private int mSingleTapY;
// Used to track the last rawX/Y coordinates for moves. This gives absolute scroll distance.
// Useful for full screen tracking.
private float mLastRawX = 0;
private float mLastRawY = 0;
// Cache of square of the scaled touch slop so we don't have to calculate it on every touch.
private int mScaledTouchSlopSquare;
// Used to track the accumulated scroll error over time. This is used to remove the
// rounding error we introduced by passing integers to webkit.
private float mAccumulatedScrollErrorX = 0;
private float mAccumulatedScrollErrorY = 0;
private static final int SNAP_NONE = 0;
private static final int SNAP_HORIZ = 1;
private static final int SNAP_VERT = 2;
private int mSnapScrollMode = SNAP_NONE;
private float mAverageAngle;
private boolean mSeenFirstScroll;
/*
* Here is the snap align logic:
* 1. If it starts nearly horizontally or vertically, snap align;
* 2. If there is a dramatic direction change, let it go;
*
* Adjustable parameters. Angle is the radians on a unit circle, limited
* to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
*/
private static final float HSLOPE_TO_START_SNAP = .25f;
private static final float HSLOPE_TO_BREAK_SNAP = .6f;
private static final float VSLOPE_TO_START_SNAP = 1.25f;
private static final float VSLOPE_TO_BREAK_SNAP = .6f;
/*
* These values are used to influence the average angle when entering
* snap mode. If it is the first movement entering snap, we set the average
* to the appropriate ideal. If the user is entering into snap after the
* first movement, then we average the average angle with these values.
*/
private static final float ANGLE_VERT = (float)(Math.PI / 2.0);
private static final float ANGLE_HORIZ = 0f;
/*
* The modified moving average weight.
* Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
*/
private static final float MMA_WEIGHT_N = 5;
static final int GESTURE_SHOW_PRESSED_STATE = 0;
static final int GESTURE_DOUBLE_TAP = 1;
static final int GESTURE_SINGLE_TAP_UP = 2;
static final int GESTURE_SINGLE_TAP_CONFIRMED = 3;
static final int GESTURE_LONG_PRESS = 4;
static final int GESTURE_SCROLL_START = 5;
static final int GESTURE_SCROLL_BY = 6;
static final int GESTURE_SCROLL_END = 7;
static final int GESTURE_FLING_START = 8;
static final int GESTURE_FLING_CANCEL = 9;
static final int GESTURE_PINCH_BEGIN = 10;
static final int GESTURE_PINCH_BY = 11;
static final int GESTURE_PINCH_END = 12;
/**
* This is an interface to handle MotionEvent related communication with the native side also
* access some ContentView specific parameters.
*/
public interface MotionEventDelegate {
/**
* Send a raw {@link MotionEvent} to the native side
* @param timeMs Time of the event in ms.
* @param action The action type for the event.
* @param pts The TouchPoint array to be sent for the event.
* @return Whether the event was sent to the native side successfully or not.
*/
public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts);
/**
* Send a gesture event to the native side.
* @param type The type of the gesture event.
* @param timeMs The time the gesture event occurred at.
* @param x The x location for the gesture event.
* @param y The y location for the gesture event.
* @param extraParams A bundle that holds specific extra parameters for certain gestures.
* Refer to gesture type definition for more information.
* @return Whether the gesture was sent successfully.
*/
boolean sendGesture(
int type, long timeMs, int x, int y, Bundle extraParams);
/**
* Gives the UI the chance to override each scroll event.
* @param x The amount scrolled in the X direction.
* @param y The amount scrolled in the Y direction.
* @return Whether or not the UI consumed and handled this event.
*/
boolean didUIStealScroll(float x, float y);
/**
* Show the zoom picker UI.
*/
public void invokeZoomPicker();
}
ContentViewGestureHandler(
Context context, MotionEventDelegate delegate, ZoomManager zoomManager) {
mExtraParamBundle = new Bundle();
mLongPressDetector = new LongPressDetector(context, this);
mMotionEventDelegate = delegate;
mZoomManager = zoomManager;
initGestureDetectors(context);
}
/**
* Used to override the default long press detector, gesture detector and listener.
* This is used for testing only.
* @param longPressDetector The new LongPressDetector to be assigned.
* @param gestureDetector The new GestureDetector to be assigned.
* @param listener The new onGestureListener to be assigned.
*/
void setTestDependencies(
LongPressDetector longPressDetector, GestureDetector gestureDetector,
OnGestureListener listener) {
mLongPressDetector = longPressDetector;
mGestureDetector = gestureDetector;
mListener = listener;
}
private void initGestureDetectors(final Context context) {
int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mScaledTouchSlopSquare = scaledTouchSlop * scaledTouchSlop;
try {
TraceEvent.begin();
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
mShowPressIsCalled = false;
mIgnoreSingleTap = false;
mSeenFirstScroll = false;
mNativeScrolling = false;
mSnapScrollMode = SNAP_NONE;
mLastRawX = e.getRawX();
mLastRawY = e.getRawY();
mAccumulatedScrollErrorX = 0;
mAccumulatedScrollErrorY = 0;
// Return true to indicate that we want to handle touch
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// Scroll snapping
if (!mSeenFirstScroll) {
mAverageAngle = calculateDragAngle(distanceX, distanceY);
// Initial scroll event
if (!mZoomManager.isScaleGestureDetectionInProgress()) {
// if it starts nearly horizontal or vertical, enforce it
if (mAverageAngle < HSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_HORIZ;
mAverageAngle = ANGLE_HORIZ;
} else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_VERT;
mAverageAngle = ANGLE_VERT;
}
}
mSeenFirstScroll = true;
// Ignore the first scroll delta to avoid a visible jump.
return true;
} else {
mAverageAngle +=
(calculateDragAngle(distanceX, distanceY) - mAverageAngle)
/ MMA_WEIGHT_N;
if (mSnapScrollMode != SNAP_NONE) {
if ((mSnapScrollMode == SNAP_VERT
&& mAverageAngle < VSLOPE_TO_BREAK_SNAP)
|| (mSnapScrollMode == SNAP_HORIZ
&& mAverageAngle > HSLOPE_TO_BREAK_SNAP)) {
// radical change means getting out of snap mode
mSnapScrollMode = SNAP_NONE;
}
} else {
if (!mZoomManager.isScaleGestureDetectionInProgress()) {
if (mAverageAngle < HSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_HORIZ;
mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2;
} else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_VERT;
mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2;
}
}
}
}
if (mSnapScrollMode != SNAP_NONE) {
if (mSnapScrollMode == SNAP_HORIZ) {
distanceY = 0;
} else {
distanceX = 0;
}
}
boolean didUIStealScroll = mMotionEventDelegate.didUIStealScroll(
e2.getRawX() - mLastRawX, e2.getRawY() - mLastRawY);
mLastRawX = e2.getRawX();
mLastRawY = e2.getRawY();
if (didUIStealScroll) return true;
if (!mNativeScrolling && mMotionEventDelegate.sendGesture(
GESTURE_SCROLL_START, e1.getEventTime(),
(int) e1.getX(), (int) e1.getY(), null)) {
mNativeScrolling = true;
}
// distanceX and distanceY is the scrolling offset since last onScroll.
// Because we are passing integers to webkit, this could introduce
// rounding errors. The rounding errors will accumulate overtime.
// To solve this, we should adding back the rounding errors each time
// when we calculate the new offset.
int dx = (int) (distanceX + mAccumulatedScrollErrorX);
int dy = (int) (distanceY + mAccumulatedScrollErrorY);
mAccumulatedScrollErrorX = distanceX + mAccumulatedScrollErrorX - dx;
mAccumulatedScrollErrorY = distanceY + mAccumulatedScrollErrorY - dy;
if ((dx | dy) != 0) {
mMotionEventDelegate.sendGesture(GESTURE_SCROLL_BY,
e2.getEventTime(), dx, dy, null);
}
mMotionEventDelegate.invokeZoomPicker();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
if (mSnapScrollMode == SNAP_NONE) {
float flingAngle = calculateDragAngle(velocityX, velocityY);
if (flingAngle < HSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_HORIZ;
mAverageAngle = ANGLE_HORIZ;
} else if (flingAngle > VSLOPE_TO_START_SNAP) {
mSnapScrollMode = SNAP_VERT;
mAverageAngle = ANGLE_VERT;
}
}
if (mSnapScrollMode != SNAP_NONE) {
if (mSnapScrollMode == SNAP_HORIZ) {
velocityY = 0;
} else {
velocityX = 0;
}
}
fling(e1.getEventTime(),(int) e1.getX(0), (int) e1.getY(0),
(int) velocityX, (int) velocityY);
return true;
}
@Override
public void onShowPress(MotionEvent e) {
mShowPressIsCalled = true;
mMotionEventDelegate.sendGesture(GESTURE_SHOW_PRESSED_STATE,
e.getEventTime(), (int) e.getX(), (int) e.getY(), null);
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (isDistanceBetweenDownAndUpTooLong(e.getRawX(), e.getRawY())) {
mIgnoreSingleTap = true;
return true;
}
// This is a hack to address the issue where user hovers
// over a link for longer than DOUBLE_TAP_TIMEOUT, then
// onSingleTapConfirmed() is not triggered. But we still
// want to trigger the tap event at UP. So we override
// onSingleTapUp() in this case. This assumes singleTapUp
// gets always called before singleTapConfirmed.
if (!mIgnoreSingleTap && !mLongPressDetector.isInLongPress() &&
(e.getEventTime() - e.getDownTime() > DOUBLE_TAP_TIMEOUT)) {
float x = e.getX();
float y = e.getY();
if (mMotionEventDelegate.sendGesture(GESTURE_SINGLE_TAP_UP,
e.getEventTime(), (int) x, (int) y, null)) {
mIgnoreSingleTap = true;
}
setClickXAndY((int) x, (int) y);
return true;
}
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// Long taps in the edges of the screen have their events delayed by
// ChromeViewHolder for tab swipe operations. As a consequence of the delay
// this method might be called after receiving the up event.
// These corner cases should be ignored.
if (mLongPressDetector.isInLongPress() || mIgnoreSingleTap) return true;
int x = (int) e.getX();
int y = (int) e.getY();
mExtraParamBundle.clear();
mExtraParamBundle.putBoolean(SHOW_PRESS, mShowPressIsCalled);
mMotionEventDelegate.sendGesture(GESTURE_SINGLE_TAP_CONFIRMED,
e.getEventTime(), x, y, mExtraParamBundle);
setClickXAndY(x, y);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
mMotionEventDelegate.sendGesture(GESTURE_DOUBLE_TAP,
e.getEventTime(), (int) e.getX(), (int) e.getY(), null);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (!mZoomManager.isScaleGestureDetectionInProgress()) {
mMotionEventDelegate.sendGesture(GESTURE_LONG_PRESS,
e.getEventTime(), (int) e.getX(), (int) e.getY(), null);
}
}
/**
* This method inspects the distance between where the user started touching
* the surface, and where she released. If the points are too far apart, we
* should assume that the web page has consumed the scroll-events in-between,
* and as such, this should not be considered a single-tap.
*
* We use the Android frameworks notion of how far a touch can wander before
* we think the user is scrolling.
*
* @param x the new x coordinate
* @param y the new y coordinate
* @return true if the distance is too long to be considered a single tap
*/
private boolean isDistanceBetweenDownAndUpTooLong(float x, float y) {
double deltaX = mLastRawX - x;
double deltaY = mLastRawY - y;
return deltaX * deltaX + deltaY * deltaY > mScaledTouchSlopSquare;
}
};
mListener = listener;
mGestureDetector = new GestureDetector(context, listener);
mGestureDetector.setIsLongpressEnabled(false);
} finally {
TraceEvent.end();
}
}
/**
* @return LongPressDetector handling setting up timers for and canceling LongPress gestures.
*/
LongPressDetector getLongPressDetector() {
return mLongPressDetector;
}
/**
* @param event Start a LongPress gesture event from the listener.
*/
@Override
public void onLongPress(MotionEvent event) {
mListener.onLongPress(event);
}
/**
* Cancels any ongoing LongPress timers.
*/
void cancelLongPress() {
mLongPressDetector.cancelLongPress();
}
/**
* Fling the ContentView from the current position.
* @param x Fling touch starting position
* @param y Fling touch starting position
* @param velocityX Initial velocity of the fling (X) measured in pixels per second.
* @param velocityY Initial velocity of the fling (Y) measured in pixels per second.
*/
void fling(long timeMs, int x, int y, int velocityX, int velocityY) {
endFling(timeMs);
mExtraParamBundle.clear();
mExtraParamBundle.putInt(VELOCITY_X, velocityX);
mExtraParamBundle.putInt(VELOCITY_Y, velocityY);
mMotionEventDelegate.sendGesture(GESTURE_FLING_START,
timeMs, x, y, mExtraParamBundle);
}
/**
* Send a FlingCancel gesture event and also cancel scrolling if it is active.
* @param timeMs The time in ms for the event initiating this gesture.
*/
void endFling(long timeMs) {
mMotionEventDelegate.sendGesture(GESTURE_FLING_CANCEL, timeMs, 0, 0, null);
tellNativeScrollingHasEnded(timeMs);
}
// If native thinks scrolling (or fling-scrolling) is going on, tell native
// it has ended.
private void tellNativeScrollingHasEnded(long timeMs) {
if (mNativeScrolling) {
mNativeScrolling = false;
mMotionEventDelegate.sendGesture(GESTURE_SCROLL_END, timeMs, 0, 0, null);
}
}
/**
* Starts a pinch gesture.
* @param timeMs The time in ms for the event initiating this gesture.
* @param x The x coordinate for the event initiating this gesture.
* @param y The x coordinate for the event initiating this gesture.
*/
void pinchBegin(long timeMs, int x, int y) {
mMotionEventDelegate.sendGesture(GESTURE_PINCH_BEGIN, timeMs, x, y, null);
}
/**
* Pinch by a given percentage.
* @param timeMs The time in ms for the event initiating this gesture.
* @param anchorX The x coordinate for the anchor point to be used in pinch.
* @param anchorY The y coordinate for the anchor point to be used in pinch.
* @param delta The percentage to pinch by.
*/
void pinchBy(long timeMs, int anchorX, int anchorY, float delta) {
mExtraParamBundle.clear();
mExtraParamBundle.putFloat(DELTA, delta);
mMotionEventDelegate.sendGesture(GESTURE_PINCH_BY,
timeMs, anchorX, anchorY, mExtraParamBundle);
mPinchInProgress = true;
}
/**
* End a pinch gesture.
* @param timeMs The time in ms for the event initiating this gesture.
*/
void pinchEnd(long timeMs) {
mMotionEventDelegate.sendGesture(GESTURE_PINCH_END, timeMs, 0, 0, null);
mPinchInProgress = false;
}
/**
* Ignore singleTap gestures.
*/
void setIgnoreSingleTap(boolean value) {
mIgnoreSingleTap = value;
}
private float calculateDragAngle(float dx, float dy) {
dx = Math.abs(dx);
dy = Math.abs(dy);
return (float) Math.atan2(dy, dx);
}
private void setClickXAndY(int x, int y) {
mSingleTapX = x;
mSingleTapY = y;
}
/**
* @return The x coordinate for the last point that a singleTap gesture was initiated from.
*/
public int getSingleTapX() {
return mSingleTapX;
}
/**
* @return The y coordinate for the last point that a singleTap gesture was initiated from.
*/
public int getSingleTapY() {
return mSingleTapY;
}
/**
* Handle the incoming MotionEvent.
* @return Whether the event was handled.
*/
boolean onTouchEvent(MotionEvent event) {
TraceEvent.begin("onTouchEvent");
mLongPressDetector.cancelLongPressIfNeeded(event);
// Notify native that scrolling has stopped whenever a down action is processed prior to
// passing the event to native as it will drop them as an optimization if scrolling is
// enabled. Ending the fling ensures scrolling has stopped as well as terminating the
// current fling if applicable.
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
endFling(event.getEventTime());
}
if (offerTouchEventToJavaScript(event)) {
// offerTouchEventToJavaScript returns true to indicate the event was sent
// to the render process. If it is not subsequently handled, it will
// be returned via confirmTouchEvent(false) and eventually passed to
// processTouchEvent asynchronously.
TraceEvent.end("onTouchEvent");
return true;
}
return processTouchEvent(event);
}
/**
* Sets the flag indicating that the content has registered listeners for touch events.
*/
void didSetNeedTouchEvents(boolean needTouchEvents) {
mNeedTouchEvents = needTouchEvents;
// When mainframe is loading, FrameLoader::transitionToCommitted will
// call this method to set mNeedTouchEvents to false. We use this as
// an indicator to clear the pending motion events so that events from
// the previous page will not be carried over to the new page.
if (!mNeedTouchEvents) mPendingMotionEvents.clear();
}
private boolean offerTouchEventToJavaScript(MotionEvent event) {
mLongPressDetector.onOfferTouchEventToJavaScript(event);
if (!mNeedTouchEvents) return false;
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
// Only send move events if the move has exceeded the slop threshold.
if (!mLongPressDetector.confirmOfferMoveEventToJavaScript(event)) {
return true;
}
// Avoid flooding the renderer process with move events: if the previous pending
// command is also a move (common case), skip sending this event to the webkit
// side and collapse it into the pending event.
Pair<MotionEvent, Boolean> previousEvent = mPendingMotionEvents.peekLast();
if (previousEvent != null && previousEvent.second == true
&& previousEvent.first.getActionMasked() == MotionEvent.ACTION_MOVE
&& previousEvent.first.getPointerCount() == event.getPointerCount()) {
MotionEvent.PointerCoords[] coords =
new MotionEvent.PointerCoords[event.getPointerCount()];
for (int i = 0; i < coords.length; ++i) {
coords[i] = new MotionEvent.PointerCoords();
event.getPointerCoords(i, coords[i]);
}
previousEvent.first.addBatch(event.getEventTime(), coords, event.getMetaState());
return true;
}
}
TouchPoint[] pts = new TouchPoint[event.getPointerCount()];
int type = TouchPoint.createTouchPoints(event, pts);
boolean forwarded = false;
if (type != TouchPoint.CONVERSION_ERROR && !mNativeScrolling && !mPinchInProgress) {
mTouchCancelEventSent = false;
forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime(), type, pts);
} else if ((mNativeScrolling || mPinchInProgress) && !mTouchCancelEventSent) {
forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime(),
TouchPoint.TOUCH_EVENT_TYPE_CANCEL, pts);
mTouchCancelEventSent = true;
}
if (forwarded || !mPendingMotionEvents.isEmpty()) {
// Copy the event, as the original may get mutated after this method returns.
mPendingMotionEvents.add(Pair.create(MotionEvent.obtain(event), forwarded));
// TODO(joth): If needed, start a watchdog timer to pump mPendingMotionEvents
// in the case of the WebKit renderer / JS being unresponsive.
return true;
}
return false;
}
private boolean processTouchEvent(MotionEvent event) {
boolean handled = false;
// The last "finger up" is an end to scrolling but may not be
// an end to movement (e.g. fling scroll). We do not tell
// native code to end scrolling until we are sure we did not
// fling.
boolean possiblyEndMovement = false;
// "Last finger raised" could be an end to movement. However,
// give the mSimpleTouchDetector a chance to continue
// scrolling with a fling.
if ((event.getAction() == MotionEvent.ACTION_UP) &&
(event.getPointerCount() == 1)) {
if (mNativeScrolling) {
possiblyEndMovement = true;
}
}
mLongPressDetector.startLongPressTimerIfNeeded(event);
// Use the framework's GestureDetector to detect pans and zooms not already
// handled by the WebKit touch events gesture manager.
if (canHandle(event)) {
handled |= mGestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) mCurrentDownEvent = event;
}
handled |= mZoomManager.processTouchEvent(event);
if (possiblyEndMovement && !handled) {
tellNativeScrollingHasEnded(event.getEventTime());
}
return handled;
}
/**
* Respond to a MotionEvent being returned from the native side.
* @param handled Whether the MotionEvent was handled on the native side.
*/
void confirmTouchEvent(boolean handled) {
MotionEvent eventToPassThrough = null;
if (mPendingMotionEvents.isEmpty()) {
Log.w(TAG, "confirmTouchEvent with Empty pending list!");
return;
}
TraceEvent.begin();
Pair<MotionEvent, Boolean> event = mPendingMotionEvents.removeFirst();
if (!handled) {
if (!processTouchEvent(event.first)) {
// TODO(joth): If the Java side gesture handler also fails to consume
// this deferred event, should it be bubbled up to the parent view?
Log.w(TAG, "Unhandled deferred touch event");
}
} else {
mZoomManager.passTouchEventThrough(event.first);
}
// Now process all events that are in the queue but not sent to the native.
Pair<MotionEvent, Boolean> nextEvent = mPendingMotionEvents.peekFirst();
while (nextEvent != null && nextEvent.second == false) {
processTouchEvent(nextEvent.first);
mPendingMotionEvents.removeFirst();
nextEvent.first.recycle();
nextEvent = mPendingMotionEvents.peekFirst();
}
// We may have pending events that could cancel the timers:
// For instance, if we received an UP before the DOWN completed
// its roundtrip (so it didn't cancel the timer during onTouchEvent()).
mLongPressDetector.cancelLongPressIfNeeded(mPendingMotionEvents.iterator());
event.first.recycle();
TraceEvent.end();
}
/**
* @return Whether the ContentViewGestureHandler can handle a MotionEvent right now. True only
* if it's the start of a new stream (ACTION_DOWN), or a continuation of the current stream.
*/
boolean canHandle(MotionEvent ev) {
return ev.getAction() == MotionEvent.ACTION_DOWN ||
(mCurrentDownEvent != null && mCurrentDownEvent.getDownTime() == ev.getDownTime());
}
}
......@@ -17,10 +17,10 @@ class TouchPoint {
// Type of motion event to send to the native side. The values originate from their
// webkit WebInputEvent counterparts, and are set via initializeConstants().
private static int TOUCH_EVENT_TYPE_START;
private static int TOUCH_EVENT_TYPE_MOVE;
private static int TOUCH_EVENT_TYPE_END;
private static int TOUCH_EVENT_TYPE_CANCEL;
static int TOUCH_EVENT_TYPE_START;
static int TOUCH_EVENT_TYPE_MOVE;
static int TOUCH_EVENT_TYPE_END;
static int TOUCH_EVENT_TYPE_CANCEL;
// Type of motion event to send to the native side. The values originate from their
// webkit WebTouchPoint counterparts, and are set via initializeConstants().
......
......@@ -55,14 +55,14 @@ class ZoomManager {
public boolean onScaleBegin(ScaleGestureDetector detector) {
if (ignoreDetectorEvents()) return false;
mPinchEventSent = false;
mContentViewCore.setIgnoreSingleTap(true);
mContentViewCore.getContentViewGestureHandler().setIgnoreSingleTap(true);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
if (!mPinchEventSent || !mContentViewCore.isAlive()) return;
mContentViewCore.pinchEnd(detector.getEventTime());
mContentViewCore.getContentViewGestureHandler().pinchEnd(detector.getEventTime());
mPinchEventSent = false;
}
......@@ -76,11 +76,11 @@ class ZoomManager {
// that pinchBy() is called without any pinchBegin().
// To solve this problem, we call pinchBegin() here if it is never called.
if (!mPinchEventSent) {
mContentViewCore.pinchBegin(detector.getEventTime(),
mContentViewCore.getContentViewGestureHandler().pinchBegin(detector.getEventTime(),
(int) detector.getFocusX(), (int) detector.getFocusY());
mPinchEventSent = true;
}
mContentViewCore.pinchBy(
mContentViewCore.getContentViewGestureHandler().pinchBy(
detector.getEventTime(), (int) detector.getFocusX(), (int) detector.getFocusY(),
detector.getScaleFactor());
return true;
......
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.GestureDetector.OnGestureListener;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.chromium.base.test.Feature;
import org.chromium.base.test.ScalableTimeout;
import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
/**
* Test suite for ContentViewGestureHandler.
*/
public class ContentViewGestureHandlerTest extends InstrumentationTestCase {
private static final int FAKE_COORD_X = 42;
private static final int FAKE_COORD_Y = 24;
private static final String TAG = ContentViewGestureHandler.class.toString();
private MockListener mMockListener;
private MockGestureDetector mMockGestureDetector;
private ContentViewGestureHandler mGestureHandler;
private LongPressDetector mLongPressDetector;
static class MockListener extends GestureDetector.SimpleOnGestureListener {
MotionEvent mLastLongPress;
MotionEvent mLastSingleTap;
MotionEvent mLastFling1;
MotionEvent mLastFling2;
CountDownLatch mLongPressCalled;
MotionEvent mLastScroll1;
MotionEvent mLastScroll2;
float mLastScrollDistanceX;
float mLastScrollDistanceY;
public MockListener() {
mLongPressCalled = new CountDownLatch(1);
}
@Override
public void onLongPress(MotionEvent e) {
mLastLongPress = MotionEvent.obtain(e);
mLongPressCalled.countDown();
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
mLastSingleTap = e;
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
mLastSingleTap = e;
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mLastScroll1 = e1;
mLastScroll2 = e2;
mLastScrollDistanceX = distanceX;
mLastScrollDistanceY = distanceY;
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mLastFling1 = e1;
mLastFling2 = e2;
return true;
}
}
static class MockGestureDetector extends GestureDetector {
MotionEvent mLastEvent;
public MockGestureDetector(Context context, OnGestureListener listener) {
super(context, listener);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mLastEvent = MotionEvent.obtain(ev);
return super.onTouchEvent(ev);
}
}
static class MockMotionEventDelegate implements MotionEventDelegate {
@Override
public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) {
// Not implemented.
return false;
}
@Override
public boolean sendGesture(int type, long timeMs, int x, int y, Bundle extraParams) {
Log.i(TAG,"Gesture event received with type id " + type);
return false;
}
@Override
public boolean didUIStealScroll(float x, float y) {
// Not implemented.
return false;
}
@Override
public void invokeZoomPicker() {
// Not implemented.
}
}
static class MockZoomManager extends ZoomManager {
MockZoomManager(Context context, ContentViewCore contentViewCore) {
super(context, contentViewCore);
}
@Override
public boolean processTouchEvent(MotionEvent event) {
return false;
}
}
private MotionEvent motionEvent(int action, long downTime, long eventTime) {
return MotionEvent.obtain(downTime, eventTime, action, FAKE_COORD_X, FAKE_COORD_Y, 0);
}
@Override
public void setUp() {
mMockListener = new MockListener();
mMockGestureDetector = new MockGestureDetector(
getInstrumentation().getTargetContext(), mMockListener);
mGestureHandler = new ContentViewGestureHandler(
getInstrumentation().getTargetContext(), new MockMotionEventDelegate(),
new MockZoomManager(getInstrumentation().getTargetContext(), null));
mLongPressDetector = new LongPressDetector(
getInstrumentation().getTargetContext(), mGestureHandler);
mGestureHandler.setTestDependencies(
mLongPressDetector, mMockGestureDetector, mMockListener);
}
/**
* Verify that a DOWN followed shortly by an UP will trigger a single tap.
*
* @throws Exception
*/
@SmallTest
@Feature({"Android-WebView"})
public void testGestureSingleClick() throws Exception {
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis();
MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime);
assertFalse(mGestureHandler.onTouchEvent(event));
assertTrue("Should have a pending gesture", mMockGestureDetector.mLastEvent != null);
assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage());
event = motionEvent(MotionEvent.ACTION_UP, downTime, eventTime + 10);
mLongPressDetector.cancelLongPressIfNeeded(event);
assertTrue("Should not have a pending LONG_PRESS", !mLongPressDetector.hasPendingMessage());
assertTrue(mGestureHandler.onTouchEvent(event));
// Synchronous, no need to wait.
assertTrue("Should have a single tap", mMockListener.mLastSingleTap != null);
}
/**
* Verify that a DOWN followed by a MOVE will trigger fling (but not LONG).
* @throws Exception
*/
@SmallTest
@Feature({"Android-WebView"})
public void testGestureFlingAndCancelLongClick() throws Exception {
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis();
MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime);
assertFalse(mGestureHandler.onTouchEvent(event));
assertTrue("Should have a pending gesture", mMockGestureDetector.mLastEvent != null);
assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage());
event = MotionEvent.obtain(
downTime, eventTime + 5, MotionEvent.ACTION_MOVE,
FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0);
mLongPressDetector.cancelLongPressIfNeeded(event);
assertTrue("Should not have a pending LONG_PRESS", !mLongPressDetector.hasPendingMessage());
assertTrue(mGestureHandler.onTouchEvent(event));
event = MotionEvent.obtain(
downTime, eventTime + 10, MotionEvent.ACTION_UP,
FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0);
assertTrue(mGestureHandler.onTouchEvent(event));
// Synchronous, no need to wait.
assertTrue("Should have a fling", mMockListener.mLastFling1 != null);
assertTrue("Should not have a long press", mMockListener.mLastLongPress == null);
}
}
......@@ -242,6 +242,9 @@ const char kDomAutomationController[] = "dom-automation";
// Enable hardware accelerated page painting.
const char kEnableAcceleratedPainting[] = "enable-accelerated-painting";
// Enables the hardware acceleration of plugins.
const char kEnableAcceleratedPlugins[] = "enable-accelerated-plugins";
// Enable gpu-accelerated SVG/W3C filters.
const char kEnableAcceleratedFilters[] = "enable-accelerated-filters";
......@@ -652,6 +655,12 @@ const char kEnableVisualWordMovement[] = "enable-visual-word-movement";
#if defined(OS_ANDROID)
// Set when Chromium should use a mobile user agent.
const char kUseMobileUserAgent[] = "use-mobile-user-agent";
// Omnibus flag setting an Android graphics mode. May be:
// "basic" (untiled software path)
// "compositor" (hardware-accelerated compositing),
const char kGraphicsMode[] = "graphics-mode";
const char kGraphicsModeValueBasic[] = "basic";
const char kGraphicsModeValueCompositor[] = "compositor";
#endif
#if defined(OS_POSIX) && !defined(OS_MACOSX)
......
......@@ -90,6 +90,7 @@ extern const char kDisableXSSAuditor[];
CONTENT_EXPORT extern const char kDomAutomationController[];
CONTENT_EXPORT extern const char kEnableAcceleratedPainting[];
CONTENT_EXPORT extern const char kEnableAcceleratedFilters[];
extern const char kEnableAcceleratedPlugins[];
extern const char kEnableAccessibilityLogging[];
CONTENT_EXPORT extern const char kEnableCompositingForFixedPosition[];
extern const char kEnableCssRegions[];
......@@ -210,6 +211,10 @@ extern const char kEnableVisualWordMovement[];
#if defined(OS_ANDROID)
extern const char kUseMobileUserAgent[];
extern const char kGraphicsMode[];
// Not actual flags, just values: for example, --graphics-mode=compositor
extern const char kGraphicsModeValueBasic[];
extern const char kGraphicsModeValueCompositor[];
#endif
#if defined(OS_POSIX) && !defined(OS_MACOSX)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment