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.annotation.CallSuper;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.SystemClock;
25import android.text.Editable;
26import android.text.NoCopySpan;
27import android.text.Selection;
28import android.text.Spannable;
29import android.text.SpannableStringBuilder;
30import android.text.Spanned;
31import android.text.TextUtils;
32import android.text.method.MetaKeyKeyListener;
33import android.util.Log;
34import android.util.LogPrinter;
35import android.view.KeyCharacterMap;
36import android.view.KeyEvent;
37import android.view.View;
38
39class 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 */
49public 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
64    BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
65        mIMM = mgr;
66        mTargetView = null;
67        mDummyMode = !fullEditor;
68    }
69
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
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
90    public static void setComposingSpans(Spannable text) {
91        setComposingSpans(text, 0, text.length());
92    }
93
94    /** @hide */
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
120    public static int getComposingSpanStart(Spannable text) {
121        return text.getSpanStart(COMPOSING);
122    }
123
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     */
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     */
145    public boolean beginBatchEdit() {
146        return false;
147    }
148
149    /**
150     * Default implementation does nothing.
151     */
152    public boolean endBatchEdit() {
153        return false;
154    }
155
156    /**
157     * Default implementation calls {@link #finishComposingText()}.
158     */
159    @CallSuper
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     */
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        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     */
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;
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
587    public boolean performContextMenuAction(int id) {
588        return false;
589    }
590
591    /**
592     * The default implementation does nothing.
593     */
594    public boolean performPrivateCommand(String action, Bundle data) {
595        return false;
596    }
597
598    /**
599     * The default implementation does nothing.
600     */
601    public boolean requestCursorUpdates(int cursorUpdateMode) {
602        return false;
603    }
604
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     */
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
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     */
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     */
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     */
696    public boolean reportFullscreenMode(boolean enabled) {
697        return true;
698    }
699
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
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
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     */
857    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
858        return false;
859    }
860}
861