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.omnibox.view; | |
Ted C
2017/05/17 18:29:33
I wouldn't introduce a new package yet. The omnib
Changwan Ryu
2017/05/17 20:18:57
Done.
| |
6 | |
7 import android.content.Context; | |
8 import android.graphics.Rect; | |
9 import android.os.StrictMode; | |
10 import android.text.Editable; | |
11 import android.text.Selection; | |
12 import android.text.Spanned; | |
13 import android.text.TextUtils; | |
14 import android.util.AttributeSet; | |
15 import android.view.accessibility.AccessibilityEvent; | |
16 import android.view.accessibility.AccessibilityManager; | |
17 import android.view.accessibility.AccessibilityNodeInfo; | |
18 import android.view.inputmethod.BaseInputConnection; | |
19 import android.view.inputmethod.EditorInfo; | |
20 import android.view.inputmethod.InputConnection; | |
21 import android.view.inputmethod.InputConnectionWrapper; | |
22 import android.widget.EditText; | |
23 | |
24 import org.chromium.base.Log; | |
25 import org.chromium.base.VisibleForTesting; | |
26 import org.chromium.chrome.browser.widget.VerticallyFixedEditText; | |
27 | |
28 /** | |
29 * An {@link EditText} that shows autocomplete text at the end. | |
30 */ | |
31 public class AutocompleteEditText extends VerticallyFixedEditText { | |
32 private static final String TAG = "cr_AutocompleteEdit"; | |
33 | |
34 private static final boolean DEBUG = false; | |
Ted C
2017/05/17 18:29:33
should we make this protected and have UrlBar key
Changwan Ryu
2017/05/17 20:18:57
At least for me, I prefer to keep DEBUG value sepa
| |
35 | |
36 private final AutocompleteSpan mAutocompleteSpan; | |
37 private final AccessibilityManager mAccessibilityManager; | |
38 | |
39 /** | |
40 * Whether default TextView scrolling should be disabled because autocomplet e has been added. | |
41 * This allows the user entered text to be shown instead of the end of the a utocomplete. | |
42 */ | |
43 private boolean mDisableTextScrollingFromAutocomplete; | |
44 | |
45 private boolean mInBatchEditMode; | |
46 private int mBeforeBatchEditAutocompleteIndex = -1; | |
47 private String mBeforeBatchEditFullText; | |
48 private boolean mSelectionChangedInBatchMode; | |
49 private boolean mTextDeletedInBatchMode; | |
50 | |
51 private boolean mIsPastedText; | |
52 | |
53 // Set to true when the text is modified programmatically. Initially set to true until the old | |
54 // state has been loaded. | |
55 private boolean mIgnoreTextChangeFromAutocomplete = true; | |
56 private boolean mLastEditWasDelete; | |
57 | |
58 public AutocompleteEditText(Context context, AttributeSet attrs) { | |
59 super(context, attrs); | |
60 mAutocompleteSpan = new AutocompleteSpan(); | |
61 mAccessibilityManager = | |
62 (AccessibilityManager) context.getSystemService(Context.ACCESSIB ILITY_SERVICE); | |
63 } | |
64 | |
65 /** | |
66 * Sets whether text changes should trigger autocomplete. | |
67 * | |
68 * @param ignoreAutocomplete Whether text changes should be ignored and no a uto complete | |
69 * triggered. | |
70 */ | |
71 public void setIgnoreTextChangesForAutocomplete(boolean ignoreAutocomplete) { | |
72 if (DEBUG) Log.i(TAG, "setIgnoreTextChangesForAutocomplete: " + ignoreAu tocomplete); | |
73 mIgnoreTextChangeFromAutocomplete = ignoreAutocomplete; | |
74 } | |
75 | |
76 /** @return Text that includes autocomplete. */ | |
77 public String getTextWithAutocomplete() { | |
78 return getEditableText() != null ? getEditableText().toString() : ""; | |
79 } | |
80 | |
81 /** | |
82 * @return Whether the current cursor position is at the end of the user typ ed text (i.e. | |
83 * at the beginning of the inline autocomplete text if present other wise the very | |
84 * end of the current text). | |
85 */ | |
86 private boolean isCursorAtEndOfTypedText() { | |
87 final int selectionStart = getSelectionStart(); | |
88 final int selectionEnd = getSelectionEnd(); | |
89 | |
90 int expectedSelectionStart = getText().getSpanStart(mAutocompleteSpan); | |
91 int expectedSelectionEnd = getText().length(); | |
92 if (expectedSelectionStart < 0) { | |
93 expectedSelectionStart = expectedSelectionEnd; | |
94 } | |
95 | |
96 return selectionStart == expectedSelectionStart && selectionEnd == expec tedSelectionEnd; | |
97 } | |
98 | |
99 /** | |
100 * @return Whether the URL is currently in batch edit mode triggered by an I ME. No external | |
101 * text changes should be triggered while this is true. | |
102 */ | |
103 // isInBatchEditMode is a package protected method on TextView, so we intent ionally chose | |
104 // a different name. | |
105 private boolean isHandlingBatchInput() { | |
106 return mInBatchEditMode; | |
107 } | |
108 | |
109 /** | |
110 * @return The user text without the autocomplete text. | |
111 */ | |
112 public String getTextWithoutAutocomplete() { | |
113 int autoCompleteIndex = getText().getSpanStart(mAutocompleteSpan); | |
114 if (autoCompleteIndex < 0) { | |
115 return getTextWithAutocomplete(); | |
116 } else { | |
117 return getTextWithAutocomplete().substring(0, autoCompleteIndex); | |
118 } | |
119 } | |
120 | |
121 /** @return Whether any autocomplete information is specified on the current text. */ | |
122 @VisibleForTesting | |
123 public boolean hasAutocomplete() { | |
124 return getText().getSpanStart(mAutocompleteSpan) >= 0 | |
125 || mAutocompleteSpan.mAutocompleteText != null | |
126 || mAutocompleteSpan.mUserText != null; | |
127 } | |
128 | |
129 /** | |
130 * Whether we want to be showing inline autocomplete results. We don't want to show them as the | |
131 * user deletes input. Also if there is a composition (e.g. while using the Japanese IME), | |
132 * we must not autocomplete or we'll destroy the composition. | |
133 * @return Whether we want to be showing inline autocomplete results. | |
134 */ | |
135 public boolean shouldAutocomplete() { | |
136 if (mLastEditWasDelete) return false; | |
137 Editable text = getText(); | |
138 | |
139 return isCursorAtEndOfTypedText() && !isPastedText() && !isHandlingBatch Input() | |
140 && BaseInputConnection.getComposingSpanEnd(text) | |
141 == BaseInputConnection.getComposingSpanStart(text); | |
142 } | |
143 | |
144 @Override | |
145 public void onBeginBatchEdit() { | |
146 if (DEBUG) Log.i(TAG, "onBeginBatchEdit"); | |
147 mBeforeBatchEditAutocompleteIndex = getText().getSpanStart(mAutocomplete Span); | |
148 mBeforeBatchEditFullText = getText().toString(); | |
149 | |
150 super.onBeginBatchEdit(); | |
151 mInBatchEditMode = true; | |
152 mTextDeletedInBatchMode = false; | |
153 } | |
154 | |
155 @Override | |
156 public void onEndBatchEdit() { | |
157 if (DEBUG) Log.i(TAG, "onEndBatchEdit"); | |
158 super.onEndBatchEdit(); | |
159 mInBatchEditMode = false; | |
160 if (mSelectionChangedInBatchMode) { | |
161 validateSelection(getSelectionStart(), getSelectionEnd()); | |
162 mSelectionChangedInBatchMode = false; | |
163 } | |
164 | |
165 String newText = getText().toString(); | |
166 if (!TextUtils.equals(mBeforeBatchEditFullText, newText) | |
167 || getText().getSpanStart(mAutocompleteSpan) != mBeforeBatchEdit AutocompleteIndex) { | |
168 // If the text being typed is a single character that matches the ne xt character in the | |
169 // previously visible autocomplete text, we reapply the autocomplete text to prevent | |
170 // a visual flickering when the autocomplete text is cleared and the n quickly reapplied | |
171 // when the next round of suggestions is received. | |
172 if (shouldAutocomplete() && mBeforeBatchEditAutocompleteIndex != -1 | |
173 && mBeforeBatchEditFullText != null | |
174 && mBeforeBatchEditFullText.startsWith(newText) && !mTextDel etedInBatchMode | |
175 && newText.length() - mBeforeBatchEditAutocompleteIndex == 1 ) { | |
176 setAutocompleteText(newText, mBeforeBatchEditFullText.substring( newText.length())); | |
177 } | |
178 notifyAutocompleteTextStateChanged(mTextDeletedInBatchMode, true); | |
179 } | |
180 | |
181 mTextDeletedInBatchMode = false; | |
182 mBeforeBatchEditAutocompleteIndex = -1; | |
183 mBeforeBatchEditFullText = null; | |
184 } | |
185 | |
186 @Override | |
187 protected void onSelectionChanged(int selStart, int selEnd) { | |
188 if (DEBUG) Log.i(TAG, "onSelectionChanged -- selStart: %d, selEnd: %d", selStart, selEnd); | |
189 if (!mInBatchEditMode) { | |
190 int beforeTextLength = getText().length(); | |
191 if (validateSelection(selStart, selEnd)) { | |
192 boolean textDeleted = getText().length() < beforeTextLength; | |
193 notifyAutocompleteTextStateChanged(textDeleted, false); | |
194 } | |
195 } else { | |
196 mSelectionChangedInBatchMode = true; | |
197 } | |
198 super.onSelectionChanged(selStart, selEnd); | |
199 } | |
200 | |
201 /** | |
202 * Validates the selection and clears the autocomplete span if needed. The autocomplete text | |
203 * will be deleted if the selection occurs entirely before the autocomplete region. | |
204 * | |
205 * @param selStart The start of the selection. | |
206 * @param selEnd The end of the selection. | |
207 * @return Whether the autocomplete span was removed as a result of this val idation. | |
208 */ | |
209 private boolean validateSelection(int selStart, int selEnd) { | |
210 int spanStart = getText().getSpanStart(mAutocompleteSpan); | |
211 int spanEnd = getText().getSpanEnd(mAutocompleteSpan); | |
212 | |
213 if (DEBUG) { | |
214 Log.i(TAG, "validateSelection -- selStart: %d, selEnd: %d, spanStart : %d, spanEnd: %d", | |
215 selStart, selEnd, spanStart, spanEnd); | |
216 } | |
217 | |
218 if (spanStart >= 0 && (spanStart != selStart || spanEnd != selEnd)) { | |
219 CharSequence previousAutocompleteText = mAutocompleteSpan.mAutocompl eteText; | |
220 | |
221 // On selection changes, the autocomplete text has been accepted by the user or needs | |
222 // to be deleted below. | |
223 mAutocompleteSpan.clearSpan(); | |
224 | |
225 // The autocomplete text will be deleted any time the selection occu rs entirely before | |
226 // the start of the autocomplete text. This is required because cer tain keyboards will | |
227 // insert characters temporarily when starting a key entry gesture ( whether it be | |
228 // swyping a word or long pressing to get a special character). Whe n this temporary | |
229 // character appears, Chrome may decide to append some autocomplete, but the keyboard | |
230 // will then remove this temporary character only while leaving the autocomplete text | |
231 // alone. See crbug/273763 for more details. | |
232 if (selEnd <= spanStart | |
233 && TextUtils.equals(previousAutocompleteText, | |
234 getText().subSequence(spanStart, getText().length ()))) { | |
235 getText().delete(spanStart, getText().length()); | |
236 } | |
237 return true; | |
238 } | |
239 return false; | |
240 } | |
241 | |
242 @Override | |
243 protected void onFocusChanged(boolean focused, int direction, Rect previousl yFocusedRect) { | |
244 if (!focused) mAutocompleteSpan.clearSpan(); | |
245 super.onFocusChanged(focused, direction, previouslyFocusedRect); | |
246 } | |
247 | |
248 @Override | |
249 public boolean bringPointIntoView(int offset) { | |
250 if (mDisableTextScrollingFromAutocomplete) return false; | |
251 return super.bringPointIntoView(offset); | |
252 } | |
253 | |
254 @Override | |
255 public boolean onPreDraw() { | |
256 boolean retVal = super.onPreDraw(); | |
257 if (mDisableTextScrollingFromAutocomplete) { | |
258 // super.onPreDraw will put the selection at the end of the text sel ection, but | |
259 // in the case of autocomplete we want the last typed character to b e shown, which | |
260 // is the start of selection. | |
261 mDisableTextScrollingFromAutocomplete = false; | |
262 bringPointIntoView(getSelectionStart()); | |
263 retVal = true; | |
264 } | |
265 return retVal; | |
266 } | |
267 | |
268 /** Call this when text is pasted. */ | |
269 public void onPaste() { | |
270 mIsPastedText = true; | |
271 } | |
272 | |
273 /** | |
274 * Autocompletes the text on the url bar and selects the text that was not e ntered by the | |
275 * user. Using append() instead of setText() to preserve the soft-keyboard l ayout. | |
276 * @param userText user The text entered by the user. | |
277 * @param inlineAutocompleteText The suggested autocompletion for the user's text. | |
278 */ | |
279 public void setAutocompleteText(CharSequence userText, CharSequence inlineAu tocompleteText) { | |
280 if (DEBUG) { | |
281 Log.i(TAG, "setAutocompleteText -- userText: %s, inlineAutocompleteT ext: %s", userText, | |
282 inlineAutocompleteText); | |
283 } | |
284 boolean emptyAutocomplete = TextUtils.isEmpty(inlineAutocompleteText); | |
285 | |
286 if (!emptyAutocomplete) mDisableTextScrollingFromAutocomplete = true; | |
287 | |
288 int autocompleteIndex = userText.length(); | |
289 | |
290 String previousText = getTextWithAutocomplete(); | |
291 CharSequence newText = TextUtils.concat(userText, inlineAutocompleteText ); | |
292 | |
293 setIgnoreTextChangesForAutocomplete(true); | |
294 | |
295 if (!TextUtils.equals(previousText, newText)) { | |
296 // The previous text may also have included autocomplete text, so we only | |
297 // append the new autocomplete text that has changed. | |
298 if (TextUtils.indexOf(newText, previousText) == 0) { | |
299 append(newText.subSequence(previousText.length(), newText.length ())); | |
300 } else { | |
301 setTextFromAutocomplete(newText.toString()); | |
302 } | |
303 } | |
304 | |
305 if (getSelectionStart() != autocompleteIndex || getSelectionEnd() != get Text().length()) { | |
306 setSelection(autocompleteIndex, getText().length()); | |
307 | |
308 if (inlineAutocompleteText.length() != 0) { | |
309 // Sending a TYPE_VIEW_TEXT_SELECTION_CHANGED accessibility even t causes the | |
310 // previous TYPE_VIEW_TEXT_CHANGED event to be swallowed. As a r esult the user | |
311 // hears the autocomplete text but *not* the text they typed. In stead we send a | |
312 // TYPE_ANNOUNCEMENT event, which doesn't swallow the text-chang ed event. | |
313 announceForAccessibility(inlineAutocompleteText); | |
314 } | |
315 } | |
316 | |
317 if (emptyAutocomplete) { | |
318 mAutocompleteSpan.clearSpan(); | |
319 } else { | |
320 mAutocompleteSpan.setSpan(userText, inlineAutocompleteText); | |
321 } | |
322 | |
323 setIgnoreTextChangesForAutocomplete(false); | |
324 } | |
325 | |
326 /** | |
327 * Returns the length of the autocomplete text currently displayed, zero if none is | |
328 * currently displayed. | |
329 */ | |
330 public int getAutocompleteLength() { | |
331 int autoCompleteIndex = getText().getSpanStart(mAutocompleteSpan); | |
332 if (autoCompleteIndex < 0) return 0; | |
333 return getText().length() - autoCompleteIndex; | |
334 } | |
335 | |
336 @Override | |
337 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { | |
338 if (DEBUG) { | |
339 Log.i(TAG, "onTextChanged -- text: %s, start: %d, lengthBefore: %d, lengthAfter: %d", | |
340 text, start, lengthBefore, lengthAfter); | |
341 } | |
342 | |
343 super.onTextChanged(text, start, lengthBefore, lengthAfter); | |
344 boolean textDeleted = lengthAfter == 0; | |
345 if (!mInBatchEditMode) { | |
346 notifyAutocompleteTextStateChanged(textDeleted, true); | |
347 } else { | |
348 mTextDeletedInBatchMode = textDeleted; | |
349 } | |
350 mIsPastedText = false; | |
351 } | |
352 | |
353 @Override | |
354 public void setText(CharSequence text, BufferType type) { | |
355 if (DEBUG) Log.i(TAG, "setText -- text: %s", text); | |
356 | |
357 mDisableTextScrollingFromAutocomplete = false; | |
358 | |
359 // Avoid setting the same text to the URL bar as it will mess up the scr oll/cursor | |
360 // position. | |
361 // Setting the text is also quite expensive, so only do it when the text has changed | |
362 // (since we apply spans when the URL is not focused, we only optimize t his when the | |
363 // URL is being edited). | |
364 if (!TextUtils.equals(getEditableText(), text)) { | |
365 // Certain OEM implementations of setText trigger disk reads. crbug. com/633298 | |
366 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads( ); | |
367 try { | |
368 super.setText(text, type); | |
369 } finally { | |
370 StrictMode.setThreadPolicy(oldPolicy); | |
371 } | |
372 } | |
373 | |
374 // Verify the autocomplete is still valid after the text change. | |
375 // Note: mAutocompleteSpan may be still null here if setText() is called in View | |
376 // constructor. | |
377 if (mAutocompleteSpan != null && mAutocompleteSpan.mUserText != null | |
378 && mAutocompleteSpan.mAutocompleteText != null) { | |
379 if (getText().getSpanStart(mAutocompleteSpan) < 0) { | |
380 mAutocompleteSpan.clearSpan(); | |
381 } else { | |
382 clearAutocompleteSpanIfInvalid(); | |
383 } | |
384 } | |
385 } | |
386 | |
387 private void clearAutocompleteSpanIfInvalid() { | |
388 Editable editableText = getEditableText(); | |
389 CharSequence previousUserText = mAutocompleteSpan.mUserText; | |
390 CharSequence previousAutocompleteText = mAutocompleteSpan.mAutocompleteT ext; | |
391 if (editableText.length() | |
392 != (previousUserText.length() + previousAutocompleteText.length( ))) { | |
393 mAutocompleteSpan.clearSpan(); | |
394 } else if (TextUtils.indexOf(getText(), previousUserText) != 0 | |
395 || TextUtils.indexOf(getText(), previousAutocompleteText, previo usUserText.length()) | |
396 != 0) { | |
397 mAutocompleteSpan.clearSpan(); | |
398 } | |
399 } | |
400 | |
401 @Override | |
402 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { | |
403 if (mIgnoreTextChangeFromAutocomplete) { | |
404 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECT ION_CHANGED | |
405 || event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT _CHANGED) { | |
406 return; | |
407 } | |
408 } | |
409 super.sendAccessibilityEventUnchecked(event); | |
410 } | |
411 | |
412 @Override | |
413 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { | |
414 // Certain OEM implementations of onInitializeAccessibilityNodeInfo trig ger disk reads | |
415 // to access the clipboard. crbug.com/640993 | |
416 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | |
417 try { | |
418 super.onInitializeAccessibilityNodeInfo(info); | |
419 } finally { | |
420 StrictMode.setThreadPolicy(oldPolicy); | |
421 } | |
422 } | |
423 | |
424 @VisibleForTesting | |
425 public InputConnectionWrapper getInputConnection() { | |
426 return mInputConnection; | |
427 } | |
428 | |
429 private InputConnectionWrapper mInputConnection = new InputConnectionWrapper (null, true) { | |
430 private final char[] mTempSelectionChar = new char[1]; | |
431 | |
432 @Override | |
433 public boolean commitText(CharSequence text, int newCursorPosition) { | |
434 if (DEBUG) Log.i(TAG, "commitText: [%s]", text); | |
435 Editable currentText = getText(); | |
436 if (currentText == null) return super.commitText(text, newCursorPosi tion); | |
437 | |
438 int selectionStart = Selection.getSelectionStart(currentText); | |
439 int selectionEnd = Selection.getSelectionEnd(currentText); | |
440 int autocompleteIndex = currentText.getSpanStart(mAutocompleteSpan); | |
441 // If the text being committed is a single character that matches th e next character | |
442 // in the selection (assumed to be the autocomplete text), we only m ove the text | |
443 // selection instead clearing the autocomplete text causing flickeri ng as the | |
444 // autocomplete text will appear once the next suggestions are recei ved. | |
445 // | |
446 // To be confident that the selection is an autocomplete, we ensure the selection | |
447 // is at least one character and the end of the selection is the end of the | |
448 // currently entered text. | |
449 if (newCursorPosition == 1 && selectionStart > 0 && selectionStart ! = selectionEnd | |
450 && selectionEnd >= currentText.length() && autocompleteIndex == selectionStart | |
451 && text.length() == 1) { | |
452 currentText.getChars(selectionStart, selectionStart + 1, mTempSe lectionChar, 0); | |
453 if (mTempSelectionChar[0] == text.charAt(0)) { | |
454 // Since the text isn't changing, TalkBack won't read out th e typed characters. | |
455 // To work around this, explicitly send an accessibility eve nt. crbug.com/416595 | |
456 if (mAccessibilityManager != null && mAccessibilityManager.i sEnabled()) { | |
457 AccessibilityEvent event = AccessibilityEvent.obtain( | |
458 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); | |
459 event.setFromIndex(selectionStart); | |
460 event.setRemovedCount(0); | |
461 event.setAddedCount(1); | |
462 event.setBeforeText(currentText.toString().substring(0, selectionStart)); | |
463 sendAccessibilityEventUnchecked(event); | |
464 } | |
465 | |
466 setAutocompleteText(currentText.subSequence(0, selectionStar t + 1), | |
467 currentText.subSequence(selectionStart + 1, selectio nEnd)); | |
468 if (!mInBatchEditMode) { | |
469 notifyAutocompleteTextStateChanged(false, false); | |
470 } | |
471 return true; | |
472 } | |
473 } | |
474 | |
475 boolean retVal = super.commitText(text, newCursorPosition); | |
476 | |
477 // Ensure the autocomplete span is removed if it is no longer valid after committing the | |
478 // text. | |
479 if (getText().getSpanStart(mAutocompleteSpan) >= 0) clearAutocomplet eSpanIfInvalid(); | |
480 | |
481 return retVal; | |
482 } | |
483 | |
484 @Override | |
485 public boolean setComposingText(CharSequence text, int newCursorPosition ) { | |
486 if (DEBUG) Log.i(TAG, "setComposingText: [%s]", text); | |
487 Editable currentText = getText(); | |
488 int autoCompleteSpanStart = currentText.getSpanStart(mAutocompleteSp an); | |
489 if (autoCompleteSpanStart >= 0) { | |
490 int composingEnd = BaseInputConnection.getComposingSpanEnd(curre ntText); | |
491 | |
492 // On certain device/keyboard combinations, the composing region s are specified | |
493 // with a noticeable delay after the initial character is typed, and in certain | |
494 // circumstances it does not check that the current state of the text matches the | |
495 // expectations of it's composing region. | |
496 // For example, you can be typing: | |
497 // chrome://f | |
498 // Chrome will autocomplete to: | |
499 // chrome://f[lags] | |
500 // And after the autocomplete has been set, the keyboard will se t the composing | |
501 // region to the last character and it assumes it is 'f' as it w as the last | |
502 // character the keyboard sent. If we commit this composition, the text will | |
503 // look like: | |
504 // chrome://flag[f] | |
505 // And if we use the autocomplete clearing logic below, it will look like: | |
506 // chrome://f[f] | |
507 // To work around this, we see if the composition matches all th e characters prior | |
508 // to the autocomplete and just readjust the composing region to be that subset. | |
509 // | |
510 // See crbug.com/366732 | |
511 if (composingEnd == currentText.length() && autoCompleteSpanStar t >= text.length() | |
512 && TextUtils.equals( | |
513 currentText.subSequence(autoCompleteSpanStart - text.length(), | |
514 autoCompleteSpanStart), | |
515 text)) { | |
516 setComposingRegion( | |
517 autoCompleteSpanStart - text.length(), autoCompleteS panStart); | |
518 } | |
519 | |
520 // Once composing text is being modified, the autocomplete text has been accepted | |
521 // or has to be deleted. | |
522 mAutocompleteSpan.clearSpan(); | |
523 Selection.setSelection(currentText, autoCompleteSpanStart); | |
524 currentText.delete(autoCompleteSpanStart, currentText.length()); | |
525 } | |
526 return super.setComposingText(text, newCursorPosition); | |
527 } | |
528 }; | |
529 | |
530 @Override | |
531 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { | |
532 if (DEBUG) Log.i(TAG, "onCreateInputConnection"); | |
533 return createInputConnection(super.onCreateInputConnection(outAttrs)); | |
534 } | |
535 | |
536 @VisibleForTesting | |
537 public InputConnection createInputConnection(InputConnection target) { | |
538 mInputConnection.setTarget(target); | |
539 return mInputConnection; | |
540 } | |
541 | |
542 /** | |
543 * @return Whether the current UrlBar input has been pasted from the clipboa rd. | |
544 */ | |
545 public boolean isPastedText() { | |
546 return mIsPastedText; | |
Ted C
2017/05/17 18:29:33
Does this need to be in this class? Looks like it
Changwan Ryu
2017/05/17 20:18:57
Done. Note that I overrode shouldAutocomplete() to
| |
547 } | |
548 | |
549 private void notifyAutocompleteTextStateChanged(boolean textDeleted, boolean updateDisplay) { | |
550 if (DEBUG) { | |
551 Log.i(TAG, "notifyAutocompleteTextStateChanged: DEL[%b] DIS[%b] IGN[ %b]", textDeleted, | |
552 updateDisplay, mIgnoreTextChangeFromAutocomplete); | |
553 } | |
554 if (mIgnoreTextChangeFromAutocomplete) return; | |
555 if (!hasFocus()) return; | |
556 mLastEditWasDelete = textDeleted; | |
557 onAutocompleteTextStateChanged(textDeleted, updateDisplay); | |
558 } | |
559 | |
560 /** | |
561 * This is called when autocomplete replaces the whole text. | |
562 * | |
563 * @param text The text. | |
564 */ | |
565 public void setTextFromAutocomplete(String text) { | |
Ted C
2017/05/17 18:29:33
seems like this should be protected. We really do
Changwan Ryu
2017/05/17 20:18:57
Done, thanks for the naming suggestion!
| |
566 setText(text); | |
567 } | |
568 | |
569 /** | |
570 * This is called when autocomplete text state changes. | |
571 * @param textDeleted True if text is just deleted. | |
572 * @param updateDisplay True if string is changed. | |
573 */ | |
574 public void onAutocompleteTextStateChanged(boolean textDeleted, boolean upda teDisplay) {} | |
575 | |
576 /** | |
577 * Simple span used for tracking the current autocomplete state. | |
578 */ | |
579 private class AutocompleteSpan { | |
580 private CharSequence mUserText; | |
581 private CharSequence mAutocompleteText; | |
582 | |
583 /** | |
584 * Adds the span to the current text. | |
585 * @param userText The user entered text. | |
586 * @param autocompleteText The autocomplete text being appended. | |
587 */ | |
588 public void setSpan(CharSequence userText, CharSequence autocompleteText ) { | |
589 Editable text = getText(); | |
590 text.removeSpan(this); | |
591 mAutocompleteText = autocompleteText; | |
592 mUserText = userText; | |
593 text.setSpan(this, userText.length(), text.length(), Spanned.SPAN_EX CLUSIVE_EXCLUSIVE); | |
594 } | |
595 | |
596 /** Removes this span from the current text and clears the internal stat e. */ | |
597 public void clearSpan() { | |
598 getText().removeSpan(this); | |
599 mAutocompleteText = null; | |
600 mUserText = null; | |
601 } | |
602 } | |
603 } | |
OLD | NEW |