• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.SystemClock;
8 import android.text.Editable;
9 import android.text.InputType;
10 import android.text.Selection;
11 import android.text.TextUtils;
12 import android.util.Log;
13 import android.view.KeyEvent;
14 import android.view.View;
15 import android.view.inputmethod.BaseInputConnection;
16 import android.view.inputmethod.EditorInfo;
17 import android.view.inputmethod.ExtractedText;
18 import android.view.inputmethod.ExtractedTextRequest;
19 
20 import org.chromium.base.VisibleForTesting;
21 
22 /**
23  * InputConnection is created by ContentView.onCreateInputConnection.
24  * It then adapts android's IME to chrome's RenderWidgetHostView using the
25  * native ImeAdapterAndroid via the class ImeAdapter.
26  */
27 public class AdapterInputConnection extends BaseInputConnection {
28     private static final String TAG = "AdapterInputConnection";
29     private static final boolean DEBUG = false;
30     /**
31      * Selection value should be -1 if not known. See EditorInfo.java for details.
32      */
33     public static final int INVALID_SELECTION = -1;
34     public static final int INVALID_COMPOSITION = -1;
35 
36     private final View mInternalView;
37     private final ImeAdapter mImeAdapter;
38     private final Editable mEditable;
39 
40     private boolean mSingleLine;
41     private int mNumNestedBatchEdits = 0;
42 
43     private int mLastUpdateSelectionStart = INVALID_SELECTION;
44     private int mLastUpdateSelectionEnd = INVALID_SELECTION;
45     private int mLastUpdateCompositionStart = INVALID_COMPOSITION;
46     private int mLastUpdateCompositionEnd = INVALID_COMPOSITION;
47 
48     @VisibleForTesting
AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs)49     AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable,
50             EditorInfo outAttrs) {
51         super(view, true);
52         mInternalView = view;
53         mImeAdapter = imeAdapter;
54         mImeAdapter.setInputConnection(this);
55         mEditable = editable;
56         // The editable passed in might have been in use by a prior keyboard and could have had
57         // prior composition spans set.  To avoid keyboard conflicts, remove all composing spans
58         // when taking ownership of an existing Editable.
59         removeComposingSpans(mEditable);
60         mSingleLine = true;
61         outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
62                 | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
63         outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
64                 | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
65 
66         int inputType = imeAdapter.getTextInputType();
67         int inputFlags = imeAdapter.getTextInputFlags();
68         if ((inputFlags & imeAdapter.sTextInputFlagAutocompleteOff) != 0) {
69             outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
70         }
71 
72         if (inputType == ImeAdapter.sTextInputTypeText) {
73             // Normal text field
74             outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
75             if ((inputFlags & imeAdapter.sTextInputFlagAutocorrectOff) == 0) {
76                 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
77             }
78         } else if (inputType == ImeAdapter.sTextInputTypeTextArea ||
79                 inputType == ImeAdapter.sTextInputTypeContentEditable) {
80             // TextArea or contenteditable.
81             outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
82                     | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
83             if ((inputFlags & imeAdapter.sTextInputFlagAutocorrectOff) == 0) {
84                 outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
85             }
86             outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE;
87             mSingleLine = false;
88         } else if (inputType == ImeAdapter.sTextInputTypePassword) {
89             // Password
90             outAttrs.inputType = InputType.TYPE_CLASS_TEXT
91                     | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
92             outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
93         } else if (inputType == ImeAdapter.sTextInputTypeSearch) {
94             // Search
95             outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH;
96         } else if (inputType == ImeAdapter.sTextInputTypeUrl) {
97             // Url
98             outAttrs.inputType = InputType.TYPE_CLASS_TEXT
99                     | InputType.TYPE_TEXT_VARIATION_URI;
100             outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
101         } else if (inputType == ImeAdapter.sTextInputTypeEmail) {
102             // Email
103             outAttrs.inputType = InputType.TYPE_CLASS_TEXT
104                     | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
105             outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
106         } else if (inputType == ImeAdapter.sTextInputTypeTel) {
107             // Telephone
108             // Number and telephone do not have both a Tab key and an
109             // action in default OSK, so set the action to NEXT
110             outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
111             outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
112         } else if (inputType == ImeAdapter.sTextInputTypeNumber) {
113             // Number
114             outAttrs.inputType = InputType.TYPE_CLASS_NUMBER
115                     | InputType.TYPE_NUMBER_VARIATION_NORMAL
116                     | InputType.TYPE_NUMBER_FLAG_DECIMAL;
117             outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
118         }
119         outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
120         outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
121         mLastUpdateSelectionStart = Selection.getSelectionStart(mEditable);
122         mLastUpdateSelectionEnd = Selection.getSelectionEnd(mEditable);
123 
124         Selection.setSelection(mEditable, outAttrs.initialSelStart, outAttrs.initialSelEnd);
125         updateSelectionIfRequired();
126     }
127 
128     /**
129      * Updates the AdapterInputConnection's internal representation of the text being edited and
130      * its selection and composition properties. The resulting Editable is accessible through the
131      * getEditable() method. If the text has not changed, this also calls updateSelection on the
132      * InputMethodManager.
133      *
134      * @param text The String contents of the field being edited.
135      * @param selectionStart The character offset of the selection start, or the caret position if
136      *                       there is no selection.
137      * @param selectionEnd The character offset of the selection end, or the caret position if there
138      *                     is no selection.
139      * @param compositionStart The character offset of the composition start, or -1 if there is no
140      *                         composition.
141      * @param compositionEnd The character offset of the composition end, or -1 if there is no
142      *                       selection.
143      * @param isNonImeChange True when the update was caused by non-IME (e.g. Javascript).
144      */
145     @VisibleForTesting
updateState(String text, int selectionStart, int selectionEnd, int compositionStart, int compositionEnd, boolean isNonImeChange)146     public void updateState(String text, int selectionStart, int selectionEnd, int compositionStart,
147             int compositionEnd, boolean isNonImeChange) {
148         if (DEBUG) {
149             Log.w(TAG, "updateState [" + text + "] [" + selectionStart + " " + selectionEnd + "] ["
150                     + compositionStart + " " + compositionEnd + "] [" + isNonImeChange + "]");
151         }
152         // If this update is from the IME, no further state modification is necessary because the
153         // state should have been updated already by the IM framework directly.
154         if (!isNonImeChange) return;
155 
156         // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces.
157         text = text.replace('\u00A0', ' ');
158 
159         selectionStart = Math.min(selectionStart, text.length());
160         selectionEnd = Math.min(selectionEnd, text.length());
161         compositionStart = Math.min(compositionStart, text.length());
162         compositionEnd = Math.min(compositionEnd, text.length());
163 
164         String prevText = mEditable.toString();
165         boolean textUnchanged = prevText.equals(text);
166 
167         if (!textUnchanged) {
168             mEditable.replace(0, mEditable.length(), text);
169         }
170 
171         Selection.setSelection(mEditable, selectionStart, selectionEnd);
172 
173         if (compositionStart == compositionEnd) {
174             removeComposingSpans(mEditable);
175         } else {
176             super.setComposingRegion(compositionStart, compositionEnd);
177         }
178         updateSelectionIfRequired();
179     }
180 
181     /**
182      * @return Editable object which contains the state of current focused editable element.
183      */
184     @Override
getEditable()185     public Editable getEditable() {
186         return mEditable;
187     }
188 
189     /**
190      * Sends selection update to the InputMethodManager unless we are currently in a batch edit or
191      * if the exact same selection and composition update was sent already.
192      */
updateSelectionIfRequired()193     private void updateSelectionIfRequired() {
194         if (mNumNestedBatchEdits != 0) return;
195         int selectionStart = Selection.getSelectionStart(mEditable);
196         int selectionEnd = Selection.getSelectionEnd(mEditable);
197         int compositionStart = getComposingSpanStart(mEditable);
198         int compositionEnd = getComposingSpanEnd(mEditable);
199         // Avoid sending update if we sent an exact update already previously.
200         if (mLastUpdateSelectionStart == selectionStart &&
201                 mLastUpdateSelectionEnd == selectionEnd &&
202                 mLastUpdateCompositionStart == compositionStart &&
203                 mLastUpdateCompositionEnd == compositionEnd) {
204             return;
205         }
206         if (DEBUG) {
207             Log.w(TAG, "updateSelectionIfRequired [" + selectionStart + " " + selectionEnd + "] ["
208                     + compositionStart + " " + compositionEnd + "]");
209         }
210         // updateSelection should be called every time the selection or composition changes
211         // if it happens not within a batch edit, or at the end of each top level batch edit.
212         getInputMethodManagerWrapper().updateSelection(mInternalView,
213                 selectionStart, selectionEnd, compositionStart, compositionEnd);
214         mLastUpdateSelectionStart = selectionStart;
215         mLastUpdateSelectionEnd = selectionEnd;
216         mLastUpdateCompositionStart = compositionStart;
217         mLastUpdateCompositionEnd = compositionEnd;
218     }
219 
220     /**
221      * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int)
222      */
223     @Override
setComposingText(CharSequence text, int newCursorPosition)224     public boolean setComposingText(CharSequence text, int newCursorPosition) {
225         if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]");
226         if (maybePerformEmptyCompositionWorkaround(text)) return true;
227         super.setComposingText(text, newCursorPosition);
228         updateSelectionIfRequired();
229         return mImeAdapter.checkCompositionQueueAndCallNative(text, newCursorPosition, false);
230     }
231 
232     /**
233      * @see BaseInputConnection#commitText(java.lang.CharSequence, int)
234      */
235     @Override
commitText(CharSequence text, int newCursorPosition)236     public boolean commitText(CharSequence text, int newCursorPosition) {
237         if (DEBUG) Log.w(TAG, "commitText [" + text + "] [" + newCursorPosition + "]");
238         if (maybePerformEmptyCompositionWorkaround(text)) return true;
239         super.commitText(text, newCursorPosition);
240         updateSelectionIfRequired();
241         return mImeAdapter.checkCompositionQueueAndCallNative(text, newCursorPosition,
242                 text.length() > 0);
243     }
244 
245     /**
246      * @see BaseInputConnection#performEditorAction(int)
247      */
248     @Override
performEditorAction(int actionCode)249     public boolean performEditorAction(int actionCode) {
250         if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]");
251         if (actionCode == EditorInfo.IME_ACTION_NEXT) {
252             restartInput();
253             // Send TAB key event
254             long timeStampMs = SystemClock.uptimeMillis();
255             mImeAdapter.sendSyntheticKeyEvent(
256                     ImeAdapter.sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0, 0);
257         } else {
258             mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER,
259                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
260                     | KeyEvent.FLAG_EDITOR_ACTION);
261         }
262         return true;
263     }
264 
265     /**
266      * @see BaseInputConnection#performContextMenuAction(int)
267      */
268     @Override
performContextMenuAction(int id)269     public boolean performContextMenuAction(int id) {
270         if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]");
271         switch (id) {
272             case android.R.id.selectAll:
273                 return mImeAdapter.selectAll();
274             case android.R.id.cut:
275                 return mImeAdapter.cut();
276             case android.R.id.copy:
277                 return mImeAdapter.copy();
278             case android.R.id.paste:
279                 return mImeAdapter.paste();
280             default:
281                 return false;
282         }
283     }
284 
285     /**
286      * @see BaseInputConnection#getExtractedText(android.view.inputmethod.ExtractedTextRequest,
287      *                                           int)
288      */
289     @Override
getExtractedText(ExtractedTextRequest request, int flags)290     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
291         if (DEBUG) Log.w(TAG, "getExtractedText");
292         ExtractedText et = new ExtractedText();
293         et.text = mEditable.toString();
294         et.partialEndOffset = mEditable.length();
295         et.selectionStart = Selection.getSelectionStart(mEditable);
296         et.selectionEnd = Selection.getSelectionEnd(mEditable);
297         et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0;
298         return et;
299     }
300 
301     /**
302      * @see BaseInputConnection#beginBatchEdit()
303      */
304     @Override
beginBatchEdit()305     public boolean beginBatchEdit() {
306         if (DEBUG) Log.w(TAG, "beginBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
307         mNumNestedBatchEdits++;
308         return true;
309     }
310 
311     /**
312      * @see BaseInputConnection#endBatchEdit()
313      */
314     @Override
endBatchEdit()315     public boolean endBatchEdit() {
316         if (mNumNestedBatchEdits == 0) return false;
317         --mNumNestedBatchEdits;
318         if (DEBUG) Log.w(TAG, "endBatchEdit [" + (mNumNestedBatchEdits == 0) + "]");
319         if (mNumNestedBatchEdits == 0) updateSelectionIfRequired();
320         return mNumNestedBatchEdits != 0;
321     }
322 
323     /**
324      * @see BaseInputConnection#deleteSurroundingText(int, int)
325      */
326     @Override
deleteSurroundingText(int beforeLength, int afterLength)327     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
328         if (DEBUG) {
329             Log.w(TAG, "deleteSurroundingText [" + beforeLength + " " + afterLength + "]");
330         }
331         int originalBeforeLength = beforeLength;
332         int originalAfterLength = afterLength;
333         int availableBefore = Selection.getSelectionStart(mEditable);
334         int availableAfter = mEditable.length() - Selection.getSelectionEnd(mEditable);
335         beforeLength = Math.min(beforeLength, availableBefore);
336         afterLength = Math.min(afterLength, availableAfter);
337         super.deleteSurroundingText(beforeLength, afterLength);
338         updateSelectionIfRequired();
339 
340         // For single-char deletion calls |ImeAdapter.sendKeyEventWithKeyCode| with the real key
341         // code. For multi-character deletion, executes deletion by calling
342         // |ImeAdapter.deleteSurroundingText| and sends synthetic key events with a dummy key code.
343         int keyCode = KeyEvent.KEYCODE_UNKNOWN;
344         if (originalBeforeLength == 1 && originalAfterLength == 0)
345             keyCode = KeyEvent.KEYCODE_DEL;
346         else if (originalBeforeLength == 0 && originalAfterLength == 1)
347             keyCode = KeyEvent.KEYCODE_FORWARD_DEL;
348 
349         boolean result = true;
350         if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
351             result = mImeAdapter.sendSyntheticKeyEvent(
352                     ImeAdapter.sEventTypeRawKeyDown, SystemClock.uptimeMillis(), keyCode, 0, 0);
353             result &= mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
354             result &= mImeAdapter.sendSyntheticKeyEvent(
355                     ImeAdapter.sEventTypeKeyUp, SystemClock.uptimeMillis(), keyCode, 0, 0);
356         } else {
357             mImeAdapter.sendKeyEventWithKeyCode(
358                     keyCode, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
359         }
360         return result;
361     }
362 
363     /**
364      * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent)
365      */
366     @Override
sendKeyEvent(KeyEvent event)367     public boolean sendKeyEvent(KeyEvent event) {
368         if (DEBUG) {
369             Log.w(TAG, "sendKeyEvent [" + event.getAction() + "] [" + event.getKeyCode() + "]");
370         }
371         // If this is a key-up, and backspace/del or if the key has a character representation,
372         // need to update the underlying Editable (i.e. the local representation of the text
373         // being edited).
374         if (event.getAction() == KeyEvent.ACTION_UP) {
375             if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
376                 deleteSurroundingText(1, 0);
377                 return true;
378             } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
379                 deleteSurroundingText(0, 1);
380                 return true;
381             } else {
382                 int unicodeChar = event.getUnicodeChar();
383                 if (unicodeChar != 0) {
384                     int selectionStart = Selection.getSelectionStart(mEditable);
385                     int selectionEnd = Selection.getSelectionEnd(mEditable);
386                     if (selectionStart > selectionEnd) {
387                         int temp = selectionStart;
388                         selectionStart = selectionEnd;
389                         selectionEnd = temp;
390                     }
391                     mEditable.replace(selectionStart, selectionEnd,
392                             Character.toString((char) unicodeChar));
393                 }
394             }
395         } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
396             // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
397             if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
398                 beginBatchEdit();
399                 finishComposingText();
400                 mImeAdapter.translateAndSendNativeEvents(event);
401                 endBatchEdit();
402                 return true;
403             } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
404                 return true;
405             } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
406                 return true;
407             }
408         }
409         mImeAdapter.translateAndSendNativeEvents(event);
410         return true;
411     }
412 
413     /**
414      * @see BaseInputConnection#finishComposingText()
415      */
416     @Override
finishComposingText()417     public boolean finishComposingText() {
418         if (DEBUG) Log.w(TAG, "finishComposingText");
419         if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) {
420             return true;
421         }
422 
423         super.finishComposingText();
424         updateSelectionIfRequired();
425         mImeAdapter.finishComposingText();
426 
427         return true;
428     }
429 
430     /**
431      * @see BaseInputConnection#setSelection(int, int)
432      */
433     @Override
setSelection(int start, int end)434     public boolean setSelection(int start, int end) {
435         if (DEBUG) Log.w(TAG, "setSelection [" + start + " " + end + "]");
436         int textLength = mEditable.length();
437         if (start < 0 || end < 0 || start > textLength || end > textLength) return true;
438         super.setSelection(start, end);
439         updateSelectionIfRequired();
440         return mImeAdapter.setEditableSelectionOffsets(start, end);
441     }
442 
443     /**
444      * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text
445      * state is no longer what the IME has and that it needs to be updated.
446      */
restartInput()447     void restartInput() {
448         if (DEBUG) Log.w(TAG, "restartInput");
449         getInputMethodManagerWrapper().restartInput(mInternalView);
450         mNumNestedBatchEdits = 0;
451     }
452 
453     /**
454      * @see BaseInputConnection#setComposingRegion(int, int)
455      */
456     @Override
setComposingRegion(int start, int end)457     public boolean setComposingRegion(int start, int end) {
458         if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]");
459         int textLength = mEditable.length();
460         int a = Math.min(start, end);
461         int b = Math.max(start, end);
462         if (a < 0) a = 0;
463         if (b < 0) b = 0;
464         if (a > textLength) a = textLength;
465         if (b > textLength) b = textLength;
466 
467         if (a == b) {
468             removeComposingSpans(mEditable);
469         } else {
470             super.setComposingRegion(a, b);
471         }
472         updateSelectionIfRequired();
473 
474         CharSequence regionText = null;
475         if (b > a) {
476             regionText = mEditable.subSequence(a, b);
477         }
478         return mImeAdapter.setComposingRegion(regionText, a, b);
479     }
480 
isActive()481     boolean isActive() {
482         return getInputMethodManagerWrapper().isActive(mInternalView);
483     }
484 
getInputMethodManagerWrapper()485     private InputMethodManagerWrapper getInputMethodManagerWrapper() {
486         return mImeAdapter.getInputMethodManagerWrapper();
487     }
488 
489     /**
490      * This method works around the issue crbug.com/373934 where Blink does not cancel
491      * the composition when we send a commit with the empty text.
492      *
493      * TODO(aurimas) Remove this once crbug.com/373934 is fixed.
494      *
495      * @param text Text that software keyboard requested to commit.
496      * @return Whether the workaround was performed.
497      */
maybePerformEmptyCompositionWorkaround(CharSequence text)498     private boolean maybePerformEmptyCompositionWorkaround(CharSequence text) {
499         int selectionStart = Selection.getSelectionStart(mEditable);
500         int selectionEnd = Selection.getSelectionEnd(mEditable);
501         int compositionStart = getComposingSpanStart(mEditable);
502         int compositionEnd = getComposingSpanEnd(mEditable);
503         if (TextUtils.isEmpty(text) && (selectionStart == selectionEnd)
504                 && compositionStart != INVALID_COMPOSITION
505                 && compositionEnd != INVALID_COMPOSITION) {
506             beginBatchEdit();
507             finishComposingText();
508             int selection = Selection.getSelectionStart(mEditable);
509             deleteSurroundingText(selection - compositionStart, selection - compositionEnd);
510             endBatchEdit();
511             return true;
512         }
513         return false;
514     }
515 
516     @VisibleForTesting
517     static class ImeState {
518         public final String text;
519         public final int selectionStart;
520         public final int selectionEnd;
521         public final int compositionStart;
522         public final int compositionEnd;
523 
ImeState(String text, int selectionStart, int selectionEnd, int compositionStart, int compositionEnd)524         public ImeState(String text, int selectionStart, int selectionEnd,
525                 int compositionStart, int compositionEnd) {
526             this.text = text;
527             this.selectionStart = selectionStart;
528             this.selectionEnd = selectionEnd;
529             this.compositionStart = compositionStart;
530             this.compositionEnd = compositionEnd;
531         }
532     }
533 
534     @VisibleForTesting
getImeStateForTesting()535     ImeState getImeStateForTesting() {
536         String text = mEditable.toString();
537         int selectionStart = Selection.getSelectionStart(mEditable);
538         int selectionEnd = Selection.getSelectionEnd(mEditable);
539         int compositionStart = getComposingSpanStart(mEditable);
540         int compositionEnd = getComposingSpanEnd(mEditable);
541         return new ImeState(text, selectionStart, selectionEnd, compositionStart, compositionEnd);
542     }
543 }
544