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

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 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.contextualsearch;
6
7 import android.os.Handler;
8
9 import org.chromium.base.VisibleForTesting;
10 import org.chromium.chrome.browser.ChromeActivity;
11 import org.chromium.chrome.browser.Tab;
12 import org.chromium.content.browser.ContentViewCore;
13 import org.chromium.content_public.browser.GestureStateListener;
14 import org.chromium.ui.touch_selection.SelectionEventType;
15
16 /**
17 * Controls selection gesture interaction for Contextual Search.
18 */
19 public class ContextualSearchSelectionController {
20
21 /**
22 * The type of selection made by the user.
23 */
24 public enum SelectionType {
25 UNDETERMINED,
26 TAP,
27 LONG_PRESS
28 }
29
30 // The number of milliseconds to wait for a selection change after a tap bef ore considering
31 // the tap invalid. This can't be too small or the subsequent taps may not have established
32 // a new selection in time. This is because selectWordAroundCaret doesn't a lways select.
33 // TODO(donnd): Fix in Blink, crbug.com/435778.
34 private static final int INVALID_IF_NO_SELECTION_CHANGE_AFTER_TAP_MS = 50;
35 private static final double RETAP_DISTANCE_SQUARED_DP = Math.pow(75, 2);
36
37 private final ChromeActivity mActivity;
38 private final ContextualSearchSelectionHandler mHandler;
39 private final Runnable mHandleInvalidTapRunnable;
40 private final Handler mRunnableHandler;
41 private final float mPxToDp;
42
43 private String mSelectedText;
44 private SelectionType mSelectionType;
45 private boolean mWasTapGestureDetected;
46 private boolean mIsSelectionBeingModified;
47 private boolean mWasLastTapValid;
48 private boolean mIsWaitingForInvalidTapDetection;
49
50 private float mX;
51 private float mY;
52
53 private class ContextualSearchGestureStateListener extends GestureStateListe ner {
54 @Override
55 public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
56 mHandler.handleScroll();
57 }
58
59 // TODO(donnd): Remove this once we get notification of the selection ch anging
60 // after a tap-select gets a subsequent tap nearby. Currently there's n o
61 // notification in this case.
62 // See crbug.com/444114.
63 @Override
64 public void onSingleTap(boolean consumed, int x, int y) {
65 // We may be notified that a tap has happened even when the system c onsumed the event.
66 // This is being considered for support for tapping an existing sele ction to show the
67 // pins. We should only process this tap if it has not been consume d by the system.
68 if (!consumed) scheduleInvalidTapNotification();
69 }
70 }
71
72 /**
73 * Constructs a new Selection controller for the given activity. Callbacks will be issued
74 * through the given selection handler.
75 * @param activity The {@link ChromeActivity} to control.
76 * @param handler The handler for callbacks.
77 */
78 public ContextualSearchSelectionController(ChromeActivity activity,
79 ContextualSearchSelectionHandler handler) {
80 mActivity = activity;
81 mHandler = handler;
82 mPxToDp = 1.f / mActivity.getResources().getDisplayMetrics().density;
83
84 mRunnableHandler = new Handler();
85 mHandleInvalidTapRunnable = new Runnable() {
86 @Override
87 public void run() {
88 onInvalidTapDetectionTimeout();
89 }
90 };
91 }
92
93 /**
94 * Returns a new {@code GestureStateListener} that will listen for events in the Base Page.
95 * This listener will handle all Contextual Search-related interactions that go through the
96 * listener.
97 */
98 public ContextualSearchGestureStateListener getGestureStateListener() {
99 return new ContextualSearchGestureStateListener();
100 }
101
102 /**
103 * @return the type of the selection.
104 */
105 SelectionType getSelectionType() {
106 return mSelectionType;
107 }
108
109 /**
110 * @return the selected text.
111 */
112 String getSelectedText() {
113 return mSelectedText;
114 }
115
116 /**
117 * Clears the selection.
118 */
119 void clearSelection() {
120 ContentViewCore baseContentView = getBaseContentView();
121 if (baseContentView != null) {
122 baseContentView.clearSelection();
123 }
124 mHandler.onClearSelection();
125
126 resetAllStates();
127 }
128
129 /**
130 * Handles a change in the current Selection.
131 * @param selection The selection portion of the context.
132 */
133 void handleSelectionChanged(String selection) {
134 if (selection == null || selection.isEmpty()) {
135 scheduleInvalidTapNotification();
136 // When the user taps on the page it will place the caret in that po sition, which
137 // will trigger a onSelectionChanged event with an empty string.
138 if (mSelectionType == SelectionType.TAP) {
139 // Since we mostly ignore a selection that's empty, we only need to partially reset.
140 resetSelectionStates();
141 return;
142 }
143 }
144 if (selection != null && !selection.isEmpty()) {
145 unscheduleInvalidTapNotification();
146 }
147 if (mIsSelectionBeingModified) {
148 mSelectedText = selection;
149 mHandler.handleSelectionModification(selection, mX, mY);
150 } else if (mWasTapGestureDetected) {
151 mSelectedText = selection;
152 mSelectionType = SelectionType.TAP;
153 mHandler.handleSelection(selection, mSelectionType, mX, mY);
154 mWasTapGestureDetected = false;
155 }
156 }
157
158 /**
159 * Handles a notification that a selection event took place.
160 * @param eventType The type of event that took place.
161 * @param posXPix The x coordinate of the selection start handle.
162 * @param posYPix The y coordinate of the selection start handle.
163 */
164 void handleSelectionEvent(int eventType, float posXPix, float posYPix) {
165 boolean shouldHandleSelection = false;
166 switch (eventType) {
167 case SelectionEventType.SELECTION_SHOWN:
168 mWasTapGestureDetected = false;
169 mSelectionType = SelectionType.LONG_PRESS;
170 shouldHandleSelection = true;
171 break;
172 case SelectionEventType.SELECTION_CLEARED:
173 mHandler.onClearSelection();
174 resetAllStates();
175 break;
176 case SelectionEventType.SELECTION_DRAG_STARTED:
177 mIsSelectionBeingModified = true;
178 break;
179 case SelectionEventType.SELECTION_DRAG_STOPPED:
180 mIsSelectionBeingModified = false;
181 shouldHandleSelection = true;
182 break;
183 default:
184 }
185
186 if (shouldHandleSelection) {
187 ContentViewCore baseContentView = getBaseContentView();
188 if (baseContentView != null) {
189 String selection = baseContentView.getSelectedText();
190 if (selection != null) {
191 mX = posXPix;
192 mY = posYPix;
193 mSelectedText = selection;
194 mHandler.handleSelection(selection, SelectionType.LONG_PRESS , mX, mY);
195 }
196 }
197 }
198 }
199
200 /**
201 * Resets all internal state of this class, including the tap state.
202 */
203 private void resetAllStates() {
204 resetSelectionStates();
205 mWasLastTapValid = false;
206 }
207
208 /**
209 * Resets all of the internal state of this class that handles the selection .
210 */
211 private void resetSelectionStates() {
212 mSelectionType = SelectionType.UNDETERMINED;
213 mSelectedText = null;
214
215 mWasTapGestureDetected = false;
216 mIsSelectionBeingModified = false;
217 }
218
219 /**
220 * Handles an unhandled tap gesture.
221 */
222 void handleShowUnhandledTapUIIfNeeded(int x, int y) {
223 mWasTapGestureDetected = false;
224 if (mSelectionType != SelectionType.LONG_PRESS && shouldHandleTap(x, y)) {
225 mX = x;
226 mY = y;
227 mWasLastTapValid = true;
228 mWasTapGestureDetected = true;
229 // TODO(donnd): Find a better way to determine that a navigation wil l be triggered
230 // by the tap, or merge with other time-consuming actions like gathe ring surrounding
231 // text or detecting page mutations.
232 new Handler().postDelayed(new Runnable() {
233 @Override
234 public void run() {
235 mHandler.handleValidTap();
236 }
237 }, ContextualSearchFieldTrial.getNavigationDetectionDelay());
238 }
239 if (!mWasTapGestureDetected) {
240 mWasLastTapValid = false;
241 mHandler.handleInvalidTap();
242 }
243 }
244
245 /**
246 * @return The Base Page's {@link ContentViewCore}, or {@code null} if there is no current tab.
247 */
248 ContentViewCore getBaseContentView() {
249 Tab currentTab = mActivity.getActivityTab();
250 return currentTab != null ? currentTab.getContentViewCore() : null;
251 }
252
253 /**
254 * @return whether a tap at the given coordinates should be handled or not.
255 */
256 private boolean shouldHandleTap(int x, int y) {
257 return !mWasLastTapValid || wasTapCloseToPreviousTap(x, y);
258 }
259
260 /**
261 * Determines whether a tap at the given coordinates is considered "close" t o the previous
262 * tap.
263 */
264 private boolean wasTapCloseToPreviousTap(int x, int y) {
265 float deltaXDp = (mX - x) * mPxToDp;
266 float deltaYDp = (mY - y) * mPxToDp;
267 float distanceSquaredDp = deltaXDp * deltaXDp + deltaYDp * deltaYDp;
268 return distanceSquaredDp <= RETAP_DISTANCE_SQUARED_DP;
269 }
270
271 /**
272 * Schedules a notification to check if the tap was invalid.
273 * When we call selectWordAroundCaret it selects nothing in cases where the tap was invalid.
274 * We have no way to know other than scheduling a notification to check late r.
275 * This allows us to hide the bar when there's no selection.
276 */
277 private void scheduleInvalidTapNotification() {
278 // TODO(donnd): Fix selectWordAroundCaret to we can tell if it selects, instead
279 // of using a timer here! See crbug.com/435778.
280 mRunnableHandler.postDelayed(mHandleInvalidTapRunnable,
281 INVALID_IF_NO_SELECTION_CHANGE_AFTER_TAP_MS);
282 }
283
284 /**
285 * Un-schedules all pending notifications to check if a tap was invalid.
286 */
287 private void unscheduleInvalidTapNotification() {
288 mRunnableHandler.removeCallbacks(mHandleInvalidTapRunnable);
289 mIsWaitingForInvalidTapDetection = true;
290 }
291
292 /**
293 * Notify's the system that tap gesture has been completed.
294 */
295 private void onInvalidTapDetectionTimeout() {
296 mHandler.handleInvalidTap();
297 mIsWaitingForInvalidTapDetection = false;
298 }
299
300 /**
301 * @return whether a tap gesture has been detected, for testing.
302 */
303 @VisibleForTesting
304 boolean wasAnyTapGestureDetected() {
305 return mIsWaitingForInvalidTapDetection;
306 }
307 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698