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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.compositor.layouts.eventfilter;
6
7 import android.content.Context;
8 import android.view.GestureDetector;
9 import android.view.MotionEvent;
10 import android.view.ViewConfiguration;
11
12 import org.chromium.base.VisibleForTesting;
13 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context ualSearchPanel;
14 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDe legate;
15
16 import java.util.ArrayList;
17
18 /**
19 * The {@link GestureEventFilter} used when Contextual Search Layout is being sh own. It filters
20 * events that happen in the Search Content View area and propagates them to the appropriate
21 * Content View Core via {@link EventFilterHost}. Events that happen outside tha t area are
22 * propagated to the {@code ContextualSearchLayout} via {@code LayoutManagerPhon e}.
23 */
24 public class ContextualSearchEventFilter extends GestureEventFilter {
25
26 /**
27 * The targets that can handle MotionEvents.
28 */
29 private enum EventTarget {
30 UNDETERMINED,
31 SEARCH_PANEL,
32 SEARCH_CONTENT_VIEW
33 }
34
35 /**
36 * The direction of the gesture.
37 */
38 private enum GestureOrientation {
39 UNDETERMINED,
40 HORIZONTAL,
41 VERTICAL
42 }
43
44 /**
45 * The boost factor that can be applied to prioritize vertical movements ove r horizontal ones.
46 */
47 private static final float VERTICAL_DETERMINATION_BOOST = 1.25f;
48
49 /**
50 * The shared state of the UI.
51 */
52 private ContextualSearchPanel mSearchPanel;
53
54 /**
55 * The delegate to talk to ContextualSearchManager.
56 */
57 private ContextualSearchManagementDelegate mManagementDelegate;
58
59 /**
60 * The {@link GestureDetector} used to distinguish tap and scroll gestures.
61 */
62 private final GestureDetector mGestureDetector;
63
64 /**
65 * The target to propagate events to.
66 */
67 private EventTarget mEventTarget;
68
69 /**
70 * Whether the code is in the middle of the process of determining the event target.
71 */
72 private boolean mIsDeterminingEventTarget;
73
74 /**
75 * Whether the event target has been determined.
76 */
77 private boolean mHasDeterminedEventTarget;
78
79 /**
80 * The previous target the events were propagated to.
81 */
82 private EventTarget mPreviousEventTarget;
83
84 /**
85 * Whether the event target has changed since the last touch event.
86 */
87 private boolean mHasChangedEventTarget;
88
89 /**
90 * Whether the event target might change. This will be true in cases we know the overscroll
91 * and/or underscroll might happen, which means we'll have to constantly mon itor the event
92 * targets in order to determine the exact moment the target has changed.
93 */
94 private boolean mMayChangeEventTarget;
95
96 /**
97 * Whether the gesture orientation has been determined.
98 */
99 private boolean mHasDeterminedGestureOrientation;
100
101 /**
102 * The current gesture orientation.
103 */
104 private GestureOrientation mGestureOrientation;
105
106 /**
107 * Whether the events are being recorded.
108 */
109 private boolean mIsRecordingEvents;
110
111 /**
112 * Whether the ACTION_DOWN that initiated the MotionEvent's stream was synth etic.
113 */
114 private boolean mWasActionDownEventSynthetic;
115
116 /**
117 * The X coordinate of the synthetic ACTION_DOWN MotionEvent.
118 */
119 private float mSyntheticActionDownX;
120
121 /**
122 * The Y coordinate of the synthetic ACTION_DOWN MotionEvent.
123 */
124 private float mSyntheticActionDownY;
125
126 /**
127 * The list of recorded events.
128 */
129 private final ArrayList<MotionEvent> mRecordedEvents = new ArrayList<MotionE vent>();
130
131 /**
132 * The initial Y position of the current gesture.
133 */
134 private float mInitialEventY;
135
136 /**
137 * The square of ViewConfiguration.getScaledTouchSlop() in pixels used to ca lculate whether
138 * the finger has moved beyond the established threshold.
139 */
140 private final float mTouchSlopSquarePx;
141
142 /**
143 * Creates a {@link GestureEventFilter} with offset touch events.
144 */
145 public ContextualSearchEventFilter(Context context, EventFilterHost host,
146 GestureHandler handler, ContextualSearchPanel contextualSearchPanel) {
147 super(context, host, handler, false, false);
148
149 mGestureDetector = new GestureDetector(context, new InternalGestureDetec tor());
150 mSearchPanel = contextualSearchPanel;
151
152 // Store the square of the platform touch slop in pixels to use in the s croll detection.
153 // See {@link ContextualSearchEventFilter#isDistanceGreaterThanTouchSlop }.
154 float touchSlopPx = ViewConfiguration.get(context).getScaledTouchSlop();
155 mTouchSlopSquarePx = touchSlopPx * touchSlopPx;
156
157 reset();
158 }
159
160 /**
161 * Sets the {@code ContextualSearchManagementDelegate} associated with this Event Filter.
162 * @param delegate The {@code ContextualSearchManagementDelegate}.
163 */
164 public void setManagementDelegate(ContextualSearchManagementDelegate delegat e) {
165 mManagementDelegate = delegate;
166 }
167
168 /**
169 * Gets the Search Content View's vertical scroll position. If the Search Co ntent View
170 * is not available it returns -1.
171 * @return The Search Content View scroll position.
172 */
173 @VisibleForTesting
174 protected float getSearchContentViewVerticalScroll() {
175 return mManagementDelegate.getSearchContentViewVerticalScroll();
176 }
177
178 @Override
179 public boolean onTouchEventInternal(MotionEvent e) {
180 final int action = e.getActionMasked();
181
182 if (action == MotionEvent.ACTION_POINTER_DOWN
183 && e.getPointerCount() == 2
184 && !mSearchPanel.isMaximized()) {
185 // We don't want the Search Content View's zoom level to change when the Search Panel
186 // is expanded (that is, not maximized) so we'll forward the events to Panel to
187 // prevent it from happening.
188 setEventTarget(EventTarget.SEARCH_PANEL);
189 } else if (!mIsDeterminingEventTarget && action == MotionEvent.ACTION_DO WN) {
190 mInitialEventY = e.getY();
191 if (mSearchPanel.isYCoordinateInsideSearchContentView(mInitialEventY * mPxToDp)) {
192 // If the DOWN event happened inside the Search Content View, we 'll need
193 // to wait until the user has moved the finger beyond a certain threshold,
194 // so we can determine the gesture's orientation and consequentl y be able
195 // to tell if the Content View will accept the gesture.
196 mIsDeterminingEventTarget = true;
197 mMayChangeEventTarget = true;
198 } else {
199 // If the DOWN event happened outside the Search Content View, t hen we know
200 // that the Search Panel will start handling the event right awa y.
201 setEventTarget(EventTarget.SEARCH_PANEL);
202 mMayChangeEventTarget = false;
203 }
204 }
205
206 // Send the event to the GestureDetector so we can distinguish between s croll and tap.
207 mGestureDetector.onTouchEvent(e);
208
209 if (mHasDeterminedEventTarget) {
210 // If the event target has been determined, resume pending events, t hen propagate
211 // the current event to the appropriate target.
212 resumeAndPropagateEvent(e);
213 } else {
214 // If the event target has not been determined, we need to record a copy of the event
215 // until we are able to determine the event target.
216 MotionEvent event = MotionEvent.obtain(e);
217 mRecordedEvents.add(event);
218 mIsRecordingEvents = true;
219 }
220
221 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANC EL) {
222 reset();
223 }
224
225 return true;
226 }
227
228 /**
229 * Resets the current and previous {@link EventTarget} as well the {@link Ge stureOrientation}
230 * to the UNDETERMINED state.
231 */
232 private void reset() {
233 mEventTarget = EventTarget.UNDETERMINED;
234 mIsDeterminingEventTarget = false;
235 mHasDeterminedEventTarget = false;
236
237 mPreviousEventTarget = EventTarget.UNDETERMINED;
238 mHasChangedEventTarget = false;
239 mMayChangeEventTarget = false;
240
241 mWasActionDownEventSynthetic = false;
242
243 mGestureOrientation = GestureOrientation.UNDETERMINED;
244 mHasDeterminedGestureOrientation = false;
245 }
246
247 /**
248 * Resumes pending events then propagates the given event to the current {@l ink EventTarget}.
249 *
250 * Resuming events might consist in simply propagating previously recorded e vents if the
251 * EventTarget was UNDETERMINED when the gesture started.
252 *
253 * For the case where the EventTarget has changed during the course of the g esture, we'll
254 * need to simulate a gesture end in the previous target (by simulating an A CTION_CANCEL
255 * event) and a gesture start in the new target (by simulating an ACTION_DOW N event).
256 *
257 * @param e The {@link MotionEvent} to be propagated after resuming the pend ing events.
258 */
259 private void resumeAndPropagateEvent(MotionEvent e) {
260 if (mIsRecordingEvents) {
261 resumeRecordedEvents();
262 }
263
264 if (mHasChangedEventTarget) {
265 // TODO(pedrosimonetti): handle cases with multiple pointers.
266 float y = e.getY();
267 // If the event target has changed since the beginning of the gestur e, then we need
268 // to send a ACTION_CANCEL to the previous event target to make sure it no longer
269 // expects events.
270 propagateAndRecycleEvent(createEvent(e, MotionEvent.ACTION_CANCEL, y ),
271 mPreviousEventTarget);
272
273 // Similarly we need to send an ACTION_DOWN to the new event target so subsequent
274 // events can be analyzed properly by the Gesture Detector.
275 MotionEvent syntheticActionDownEvent = createEvent(e, MotionEvent.AC TION_DOWN, y);
276
277 // Store the synthetic ACTION_DOWN coordinates to prevent unwanted t aps from
278 // happening. See {@link ContextualSearchEventFilter#propagateEventT oSearchContentView}.
279 mWasActionDownEventSynthetic = true;
280 mSyntheticActionDownX = syntheticActionDownEvent.getX();
281 mSyntheticActionDownY = syntheticActionDownEvent.getY()
282 - mSearchPanel.getSearchContentViewOffsetY() / mPxToDp;
283
284 propagateAndRecycleEvent(syntheticActionDownEvent, mEventTarget);
285
286 mHasChangedEventTarget = false;
287 }
288
289 propagateEvent(e, mEventTarget);
290 }
291
292 /**
293 * Resumes recorded events by propagating all of them to the current {@link EventTarget}.
294 */
295 private void resumeRecordedEvents() {
296 for (int i = 0, size = mRecordedEvents.size(); i < size; i++) {
297 propagateAndRecycleEvent(mRecordedEvents.get(i), mEventTarget);
298 }
299
300 mRecordedEvents.clear();
301 mIsRecordingEvents = false;
302 }
303
304 /**
305 * Propagates the given {@link MotionEvent} to the given {@link EventTarget} , recycling it
306 * afterwards. This is intended for synthetic events only, those create by
307 * {@link MotionEvent#obtain} or the helper {@link ContextualSearchEventFilt er#createEvent}.
308 * @param e The {@link MotionEvent} to be propagated.
309 * @param target The {@link EventTarget} to propagate events to.
310 */
311 private void propagateAndRecycleEvent(MotionEvent e, EventTarget target) {
312 propagateEvent(e, target);
313 e.recycle();
314 }
315
316 /**
317 * Propagates the given {@link MotionEvent} to the given {@link EventTarget} .
318 * @param e The {@link MotionEvent} to be propagated.
319 * @param target The {@link EventTarget} to propagate events to.
320 */
321 private void propagateEvent(MotionEvent e, EventTarget target) {
322 if (target == EventTarget.SEARCH_PANEL) {
323 super.onTouchEventInternal(e);
324 } else if (target == EventTarget.SEARCH_CONTENT_VIEW) {
325 propagateEventToSearchContentView(e);
326 }
327 }
328
329 /**
330 * Propagates the given {@link MotionEvent} to the Search Content View.
331 * @param e The {@link MotionEvent} to be propagated.
332 */
333 @VisibleForTesting
334 protected void propagateEventToSearchContentView(MotionEvent e) {
335 MotionEvent event = e;
336 boolean isSyntheticEvent = false;
337 if (mGestureOrientation == GestureOrientation.HORIZONTAL
338 && !mSearchPanel.isMaximized()) {
339 // Lock horizontal motion, ignoring all vertical changes, when the P anel is not
340 // maximized. This is to prevent the Search Result Page from scrolli ng when
341 // side swiping on the expanded Panel.
342 event = createEvent(e, e.getAction(), mInitialEventY);
343 isSyntheticEvent = true;
344 }
345
346 int action = event.getActionMasked();
347 float searchContentViewOffsetYPx = mSearchPanel.getSearchContentViewOffs etY() / mPxToDp;
348
349 // Adjust the offset to be relative to the Search Contents View.
350 event.offsetLocation(0.f, -searchContentViewOffsetYPx);
351
352 boolean wasEventCanceled = false;
353 if (mWasActionDownEventSynthetic && action == MotionEvent.ACTION_UP) {
354 float deltaX = event.getX() - mSyntheticActionDownX;
355 float deltaY = event.getY() - mSyntheticActionDownY;
356 // NOTE(pedrosimonetti): If the ACTION_DOWN event was synthetic and the distance
357 // between it and the ACTION_UP event was short, then we should synt hesize an
358 // ACTION_CANCEL event to prevent a Tap gesture from being triggered on the Search
359 // Content View. See crbug.com/408654
360 if (!isDistanceGreaterThanTouchSlop(deltaX, deltaY)) {
361 event.setAction(MotionEvent.ACTION_CANCEL);
362 mHost.propagateEvent(event);
363 wasEventCanceled = true;
364 }
365 } else if (action == MotionEvent.ACTION_DOWN) {
366 mSearchPanel.onTouchSearchContentViewAck();
367 }
368
369 // Propagate the event to the appropriate view
370 if (!wasEventCanceled) mHost.propagateEvent(event);
371
372 // Synthetic events should be recycled.
373 if (isSyntheticEvent) event.recycle();
374 }
375
376 /**
377 * Creates a {@link MotionEvent} inheriting from a given |e| event.
378 * @param e The {@link MotionEvent} to inherit properties from.
379 * @param action The MotionEvent's Action to be used.
380 * @param y The y coordinate to be used.
381 * @return A new {@link MotionEvent}.
382 */
383 private MotionEvent createEvent(MotionEvent e, int action, float y) {
384 return MotionEvent.obtain(
385 e.getDownTime(),
386 e.getEventTime(),
387 action,
388 e.getX(),
389 y,
390 e.getMetaState());
391 }
392
393 /**
394 * Handles the tap event, determining the event target.
395 * @param e The tap {@link MotionEvent}.
396 * @return Whether the event has been consumed.
397 */
398 private boolean handleSingleTapUp(MotionEvent e) {
399 setEventTarget(mSearchPanel.isYCoordinateInsideSearchContentView(e.getY( ) * mPxToDp)
400 ? EventTarget.SEARCH_CONTENT_VIEW : EventTarget.SEARCH_PANEL);
401
402 return false;
403 }
404
405 /**
406 * Handles the scroll event, determining the gesture orientation and event t arget,
407 * when appropriate.
408 * @param e1 The first down {@link MotionEvent} that started the scrolling.
409 * @param e2 The move {@link MotionEvent} that triggered the current scroll.
410 * @param distanceY The distance along the Y axis that has been scrolled sin ce the last call
411 * to handleScroll.
412 * @return Whether the event has been consumed.
413 */
414 private boolean handleScroll(MotionEvent e1, MotionEvent e2, float distanceY ) {
415 // Only determines the gesture orientation if it hasn't been determined yet,
416 // affectively "locking" the orientation once the gesture has started.
417 if (!mHasDeterminedGestureOrientation && isDistanceGreaterThanTouchSlop( e1, e2)) {
418 determineGestureOrientation(e1, e2);
419 }
420
421 // Only determines the event target after determining the gesture orient ation and
422 // if it hasn't been determined yet or if changing the event target duri ng the
423 // middle of the gesture is supported. This will allow a smooth transiti on from
424 // swiping the Panel and scrolling the Search Content View.
425 if (mHasDeterminedGestureOrientation
426 && (!mHasDeterminedEventTarget || mMayChangeEventTarget)) {
427 determineEventTarget(distanceY);
428 }
429
430 return false;
431 }
432
433 /**
434 * Determines the gesture orientation.
435 * @param e1 The first down {@link MotionEvent} that started the scrolling.
436 * @param e2 The move {@link MotionEvent} that triggered the current scroll.
437 */
438 private void determineGestureOrientation(MotionEvent e1, MotionEvent e2) {
439 float deltaX = Math.abs(e2.getX() - e1.getX());
440 float deltaY = Math.abs(e2.getY() - e1.getY());
441 mGestureOrientation = deltaY * VERTICAL_DETERMINATION_BOOST > deltaX
442 ? GestureOrientation.VERTICAL : GestureOrientation.HORIZONTAL;
443 mHasDeterminedGestureOrientation = true;
444 }
445
446 /**
447 * Determines the target to propagate events to. This will not only update t he
448 * {@code mEventTarget} but also save the previous target and determine whet her the
449 * target has changed.
450 * @param distanceY The distance along the Y axis that has been scrolled sin ce the last call
451 * to handleScroll.
452 */
453 private void determineEventTarget(float distanceY) {
454 boolean isVertical = mGestureOrientation == GestureOrientation.VERTICAL;
455
456 boolean shouldPropagateEventsToSearchPanel;
457 if (mSearchPanel.isMaximized()) {
458 // Allow overscroll in the Search Content View to move the Search Pa nel instead
459 // of scrolling the Search Result Page.
460 boolean isMovingDown = distanceY < 0;
461 shouldPropagateEventsToSearchPanel = isVertical
462 && isMovingDown
463 && getSearchContentViewVerticalScroll() == 0;
464 } else {
465 // Only allow horizontal movements to be propagated to the Search Co ntent View
466 // when the Panel is expanded (that is, not maximized).
467 shouldPropagateEventsToSearchPanel = isVertical;
468 }
469
470 mPreviousEventTarget = mEventTarget;
471 setEventTarget(shouldPropagateEventsToSearchPanel
472 ? EventTarget.SEARCH_PANEL : EventTarget.SEARCH_CONTENT_VIEW);
473
474 mHasChangedEventTarget = mEventTarget != mPreviousEventTarget
475 && mPreviousEventTarget != EventTarget.UNDETERMINED;
476 }
477
478 /**
479 * Sets the {@link EventTarget}.
480 * @param target The {@link EventTarget} to be set.
481 */
482 private void setEventTarget(EventTarget target) {
483 mEventTarget = target;
484
485 mIsDeterminingEventTarget = false;
486 mHasDeterminedEventTarget = true;
487 }
488
489 /**
490 * @param e1 The first down {@link MotionEvent} that started the scrolling.
491 * @param e2 The move {@link MotionEvent} that triggered the current scroll.
492 * @return Whether the distance is greater than the touch slop threshold.
493 */
494 private boolean isDistanceGreaterThanTouchSlop(MotionEvent e1, MotionEvent e 2) {
495 float deltaX = e2.getX() - e1.getX();
496 float deltaY = e2.getY() - e1.getY();
497 // Check if the distance between the events |e1| and |e2| is greater tha n the touch slop.
498 return isDistanceGreaterThanTouchSlop(deltaX, deltaY);
499 }
500
501 /**
502 * @param deltaX The delta X in pixels.
503 * @param deltaY The delta Y in pixels.
504 * @return Whether the distance is greater than the touch slop threshold.
505 */
506 private boolean isDistanceGreaterThanTouchSlop(float deltaX, float deltaY) {
507 return deltaX * deltaX + deltaY * deltaY > mTouchSlopSquarePx;
508 }
509
510 /**
511 * Internal GestureDetector class that is responsible for determining the ev ent target.
512 */
513 private class InternalGestureDetector extends GestureDetector.SimpleOnGestur eListener {
514 @Override
515 public boolean onSingleTapUp(MotionEvent e) {
516 return handleSingleTapUp(e);
517 }
518
519 @Override
520 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
521 return handleScroll(e1, e2, distanceY);
522 }
523 }
524 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698