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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ed4ea7976765bf587fad0606b0d2b2bb97a470c
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -0,0 +1,307 @@
+// 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.contextualsearch;
+
+import android.os.Handler;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.ui.touch_selection.SelectionEventType;
+
+/**
+ * Controls selection gesture interaction for Contextual Search.
+ */
+public class ContextualSearchSelectionController {
+
+ /**
+ * The type of selection made by the user.
+ */
+ public enum SelectionType {
+ UNDETERMINED,
+ TAP,
+ LONG_PRESS
+ }
+
+ // The number of milliseconds to wait for a selection change after a tap before considering
+ // the tap invalid. This can't be too small or the subsequent taps may not have established
+ // a new selection in time. This is because selectWordAroundCaret doesn't always select.
+ // TODO(donnd): Fix in Blink, crbug.com/435778.
+ private static final int INVALID_IF_NO_SELECTION_CHANGE_AFTER_TAP_MS = 50;
+ private static final double RETAP_DISTANCE_SQUARED_DP = Math.pow(75, 2);
+
+ private final ChromeActivity mActivity;
+ private final ContextualSearchSelectionHandler mHandler;
+ private final Runnable mHandleInvalidTapRunnable;
+ private final Handler mRunnableHandler;
+ private final float mPxToDp;
+
+ private String mSelectedText;
+ private SelectionType mSelectionType;
+ private boolean mWasTapGestureDetected;
+ private boolean mIsSelectionBeingModified;
+ private boolean mWasLastTapValid;
+ private boolean mIsWaitingForInvalidTapDetection;
+
+ private float mX;
+ private float mY;
+
+ private class ContextualSearchGestureStateListener extends GestureStateListener {
+ @Override
+ public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
+ mHandler.handleScroll();
+ }
+
+ // TODO(donnd): Remove this once we get notification of the selection changing
+ // after a tap-select gets a subsequent tap nearby. Currently there's no
+ // notification in this case.
+ // See crbug.com/444114.
+ @Override
+ public void onSingleTap(boolean consumed, int x, int y) {
+ // We may be notified that a tap has happened even when the system consumed the event.
+ // This is being considered for support for tapping an existing selection to show the
+ // pins. We should only process this tap if it has not been consumed by the system.
+ if (!consumed) scheduleInvalidTapNotification();
+ }
+ }
+
+ /**
+ * Constructs a new Selection controller for the given activity. Callbacks will be issued
+ * through the given selection handler.
+ * @param activity The {@link ChromeActivity} to control.
+ * @param handler The handler for callbacks.
+ */
+ public ContextualSearchSelectionController(ChromeActivity activity,
+ ContextualSearchSelectionHandler handler) {
+ mActivity = activity;
+ mHandler = handler;
+ mPxToDp = 1.f / mActivity.getResources().getDisplayMetrics().density;
+
+ mRunnableHandler = new Handler();
+ mHandleInvalidTapRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onInvalidTapDetectionTimeout();
+ }
+ };
+ }
+
+ /**
+ * Returns a new {@code GestureStateListener} that will listen for events in the Base Page.
+ * This listener will handle all Contextual Search-related interactions that go through the
+ * listener.
+ */
+ public ContextualSearchGestureStateListener getGestureStateListener() {
+ return new ContextualSearchGestureStateListener();
+ }
+
+ /**
+ * @return the type of the selection.
+ */
+ SelectionType getSelectionType() {
+ return mSelectionType;
+ }
+
+ /**
+ * @return the selected text.
+ */
+ String getSelectedText() {
+ return mSelectedText;
+ }
+
+ /**
+ * Clears the selection.
+ */
+ void clearSelection() {
+ ContentViewCore baseContentView = getBaseContentView();
+ if (baseContentView != null) {
+ baseContentView.clearSelection();
+ }
+ mHandler.onClearSelection();
+
+ resetAllStates();
+ }
+
+ /**
+ * Handles a change in the current Selection.
+ * @param selection The selection portion of the context.
+ */
+ void handleSelectionChanged(String selection) {
+ if (selection == null || selection.isEmpty()) {
+ scheduleInvalidTapNotification();
+ // When the user taps on the page it will place the caret in that position, which
+ // will trigger a onSelectionChanged event with an empty string.
+ if (mSelectionType == SelectionType.TAP) {
+ // Since we mostly ignore a selection that's empty, we only need to partially reset.
+ resetSelectionStates();
+ return;
+ }
+ }
+ if (selection != null && !selection.isEmpty()) {
+ unscheduleInvalidTapNotification();
+ }
+ if (mIsSelectionBeingModified) {
+ mSelectedText = selection;
+ mHandler.handleSelectionModification(selection, mX, mY);
+ } else if (mWasTapGestureDetected) {
+ mSelectedText = selection;
+ mSelectionType = SelectionType.TAP;
+ mHandler.handleSelection(selection, mSelectionType, mX, mY);
+ mWasTapGestureDetected = false;
+ }
+ }
+
+ /**
+ * Handles a notification that a selection event took place.
+ * @param eventType The type of event that took place.
+ * @param posXPix The x coordinate of the selection start handle.
+ * @param posYPix The y coordinate of the selection start handle.
+ */
+ void handleSelectionEvent(int eventType, float posXPix, float posYPix) {
+ boolean shouldHandleSelection = false;
+ switch (eventType) {
+ case SelectionEventType.SELECTION_SHOWN:
+ mWasTapGestureDetected = false;
+ mSelectionType = SelectionType.LONG_PRESS;
+ shouldHandleSelection = true;
+ break;
+ case SelectionEventType.SELECTION_CLEARED:
+ mHandler.onClearSelection();
+ resetAllStates();
+ break;
+ case SelectionEventType.SELECTION_DRAG_STARTED:
+ mIsSelectionBeingModified = true;
+ break;
+ case SelectionEventType.SELECTION_DRAG_STOPPED:
+ mIsSelectionBeingModified = false;
+ shouldHandleSelection = true;
+ break;
+ default:
+ }
+
+ if (shouldHandleSelection) {
+ ContentViewCore baseContentView = getBaseContentView();
+ if (baseContentView != null) {
+ String selection = baseContentView.getSelectedText();
+ if (selection != null) {
+ mX = posXPix;
+ mY = posYPix;
+ mSelectedText = selection;
+ mHandler.handleSelection(selection, SelectionType.LONG_PRESS, mX, mY);
+ }
+ }
+ }
+ }
+
+ /**
+ * Resets all internal state of this class, including the tap state.
+ */
+ private void resetAllStates() {
+ resetSelectionStates();
+ mWasLastTapValid = false;
+ }
+
+ /**
+ * Resets all of the internal state of this class that handles the selection.
+ */
+ private void resetSelectionStates() {
+ mSelectionType = SelectionType.UNDETERMINED;
+ mSelectedText = null;
+
+ mWasTapGestureDetected = false;
+ mIsSelectionBeingModified = false;
+ }
+
+ /**
+ * Handles an unhandled tap gesture.
+ */
+ void handleShowUnhandledTapUIIfNeeded(int x, int y) {
+ mWasTapGestureDetected = false;
+ if (mSelectionType != SelectionType.LONG_PRESS && shouldHandleTap(x, y)) {
+ mX = x;
+ mY = y;
+ mWasLastTapValid = true;
+ mWasTapGestureDetected = true;
+ // TODO(donnd): Find a better way to determine that a navigation will be triggered
+ // by the tap, or merge with other time-consuming actions like gathering surrounding
+ // text or detecting page mutations.
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.handleValidTap();
+ }
+ }, ContextualSearchFieldTrial.getNavigationDetectionDelay());
+ }
+ if (!mWasTapGestureDetected) {
+ mWasLastTapValid = false;
+ mHandler.handleInvalidTap();
+ }
+ }
+
+ /**
+ * @return The Base Page's {@link ContentViewCore}, or {@code null} if there is no current tab.
+ */
+ ContentViewCore getBaseContentView() {
+ Tab currentTab = mActivity.getActivityTab();
+ return currentTab != null ? currentTab.getContentViewCore() : null;
+ }
+
+ /**
+ * @return whether a tap at the given coordinates should be handled or not.
+ */
+ private boolean shouldHandleTap(int x, int y) {
+ return !mWasLastTapValid || wasTapCloseToPreviousTap(x, y);
+ }
+
+ /**
+ * Determines whether a tap at the given coordinates is considered "close" to the previous
+ * tap.
+ */
+ private boolean wasTapCloseToPreviousTap(int x, int y) {
+ float deltaXDp = (mX - x) * mPxToDp;
+ float deltaYDp = (mY - y) * mPxToDp;
+ float distanceSquaredDp = deltaXDp * deltaXDp + deltaYDp * deltaYDp;
+ return distanceSquaredDp <= RETAP_DISTANCE_SQUARED_DP;
+ }
+
+ /**
+ * Schedules a notification to check if the tap was invalid.
+ * When we call selectWordAroundCaret it selects nothing in cases where the tap was invalid.
+ * We have no way to know other than scheduling a notification to check later.
+ * This allows us to hide the bar when there's no selection.
+ */
+ private void scheduleInvalidTapNotification() {
+ // TODO(donnd): Fix selectWordAroundCaret to we can tell if it selects, instead
+ // of using a timer here! See crbug.com/435778.
+ mRunnableHandler.postDelayed(mHandleInvalidTapRunnable,
+ INVALID_IF_NO_SELECTION_CHANGE_AFTER_TAP_MS);
+ }
+
+ /**
+ * Un-schedules all pending notifications to check if a tap was invalid.
+ */
+ private void unscheduleInvalidTapNotification() {
+ mRunnableHandler.removeCallbacks(mHandleInvalidTapRunnable);
+ mIsWaitingForInvalidTapDetection = true;
+ }
+
+ /**
+ * Notify's the system that tap gesture has been completed.
+ */
+ private void onInvalidTapDetectionTimeout() {
+ mHandler.handleInvalidTap();
+ mIsWaitingForInvalidTapDetection = false;
+ }
+
+ /**
+ * @return whether a tap gesture has been detected, for testing.
+ */
+ @VisibleForTesting
+ boolean wasAnyTapGestureDetected() {
+ return mIsWaitingForInvalidTapDetection;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698