Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(252)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/ContextualSearchEventFilter.java

Issue 987883002: Upstream Layout.java and associated files (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Move to SuppressFBWarnings from findbugs_exclude.xml Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/ContextualSearchEventFilter.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/ContextualSearchEventFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/ContextualSearchEventFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f85861da782d3d42fee2f8813af5b14589f4e52d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/ContextualSearchEventFilter.java
@@ -0,0 +1,524 @@
+// Copyright 2015 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.chrome.browser.compositor.layouts.eventfilter;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate;
+
+import java.util.ArrayList;
+
+/**
+ * The {@link GestureEventFilter} used when Contextual Search Layout is being shown. It filters
+ * events that happen in the Search Content View area and propagates them to the appropriate
+ * Content View Core via {@link EventFilterHost}. Events that happen outside that area are
+ * propagated to the {@code ContextualSearchLayout} via {@code LayoutManagerPhone}.
+ */
+public class ContextualSearchEventFilter extends GestureEventFilter {
+
+ /**
+ * The targets that can handle MotionEvents.
+ */
+ private enum EventTarget {
+ UNDETERMINED,
+ SEARCH_PANEL,
+ SEARCH_CONTENT_VIEW
+ }
+
+ /**
+ * The direction of the gesture.
+ */
+ private enum GestureOrientation {
+ UNDETERMINED,
+ HORIZONTAL,
+ VERTICAL
+ }
+
+ /**
+ * The boost factor that can be applied to prioritize vertical movements over horizontal ones.
+ */
+ private static final float VERTICAL_DETERMINATION_BOOST = 1.25f;
+
+ /**
+ * The shared state of the UI.
+ */
+ private ContextualSearchPanel mSearchPanel;
+
+ /**
+ * The delegate to talk to ContextualSearchManager.
+ */
+ private ContextualSearchManagementDelegate mManagementDelegate;
+
+ /**
+ * The {@link GestureDetector} used to distinguish tap and scroll gestures.
+ */
+ private final GestureDetector mGestureDetector;
+
+ /**
+ * The target to propagate events to.
+ */
+ private EventTarget mEventTarget;
+
+ /**
+ * Whether the code is in the middle of the process of determining the event target.
+ */
+ private boolean mIsDeterminingEventTarget;
+
+ /**
+ * Whether the event target has been determined.
+ */
+ private boolean mHasDeterminedEventTarget;
+
+ /**
+ * The previous target the events were propagated to.
+ */
+ private EventTarget mPreviousEventTarget;
+
+ /**
+ * Whether the event target has changed since the last touch event.
+ */
+ private boolean mHasChangedEventTarget;
+
+ /**
+ * Whether the event target might change. This will be true in cases we know the overscroll
+ * and/or underscroll might happen, which means we'll have to constantly monitor the event
+ * targets in order to determine the exact moment the target has changed.
+ */
+ private boolean mMayChangeEventTarget;
+
+ /**
+ * Whether the gesture orientation has been determined.
+ */
+ private boolean mHasDeterminedGestureOrientation;
+
+ /**
+ * The current gesture orientation.
+ */
+ private GestureOrientation mGestureOrientation;
+
+ /**
+ * Whether the events are being recorded.
+ */
+ private boolean mIsRecordingEvents;
+
+ /**
+ * Whether the ACTION_DOWN that initiated the MotionEvent's stream was synthetic.
+ */
+ private boolean mWasActionDownEventSynthetic;
+
+ /**
+ * The X coordinate of the synthetic ACTION_DOWN MotionEvent.
+ */
+ private float mSyntheticActionDownX;
+
+ /**
+ * The Y coordinate of the synthetic ACTION_DOWN MotionEvent.
+ */
+ private float mSyntheticActionDownY;
+
+ /**
+ * The list of recorded events.
+ */
+ private final ArrayList<MotionEvent> mRecordedEvents = new ArrayList<MotionEvent>();
+
+ /**
+ * The initial Y position of the current gesture.
+ */
+ private float mInitialEventY;
+
+ /**
+ * The square of ViewConfiguration.getScaledTouchSlop() in pixels used to calculate whether
+ * the finger has moved beyond the established threshold.
+ */
+ private final float mTouchSlopSquarePx;
+
+ /**
+ * Creates a {@link GestureEventFilter} with offset touch events.
+ */
+ public ContextualSearchEventFilter(Context context, EventFilterHost host,
+ GestureHandler handler, ContextualSearchPanel contextualSearchPanel) {
+ super(context, host, handler, false, false);
+
+ mGestureDetector = new GestureDetector(context, new InternalGestureDetector());
+ mSearchPanel = contextualSearchPanel;
+
+ // Store the square of the platform touch slop in pixels to use in the scroll detection.
+ // See {@link ContextualSearchEventFilter#isDistanceGreaterThanTouchSlop}.
+ float touchSlopPx = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTouchSlopSquarePx = touchSlopPx * touchSlopPx;
+
+ reset();
+ }
+
+ /**
+ * Sets the {@code ContextualSearchManagementDelegate} associated with this Event Filter.
+ * @param delegate The {@code ContextualSearchManagementDelegate}.
+ */
+ public void setManagementDelegate(ContextualSearchManagementDelegate delegate) {
+ mManagementDelegate = delegate;
+ }
+
+ /**
+ * Gets the Search Content View's vertical scroll position. If the Search Content View
+ * is not available it returns -1.
+ * @return The Search Content View scroll position.
+ */
+ @VisibleForTesting
+ protected float getSearchContentViewVerticalScroll() {
+ return mManagementDelegate.getSearchContentViewVerticalScroll();
+ }
+
+ @Override
+ public boolean onTouchEventInternal(MotionEvent e) {
+ final int action = e.getActionMasked();
+
+ if (action == MotionEvent.ACTION_POINTER_DOWN
+ && e.getPointerCount() == 2
+ && !mSearchPanel.isMaximized()) {
+ // We don't want the Search Content View's zoom level to change when the Search Panel
+ // is expanded (that is, not maximized) so we'll forward the events to Panel to
+ // prevent it from happening.
+ setEventTarget(EventTarget.SEARCH_PANEL);
+ } else if (!mIsDeterminingEventTarget && action == MotionEvent.ACTION_DOWN) {
+ mInitialEventY = e.getY();
+ if (mSearchPanel.isYCoordinateInsideSearchContentView(mInitialEventY * mPxToDp)) {
+ // If the DOWN event happened inside the Search Content View, we'll need
+ // to wait until the user has moved the finger beyond a certain threshold,
+ // so we can determine the gesture's orientation and consequently be able
+ // to tell if the Content View will accept the gesture.
+ mIsDeterminingEventTarget = true;
+ mMayChangeEventTarget = true;
+ } else {
+ // If the DOWN event happened outside the Search Content View, then we know
+ // that the Search Panel will start handling the event right away.
+ setEventTarget(EventTarget.SEARCH_PANEL);
+ mMayChangeEventTarget = false;
+ }
+ }
+
+ // Send the event to the GestureDetector so we can distinguish between scroll and tap.
+ mGestureDetector.onTouchEvent(e);
+
+ if (mHasDeterminedEventTarget) {
+ // If the event target has been determined, resume pending events, then propagate
+ // the current event to the appropriate target.
+ resumeAndPropagateEvent(e);
+ } else {
+ // If the event target has not been determined, we need to record a copy of the event
+ // until we are able to determine the event target.
+ MotionEvent event = MotionEvent.obtain(e);
+ mRecordedEvents.add(event);
+ mIsRecordingEvents = true;
+ }
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ reset();
+ }
+
+ return true;
+ }
+
+ /**
+ * Resets the current and previous {@link EventTarget} as well the {@link GestureOrientation}
+ * to the UNDETERMINED state.
+ */
+ private void reset() {
+ mEventTarget = EventTarget.UNDETERMINED;
+ mIsDeterminingEventTarget = false;
+ mHasDeterminedEventTarget = false;
+
+ mPreviousEventTarget = EventTarget.UNDETERMINED;
+ mHasChangedEventTarget = false;
+ mMayChangeEventTarget = false;
+
+ mWasActionDownEventSynthetic = false;
+
+ mGestureOrientation = GestureOrientation.UNDETERMINED;
+ mHasDeterminedGestureOrientation = false;
+ }
+
+ /**
+ * Resumes pending events then propagates the given event to the current {@link EventTarget}.
+ *
+ * Resuming events might consist in simply propagating previously recorded events if the
+ * EventTarget was UNDETERMINED when the gesture started.
+ *
+ * For the case where the EventTarget has changed during the course of the gesture, we'll
+ * need to simulate a gesture end in the previous target (by simulating an ACTION_CANCEL
+ * event) and a gesture start in the new target (by simulating an ACTION_DOWN event).
+ *
+ * @param e The {@link MotionEvent} to be propagated after resuming the pending events.
+ */
+ private void resumeAndPropagateEvent(MotionEvent e) {
+ if (mIsRecordingEvents) {
+ resumeRecordedEvents();
+ }
+
+ if (mHasChangedEventTarget) {
+ // TODO(pedrosimonetti): handle cases with multiple pointers.
+ float y = e.getY();
+ // If the event target has changed since the beginning of the gesture, then we need
+ // to send a ACTION_CANCEL to the previous event target to make sure it no longer
+ // expects events.
+ propagateAndRecycleEvent(createEvent(e, MotionEvent.ACTION_CANCEL, y),
+ mPreviousEventTarget);
+
+ // Similarly we need to send an ACTION_DOWN to the new event target so subsequent
+ // events can be analyzed properly by the Gesture Detector.
+ MotionEvent syntheticActionDownEvent = createEvent(e, MotionEvent.ACTION_DOWN, y);
+
+ // Store the synthetic ACTION_DOWN coordinates to prevent unwanted taps from
+ // happening. See {@link ContextualSearchEventFilter#propagateEventToSearchContentView}.
+ mWasActionDownEventSynthetic = true;
+ mSyntheticActionDownX = syntheticActionDownEvent.getX();
+ mSyntheticActionDownY = syntheticActionDownEvent.getY()
+ - mSearchPanel.getSearchContentViewOffsetY() / mPxToDp;
+
+ propagateAndRecycleEvent(syntheticActionDownEvent, mEventTarget);
+
+ mHasChangedEventTarget = false;
+ }
+
+ propagateEvent(e, mEventTarget);
+ }
+
+ /**
+ * Resumes recorded events by propagating all of them to the current {@link EventTarget}.
+ */
+ private void resumeRecordedEvents() {
+ for (int i = 0, size = mRecordedEvents.size(); i < size; i++) {
+ propagateAndRecycleEvent(mRecordedEvents.get(i), mEventTarget);
+ }
+
+ mRecordedEvents.clear();
+ mIsRecordingEvents = false;
+ }
+
+ /**
+ * Propagates the given {@link MotionEvent} to the given {@link EventTarget}, recycling it
+ * afterwards. This is intended for synthetic events only, those create by
+ * {@link MotionEvent#obtain} or the helper {@link ContextualSearchEventFilter#createEvent}.
+ * @param e The {@link MotionEvent} to be propagated.
+ * @param target The {@link EventTarget} to propagate events to.
+ */
+ private void propagateAndRecycleEvent(MotionEvent e, EventTarget target) {
+ propagateEvent(e, target);
+ e.recycle();
+ }
+
+ /**
+ * Propagates the given {@link MotionEvent} to the given {@link EventTarget}.
+ * @param e The {@link MotionEvent} to be propagated.
+ * @param target The {@link EventTarget} to propagate events to.
+ */
+ private void propagateEvent(MotionEvent e, EventTarget target) {
+ if (target == EventTarget.SEARCH_PANEL) {
+ super.onTouchEventInternal(e);
+ } else if (target == EventTarget.SEARCH_CONTENT_VIEW) {
+ propagateEventToSearchContentView(e);
+ }
+ }
+
+ /**
+ * Propagates the given {@link MotionEvent} to the Search Content View.
+ * @param e The {@link MotionEvent} to be propagated.
+ */
+ @VisibleForTesting
+ protected void propagateEventToSearchContentView(MotionEvent e) {
+ MotionEvent event = e;
+ boolean isSyntheticEvent = false;
+ if (mGestureOrientation == GestureOrientation.HORIZONTAL
+ && !mSearchPanel.isMaximized()) {
+ // Lock horizontal motion, ignoring all vertical changes, when the Panel is not
+ // maximized. This is to prevent the Search Result Page from scrolling when
+ // side swiping on the expanded Panel.
+ event = createEvent(e, e.getAction(), mInitialEventY);
+ isSyntheticEvent = true;
+ }
+
+ int action = event.getActionMasked();
+ float searchContentViewOffsetYPx = mSearchPanel.getSearchContentViewOffsetY() / mPxToDp;
+
+ // Adjust the offset to be relative to the Search Contents View.
+ event.offsetLocation(0.f, -searchContentViewOffsetYPx);
+
+ boolean wasEventCanceled = false;
+ if (mWasActionDownEventSynthetic && action == MotionEvent.ACTION_UP) {
+ float deltaX = event.getX() - mSyntheticActionDownX;
+ float deltaY = event.getY() - mSyntheticActionDownY;
+ // NOTE(pedrosimonetti): If the ACTION_DOWN event was synthetic and the distance
+ // between it and the ACTION_UP event was short, then we should synthesize an
+ // ACTION_CANCEL event to prevent a Tap gesture from being triggered on the Search
+ // Content View. See crbug.com/408654
+ if (!isDistanceGreaterThanTouchSlop(deltaX, deltaY)) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ mHost.propagateEvent(event);
+ wasEventCanceled = true;
+ }
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ mSearchPanel.onTouchSearchContentViewAck();
+ }
+
+ // Propagate the event to the appropriate view
+ if (!wasEventCanceled) mHost.propagateEvent(event);
+
+ // Synthetic events should be recycled.
+ if (isSyntheticEvent) event.recycle();
+ }
+
+ /**
+ * Creates a {@link MotionEvent} inheriting from a given |e| event.
+ * @param e The {@link MotionEvent} to inherit properties from.
+ * @param action The MotionEvent's Action to be used.
+ * @param y The y coordinate to be used.
+ * @return A new {@link MotionEvent}.
+ */
+ private MotionEvent createEvent(MotionEvent e, int action, float y) {
+ return MotionEvent.obtain(
+ e.getDownTime(),
+ e.getEventTime(),
+ action,
+ e.getX(),
+ y,
+ e.getMetaState());
+ }
+
+ /**
+ * Handles the tap event, determining the event target.
+ * @param e The tap {@link MotionEvent}.
+ * @return Whether the event has been consumed.
+ */
+ private boolean handleSingleTapUp(MotionEvent e) {
+ setEventTarget(mSearchPanel.isYCoordinateInsideSearchContentView(e.getY() * mPxToDp)
+ ? EventTarget.SEARCH_CONTENT_VIEW : EventTarget.SEARCH_PANEL);
+
+ return false;
+ }
+
+ /**
+ * Handles the scroll event, determining the gesture orientation and event target,
+ * when appropriate.
+ * @param e1 The first down {@link MotionEvent} that started the scrolling.
+ * @param e2 The move {@link MotionEvent} that triggered the current scroll.
+ * @param distanceY The distance along the Y axis that has been scrolled since the last call
+ * to handleScroll.
+ * @return Whether the event has been consumed.
+ */
+ private boolean handleScroll(MotionEvent e1, MotionEvent e2, float distanceY) {
+ // Only determines the gesture orientation if it hasn't been determined yet,
+ // affectively "locking" the orientation once the gesture has started.
+ if (!mHasDeterminedGestureOrientation && isDistanceGreaterThanTouchSlop(e1, e2)) {
+ determineGestureOrientation(e1, e2);
+ }
+
+ // Only determines the event target after determining the gesture orientation and
+ // if it hasn't been determined yet or if changing the event target during the
+ // middle of the gesture is supported. This will allow a smooth transition from
+ // swiping the Panel and scrolling the Search Content View.
+ if (mHasDeterminedGestureOrientation
+ && (!mHasDeterminedEventTarget || mMayChangeEventTarget)) {
+ determineEventTarget(distanceY);
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines the gesture orientation.
+ * @param e1 The first down {@link MotionEvent} that started the scrolling.
+ * @param e2 The move {@link MotionEvent} that triggered the current scroll.
+ */
+ private void determineGestureOrientation(MotionEvent e1, MotionEvent e2) {
+ float deltaX = Math.abs(e2.getX() - e1.getX());
+ float deltaY = Math.abs(e2.getY() - e1.getY());
+ mGestureOrientation = deltaY * VERTICAL_DETERMINATION_BOOST > deltaX
+ ? GestureOrientation.VERTICAL : GestureOrientation.HORIZONTAL;
+ mHasDeterminedGestureOrientation = true;
+ }
+
+ /**
+ * Determines the target to propagate events to. This will not only update the
+ * {@code mEventTarget} but also save the previous target and determine whether the
+ * target has changed.
+ * @param distanceY The distance along the Y axis that has been scrolled since the last call
+ * to handleScroll.
+ */
+ private void determineEventTarget(float distanceY) {
+ boolean isVertical = mGestureOrientation == GestureOrientation.VERTICAL;
+
+ boolean shouldPropagateEventsToSearchPanel;
+ if (mSearchPanel.isMaximized()) {
+ // Allow overscroll in the Search Content View to move the Search Panel instead
+ // of scrolling the Search Result Page.
+ boolean isMovingDown = distanceY < 0;
+ shouldPropagateEventsToSearchPanel = isVertical
+ && isMovingDown
+ && getSearchContentViewVerticalScroll() == 0;
+ } else {
+ // Only allow horizontal movements to be propagated to the Search Content View
+ // when the Panel is expanded (that is, not maximized).
+ shouldPropagateEventsToSearchPanel = isVertical;
+ }
+
+ mPreviousEventTarget = mEventTarget;
+ setEventTarget(shouldPropagateEventsToSearchPanel
+ ? EventTarget.SEARCH_PANEL : EventTarget.SEARCH_CONTENT_VIEW);
+
+ mHasChangedEventTarget = mEventTarget != mPreviousEventTarget
+ && mPreviousEventTarget != EventTarget.UNDETERMINED;
+ }
+
+ /**
+ * Sets the {@link EventTarget}.
+ * @param target The {@link EventTarget} to be set.
+ */
+ private void setEventTarget(EventTarget target) {
+ mEventTarget = target;
+
+ mIsDeterminingEventTarget = false;
+ mHasDeterminedEventTarget = true;
+ }
+
+ /**
+ * @param e1 The first down {@link MotionEvent} that started the scrolling.
+ * @param e2 The move {@link MotionEvent} that triggered the current scroll.
+ * @return Whether the distance is greater than the touch slop threshold.
+ */
+ private boolean isDistanceGreaterThanTouchSlop(MotionEvent e1, MotionEvent e2) {
+ float deltaX = e2.getX() - e1.getX();
+ float deltaY = e2.getY() - e1.getY();
+ // Check if the distance between the events |e1| and |e2| is greater than the touch slop.
+ return isDistanceGreaterThanTouchSlop(deltaX, deltaY);
+ }
+
+ /**
+ * @param deltaX The delta X in pixels.
+ * @param deltaY The delta Y in pixels.
+ * @return Whether the distance is greater than the touch slop threshold.
+ */
+ private boolean isDistanceGreaterThanTouchSlop(float deltaX, float deltaY) {
+ return deltaX * deltaX + deltaY * deltaY > mTouchSlopSquarePx;
+ }
+
+ /**
+ * Internal GestureDetector class that is responsible for determining the event target.
+ */
+ private class InternalGestureDetector extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return handleSingleTapUp(e);
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return handleScroll(e1, e2, distanceY);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698