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

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/AdapterInputConnection.java

Issue 1278593004: Introduce ThreadedInputConnection behind a switch (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: removed ImeTest#testDoesNotHang_rendererCrashes which does not test anything Created 4 years, 10 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 2013 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.content.browser.input;
6
7 import android.text.Editable;
8 import android.text.InputType;
9 import android.text.Selection;
10 import android.util.StringBuilderPrinter;
11 import android.view.KeyCharacterMap;
12 import android.view.KeyEvent;
13 import android.view.View;
14 import android.view.inputmethod.BaseInputConnection;
15 import android.view.inputmethod.EditorInfo;
16 import android.view.inputmethod.ExtractedText;
17 import android.view.inputmethod.ExtractedTextRequest;
18
19 import org.chromium.base.Log;
20 import org.chromium.base.VisibleForTesting;
21 import org.chromium.blink_public.web.WebTextInputFlags;
22 import org.chromium.ui.base.ime.TextInputType;
23
24 import java.util.Locale;
25
26 /**
27 * InputConnection is created by ContentView.onCreateInputConnection.
28 * It then adapts android's IME to chrome's RenderWidgetHostView using the
29 * native ImeAdapterAndroid via the class ImeAdapter.
30 */
31 public class AdapterInputConnection extends BaseInputConnection {
32 private static final String TAG = "cr_Ime";
33 private static final boolean DEBUG_LOGS = false;
34 /**
35 * Selection value should be -1 if not known. See EditorInfo.java for detail s.
36 */
37 public static final int INVALID_SELECTION = -1;
38 public static final int INVALID_COMPOSITION = -1;
39
40 private final ImeAdapter mImeAdapter;
41
42 private boolean mSingleLine;
43 private int mNumNestedBatchEdits = 0;
44 private int mPendingAccent;
45
46 private int mLastUpdateSelectionStart = INVALID_SELECTION;
47 private int mLastUpdateSelectionEnd = INVALID_SELECTION;
48 private int mLastUpdateCompositionStart = INVALID_COMPOSITION;
49 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION;
50
51 @VisibleForTesting
52 AdapterInputConnection(View view, ImeAdapter imeAdapter, int initialSelStart , int initialSelEnd,
53 EditorInfo outAttrs) {
54 super(view, true);
55 mImeAdapter = imeAdapter;
56 mImeAdapter.setInputConnection(this);
57
58 mSingleLine = true;
59 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
60 | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
61 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
62 | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
63
64 int inputType = imeAdapter.getTextInputType();
65 int inputFlags = imeAdapter.getTextInputFlags();
66 if ((inputFlags & WebTextInputFlags.AutocompleteOff) != 0) {
67 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
68 }
69
70 if (inputType == TextInputType.TEXT) {
71 // Normal text field
72 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
73 if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) {
74 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
75 }
76 } else if (inputType == TextInputType.TEXT_AREA
77 || inputType == TextInputType.CONTENT_EDITABLE) {
78 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
79 if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) {
80 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
81 }
82 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE;
83 mSingleLine = false;
84 } else if (inputType == TextInputType.PASSWORD) {
85 // Password
86 outAttrs.inputType = InputType.TYPE_CLASS_TEXT
87 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
88 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
89 } else if (inputType == TextInputType.SEARCH) {
90 // Search
91 outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH;
92 } else if (inputType == TextInputType.URL) {
93 // Url
94 outAttrs.inputType = InputType.TYPE_CLASS_TEXT
95 | InputType.TYPE_TEXT_VARIATION_URI;
96 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
97 } else if (inputType == TextInputType.EMAIL) {
98 // Email
99 outAttrs.inputType = InputType.TYPE_CLASS_TEXT
100 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
101 outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
102 } else if (inputType == TextInputType.TELEPHONE) {
103 // Telephone
104 // Number and telephone do not have both a Tab key and an
105 // action in default OSK, so set the action to NEXT
106 outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
107 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
108 } else if (inputType == TextInputType.NUMBER) {
109 // Number
110 outAttrs.inputType = InputType.TYPE_CLASS_NUMBER
111 | InputType.TYPE_NUMBER_VARIATION_NORMAL
112 | InputType.TYPE_NUMBER_FLAG_DECIMAL;
113 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
114 }
115
116 // Handling of autocapitalize. Blink will send the flag taking into acco unt the element's
117 // type. This is not using AutocapitalizeNone because Android does not a utocapitalize by
118 // default and there is no way to express no capitalization.
119 // Autocapitalize is meant as a hint to the virtual keyboard.
120 if ((inputFlags & WebTextInputFlags.AutocapitalizeCharacters) != 0) {
121 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
122 } else if ((inputFlags & WebTextInputFlags.AutocapitalizeWords) != 0) {
123 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
124 } else if ((inputFlags & WebTextInputFlags.AutocapitalizeSentences) != 0 ) {
125 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
126 }
127 // Content editable doesn't use autocapitalize so we need to set it manu ally.
128 if (inputType == TextInputType.CONTENT_EDITABLE) {
129 outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
130 }
131
132 outAttrs.initialSelStart = initialSelStart;
133 outAttrs.initialSelEnd = initialSelEnd;
134 mLastUpdateSelectionStart = outAttrs.initialSelStart;
135 mLastUpdateSelectionEnd = outAttrs.initialSelEnd;
136 if (DEBUG_LOGS) {
137 Log.w(TAG, "Constructor called with outAttrs: %s", dumpEditorInfo(ou tAttrs));
138 }
139 }
140
141 private static String dumpEditorInfo(EditorInfo editorInfo) {
142 StringBuilder builder = new StringBuilder();
143 StringBuilderPrinter printer = new StringBuilderPrinter(builder);
144 editorInfo.dump(printer, "");
145 return builder.toString();
146 }
147
148 private static String dumpEditable(Editable editable) {
149 return String.format(Locale.US, "Editable {[%s] SEL[%d %d] COM[%d %d]}",
150 editable.toString(),
151 Selection.getSelectionStart(editable),
152 Selection.getSelectionEnd(editable),
153 getComposingSpanStart(editable),
154 getComposingSpanEnd(editable));
155 }
156
157 /**
158 * Updates the AdapterInputConnection's internal representation of the text being edited and
159 * its selection and composition properties. The resulting Editable is acces sible through the
160 * getEditable() method. If the text has not changed, this also calls update Selection on the
161 * InputMethodManager.
162 *
163 * @param text The String contents of the field being edited.
164 * @param selectionStart The character offset of the selection start, or the caret position if
165 * there is no selection.
166 * @param selectionEnd The character offset of the selection end, or the car et position if there
167 * is no selection.
168 * @param compositionStart The character offset of the composition start, or -1 if there is no
169 * composition.
170 * @param compositionEnd The character offset of the composition end, or -1 if there is no
171 * selection.
172 * @param isNonImeChange True when the update was caused by non-IME (e.g. Ja vascript).
173 */
174 @VisibleForTesting
175 public void updateState(String text, int selectionStart, int selectionEnd, i nt compositionStart,
176 int compositionEnd, boolean isNonImeChange) {
177 if (DEBUG_LOGS) Log.w(TAG, "updateState [%s] [%s %s] [%s %s] [%b]", text , selectionStart,
178 selectionEnd, compositionStart, compositionEnd, isNonImeChange);
179 // If this update is from the IME, no further state modification is nece ssary because the
180 // state should have been updated already by the IM framework directly.
181 if (!isNonImeChange) return;
182
183 // Non-breaking spaces can cause the IME to get confused. Replace with n ormal spaces.
184 text = text.replace('\u00A0', ' ');
185
186 selectionStart = Math.min(selectionStart, text.length());
187 selectionEnd = Math.min(selectionEnd, text.length());
188 compositionStart = Math.min(compositionStart, text.length());
189 compositionEnd = Math.min(compositionEnd, text.length());
190
191 Editable editable = getEditableInternal();
192
193 String prevText = editable.toString();
194 boolean textUnchanged = prevText.equals(text);
195
196 if (!textUnchanged) {
197 editable.replace(0, editable.length(), text);
198 }
199
200 Selection.setSelection(editable, selectionStart, selectionEnd);
201
202 if (compositionStart == compositionEnd) {
203 removeComposingSpans(editable);
204 } else {
205 super.setComposingRegion(compositionStart, compositionEnd);
206 }
207 updateSelectionIfRequired();
208 }
209
210 /**
211 * @see BaseInputConnection#getEditable()
212 */
213 @Override
214 public Editable getEditable() {
215 Editable editable = getEditableInternal();
216 if (DEBUG_LOGS) Log.w(TAG, "getEditable: %s", dumpEditable(editable));
217 return editable;
218 }
219
220 private Editable getEditableInternal() {
221 return mImeAdapter.getEditable();
222 }
223
224 /**
225 * Sends selection update to the InputMethodManager unless we are currently in a batch edit or
226 * if the exact same selection and composition update was sent already.
227 */
228 private void updateSelectionIfRequired() {
229 if (mNumNestedBatchEdits != 0) return;
230 Editable editable = getEditableInternal();
231 int selectionStart = Selection.getSelectionStart(editable);
232 int selectionEnd = Selection.getSelectionEnd(editable);
233 int compositionStart = getComposingSpanStart(editable);
234 int compositionEnd = getComposingSpanEnd(editable);
235 // Avoid sending update if we sent an exact update already previously.
236 if (mLastUpdateSelectionStart == selectionStart
237 && mLastUpdateSelectionEnd == selectionEnd
238 && mLastUpdateCompositionStart == compositionStart
239 && mLastUpdateCompositionEnd == compositionEnd) {
240 return;
241 }
242 if (DEBUG_LOGS) Log.w(TAG, "updateSelectionIfRequired [%d %d] [%d %d]", selectionStart,
243 selectionEnd, compositionStart, compositionEnd);
244 // updateSelection should be called every time the selection or composit ion changes
245 // if it happens not within a batch edit, or at the end of each top leve l batch edit.
246 mImeAdapter.updateSelection(selectionStart, selectionEnd, compositionSta rt, compositionEnd);
247 mLastUpdateSelectionStart = selectionStart;
248 mLastUpdateSelectionEnd = selectionEnd;
249 mLastUpdateCompositionStart = compositionStart;
250 mLastUpdateCompositionEnd = compositionEnd;
251 }
252
253 /**
254 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int)
255 */
256 @Override
257 public boolean setComposingText(CharSequence text, int newCursorPosition) {
258 if (DEBUG_LOGS) Log.w(TAG, "setComposingText [%s] [%d]", text, newCursor Position);
259 mPendingAccent = 0;
260 super.setComposingText(text, newCursorPosition);
261 updateSelectionIfRequired();
262 return mImeAdapter.sendCompositionToNative(text, newCursorPosition, fals e);
263 }
264
265 /**
266 * @see BaseInputConnection#commitText(java.lang.CharSequence, int)
267 */
268 @Override
269 public boolean commitText(CharSequence text, int newCursorPosition) {
270 if (DEBUG_LOGS) Log.w(TAG, "commitText [%s] [%d]", text, newCursorPositi on);
271 mPendingAccent = 0;
272 super.commitText(text, newCursorPosition);
273 updateSelectionIfRequired();
274 return mImeAdapter.sendCompositionToNative(text, newCursorPosition, text .length() > 0);
275 }
276
277 /**
278 * @see BaseInputConnection#performEditorAction(int)
279 */
280 @Override
281 public boolean performEditorAction(int actionCode) {
282 if (DEBUG_LOGS) Log.w(TAG, "performEditorAction [%d]", actionCode);
283 return mImeAdapter.performEditorAction(actionCode);
284 }
285
286 /**
287 * @see BaseInputConnection#performContextMenuAction(int)
288 */
289 @Override
290 public boolean performContextMenuAction(int id) {
291 if (DEBUG_LOGS) Log.w(TAG, "performContextMenuAction [%d]", id);
292 return mImeAdapter.performContextMenuAction(id);
293 }
294
295 /**
296 * @see BaseInputConnection#getExtractedText(android.view.inputmethod.Extrac tedTextRequest,
297 * int)
298 */
299 @Override
300 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) {
301 if (DEBUG_LOGS) Log.w(TAG, "getExtractedText");
302 Editable editable = getEditableInternal();
303 ExtractedText et = new ExtractedText();
304 et.text = editable.toString();
305 et.partialEndOffset = editable.length();
306 et.selectionStart = Selection.getSelectionStart(editable);
307 et.selectionEnd = Selection.getSelectionEnd(editable);
308 et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0;
309 return et;
310 }
311
312 /**
313 * @see BaseInputConnection#beginBatchEdit()
314 */
315 @Override
316 public boolean beginBatchEdit() {
317 if (DEBUG_LOGS) Log.w(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0));
318 mNumNestedBatchEdits++;
319 return true;
320 }
321
322 /**
323 * @see BaseInputConnection#endBatchEdit()
324 */
325 @Override
326 public boolean endBatchEdit() {
327 if (mNumNestedBatchEdits == 0) return false;
328 --mNumNestedBatchEdits;
329 if (DEBUG_LOGS) Log.w(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0));
330 if (mNumNestedBatchEdits == 0) updateSelectionIfRequired();
331 return mNumNestedBatchEdits != 0;
332 }
333
334 /**
335 * @see BaseInputConnection#deleteSurroundingText(int, int)
336 */
337 @Override
338 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
339 return deleteSurroundingTextImpl(beforeLength, afterLength, false);
340 }
341
342 /**
343 * Check if the given {@code index} is between UTF-16 surrogate pair.
344 * @param str The String.
345 * @param index The index
346 * @return True if the index is between UTF-16 surrogate pair, false otherwi se.
347 */
348 @VisibleForTesting
349 static boolean isIndexBetweenUtf16SurrogatePair(CharSequence str, int index) {
350 return index > 0 && index < str.length() && Character.isHighSurrogate(st r.charAt(index - 1))
351 && Character.isLowSurrogate(str.charAt(index));
352 }
353
354 private boolean deleteSurroundingTextImpl(
355 int beforeLength, int afterLength, boolean fromPhysicalKey) {
356 if (DEBUG_LOGS) {
357 Log.w(TAG, "deleteSurroundingText [%d %d %b]", beforeLength, afterLe ngth,
358 fromPhysicalKey);
359 }
360
361 if (mPendingAccent != 0) {
362 finishComposingText();
363 }
364
365 Editable editable = getEditableInternal();
366 int selectionStart = Selection.getSelectionStart(editable);
367 int selectionEnd = Selection.getSelectionEnd(editable);
368 int availableBefore = selectionStart;
369 int availableAfter = editable.length() - selectionEnd;
370 beforeLength = Math.min(beforeLength, availableBefore);
371 afterLength = Math.min(afterLength, availableAfter);
372
373 // Adjust these values even before calling super.deleteSurroundingText() to be consistent
374 // with the super class.
375 if (isIndexBetweenUtf16SurrogatePair(editable, selectionStart - beforeLe ngth)) {
376 beforeLength += 1;
377 }
378 if (isIndexBetweenUtf16SurrogatePair(editable, selectionEnd + afterLengt h)) {
379 afterLength += 1;
380 }
381
382 super.deleteSurroundingText(beforeLength, afterLength);
383 updateSelectionIfRequired();
384
385 // If this was called due to a physical key, no need to generate a key e vent here as
386 // the caller will take care of forwarding the original.
387 if (fromPhysicalKey) {
388 return true;
389 }
390
391 return mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
392 }
393
394 /**
395 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent)
396 */
397 @Override
398 public boolean sendKeyEvent(KeyEvent event) {
399 if (DEBUG_LOGS) {
400 Log.w(TAG, "sendKeyEvent [%d] [%d] [%d]", event.getAction(), event.g etKeyCode(),
401 event.getUnicodeChar());
402 }
403
404 int action = event.getAction();
405 int keycode = event.getKeyCode();
406 int unicodeChar = event.getUnicodeChar();
407
408 // If this isn't a KeyDown event, no need to update composition state; j ust pass the key
409 // event through and return. But note that some keys, such as enter, may actually be
410 // handled on ACTION_UP in Blink.
411 if (action != KeyEvent.ACTION_DOWN) {
412 mImeAdapter.sendKeyEvent(event);
413 return true;
414 }
415
416 // If this is backspace/del or if the key has a character representation ,
417 // need to update the underlying Editable (i.e. the local representation of the text
418 // being edited). Some IMEs like Jellybean stock IME and Samsung IME mi x in delete
419 // KeyPress events instead of calling deleteSurroundingText.
420 if (keycode == KeyEvent.KEYCODE_DEL) {
421 deleteSurroundingTextImpl(1, 0, true);
422 } else if (keycode == KeyEvent.KEYCODE_FORWARD_DEL) {
423 deleteSurroundingTextImpl(0, 1, true);
424 } else if (keycode == KeyEvent.KEYCODE_ENTER) {
425 // Finish text composition when pressing enter, as that may submit a form field.
426 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi xed.
427 finishComposingText();
428 } else if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
429 // Store a pending accent character and make it the current composit ion.
430 int pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_M ASK;
431 StringBuilder builder = new StringBuilder();
432 builder.appendCodePoint(pendingAccent);
433 setComposingText(builder.toString(), 1);
434 mPendingAccent = pendingAccent;
435 return true;
436 } else if (mPendingAccent != 0 && unicodeChar != 0) {
437 int combined = KeyEvent.getDeadChar(mPendingAccent, unicodeChar);
438 if (combined != 0) {
439 StringBuilder builder = new StringBuilder();
440 builder.appendCodePoint(combined);
441 commitText(builder.toString(), 1);
442 return true;
443 }
444 // Noncombinable character; commit the accent character and fall thr ough to sending
445 // the key event for the character afterwards.
446 finishComposingText();
447 }
448 replaceSelectionWithUnicodeChar(unicodeChar);
449 mImeAdapter.sendKeyEvent(event);
450 return true;
451 }
452
453 /**
454 * Update the Editable to reflect what Blink will do in response to the KeyD own for a
455 * unicode-mapped key event.
456 * @param unicodeChar The Unicode character to update selection with.
457 */
458 private void replaceSelectionWithUnicodeChar(int unicodeChar) {
459 if (unicodeChar == 0) return;
460 Editable editable = getEditableInternal();
461 int selectionStart = Selection.getSelectionStart(editable);
462 int selectionEnd = Selection.getSelectionEnd(editable);
463 if (selectionStart > selectionEnd) {
464 int temp = selectionStart;
465 selectionStart = selectionEnd;
466 selectionEnd = temp;
467 }
468 editable.replace(selectionStart, selectionEnd, Character.toString((char) unicodeChar));
469 updateSelectionIfRequired();
470 }
471
472 /**
473 * @see BaseInputConnection#finishComposingText()
474 */
475 @Override
476 public boolean finishComposingText() {
477 if (DEBUG_LOGS) Log.w(TAG, "finishComposingText");
478 mPendingAccent = 0;
479
480 if (getComposingSpanStart(getEditableInternal())
481 == getComposingSpanEnd(getEditableInternal())) {
482 return true;
483 }
484
485 super.finishComposingText();
486 updateSelectionIfRequired();
487 mImeAdapter.finishComposingText();
488
489 return true;
490 }
491
492 /**
493 * @see BaseInputConnection#setSelection(int, int)
494 */
495 @Override
496 public boolean setSelection(int start, int end) {
497 if (DEBUG_LOGS) Log.w(TAG, "setSelection [%d %d]", start, end);
498 int textLength = getEditableInternal().length();
499 if (start < 0 || end < 0 || start > textLength || end > textLength) retu rn true;
500 super.setSelection(start, end);
501 updateSelectionIfRequired();
502 return mImeAdapter.setEditableSelectionOffsets(start, end);
503 }
504
505 /**
506 * Call this when restartInput() is called.
507 */
508 void onRestartInput() {
509 if (DEBUG_LOGS) Log.w(TAG, "onRestartInput");
510 mNumNestedBatchEdits = 0;
511 mPendingAccent = 0;
512 }
513
514 /**
515 * @see BaseInputConnection#setComposingRegion(int, int)
516 */
517 @Override
518 public boolean setComposingRegion(int start, int end) {
519 if (DEBUG_LOGS) Log.w(TAG, "setComposingRegion [%d %d]", start, end);
520 Editable editable = getEditableInternal();
521 int textLength = editable.length();
522 int a = Math.min(start, end);
523 int b = Math.max(start, end);
524 if (a < 0) a = 0;
525 if (b < 0) b = 0;
526 if (a > textLength) a = textLength;
527 if (b > textLength) b = textLength;
528
529 if (a == b) {
530 removeComposingSpans(editable);
531 } else {
532 super.setComposingRegion(a, b);
533 }
534 updateSelectionIfRequired();
535
536 CharSequence regionText = null;
537 if (b > a) {
538 regionText = editable.subSequence(a, b);
539 }
540 return mImeAdapter.setComposingRegion(regionText, a, b);
541 }
542
543 @VisibleForTesting
544 static class ImeState {
545 public final String text;
546 public final int selectionStart;
547 public final int selectionEnd;
548 public final int compositionStart;
549 public final int compositionEnd;
550
551 public ImeState(String text, int selectionStart, int selectionEnd,
552 int compositionStart, int compositionEnd) {
553 this.text = text;
554 this.selectionStart = selectionStart;
555 this.selectionEnd = selectionEnd;
556 this.compositionStart = compositionStart;
557 this.compositionEnd = compositionEnd;
558 }
559 }
560
561 @VisibleForTesting
562 ImeState getImeStateForTesting() {
563 Editable editable = getEditableInternal();
564 String text = editable.toString();
565 int selectionStart = Selection.getSelectionStart(editable);
566 int selectionEnd = Selection.getSelectionEnd(editable);
567 int compositionStart = getComposingSpanStart(editable);
568 int compositionEnd = getComposingSpanEnd(editable);
569 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd);
570 }
571 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698