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.
     46  */
     47 public class BaseInputConnection implements InputConnection {
     48     private static final boolean DEBUG = false;
     49     private static final String TAG = "BaseInputConnection";
     50     static final Object COMPOSING = new ComposingText();
     51 
     52     /** @hide */
     53     protected final InputMethodManager mIMM;
     54     final View mTargetView;
     55     final boolean mDummyMode;
     56 
     57     private Object[] mDefaultComposingSpans;
     58 
     59     Editable mEditable;
     60     KeyCharacterMap mKeyCharacterMap;
     61 
     62     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
     63         mIMM = mgr;
     64         mTargetView = null;
     65         mDummyMode = !fullEditor;
     66     }
     67 
     68     public BaseInputConnection(View targetView, boolean fullEditor) {
     69         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
     70                 Context.INPUT_METHOD_SERVICE);
     71         mTargetView = targetView;
     72         mDummyMode = !fullEditor;
     73     }
     74 
     75     public static final void removeComposingSpans(Spannable text) {
     76         text.removeSpan(COMPOSING);
     77         Object[] sps = text.getSpans(0, text.length(), Object.class);
     78         if (sps != null) {
     79             for (int i=sps.length-1; i>=0; i--) {
     80                 Object o = sps[i];
     81                 if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
     82                     text.removeSpan(o);
     83                 }
     84             }
     85         }
     86     }
     87 
     88     public static void setComposingSpans(Spannable text) {
     89         setComposingSpans(text, 0, text.length());
     90     }
     91 
     92     /** @hide */
     93     public static void setComposingSpans(Spannable text, int start, int end) {
     94         final Object[] sps = text.getSpans(start, end, Object.class);
     95         if (sps != null) {
     96             for (int i=sps.length-1; i>=0; i--) {
     97                 final Object o = sps[i];
     98                 if (o == COMPOSING) {
     99                     text.removeSpan(o);
    100                     continue;
    101                 }
    102 
    103                 final int fl = text.getSpanFlags(o);
    104                 if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
    105                         != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
    106                     text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
    107                             (fl & ~Spanned.SPAN_POINT_MARK_MASK)
    108                                     | Spanned.SPAN_COMPOSING
    109                                     | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    110                 }
    111             }
    112         }
    113 
    114         text.setSpan(COMPOSING, start, end,
    115                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    116     }
    117 
    118     public static int getComposingSpanStart(Spannable text) {
    119         return text.getSpanStart(COMPOSING);
    120     }
    121 
    122     public static int getComposingSpanEnd(Spannable text) {
    123         return text.getSpanEnd(COMPOSING);
    124     }
    125 
    126     /**
    127      * Return the target of edit operations.  The default implementation
    128      * returns its own fake editable that is just used for composing text;
    129      * subclasses that are real text editors should override this and
    130      * supply their own.
    131      */
    132     public Editable getEditable() {
    133         if (mEditable == null) {
    134             mEditable = Editable.Factory.getInstance().newEditable("");
    135             Selection.setSelection(mEditable, 0);
    136         }
    137         return mEditable;
    138     }
    139 
    140     /**
    141      * Default implementation does nothing.
    142      */
    143     public boolean beginBatchEdit() {
    144         return false;
    145     }
    146 
    147     /**
    148      * Default implementation does nothing.
    149      */
    150     public boolean endBatchEdit() {
    151         return false;
    152     }
    153 
    154     /**
    155      * Called when this InputConnection is no longer used by the InputMethodManager.
    156      *
    157      * @hide
    158      */
    159     protected void reportFinish() {
    160         // Intentionaly empty
    161     }
    162 
    163     /**
    164      * Default implementation uses
    165      * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
    166      * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
    167      */
    168     public boolean clearMetaKeyStates(int states) {
    169         final Editable content = getEditable();
    170         if (content == null) return false;
    171         MetaKeyKeyListener.clearMetaKeyState(content, states);
    172         return true;
    173     }
    174 
    175     /**
    176      * Default implementation does nothing and returns false.
    177      */
    178     public boolean commitCompletion(CompletionInfo text) {
    179         return false;
    180     }
    181 
    182     /**
    183      * Default implementation does nothing and returns false.
    184      */
    185     public boolean commitCorrection(CorrectionInfo correctionInfo) {
    186         return false;
    187     }
    188 
    189     /**
    190      * Default implementation replaces any existing composing text with
    191      * the given text.  In addition, only if dummy mode, a key event is
    192      * sent for the new text and the current editable buffer cleared.
    193      */
    194     public boolean commitText(CharSequence text, int newCursorPosition) {
    195         if (DEBUG) Log.v(TAG, "commitText " + text);
    196         replaceText(text, newCursorPosition, false);
    197         sendCurrentText();
    198         return true;
    199     }
    200 
    201     /**
    202      * The default implementation performs the deletion around the current
    203      * selection position of the editable text.
    204      * @param beforeLength
    205      * @param afterLength
    206      */
    207     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
    208         if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
    209                 + " / " + afterLength);
    210         final Editable content = getEditable();
    211         if (content == null) return false;
    212 
    213         beginBatchEdit();
    214 
    215         int a = Selection.getSelectionStart(content);
    216         int b = Selection.getSelectionEnd(content);
    217 
    218         if (a > b) {
    219             int tmp = a;
    220             a = b;
    221             b = tmp;
    222         }
    223 
    224         // ignore the composing text.
    225         int ca = getComposingSpanStart(content);
    226         int cb = getComposingSpanEnd(content);
    227         if (cb < ca) {
    228             int tmp = ca;
    229             ca = cb;
    230             cb = tmp;
    231         }
    232         if (ca != -1 && cb != -1) {
    233             if (ca < a) a = ca;
    234             if (cb > b) b = cb;
    235         }
    236 
    237         int deleted = 0;
    238 
    239         if (beforeLength > 0) {
    240             int start = a - beforeLength;
    241             if (start < 0) start = 0;
    242             content.delete(start, a);
    243             deleted = a - start;
    244         }
    245 
    246         if (afterLength > 0) {
    247             b = b - deleted;
    248 
    249             int end = b + afterLength;
    250             if (end > content.length()) end = content.length();
    251 
    252             content.delete(b, end);
    253         }
    254 
    255         endBatchEdit();
    256 
    257         return true;
    258     }
    259 
    260     /**
    261      * The default implementation removes the composing state from the
    262      * current editable text.  In addition, only if dummy mode, a key event is
    263      * sent for the new text and the current editable buffer cleared.
    264      */
    265     public boolean finishComposingText() {
    266         if (DEBUG) Log.v(TAG, "finishComposingText");
    267         final Editable content = getEditable();
    268         if (content != null) {
    269             beginBatchEdit();
    270             removeComposingSpans(content);
    271             endBatchEdit();
    272             sendCurrentText();
    273         }
    274         return true;
    275     }
    276 
    277     /**
    278      * The default implementation uses TextUtils.getCapsMode to get the
    279      * cursor caps mode for the current selection position in the editable
    280      * text, unless in dummy mode in which case 0 is always returned.
    281      */
    282     public int getCursorCapsMode(int reqModes) {
    283         if (mDummyMode) return 0;
    284 
    285         final Editable content = getEditable();
    286         if (content == null) return 0;
    287 
    288         int a = Selection.getSelectionStart(content);
    289         int b = Selection.getSelectionEnd(content);
    290 
    291         if (a > b) {
    292             int tmp = a;
    293             a = b;
    294             b = tmp;
    295         }
    296 
    297         return TextUtils.getCapsMode(content, a, reqModes);
    298     }
    299 
    300     /**
    301      * The default implementation always returns null.
    302      */
    303     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
    304         return null;
    305     }
    306 
    307     /**
    308      * The default implementation returns the given amount of text from the
    309      * current cursor position in the buffer.
    310      */
    311     public CharSequence getTextBeforeCursor(int length, int flags) {
    312         final Editable content = getEditable();
    313         if (content == null) return null;
    314 
    315         int a = Selection.getSelectionStart(content);
    316         int b = Selection.getSelectionEnd(content);
    317 
    318         if (a > b) {
    319             int tmp = a;
    320             a = b;
    321             b = tmp;
    322         }
    323 
    324         if (a <= 0) {
    325             return "";
    326         }
    327 
    328         if (length > a) {
    329             length = a;
    330         }
    331 
    332         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    333             return content.subSequence(a - length, a);
    334         }
    335         return TextUtils.substring(content, a - length, a);
    336     }
    337 
    338     /**
    339      * The default implementation returns the text currently selected, or null if none is
    340      * selected.
    341      */
    342     public CharSequence getSelectedText(int flags) {
    343         final Editable content = getEditable();
    344         if (content == null) return null;
    345 
    346         int a = Selection.getSelectionStart(content);
    347         int b = Selection.getSelectionEnd(content);
    348 
    349         if (a > b) {
    350             int tmp = a;
    351             a = b;
    352             b = tmp;
    353         }
    354 
    355         if (a == b) return null;
    356 
    357         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    358             return content.subSequence(a, b);
    359         }
    360         return TextUtils.substring(content, a, b);
    361     }
    362 
    363     /**
    364      * The default implementation returns the given amount of text from the
    365      * current cursor position in the buffer.
    366      */
    367     public CharSequence getTextAfterCursor(int length, int flags) {
    368         final Editable content = getEditable();
    369         if (content == null) return null;
    370 
    371         int a = Selection.getSelectionStart(content);
    372         int b = Selection.getSelectionEnd(content);
    373 
    374         if (a > b) {
    375             int tmp = a;
    376             a = b;
    377             b = tmp;
    378         }
    379 
    380         // Guard against the case where the cursor has not been positioned yet.
    381         if (b < 0) {
    382             b = 0;
    383         }
    384 
    385         if (b + length > content.length()) {
    386             length = content.length() - b;
    387         }
    388 
    389 
    390         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
    391             return content.subSequence(b, b + length);
    392         }
    393         return TextUtils.substring(content, b, b + length);
    394     }
    395 
    396     /**
    397      * The default implementation turns this into the enter key.
    398      */
    399     public boolean performEditorAction(int actionCode) {
    400         long eventTime = SystemClock.uptimeMillis();
    401         sendKeyEvent(new KeyEvent(eventTime, eventTime,
    402                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
    403                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    404                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
    405                 | KeyEvent.FLAG_EDITOR_ACTION));
    406         sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
    407                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
    408                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    409                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
    410                 | KeyEvent.FLAG_EDITOR_ACTION));
    411         return true;
    412     }
    413 
    414     /**
    415      * The default implementation does nothing.
    416      */
    417     public boolean performContextMenuAction(int id) {
    418         return false;
    419     }
    420 
    421     /**
    422      * The default implementation does nothing.
    423      */
    424     public boolean performPrivateCommand(String action, Bundle data) {
    425         return false;
    426     }
    427 
    428     /**
    429      * The default implementation places the given text into the editable,
    430      * replacing any existing composing text.  The new text is marked as
    431      * in a composing state with the composing style.
    432      */
    433     public boolean setComposingText(CharSequence text, int newCursorPosition) {
    434         if (DEBUG) Log.v(TAG, "setComposingText " + text);
    435         replaceText(text, newCursorPosition, true);
    436         return true;
    437     }
    438 
    439     public boolean setComposingRegion(int start, int end) {
    440         final Editable content = getEditable();
    441         if (content != null) {
    442             beginBatchEdit();
    443             removeComposingSpans(content);
    444             int a = start;
    445             int b = end;
    446             if (a > b) {
    447                 int tmp = a;
    448                 a = b;
    449                 b = tmp;
    450             }
    451             // Clip the end points to be within the content bounds.
    452             final int length = content.length();
    453             if (a < 0) a = 0;
    454             if (b < 0) b = 0;
    455             if (a > length) a = length;
    456             if (b > length) b = length;
    457 
    458             ensureDefaultComposingSpans();
    459             if (mDefaultComposingSpans != null) {
    460                 for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
    461                     content.setSpan(mDefaultComposingSpans[i], a, b,
    462                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    463                 }
    464             }
    465 
    466             content.setSpan(COMPOSING, a, b,
    467                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    468 
    469             endBatchEdit();
    470             sendCurrentText();
    471         }
    472         return true;
    473     }
    474 
    475     /**
    476      * The default implementation changes the selection position in the
    477      * current editable text.
    478      */
    479     public boolean setSelection(int start, int end) {
    480         if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
    481         final Editable content = getEditable();
    482         if (content == null) return false;
    483         int len = content.length();
    484         if (start > len || end > len) {
    485             // If the given selection is out of bounds, just ignore it.
    486             // Most likely the text was changed out from under the IME,
    487             // the the IME is going to have to update all of its state
    488             // anyway.
    489             return true;
    490         }
    491         if (start == end && MetaKeyKeyListener.getMetaState(content,
    492                 MetaKeyKeyListener.META_SELECTING) != 0) {
    493             // If we are in selection mode, then we want to extend the
    494             // selection instead of replacing it.
    495             Selection.extendSelection(content, start);
    496         } else {
    497             Selection.setSelection(content, start, end);
    498         }
    499         return true;
    500     }
    501 
    502     /**
    503      * Provides standard implementation for sending a key event to the window
    504      * attached to the input connection's view.
    505      */
    506     public boolean sendKeyEvent(KeyEvent event) {
    507         synchronized (mIMM.mH) {
    508             ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
    509             if (viewRootImpl == null) {
    510                 if (mIMM.mServedView != null) {
    511                     viewRootImpl = mIMM.mServedView.getViewRootImpl();
    512                 }
    513             }
    514             if (viewRootImpl != null) {
    515                 viewRootImpl.dispatchKeyFromIme(event);
    516             }
    517         }
    518         return false;
    519     }
    520 
    521     /**
    522      * Updates InputMethodManager with the current fullscreen mode.
    523      */
    524     public boolean reportFullscreenMode(boolean enabled) {
    525         mIMM.setFullscreenMode(enabled);
    526         return true;
    527     }
    528 
    529     private void sendCurrentText() {
    530         if (!mDummyMode) {
    531             return;
    532         }
    533 
    534         Editable content = getEditable();
    535         if (content != null) {
    536             final int N = content.length();
    537             if (N == 0) {
    538                 return;
    539             }
    540             if (N == 1) {
    541                 // If it's 1 character, we have a chance of being
    542                 // able to generate normal key events...
    543                 if (mKeyCharacterMap == null) {
    544                     mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
    545                 }
    546                 char[] chars = new char[1];
    547                 content.getChars(0, 1, chars, 0);
    548                 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
    549                 if (events != null) {
    550                     for (int i=0; i<events.length; i++) {
    551                         if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
    552                         sendKeyEvent(events[i]);
    553                     }
    554                     content.clear();
    555                     return;
    556                 }
    557             }
    558 
    559             // Otherwise, revert to the special key event containing
    560             // the actual characters.
    561             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
    562                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
    563             sendKeyEvent(event);
    564             content.clear();
    565         }
    566     }
    567 
    568     private void ensureDefaultComposingSpans() {
    569         if (mDefaultComposingSpans == null) {
    570             Context context;
    571             if (mTargetView != null) {
    572                 context = mTargetView.getContext();
    573             } else if (mIMM.mServedView != null) {
    574                 context = mIMM.mServedView.getContext();
    575             } else {
    576                 context = null;
    577             }
    578             if (context != null) {
    579                 TypedArray ta = context.getTheme()
    580                         .obtainStyledAttributes(new int[] {
    581                                 com.android.internal.R.attr.candidatesTextStyleSpans
    582                         });
    583                 CharSequence style = ta.getText(0);
    584                 ta.recycle();
    585                 if (style != null && style instanceof Spanned) {
    586                     mDefaultComposingSpans = ((Spanned)style).getSpans(
    587                             0, style.length(), Object.class);
    588                 }
    589             }
    590         }
    591     }
    592 
    593     private void replaceText(CharSequence text, int newCursorPosition,
    594             boolean composing) {
    595         final Editable content = getEditable();
    596         if (content == null) {
    597             return;
    598         }
    599 
    600         beginBatchEdit();
    601 
    602         // delete composing text set previously.
    603         int a = getComposingSpanStart(content);
    604         int b = getComposingSpanEnd(content);
    605 
    606         if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
    607 
    608         if (b < a) {
    609             int tmp = a;
    610             a = b;
    611             b = tmp;
    612         }
    613 
    614         if (a != -1 && b != -1) {
    615             removeComposingSpans(content);
    616         } else {
    617             a = Selection.getSelectionStart(content);
    618             b = Selection.getSelectionEnd(content);
    619             if (a < 0) a = 0;
    620             if (b < 0) b = 0;
    621             if (b < a) {
    622                 int tmp = a;
    623                 a = b;
    624                 b = tmp;
    625             }
    626         }
    627 
    628         if (composing) {
    629             Spannable sp = null;
    630             if (!(text instanceof Spannable)) {
    631                 sp = new SpannableStringBuilder(text);
    632                 text = sp;
    633                 ensureDefaultComposingSpans();
    634                 if (mDefaultComposingSpans != null) {
    635                     for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
    636                         sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
    637                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    638                     }
    639                 }
    640             } else {
    641                 sp = (Spannable)text;
    642             }
    643             setComposingSpans(sp);
    644         }
    645 
    646         if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
    647                 + text + "\", composing=" + composing
    648                 + ", type=" + text.getClass().getCanonicalName());
    649 
    650         if (DEBUG) {
    651             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
    652             lp.println("Current text:");
    653             TextUtils.dumpSpans(content, lp, "  ");
    654             lp.println("Composing text:");
    655             TextUtils.dumpSpans(text, lp, "  ");
    656         }
    657 
    658         // Position the cursor appropriately, so that after replacing the
    659         // desired range of text it will be located in the correct spot.
    660         // This allows us to deal with filters performing edits on the text
    661         // we are providing here.
    662         if (newCursorPosition > 0) {
    663             newCursorPosition += b - 1;
    664         } else {
    665             newCursorPosition += a;
    666         }
    667         if (newCursorPosition < 0) newCursorPosition = 0;
    668         if (newCursorPosition > content.length())
    669             newCursorPosition = content.length();
    670         Selection.setSelection(content, newCursorPosition);
    671 
    672         content.replace(a, b, text);
    673 
    674         if (DEBUG) {
    675             LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
    676             lp.println("Final text:");
    677             TextUtils.dumpSpans(content, lp, "  ");
    678         }
    679 
    680         endBatchEdit();
    681     }
    682 }
    683