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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchStateController.java

Issue 2703473002: [TTS] Extract tapped text before showing UI. (Closed)
Patch Set: Minor update. Created 3 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/contextualsearch/ContextualSearchStateController.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchStateController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchStateController.java
new file mode 100644
index 0000000000000000000000000000000000000000..b14ea48e8fbfa7e083f250a17284b1e2d41ee010
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchStateController.java
@@ -0,0 +1,276 @@
+// Copyright 2017 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.util.Log;
+
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionController.SelectionType;
+
+import javax.annotation.Nullable;
+
+/**
+ * Controls the State of the Contextual Search Manager.
+ * <p>
+ * This class keeps track of the current state of the {@code ContextualSearchManager} and helps it
+ * to transition between states return to the idle state when work has been interrupted.
+ * <p>
+ * Usage: Call {@link #reset(StateChangeReason)} to reset to the {@code IDLE} state.<br>
+ * Call {@link #enter(State)} to enter a start-state (when a gesture is recognized).
+ * When doing some work in an asynchronous manner:<ol>
+ * <li>call {@link #notifyStartingWorkOn(State)} to note that work is starting on that state
+ * <li>call {@link #notifyFinishedWorkOn(State)} when work is completed.
+ * <li>If a handler needs to do additional work, such as updating the UX, it should first call
+ * {@link #isStillWorkingOn(State)} to check that work has not been interrupted.
+ * </ol><p>
+ * The {@link #notifyFinishedWorkOn(State)} method will automatically start a transition to the
+ * next state.
+ * <p>
+ * Policy decisions about state transitions should only be done in the private
+ * {@link #transitionTo(State)} method of this class, not in the {@code ContextualSearchManager}.
+ */
+class ContextualSearchStateController {
+ private static final String TAG = "Contextual Search";
+
+ private final ContextualSearchSelectionController mSelectionController;
+ private final ContextualSearchPolicy mPolicy;
+ private final ContextualSearchStateControlled mControlled;
Theresa 2017/03/28 15:52:55 I'd prefer a different name for this class. Maybe
Donn Denman 2017/03/29 18:56:51 Changed to ContextualSearchInternalStateHandler.
+
+ /** The current state of the manager. */
+ public static enum State {
+ // When not yet initialized or already destroyed only
+ UNDEFINED,
+ // This is the default resting state.
+ IDLE,
Theresa 2017/03/28 15:52:55 Is this equivalent to the panel "closed" state (as
Donn Denman 2017/03/29 18:56:51 Yes, updated the comment to reflect that the IDLE
+
+ // This is a sequence of transition states needed to get to the SHOWING_TAP resting state.
Theresa 2017/03/28 15:52:55 s/SHOWING_STATE/SHOWING_LONGPRESS_SEARCH?
Donn Denman 2017/03/29 18:56:51 Done.
+ LONG_PRESS_RECOGNIZED,
+ // Transitional state when extending the selection.
+ EXTENDING_SELECTION, // needed???
Theresa 2017/03/28 15:52:55 Would this be used when the user is dragging the s
Donn Denman 2017/03/29 18:56:51 Yep. Still not sure it's needed, but we're suppos
+ // Resting state when showing the panel in response to a Long-press gesture.
+ SHOWING_LONGPRESS_SEARCH,
+
+ // This is a sequence of transition states needed to get to the SHOWING_TAP resting state.
+ TAP_RECOGNIZED,
+ // May be done for Tap or Long-press.
+ GATHERING_SURROUNDINGS,
+ DECIDING_SUPPRESSION,
Theresa 2017/03/28 15:52:55 I expected tap suppression be synchronous (for now
Donn Denman 2017/03/29 18:56:51 This CL adds both sequencing and handling of async
+ WAITING_FOR_POSSIBLE_NAVIGATION,
+ SELECTING_WORD,
+ // Resolving the Search Term using the surrounding text and additional context.
+ RESOLVING,
+ // Resting state when showing the panel in response to a Tap gesture.
+ SHOWING_TAP_SEARCH
+ }
+
+ // The current state of this instance.
+ private State mState;
+
+ // Whether work has started on the current state.
+ private boolean mDidStartWork;
+
+ /**
+ * Constructs an instance of this class, which has the same lifetime as the
+ * {@code ContextualSearchManager} and the given parameters.
+ */
+ ContextualSearchStateController(ContextualSearchSelectionController selectionController,
+ ContextualSearchPolicy policy, ContextualSearchStateControlled controlled) {
+ mSelectionController = selectionController;
+ mPolicy = policy;
+ mControlled = controlled;
+ }
+
+ // ============================================================================================
+ // State-transition management.
+ // This code is designed to solve several problems:
+ // 1) Document the sequencing of handling a gesture in code. Now there's a single method that
+ // determines the sequence that should be followed for Tap handling (our most complicated
+ // case.
+ // 2) Document the initiation and subsequent notification/handling of operations. Now the
+ // method that starts an operation and the notification handler are tied together by their
+ // references to the same state. This allows a simple search to find the
+ // initiation and handler together (which is not always easy, e.g. SelectWordAroundCaret
+ // does not yet have an ACK so we infer that it's complete when the selection change -- or
+ // does not change after some short waiting period).
+ // 3) Gracefully handle sequence interruptions. When an asynchronous operation is in progress
+ // the user may start a new sequence or abort the current sequence. Now the handler for an
+ // asynchronous operation can easily detect that it's no longer working on that operation
+ // and skip the normal completion of the operation.
+ // ============================================================================================
+
+ /**
+ * Reset the current state to the IDLE state.
+ * @param reason the reason for the reset
+ */
+ void reset(StateChangeReason reason) {
+ transitionTo(State.IDLE, reason);
+ }
+
+ /**
+ * Enters the given starting state immediately.
+ * @param state the new starting {@link State} we're now in
+ */
+ void enter(State state) {
+ assert state == State.UNDEFINED || state == State.IDLE
+ || state == State.LONG_PRESS_RECOGNIZED || state == State.TAP_RECOGNIZED;
+ mState = state;
+ System.out.println("ctxs enter : " + mSelectionController.getSelectionType());
+ notifyStartingWorkOn(mState);
+ notifyFinishedWorkOn(mState);
+ }
+
+ /**
+ * Confirms that work is starting on the given state.
+ * @param state the {@link State} that we're now working on
+ */
+ void notifyStartingWorkOn(State state) {
+ assert mState == state;
+ mDidStartWork = true;
+ }
+
+ /**
+ * @return whether we're still working on the given state
+ */
+ boolean isStillWorkingOn(State state) {
+ return mState == state;
+ }
+
+ /**
+ * Confirms that work has been finished on the given state.
+ * This should be called by every operation that waits for some kind of completion when it
+ * completes. The operation's start must be flagged using {@link #starting}.
+ * @param state the {@link State} that we've finished working on.
+ */
+ void notifyFinishedWorkOn(State state) {
Theresa 2017/03/28 15:52:55 There's nothing here enforcing the order of state
Donn Denman 2017/03/29 18:56:51 I tried to do this with the initial implementation
+ if (state != mState) return;
+
+ assert mDidStartWork;
+
+ if (mState == State.IDLE) {
+ Log.w(TAG, "Warning, the " + state.toString() + " state was aborted.");
+ return;
+ }
+
+ switch (state) {
+ case LONG_PRESS_RECOGNIZED:
+ transitionTo(State.GATHERING_SURROUNDINGS);
+ break;
+ case TAP_RECOGNIZED:
+ transitionTo(State.GATHERING_SURROUNDINGS);
+ break;
+ case GATHERING_SURROUNDINGS:
+ System.out.println("ctxs finished gathering, checking if tap..."
+ + mSelectionController.getSelectionType());
+ if (mSelectionController.getSelectionType() == SelectionType.TAP) {
+ transitionTo(State.DECIDING_SUPPRESSION);
+ } else {
+ // No suppression yet for Long-press.
+ transitionTo(State.SHOWING_LONGPRESS_SEARCH,
+ StateChangeReason.TEXT_SELECT_LONG_PRESS);
+ }
+ break;
+ case DECIDING_SUPPRESSION:
+ transitionTo(State.WAITING_FOR_POSSIBLE_NAVIGATION);
+ break;
+ case WAITING_FOR_POSSIBLE_NAVIGATION:
+ transitionTo(State.SELECTING_WORD);
+ break;
+ case SELECTING_WORD:
+ System.out.println("checking if we should resolve...");
+ if (mPolicy.shouldPreviousTapResolve()) {
+ transitionTo(State.RESOLVING);
+ } else {
+ transitionTo(State.SHOWING_TAP_SEARCH);
+ }
+ break;
+ case RESOLVING:
+ transitionTo(State.SHOWING_TAP_SEARCH);
+ break;
+ default:
+ System.out.println("The state " + state.toString() + " is not transitional!");
+ Log.e(TAG, "The state " + state.toString() + " is not transitional!");
+ assert false;
+ }
+ }
+
+ /**
+ * @return The current State.
+ */
+ State getState() {
+ return mState;
+ }
+
+ /**
+ * Establishes the given state by calling code that starts work on that state.
+ * @param state the new {@link State} to establish.
+ */
+ private void transitionTo(State state) {
+ transitionTo(state, null);
+ }
+
+ // TODO(donnd): remove!
+ private String nameOf(State state) {
+ if (state == null) return "null";
+ return state.name();
+ }
+
+ /**
+ * Establishes the given state by calling code that starts work on that state or simply
+ * displays the appropriate UX for that state.
+ * @param state the new {@link State} to establish.
+ * @param reason the reason we're starting this state, or {@code null} if not significant
+ * or known. Only needed when we enter the IDLE state.
+ */
+ private void transitionTo(State state, @Nullable StateChangeReason reason) {
+ State previousState = mState;
+ mState = state;
+ mDidStartWork = false;
+ System.out.println(
+ "ctxs transition from " + nameOf(previousState) + " to " + nameOf(mState));
+
+ switch (state) {
+ case IDLE:
+ assert reason != null;
+ mControlled.hideContextualSearchUx(reason);
+ break;
+ case SHOWING_LONGPRESS_SEARCH:
+ mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT_LONG_PRESS);
+ break;
+
+ case TAP_RECOGNIZED:
+ // Fall through:
+ case LONG_PRESS_RECOGNIZED:
+ // Fall through:
+ case GATHERING_SURROUNDINGS:
+ System.out.println(
+ "ctxs gather... is tap? " + mSelectionController.getSelectionType());
+ mControlled.gatherSurroundingText();
+ break;
+ case WAITING_FOR_POSSIBLE_NAVIGATION:
+ mControlled.waitForPossibleNavigation();
+ break;
+ case SELECTING_WORD:
+ mControlled.selectWordAroundCaret();
+ break;
+ case DECIDING_SUPPRESSION:
+ mControlled.decideTapSuppression();
+ break;
+ case RESOLVING:
+ System.out.println("RESOLVING...");
+ mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT_TAP);
+ mControlled.startSearchTermResolutionRequest();
+ break;
+ case SHOWING_TAP_SEARCH:
+ System.out.println("SHOWING_TAP_SEARCH...");
+ // The panel may already be showing a Tap, but we'll make sure it's showing now.
+ mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT_TAP);
+ break;
+
+ default:
+ System.out.println("ctxs Warning! Transition ignored to " + state);
+ break;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698