Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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.util.Log; | |
| 8 | |
| 9 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChange Reason; | |
| 10 import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionCon troller.SelectionType; | |
| 11 | |
| 12 import javax.annotation.Nullable; | |
| 13 | |
| 14 /** | |
| 15 * Controls the State of the Contextual Search Manager. | |
| 16 * <p> | |
| 17 * This class keeps track of the current state of the {@code ContextualSearchMan ager} and helps it | |
| 18 * to transition between states return to the idle state when work has been inte rrupted. | |
| 19 * <p> | |
| 20 * Usage: Call {@link #reset(StateChangeReason)} to reset to the {@code IDLE} st ate.<br> | |
| 21 * Call {@link #enter(State)} to enter a start-state (when a gesture is recogniz ed). | |
| 22 * When doing some work in an asynchronous manner:<ol> | |
| 23 * <li>call {@link #notifyStartingWorkOn(State)} to note that work is starting o n that state | |
| 24 * <li>call {@link #notifyFinishedWorkOn(State)} when work is completed. | |
| 25 * <li>If a handler needs to do additional work, such as updating the UX, it sho uld first call | |
| 26 * {@link #isStillWorkingOn(State)} to check that work has not been interrupted. | |
| 27 * </ol><p> | |
| 28 * The {@link #notifyFinishedWorkOn(State)} method will automatically start a tr ansition to the | |
| 29 * next state. | |
| 30 * <p> | |
| 31 * Policy decisions about state transitions should only be done in the private | |
| 32 * {@link #transitionTo(State)} method of this class, not in the {@code Contextu alSearchManager}. | |
| 33 */ | |
| 34 class ContextualSearchStateController { | |
| 35 private static final String TAG = "Contextual Search"; | |
| 36 | |
| 37 private final ContextualSearchSelectionController mSelectionController; | |
| 38 private final ContextualSearchPolicy mPolicy; | |
| 39 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.
| |
| 40 | |
| 41 /** The current state of the manager. */ | |
| 42 public static enum State { | |
| 43 // When not yet initialized or already destroyed only | |
| 44 UNDEFINED, | |
| 45 // This is the default resting state. | |
| 46 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
| |
| 47 | |
| 48 // 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.
| |
| 49 LONG_PRESS_RECOGNIZED, | |
| 50 // Transitional state when extending the selection. | |
| 51 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
| |
| 52 // Resting state when showing the panel in response to a Long-press gest ure. | |
| 53 SHOWING_LONGPRESS_SEARCH, | |
| 54 | |
| 55 // This is a sequence of transition states needed to get to the SHOWING_ TAP resting state. | |
| 56 TAP_RECOGNIZED, | |
| 57 // May be done for Tap or Long-press. | |
| 58 GATHERING_SURROUNDINGS, | |
| 59 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
| |
| 60 WAITING_FOR_POSSIBLE_NAVIGATION, | |
| 61 SELECTING_WORD, | |
| 62 // Resolving the Search Term using the surrounding text and additional c ontext. | |
| 63 RESOLVING, | |
| 64 // Resting state when showing the panel in response to a Tap gesture. | |
| 65 SHOWING_TAP_SEARCH | |
| 66 } | |
| 67 | |
| 68 // The current state of this instance. | |
| 69 private State mState; | |
| 70 | |
| 71 // Whether work has started on the current state. | |
| 72 private boolean mDidStartWork; | |
| 73 | |
| 74 /** | |
| 75 * Constructs an instance of this class, which has the same lifetime as the | |
| 76 * {@code ContextualSearchManager} and the given parameters. | |
| 77 */ | |
| 78 ContextualSearchStateController(ContextualSearchSelectionController selectio nController, | |
| 79 ContextualSearchPolicy policy, ContextualSearchStateControlled contr olled) { | |
| 80 mSelectionController = selectionController; | |
| 81 mPolicy = policy; | |
| 82 mControlled = controlled; | |
| 83 } | |
| 84 | |
| 85 // ========================================================================= =================== | |
| 86 // State-transition management. | |
| 87 // This code is designed to solve several problems: | |
| 88 // 1) Document the sequencing of handling a gesture in code. Now there's a single method that | |
| 89 // determines the sequence that should be followed for Tap handling (our most complicated | |
| 90 // case. | |
| 91 // 2) Document the initiation and subsequent notification/handling of operat ions. Now the | |
| 92 // method that starts an operation and the notification handler are tied together by their | |
| 93 // references to the same state. This allows a simple search to find the | |
| 94 // initiation and handler together (which is not always easy, e.g. Select WordAroundCaret | |
| 95 // does not yet have an ACK so we infer that it's complete when the selec tion change -- or | |
| 96 // does not change after some short waiting period). | |
| 97 // 3) Gracefully handle sequence interruptions. When an asynchronous operat ion is in progress | |
| 98 // the user may start a new sequence or abort the current sequence. Now the handler for an | |
| 99 // asynchronous operation can easily detect that it's no longer working o n that operation | |
| 100 // and skip the normal completion of the operation. | |
| 101 // ========================================================================= =================== | |
| 102 | |
| 103 /** | |
| 104 * Reset the current state to the IDLE state. | |
| 105 * @param reason the reason for the reset | |
| 106 */ | |
| 107 void reset(StateChangeReason reason) { | |
| 108 transitionTo(State.IDLE, reason); | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * Enters the given starting state immediately. | |
| 113 * @param state the new starting {@link State} we're now in | |
| 114 */ | |
| 115 void enter(State state) { | |
| 116 assert state == State.UNDEFINED || state == State.IDLE | |
| 117 || state == State.LONG_PRESS_RECOGNIZED || state == State.TAP_RE COGNIZED; | |
| 118 mState = state; | |
| 119 System.out.println("ctxs enter : " + mSelectionController.getSelectionTy pe()); | |
| 120 notifyStartingWorkOn(mState); | |
| 121 notifyFinishedWorkOn(mState); | |
| 122 } | |
| 123 | |
| 124 /** | |
| 125 * Confirms that work is starting on the given state. | |
| 126 * @param state the {@link State} that we're now working on | |
| 127 */ | |
| 128 void notifyStartingWorkOn(State state) { | |
| 129 assert mState == state; | |
| 130 mDidStartWork = true; | |
| 131 } | |
| 132 | |
| 133 /** | |
| 134 * @return whether we're still working on the given state | |
| 135 */ | |
| 136 boolean isStillWorkingOn(State state) { | |
| 137 return mState == state; | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * Confirms that work has been finished on the given state. | |
| 142 * This should be called by every operation that waits for some kind of comp letion when it | |
| 143 * completes. The operation's start must be flagged using {@link #starting} . | |
| 144 * @param state the {@link State} that we've finished working on. | |
| 145 */ | |
| 146 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
| |
| 147 if (state != mState) return; | |
| 148 | |
| 149 assert mDidStartWork; | |
| 150 | |
| 151 if (mState == State.IDLE) { | |
| 152 Log.w(TAG, "Warning, the " + state.toString() + " state was aborted. "); | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 switch (state) { | |
| 157 case LONG_PRESS_RECOGNIZED: | |
| 158 transitionTo(State.GATHERING_SURROUNDINGS); | |
| 159 break; | |
| 160 case TAP_RECOGNIZED: | |
| 161 transitionTo(State.GATHERING_SURROUNDINGS); | |
| 162 break; | |
| 163 case GATHERING_SURROUNDINGS: | |
| 164 System.out.println("ctxs finished gathering, checking if tap..." | |
| 165 + mSelectionController.getSelectionType()); | |
| 166 if (mSelectionController.getSelectionType() == SelectionType.TAP ) { | |
| 167 transitionTo(State.DECIDING_SUPPRESSION); | |
| 168 } else { | |
| 169 // No suppression yet for Long-press. | |
| 170 transitionTo(State.SHOWING_LONGPRESS_SEARCH, | |
| 171 StateChangeReason.TEXT_SELECT_LONG_PRESS); | |
| 172 } | |
| 173 break; | |
| 174 case DECIDING_SUPPRESSION: | |
| 175 transitionTo(State.WAITING_FOR_POSSIBLE_NAVIGATION); | |
| 176 break; | |
| 177 case WAITING_FOR_POSSIBLE_NAVIGATION: | |
| 178 transitionTo(State.SELECTING_WORD); | |
| 179 break; | |
| 180 case SELECTING_WORD: | |
| 181 System.out.println("checking if we should resolve..."); | |
| 182 if (mPolicy.shouldPreviousTapResolve()) { | |
| 183 transitionTo(State.RESOLVING); | |
| 184 } else { | |
| 185 transitionTo(State.SHOWING_TAP_SEARCH); | |
| 186 } | |
| 187 break; | |
| 188 case RESOLVING: | |
| 189 transitionTo(State.SHOWING_TAP_SEARCH); | |
| 190 break; | |
| 191 default: | |
| 192 System.out.println("The state " + state.toString() + " is not tr ansitional!"); | |
| 193 Log.e(TAG, "The state " + state.toString() + " is not transition al!"); | |
| 194 assert false; | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 /** | |
| 199 * @return The current State. | |
| 200 */ | |
| 201 State getState() { | |
| 202 return mState; | |
| 203 } | |
| 204 | |
| 205 /** | |
| 206 * Establishes the given state by calling code that starts work on that stat e. | |
| 207 * @param state the new {@link State} to establish. | |
| 208 */ | |
| 209 private void transitionTo(State state) { | |
| 210 transitionTo(state, null); | |
| 211 } | |
| 212 | |
| 213 // TODO(donnd): remove! | |
| 214 private String nameOf(State state) { | |
| 215 if (state == null) return "null"; | |
| 216 return state.name(); | |
| 217 } | |
| 218 | |
| 219 /** | |
| 220 * Establishes the given state by calling code that starts work on that stat e or simply | |
| 221 * displays the appropriate UX for that state. | |
| 222 * @param state the new {@link State} to establish. | |
| 223 * @param reason the reason we're starting this state, or {@code null} if no t significant | |
| 224 * or known. Only needed when we enter the IDLE state. | |
| 225 */ | |
| 226 private void transitionTo(State state, @Nullable StateChangeReason reason) { | |
| 227 State previousState = mState; | |
| 228 mState = state; | |
| 229 mDidStartWork = false; | |
| 230 System.out.println( | |
| 231 "ctxs transition from " + nameOf(previousState) + " to " + nameO f(mState)); | |
| 232 | |
| 233 switch (state) { | |
| 234 case IDLE: | |
| 235 assert reason != null; | |
| 236 mControlled.hideContextualSearchUx(reason); | |
| 237 break; | |
| 238 case SHOWING_LONGPRESS_SEARCH: | |
| 239 mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT _LONG_PRESS); | |
| 240 break; | |
| 241 | |
| 242 case TAP_RECOGNIZED: | |
| 243 // Fall through: | |
| 244 case LONG_PRESS_RECOGNIZED: | |
| 245 // Fall through: | |
| 246 case GATHERING_SURROUNDINGS: | |
| 247 System.out.println( | |
| 248 "ctxs gather... is tap? " + mSelectionController.getSel ectionType()); | |
| 249 mControlled.gatherSurroundingText(); | |
| 250 break; | |
| 251 case WAITING_FOR_POSSIBLE_NAVIGATION: | |
| 252 mControlled.waitForPossibleNavigation(); | |
| 253 break; | |
| 254 case SELECTING_WORD: | |
| 255 mControlled.selectWordAroundCaret(); | |
| 256 break; | |
| 257 case DECIDING_SUPPRESSION: | |
| 258 mControlled.decideTapSuppression(); | |
| 259 break; | |
| 260 case RESOLVING: | |
| 261 System.out.println("RESOLVING..."); | |
| 262 mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT _TAP); | |
| 263 mControlled.startSearchTermResolutionRequest(); | |
| 264 break; | |
| 265 case SHOWING_TAP_SEARCH: | |
| 266 System.out.println("SHOWING_TAP_SEARCH..."); | |
| 267 // The panel may already be showing a Tap, but we'll make sure i t's showing now. | |
| 268 mControlled.showContextualSearchUx(StateChangeReason.TEXT_SELECT _TAP); | |
| 269 break; | |
| 270 | |
| 271 default: | |
| 272 System.out.println("ctxs Warning! Transition ignored to " + stat e); | |
| 273 break; | |
| 274 } | |
| 275 } | |
| 276 } | |
| OLD | NEW |