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.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.os.Bundle;
     22 import android.os.SystemClock;
     23 import android.text.Editable;
     24 import android.text.NoCopySpan;
     25 import android.text.Selection;
     26 import android.text.Spannable;
     27 import android.text.SpannableStringBuilder;
     28 import android.text.Spanned;
     29 import android.text.TextUtils;
     30 import android.text.method.MetaKeyKeyListener;
     31 import android.util.Log;
     32 import android.util.LogPrinter;
     33 import android.view.KeyCharacterMap;
     34 import android.view.KeyEvent;
     35 import android.view.View;
     36 import android.view.ViewRootImpl;
     37 
     38 class 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  */
     48 public 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
    205      * selection position of the editable text.
    206      * @param beforeLength
    207      * @param afterLength
    208      */
    209     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
    210         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
    211                 + " / " + afterLength);
    212         final Editable content = getEditable();
    213         if (content == null) return false;
    214 
    215         beginBatchEdit();
    216 
    217         int a = Selection.getSelectionStart(content);
    218         int b = Selection.getSelectionEnd(content);
    219 
    220         if (a > b) {
    221             int tmp = a;
    222             a = b;
    223             b = tmp;
    224         }
    225 
    226         // ignore the composing text.
    227         int ca = getComposingSpanStart(content);
    228         int cb = getComposingSpanEnd(content);
    229         if (cb < ca) {
    230             int tmp = ca;
    231             ca = cb;
    232             cb = tmp;
    233         }
    234         if (ca != -1 && cb != -1) {
    235             if (ca < a) a = ca;
    236             if (cb > b) b = cb;
    237         }
    238 
    239         int deleted = 0;
    240 
    241         if (beforeLength > 0) {
    242             int start = a - beforeLength;
    243             if (start < 0) start = 0;
    244             content.delete(start, a);
    245             deleted = a - start;
    246         }
    247 
    248         if (afterLength > 0) {
    249             b = b - deleted;
    250 
    251             int end = b + afterLength;
    252             if (end > content.length()) end = content.length();
    253 
    254             content.delete(b, end);
    255         }
    256 
    257         endBatchEdit();
    258 
    259         return true;
    260     }
    261 
    262     /**
    263      * The default implementation removes the composing state from the
    264      * current editable text.  In addition, only if dummy mode, a key event is
    265      * sent for the new text and the current editable buffer cleared.
    266      */
    267     public boolean finishComposingText() {
    268         if (DEBUG) Log.v(TAG, "finishComposingText");
    269         final Editable content = getEditable();
    270         if (content != null) {
    271             beginBatchEdit();
    272             removeComposingSpans(content);
    273             // Note: sendCurrentText does nothing unless mDummyMode is set
    274             sendCurrentText();
    275             endBatchEdit();
    276         }
    277         return true;
    278     }
    279 
    280     /**
    281      * The default implementation uses TextUtils.getCapsMode to get the
    282      * cursor caps mode for the current selection position in the editable
    283      * text, unless in dummy mode in which case 0 is always returned.
    284      */
    285     public int getCursorCapsMode(int reqModes) {
    286         if (mDummyMode) return 0;
    287 
    288         final Editable content = getEditable();
    289         if (content == null) return 0;
    290 
    291         int a = Selection.getSelectionStart(content);
    292         int b = Selection.getSelectionEnd(content);
    293 
    294         if (a > b) {
    295             int tmp = a;
    296             a = b;
    297             b = tmp;
    298         }
    299 
    300         return TextUtils.getCapsMode(content, a, reqModes);
    301     }
    302 
    303     /**
    304      * The default implementation always returns null.
    305      */
    306     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
    307         return null;
    308     }
    309 
    310     /**
    311      * The default implementation returns the given amount of text from the
    312      * current cursor position in the buffer.
    313      */
    314     public CharSequence getTextBeforeCursor(int length, int flags) {
    315         final Editable content = getEditable();
    316         if (content == null) return null;
    317 
    318         int a = Selection.getSelectionStart(content);
    319         int b = Selection.getSelectionEnd(content);
    320 
    321         if (a > b) {
    322             int tmp = a;
    323             a = b;
    324             b = tmp;
    325         }
    326 
    327         if (a <= 0) {
    328             return "";
    329         }
    330 
    331         if (length > a) {
    332             length = a;
    333         }
    334 
    335         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    336             return content.subSequence(a - length, a);
    337         }
    338         return TextUtils.substring(content, a - length, a);
    339     }
    340 
    341     /**
    342      * The default implementation returns the text currently selected, or null if none is
    343      * selected.
    344      */
    345     public CharSequence getSelectedText(int flags) {
    346         final Editable content = getEditable();
    347         if (content == null) return null;
    348 
    349         int a = Selection.getSelectionStart(content);
    350         int b = Selection.getSelectionEnd(content);
    351 
    352         if (a > b) {
    353             int tmp = a;
    354             a = b;
    355             b = tmp;
    356         }
    357 
    358         if (a == b) return null;
    359 
    360         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    361             return content.subSequence(a, b);
    362         }
    363         return TextUtils.substring(content, a, b);
    364     }
    365 
    366     /**
    367      * The default implementation returns the given amount of text from the
    368      * current cursor position in the buffer.
    369      */
    370     public CharSequence getTextAfterCursor(int length, int flags) {
    371         final Editable content = getEditable();
    372         if (content == null) return null;
    373 
    374         int a = Selection.getSelectionStart(content);
    375         int b = Selection.getSelectionEnd(content);
    376 
    377         if (a > b) {
    378             int tmp = a;
    379             a = b;
    380             b = tmp;
    381         }
    382 
    383         // Guard against the case where the cursor has not been positioned yet.
    384         if (b < 0) {
    385             b = 0;
    386         }
    387 
    388         if (b + length > content.length()) {
    389             length = content.length() - b;
    390         }
    391 
    392 
    393         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    394             return content.subSequence(b, b + length);
    395         }
    396         return TextUtils.substring(content, b, b + length);
    397     }
    398 
    399     /**
    400      * The default implementation turns this into the enter key.
    401      */
    402     public boolean performEditorAction(int actionCode) {
    403         long eventTime = SystemClock.uptimeMillis();
    404         sendKeyEvent(new KeyEvent(eventTime, eventTime,
    405                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
    406                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    407                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
    408                 | KeyEvent.FLAG_EDITOR_ACTION));
    409         sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
    410                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
    411                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    412                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
    413                 | KeyEvent.FLAG_EDITOR_ACTION));
    414         return true;
    415     }
    416 
    417     /**
    418      * The default implementation does nothing.
    419      */
    420     public boolean performContextMenuAction(int id) {
    421         return false;
    422     }
    423 
    424     /**
    425      * The default implementation does nothing.
    426      */
    427     public boolean performPrivateCommand(String action, Bundle data) {
    428         return false;
    429     }
    430 
    431     /**
    432      * The default implementation does nothing.
    433      */
    434     public boolean requestCursorUpdates(int cursorUpdateMode) {
    435         return false;
    436     }
    437 
    438     /**
    439      * The default implementation places the given text into the editable,
    440      * replacing any existing composing text.  The new text is marked as
    441      * in a composing state with the composing style.
    442      */
    443     public boolean setComposingText(CharSequence text, int newCursorPosition) {
    444         if (DEBUG) Log.v(TAG, "setComposingText " + text);
    445         replaceText(text, newCursorPosition, true);
    446         mIMM.notifyUserAction();
    447         return true;
    448     }
    449 
    450     public boolean setComposingRegion(int start, int end) {
    451         final Editable content = getEditable();
    452         if (content != null) {
    453             beginBatchEdit();
    454             removeComposingSpans(content);
    455             int a = start;
    456             int b = end;
    457             if (a > b) {
    458                 int tmp = a;
    459                 a = b;
    460                 b = tmp;
    461             }
    462             // Clip the end points to be within the content bounds.
    463             final int length = content.length();
    464             if (a < 0) a = 0;
    465             if (b < 0) b = 0;
    466             if (a > length) a = length;
    467             if (b > length) b = length;
    468 
    469             ensureDefaultComposingSpans();
    470             if (mDefaultComposingSpans != null) {
    471                 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
    472                     content.setSpan(mDefaultComposingSpans[i], a, b,
    473                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    474                 }
    475             }
    476 
    477             content.setSpan(COMPOSING, a, b,
    478                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    479 
    480             // Note: sendCurrentText does nothing unless mDummyMode is set
    481             sendCurrentText();
    482             endBatchEdit();
    483         }
    484         return true;
    485     }
    486 
    487     /**
    488      * The default implementation changes the selection position in the
    489      * current editable text.
    490      */
    491     public boolean setSelection(int start, int end) {
    492         if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
    493         final Editable content = getEditable();
    494         if (content == null) return false;
    495         int len = content.length();
    496         if (start > len || end > len || start < 0 || end < 0) {
    497             // If the given selection is out of bounds, just ignore it.
    498             // Most likely the text was changed out from under the IME,
    499             // and the IME is going to have to update all of its state
    500             // anyway.
    501             return true;
    502         }
    503         if (start == end && MetaKeyKeyListener.getMetaState(content,
    504                 MetaKeyKeyListener.META_SELECTING) != 0) {
    505             // If we are in selection mode, then we want to extend the
    506             // selection instead of replacing it.
    507             Selection.extendSelection(content, start);
    508         } else {
    509             Selection.setSelection(content, start, end);
    510         }
    511         return true;
    512     }
    513 
    514     /**
    515      * Provides standard implementation for sending a key event to the window
    516      * attached to the input connection's view.
    517      */
    518     public boolean sendKeyEvent(KeyEvent event) {
    519         synchronized (mIMM.mH) {
    520             ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
    521             if (viewRootImpl == null) {
    522                 if (mIMM.mServedView != null) {
    523                     viewRootImpl = mIMM.mServedView.getViewRootImpl();
    524                 }
    525             }
    526             if (viewRootImpl != null) {
    527                 viewRootImpl.dispatchKeyFromIme(event);
    528             }
    529         }
    530         mIMM.notifyUserAction();
    531         return false;
    532     }
    533 
    534     /**
    535      * Updates InputMethodManager with the current fullscreen mode.
    536      */
    537     public boolean reportFullscreenMode(boolean enabled) {
    538         mIMM.setFullscreenMode(enabled);
    539         return true;
    540     }
    541 
    542     private void sendCurrentText() {
    543         if (!mDummyMode) {
    544             return;
    545         }
    546 
    547         Editable content = getEditable();
    548         if (content != null) {
    549             final int N = content.length();
    550             if (N == 0) {
    551                 return;
    552             }
    553             if (N == 1) {
    554                 // If it's 1 character, we have a chance of being
    555                 // able to generate normal key events...
    556                 if (mKeyCharacterMap == null) {
    557                     mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
    558                 }
    559                 char[] chars = new char[1];
    560                 content.getChars(0, 1, chars, 0);
    561                 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
    562                 if (events != null) {
    563                     for (int i=0; i<events.length; i++) {
    564                         if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
    565                         sendKeyEvent(events[i]);
    566                     }
    567                     content.clear();
    568                     return;
    569                 }
    570             }
    571 
    572             // Otherwise, revert to the special key event containing
    573             // the actual characters.
    574             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
    575                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
    576             sendKeyEvent(event);
    577             content.clear();
    578         }
    579     }
    580 
    581     private void ensureDefaultComposingSpans() {
    582         if (mDefaultComposingSpans == null) {
    583             Context context;
    584             if (mTargetView != null) {
    585                 context = mTargetView.getContext();
    586             } else if (mIMM.mServedView != null) {
    587                 context = mIMM.mServedView.getContext();
    588             } else {
    589                 context = null;
    590             }
    591             if (context != null) {
    592                 TypedArray ta = context.getTheme()
    593                         .obtainStyledAttributes(new int[] {
    594                                 com.android.internal.R.attr.candidatesTextStyleSpans
    595                         });
    596                 CharSequence style = ta.getText(0);
    597                 ta.recycle();
    598                 if (style != null && style instanceof Spanned) {
    599                     mDefaultComposingSpans = ((Spanned)style).getSpans(
    600                             0, style.length(), Object.class);
    601                 }
    602             }
    603         }
    604     }
    605 
    606     private void replaceText(CharSequence text, int newCursorPosition,
    607             boolean composing) {
    608         final Editable content = getEditable();
    609         if (content == null) {
    610             return;
    611         }
    612 
    613         beginBatchEdit();
    614 
    615         // delete composing text set previously.
    616         int a = getComposingSpanStart(content);
    617         int b = getComposingSpanEnd(content);
    618 
    619         if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
    620 
    621         if (b < a) {
    622             int tmp = a;
    623             a = b;
    624             b = tmp;
    625         }
    626 
    627         if (a != -1 && b != -1) {
    628             removeComposingSpans(content);
    629         } else {
    630             a = Selection.getSelectionStart(content);
    631             b = Selection.getSelectionEnd(content);
    632             if (a < 0) a = 0;
    633             if (b < 0) b = 0;
    634             if (b < a) {
    635                 int tmp = a;
    636                 a = b;
    637                 b = tmp;
    638             }
    639         }
    640 
    641         if (composing) {
    642             Spannable sp = null;
    643             if (!(text instanceof Spannable)) {
    644                 sp = new SpannableStringBuilder(text);
    645                 text = sp;
    646                 ensureDefaultComposingSpans();
    647                 if (mDefaultComposingSpans != null) {
    648                     for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
    649                         sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
    650                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    651                     }
    652                 }
    653             } else {
    654                 sp = (Spannable)text;
    655             }
    656             setComposingSpans(sp);
    657         }
    658 
    659         if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
    660                 + text + "\", composing=" + composing
    661                 + ", type=" + text.getClass().getCanonicalName());
    662 
    663         if (DEBUG) {
    664             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
    665             lp.println("Current text:");
    666             TextUtils.dumpSpans(content, lp, "  ");
    667             lp.println("Composing text:");
    668             TextUtils.dumpSpans(text, lp, "  ");
    669         }
    670 
    671         // Position the cursor appropriately, so that after replacing the
    672         // desired range of text it will be located in the correct spot.
    673         // This allows us to deal with filters performing edits on the text
    674         // we are providing here.
    675         if (newCursorPosition > 0) {
    676             newCursorPosition += b - 1;
    677         } else {
    678             newCursorPosition += a;
    679         }
    680         if (newCursorPosition < 0) newCursorPosition = 0;
    681         if (newCursorPosition > content.length())
    682             newCursorPosition = content.length();
    683         Selection.setSelection(content, newCursorPosition);
    684 
    685         content.replace(a, b, text);
    686 
    687         if (DEBUG) {
    688             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
    689             lp.println("Final text:");
    690             TextUtils.dumpSpans(content, lp, "  ");
    691         }
    692 
    693         endBatchEdit();
    694     }
    695 }
    696