• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.view.inputmethod;
18 
19 import android.annotation.CallSuper;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.SystemClock;
25 import android.text.Editable;
26 import android.text.NoCopySpan;
27 import android.text.Selection;
28 import android.text.Spannable;
29 import android.text.SpannableStringBuilder;
30 import android.text.Spanned;
31 import android.text.TextUtils;
32 import android.text.method.MetaKeyKeyListener;
33 import android.util.Log;
34 import android.util.LogPrinter;
35 import android.view.KeyCharacterMap;
36 import android.view.KeyEvent;
37 import android.view.View;
38 
39 class ComposingText implements NoCopySpan {
40 }
41 
42 /**
43  * Base class for implementors of the InputConnection interface, taking care
44  * of most of the common behavior for providing a connection to an Editable.
45  * Implementors of this class will want to be sure to implement
46  * {@link #getEditable} to provide access to their own editable object, and
47  * to refer to the documentation in {@link InputConnection}.
48  */
49 public class BaseInputConnection implements InputConnection {
50     private static final boolean DEBUG = false;
51     private static final String TAG = "BaseInputConnection";
52     static final Object COMPOSING = new ComposingText();
53 
54     /** @hide */
55     protected final InputMethodManager mIMM;
56     final View mTargetView;
57     final boolean mDummyMode;
58 
59     private Object[] mDefaultComposingSpans;
60 
61     Editable mEditable;
62     KeyCharacterMap mKeyCharacterMap;
63 
BaseInputConnection(InputMethodManager mgr, boolean fullEditor)64     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
65         mIMM = mgr;
66         mTargetView = null;
67         mDummyMode = !fullEditor;
68     }
69 
BaseInputConnection(View targetView, boolean fullEditor)70     public BaseInputConnection(View targetView, boolean fullEditor) {
71         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
72                 Context.INPUT_METHOD_SERVICE);
73         mTargetView = targetView;
74         mDummyMode = !fullEditor;
75     }
76 
removeComposingSpans(Spannable text)77     public static final void removeComposingSpans(Spannable text) {
78         text.removeSpan(COMPOSING);
79         Object[] sps = text.getSpans(0, text.length(), Object.class);
80         if (sps != null) {
81             for (int i=sps.length-1; i>=0; i--) {
82                 Object o = sps[i];
83                 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
84                     text.removeSpan(o);
85                 }
86             }
87         }
88     }
89 
setComposingSpans(Spannable text)90     public static void setComposingSpans(Spannable text) {
91         setComposingSpans(text, 0, text.length());
92     }
93 
94     /** @hide */
setComposingSpans(Spannable text, int start, int end)95     public static void setComposingSpans(Spannable text, int start, int end) {
96         final Object[] sps = text.getSpans(start, end, Object.class);
97         if (sps != null) {
98             for (int i=sps.length-1; i>=0; i--) {
99                 final Object o = sps[i];
100                 if (o == COMPOSING) {
101                     text.removeSpan(o);
102                     continue;
103                 }
104 
105                 final int fl = text.getSpanFlags(o);
106                 if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
107                         != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
108                     text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
109                             (fl & ~Spanned.SPAN_POINT_MARK_MASK)
110                                     | Spanned.SPAN_COMPOSING
111                                     | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
112                 }
113             }
114         }
115 
116         text.setSpan(COMPOSING, start, end,
117                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
118     }
119 
getComposingSpanStart(Spannable text)120     public static int getComposingSpanStart(Spannable text) {
121         return text.getSpanStart(COMPOSING);
122     }
123 
getComposingSpanEnd(Spannable text)124     public static int getComposingSpanEnd(Spannable text) {
125         return text.getSpanEnd(COMPOSING);
126     }
127 
128     /**
129      * Return the target of edit operations.  The default implementation
130      * returns its own fake editable that is just used for composing text;
131      * subclasses that are real text editors should override this and
132      * supply their own.
133      */
getEditable()134     public Editable getEditable() {
135         if (mEditable == null) {
136             mEditable = Editable.Factory.getInstance().newEditable("");
137             Selection.setSelection(mEditable, 0);
138         }
139         return mEditable;
140     }
141 
142     /**
143      * Default implementation does nothing.
144      */
beginBatchEdit()145     public boolean beginBatchEdit() {
146         return false;
147     }
148 
149     /**
150      * Default implementation does nothing.
151      */
endBatchEdit()152     public boolean endBatchEdit() {
153         return false;
154     }
155 
156     /**
157      * Default implementation calls {@link #finishComposingText()}.
158      */
159     @CallSuper
closeConnection()160     public void closeConnection() {
161         finishComposingText();
162     }
163 
164     /**
165      * Default implementation uses
166      * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
167      * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
168      */
clearMetaKeyStates(int states)169     public boolean clearMetaKeyStates(int states) {
170         final Editable content = getEditable();
171         if (content == null) return false;
172         MetaKeyKeyListener.clearMetaKeyState(content, states);
173         return true;
174     }
175 
176     /**
177      * Default implementation does nothing and returns false.
178      */
commitCompletion(CompletionInfo text)179     public boolean commitCompletion(CompletionInfo text) {
180         return false;
181     }
182 
183     /**
184      * Default implementation does nothing and returns false.
185      */
commitCorrection(CorrectionInfo correctionInfo)186     public boolean commitCorrection(CorrectionInfo correctionInfo) {
187         return false;
188     }
189 
190     /**
191      * Default implementation replaces any existing composing text with
192      * the given text.  In addition, only if dummy mode, a key event is
193      * sent for the new text and the current editable buffer cleared.
194      */
commitText(CharSequence text, int newCursorPosition)195     public boolean commitText(CharSequence text, int newCursorPosition) {
196         if (DEBUG) Log.v(TAG, "commitText " + text);
197         replaceText(text, newCursorPosition, false);
198         sendCurrentText();
199         return true;
200     }
201 
202     /**
203      * The default implementation performs the deletion around the current selection position of the
204      * editable text.
205      *
206      * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
207      *        If this is greater than the number of existing characters between the beginning of the
208      *        text and the cursor, then this method does not fail but deletes all the characters in
209      *        that range.
210      * @param afterLength The number of characters after the cursor to be deleted, in code unit.
211      *        If this is greater than the number of existing characters between the cursor and
212      *        the end of the text, then this method does not fail but deletes all the characters in
213      *        that range.
214      */
deleteSurroundingText(int beforeLength, int afterLength)215     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
216         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
217                 + " / " + afterLength);
218         final Editable content = getEditable();
219         if (content == null) return false;
220 
221         beginBatchEdit();
222 
223         int a = Selection.getSelectionStart(content);
224         int b = Selection.getSelectionEnd(content);
225 
226         if (a > b) {
227             int tmp = a;
228             a = b;
229             b = tmp;
230         }
231 
232         // Ignore the composing text.
233         int ca = getComposingSpanStart(content);
234         int cb = getComposingSpanEnd(content);
235         if (cb < ca) {
236             int tmp = ca;
237             ca = cb;
238             cb = tmp;
239         }
240         if (ca != -1 && cb != -1) {
241             if (ca < a) a = ca;
242             if (cb > b) b = cb;
243         }
244 
245         int deleted = 0;
246 
247         if (beforeLength > 0) {
248             int start = a - beforeLength;
249             if (start < 0) start = 0;
250             content.delete(start, a);
251             deleted = a - start;
252         }
253 
254         if (afterLength > 0) {
255             b = b - deleted;
256 
257             int end = b + afterLength;
258             if (end > content.length()) end = content.length();
259 
260             content.delete(b, end);
261         }
262 
263         endBatchEdit();
264 
265         return true;
266     }
267 
268     private static int INVALID_INDEX = -1;
findIndexBackward(final CharSequence cs, final int from, final int numCodePoints)269     private static int findIndexBackward(final CharSequence cs, final int from,
270             final int numCodePoints) {
271         int currentIndex = from;
272         boolean waitingHighSurrogate = false;
273         final int N = cs.length();
274         if (currentIndex < 0 || N < currentIndex) {
275             return INVALID_INDEX;  // The starting point is out of range.
276         }
277         if (numCodePoints < 0) {
278             return INVALID_INDEX;  // Basically this should not happen.
279         }
280         int remainingCodePoints = numCodePoints;
281         while (true) {
282             if (remainingCodePoints == 0) {
283                 return currentIndex;  // Reached to the requested length in code points.
284             }
285 
286             --currentIndex;
287             if (currentIndex < 0) {
288                 if (waitingHighSurrogate) {
289                     return INVALID_INDEX;  // An invalid surrogate pair is found.
290                 }
291                 return 0;  // Reached to the beginning of the text w/o any invalid surrogate pair.
292             }
293             final char c = cs.charAt(currentIndex);
294             if (waitingHighSurrogate) {
295                 if (!java.lang.Character.isHighSurrogate(c)) {
296                     return INVALID_INDEX;  // An invalid surrogate pair is found.
297                 }
298                 waitingHighSurrogate = false;
299                 --remainingCodePoints;
300                 continue;
301             }
302             if (!java.lang.Character.isSurrogate(c)) {
303                 --remainingCodePoints;
304                 continue;
305             }
306             if (java.lang.Character.isHighSurrogate(c)) {
307                 return INVALID_INDEX;  // A invalid surrogate pair is found.
308             }
309             waitingHighSurrogate = true;
310         }
311     }
312 
findIndexForward(final CharSequence cs, final int from, final int numCodePoints)313     private static int findIndexForward(final CharSequence cs, final int from,
314             final int numCodePoints) {
315         int currentIndex = from;
316         boolean waitingLowSurrogate = false;
317         final int N = cs.length();
318         if (currentIndex < 0 || N < currentIndex) {
319             return INVALID_INDEX;  // The starting point is out of range.
320         }
321         if (numCodePoints < 0) {
322             return INVALID_INDEX;  // Basically this should not happen.
323         }
324         int remainingCodePoints = numCodePoints;
325 
326         while (true) {
327             if (remainingCodePoints == 0) {
328                 return currentIndex;  // Reached to the requested length in code points.
329             }
330 
331             if (currentIndex >= N) {
332                 if (waitingLowSurrogate) {
333                     return INVALID_INDEX;  // An invalid surrogate pair is found.
334                 }
335                 return N;  // Reached to the end of the text w/o any invalid surrogate pair.
336             }
337             final char c = cs.charAt(currentIndex);
338             if (waitingLowSurrogate) {
339                 if (!java.lang.Character.isLowSurrogate(c)) {
340                     return INVALID_INDEX;  // An invalid surrogate pair is found.
341                 }
342                 --remainingCodePoints;
343                 waitingLowSurrogate = false;
344                 ++currentIndex;
345                 continue;
346             }
347             if (!java.lang.Character.isSurrogate(c)) {
348                 --remainingCodePoints;
349                 ++currentIndex;
350                 continue;
351             }
352             if (java.lang.Character.isLowSurrogate(c)) {
353                 return INVALID_INDEX;  // A invalid surrogate pair is found.
354             }
355             waitingLowSurrogate = true;
356             ++currentIndex;
357         }
358     }
359 
360     /**
361      * The default implementation performs the deletion around the current selection position of the
362      * editable text.
363      * @param beforeLength The number of characters before the cursor to be deleted, in code points.
364      *        If this is greater than the number of existing characters between the beginning of the
365      *        text and the cursor, then this method does not fail but deletes all the characters in
366      *        that range.
367      * @param afterLength The number of characters after the cursor to be deleted, in code points.
368      *        If this is greater than the number of existing characters between the cursor and
369      *        the end of the text, then this method does not fail but deletes all the characters in
370      *        that range.
371      */
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)372     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
373         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
374                 + " / " + afterLength);
375         final Editable content = getEditable();
376         if (content == null) return false;
377 
378         beginBatchEdit();
379 
380         int a = Selection.getSelectionStart(content);
381         int b = Selection.getSelectionEnd(content);
382 
383         if (a > b) {
384             int tmp = a;
385             a = b;
386             b = tmp;
387         }
388 
389         // Ignore the composing text.
390         int ca = getComposingSpanStart(content);
391         int cb = getComposingSpanEnd(content);
392         if (cb < ca) {
393             int tmp = ca;
394             ca = cb;
395             cb = tmp;
396         }
397         if (ca != -1 && cb != -1) {
398             if (ca < a) a = ca;
399             if (cb > b) b = cb;
400         }
401 
402         if (a >= 0 && b >= 0) {
403             final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
404             if (start != INVALID_INDEX) {
405                 final int end = findIndexForward(content, b, Math.max(afterLength, 0));
406                 if (end != INVALID_INDEX) {
407                     final int numDeleteBefore = a - start;
408                     if (numDeleteBefore > 0) {
409                         content.delete(start, a);
410                     }
411                     final int numDeleteAfter = end - b;
412                     if (numDeleteAfter > 0) {
413                         content.delete(b - numDeleteBefore, end - numDeleteBefore);
414                     }
415                 }
416             }
417             // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
418             // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
419             // always returns true to the IME without waiting for the completion of this method as
420             // IInputConnectionWrapper#isAtive() returns true.  This is actually why some methods
421             // including this method look like asynchronous calls from the IME.
422         }
423 
424         endBatchEdit();
425 
426         return true;
427     }
428 
429     /**
430      * The default implementation removes the composing state from the
431      * current editable text.  In addition, only if dummy mode, a key event is
432      * sent for the new text and the current editable buffer cleared.
433      */
finishComposingText()434     public boolean finishComposingText() {
435         if (DEBUG) Log.v(TAG, "finishComposingText");
436         final Editable content = getEditable();
437         if (content != null) {
438             beginBatchEdit();
439             removeComposingSpans(content);
440             // Note: sendCurrentText does nothing unless mDummyMode is set
441             sendCurrentText();
442             endBatchEdit();
443         }
444         return true;
445     }
446 
447     /**
448      * The default implementation uses TextUtils.getCapsMode to get the
449      * cursor caps mode for the current selection position in the editable
450      * text, unless in dummy mode in which case 0 is always returned.
451      */
getCursorCapsMode(int reqModes)452     public int getCursorCapsMode(int reqModes) {
453         if (mDummyMode) return 0;
454 
455         final Editable content = getEditable();
456         if (content == null) return 0;
457 
458         int a = Selection.getSelectionStart(content);
459         int b = Selection.getSelectionEnd(content);
460 
461         if (a > b) {
462             int tmp = a;
463             a = b;
464             b = tmp;
465         }
466 
467         return TextUtils.getCapsMode(content, a, reqModes);
468     }
469 
470     /**
471      * The default implementation always returns null.
472      */
getExtractedText(ExtractedTextRequest request, int flags)473     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
474         return null;
475     }
476 
477     /**
478      * The default implementation returns the given amount of text from the
479      * current cursor position in the buffer.
480      */
getTextBeforeCursor(int length, int flags)481     public CharSequence getTextBeforeCursor(int length, int flags) {
482         final Editable content = getEditable();
483         if (content == null) return null;
484 
485         int a = Selection.getSelectionStart(content);
486         int b = Selection.getSelectionEnd(content);
487 
488         if (a > b) {
489             int tmp = a;
490             a = b;
491             b = tmp;
492         }
493 
494         if (a <= 0) {
495             return "";
496         }
497 
498         if (length > a) {
499             length = a;
500         }
501 
502         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
503             return content.subSequence(a - length, a);
504         }
505         return TextUtils.substring(content, a - length, a);
506     }
507 
508     /**
509      * The default implementation returns the text currently selected, or null if none is
510      * selected.
511      */
getSelectedText(int flags)512     public CharSequence getSelectedText(int flags) {
513         final Editable content = getEditable();
514         if (content == null) return null;
515 
516         int a = Selection.getSelectionStart(content);
517         int b = Selection.getSelectionEnd(content);
518 
519         if (a > b) {
520             int tmp = a;
521             a = b;
522             b = tmp;
523         }
524 
525         if (a == b) return null;
526 
527         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
528             return content.subSequence(a, b);
529         }
530         return TextUtils.substring(content, a, b);
531     }
532 
533     /**
534      * The default implementation returns the given amount of text from the
535      * current cursor position in the buffer.
536      */
getTextAfterCursor(int length, int flags)537     public CharSequence getTextAfterCursor(int length, int flags) {
538         final Editable content = getEditable();
539         if (content == null) return null;
540 
541         int a = Selection.getSelectionStart(content);
542         int b = Selection.getSelectionEnd(content);
543 
544         if (a > b) {
545             int tmp = a;
546             a = b;
547             b = tmp;
548         }
549 
550         // Guard against the case where the cursor has not been positioned yet.
551         if (b < 0) {
552             b = 0;
553         }
554 
555         if (b + length > content.length()) {
556             length = content.length() - b;
557         }
558 
559 
560         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
561             return content.subSequence(b, b + length);
562         }
563         return TextUtils.substring(content, b, b + length);
564     }
565 
566     /**
567      * The default implementation turns this into the enter key.
568      */
performEditorAction(int actionCode)569     public boolean performEditorAction(int actionCode) {
570         long eventTime = SystemClock.uptimeMillis();
571         sendKeyEvent(new KeyEvent(eventTime, eventTime,
572                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
573                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
574                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
575                 | KeyEvent.FLAG_EDITOR_ACTION));
576         sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
577                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
578                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
579                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
580                 | KeyEvent.FLAG_EDITOR_ACTION));
581         return true;
582     }
583 
584     /**
585      * The default implementation does nothing.
586      */
performContextMenuAction(int id)587     public boolean performContextMenuAction(int id) {
588         return false;
589     }
590 
591     /**
592      * The default implementation does nothing.
593      */
performPrivateCommand(String action, Bundle data)594     public boolean performPrivateCommand(String action, Bundle data) {
595         return false;
596     }
597 
598     /**
599      * The default implementation does nothing.
600      */
requestCursorUpdates(int cursorUpdateMode)601     public boolean requestCursorUpdates(int cursorUpdateMode) {
602         return false;
603     }
604 
getHandler()605     public Handler getHandler() {
606         return null;
607     }
608 
609     /**
610      * The default implementation places the given text into the editable,
611      * replacing any existing composing text.  The new text is marked as
612      * in a composing state with the composing style.
613      */
setComposingText(CharSequence text, int newCursorPosition)614     public boolean setComposingText(CharSequence text, int newCursorPosition) {
615         if (DEBUG) Log.v(TAG, "setComposingText " + text);
616         replaceText(text, newCursorPosition, true);
617         return true;
618     }
619 
setComposingRegion(int start, int end)620     public boolean setComposingRegion(int start, int end) {
621         final Editable content = getEditable();
622         if (content != null) {
623             beginBatchEdit();
624             removeComposingSpans(content);
625             int a = start;
626             int b = end;
627             if (a > b) {
628                 int tmp = a;
629                 a = b;
630                 b = tmp;
631             }
632             // Clip the end points to be within the content bounds.
633             final int length = content.length();
634             if (a < 0) a = 0;
635             if (b < 0) b = 0;
636             if (a > length) a = length;
637             if (b > length) b = length;
638 
639             ensureDefaultComposingSpans();
640             if (mDefaultComposingSpans != null) {
641                 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
642                     content.setSpan(mDefaultComposingSpans[i], a, b,
643                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
644                 }
645             }
646 
647             content.setSpan(COMPOSING, a, b,
648                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
649 
650             // Note: sendCurrentText does nothing unless mDummyMode is set
651             sendCurrentText();
652             endBatchEdit();
653         }
654         return true;
655     }
656 
657     /**
658      * The default implementation changes the selection position in the
659      * current editable text.
660      */
setSelection(int start, int end)661     public boolean setSelection(int start, int end) {
662         if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
663         final Editable content = getEditable();
664         if (content == null) return false;
665         int len = content.length();
666         if (start > len || end > len || start < 0 || end < 0) {
667             // If the given selection is out of bounds, just ignore it.
668             // Most likely the text was changed out from under the IME,
669             // and the IME is going to have to update all of its state
670             // anyway.
671             return true;
672         }
673         if (start == end && MetaKeyKeyListener.getMetaState(content,
674                 MetaKeyKeyListener.META_SELECTING) != 0) {
675             // If we are in selection mode, then we want to extend the
676             // selection instead of replacing it.
677             Selection.extendSelection(content, start);
678         } else {
679             Selection.setSelection(content, start, end);
680         }
681         return true;
682     }
683 
684     /**
685      * Provides standard implementation for sending a key event to the window
686      * attached to the input connection's view.
687      */
sendKeyEvent(KeyEvent event)688     public boolean sendKeyEvent(KeyEvent event) {
689         mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
690         return false;
691     }
692 
693     /**
694      * Updates InputMethodManager with the current fullscreen mode.
695      */
reportFullscreenMode(boolean enabled)696     public boolean reportFullscreenMode(boolean enabled) {
697         return true;
698     }
699 
sendCurrentText()700     private void sendCurrentText() {
701         if (!mDummyMode) {
702             return;
703         }
704 
705         Editable content = getEditable();
706         if (content != null) {
707             final int N = content.length();
708             if (N == 0) {
709                 return;
710             }
711             if (N == 1) {
712                 // If it's 1 character, we have a chance of being
713                 // able to generate normal key events...
714                 if (mKeyCharacterMap == null) {
715                     mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
716                 }
717                 char[] chars = new char[1];
718                 content.getChars(0, 1, chars, 0);
719                 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
720                 if (events != null) {
721                     for (int i=0; i<events.length; i++) {
722                         if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
723                         sendKeyEvent(events[i]);
724                     }
725                     content.clear();
726                     return;
727                 }
728             }
729 
730             // Otherwise, revert to the special key event containing
731             // the actual characters.
732             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
733                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
734             sendKeyEvent(event);
735             content.clear();
736         }
737     }
738 
ensureDefaultComposingSpans()739     private void ensureDefaultComposingSpans() {
740         if (mDefaultComposingSpans == null) {
741             Context context;
742             if (mTargetView != null) {
743                 context = mTargetView.getContext();
744             } else if (mIMM.mServedView != null) {
745                 context = mIMM.mServedView.getContext();
746             } else {
747                 context = null;
748             }
749             if (context != null) {
750                 TypedArray ta = context.getTheme()
751                         .obtainStyledAttributes(new int[] {
752                                 com.android.internal.R.attr.candidatesTextStyleSpans
753                         });
754                 CharSequence style = ta.getText(0);
755                 ta.recycle();
756                 if (style != null && style instanceof Spanned) {
757                     mDefaultComposingSpans = ((Spanned)style).getSpans(
758                             0, style.length(), Object.class);
759                 }
760             }
761         }
762     }
763 
replaceText(CharSequence text, int newCursorPosition, boolean composing)764     private void replaceText(CharSequence text, int newCursorPosition,
765             boolean composing) {
766         final Editable content = getEditable();
767         if (content == null) {
768             return;
769         }
770 
771         beginBatchEdit();
772 
773         // delete composing text set previously.
774         int a = getComposingSpanStart(content);
775         int b = getComposingSpanEnd(content);
776 
777         if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
778 
779         if (b < a) {
780             int tmp = a;
781             a = b;
782             b = tmp;
783         }
784 
785         if (a != -1 && b != -1) {
786             removeComposingSpans(content);
787         } else {
788             a = Selection.getSelectionStart(content);
789             b = Selection.getSelectionEnd(content);
790             if (a < 0) a = 0;
791             if (b < 0) b = 0;
792             if (b < a) {
793                 int tmp = a;
794                 a = b;
795                 b = tmp;
796             }
797         }
798 
799         if (composing) {
800             Spannable sp = null;
801             if (!(text instanceof Spannable)) {
802                 sp = new SpannableStringBuilder(text);
803                 text = sp;
804                 ensureDefaultComposingSpans();
805                 if (mDefaultComposingSpans != null) {
806                     for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
807                         sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
808                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
809                     }
810                 }
811             } else {
812                 sp = (Spannable)text;
813             }
814             setComposingSpans(sp);
815         }
816 
817         if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
818                 + text + "\", composing=" + composing
819                 + ", type=" + text.getClass().getCanonicalName());
820 
821         if (DEBUG) {
822             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
823             lp.println("Current text:");
824             TextUtils.dumpSpans(content, lp, "  ");
825             lp.println("Composing text:");
826             TextUtils.dumpSpans(text, lp, "  ");
827         }
828 
829         // Position the cursor appropriately, so that after replacing the
830         // desired range of text it will be located in the correct spot.
831         // This allows us to deal with filters performing edits on the text
832         // we are providing here.
833         if (newCursorPosition > 0) {
834             newCursorPosition += b - 1;
835         } else {
836             newCursorPosition += a;
837         }
838         if (newCursorPosition < 0) newCursorPosition = 0;
839         if (newCursorPosition > content.length())
840             newCursorPosition = content.length();
841         Selection.setSelection(content, newCursorPosition);
842 
843         content.replace(a, b, text);
844 
845         if (DEBUG) {
846             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
847             lp.println("Final text:");
848             TextUtils.dumpSpans(content, lp, "  ");
849         }
850 
851         endBatchEdit();
852     }
853 
854     /**
855      * The default implementation does nothing.
856      */
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)857     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
858         return false;
859     }
860 }
861