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