Home | History | Annotate | Download | only in inputmethod
      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 
     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 || a < 0) 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