Home | History | Annotate | Download | only in softkeyboard
      1 /*
      2  * Copyright (C) 2008-2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.softkeyboard;
     18 
     19 import android.inputmethodservice.InputMethodService;
     20 import android.inputmethodservice.Keyboard;
     21 import android.inputmethodservice.KeyboardView;
     22 import android.text.InputType;
     23 import android.text.method.MetaKeyKeyListener;
     24 import android.view.KeyCharacterMap;
     25 import android.view.KeyEvent;
     26 import android.view.View;
     27 import android.view.inputmethod.CompletionInfo;
     28 import android.view.inputmethod.EditorInfo;
     29 import android.view.inputmethod.InputConnection;
     30 import android.view.inputmethod.InputMethodManager;
     31 import android.view.inputmethod.InputMethodSubtype;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 
     36 /**
     37  * Example of writing an input method for a soft keyboard.  This code is
     38  * focused on simplicity over completeness, so it should in no way be considered
     39  * to be a complete soft keyboard implementation.  Its purpose is to provide
     40  * a basic example for how you would get started writing an input method, to
     41  * be fleshed out as appropriate.
     42  */
     43 public class SoftKeyboard extends InputMethodService
     44         implements KeyboardView.OnKeyboardActionListener {
     45     static final boolean DEBUG = false;
     46 
     47     /**
     48      * This boolean indicates the optional example code for performing
     49      * processing of hard keys in addition to regular text generation
     50      * from on-screen interaction.  It would be used for input methods that
     51      * perform language translations (such as converting text entered on
     52      * a QWERTY keyboard to Chinese), but may not be used for input methods
     53      * that are primarily intended to be used for on-screen text entry.
     54      */
     55     static final boolean PROCESS_HARD_KEYS = true;
     56 
     57     private InputMethodManager mInputMethodManager;
     58 
     59     private LatinKeyboardView mInputView;
     60     private CandidateView mCandidateView;
     61     private CompletionInfo[] mCompletions;
     62 
     63     private StringBuilder mComposing = new StringBuilder();
     64     private boolean mPredictionOn;
     65     private boolean mCompletionOn;
     66     private int mLastDisplayWidth;
     67     private boolean mCapsLock;
     68     private long mLastShiftTime;
     69     private long mMetaState;
     70 
     71     private LatinKeyboard mSymbolsKeyboard;
     72     private LatinKeyboard mSymbolsShiftedKeyboard;
     73     private LatinKeyboard mQwertyKeyboard;
     74 
     75     private LatinKeyboard mCurKeyboard;
     76 
     77     private String mWordSeparators;
     78 
     79     /**
     80      * Main initialization of the input method component.  Be sure to call
     81      * to super class.
     82      */
     83     @Override public void onCreate() {
     84         super.onCreate();
     85         mInputMethodManager = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
     86         mWordSeparators = getResources().getString(R.string.word_separators);
     87     }
     88 
     89     /**
     90      * This is the point where you can do all of your UI initialization.  It
     91      * is called after creation and any configuration change.
     92      */
     93     @Override public void onInitializeInterface() {
     94         if (mQwertyKeyboard != null) {
     95             // Configuration changes can happen after the keyboard gets recreated,
     96             // so we need to be able to re-build the keyboards if the available
     97             // space has changed.
     98             int displayWidth = getMaxWidth();
     99             if (displayWidth == mLastDisplayWidth) return;
    100             mLastDisplayWidth = displayWidth;
    101         }
    102         mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty);
    103         mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols);
    104         mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift);
    105     }
    106 
    107     /**
    108      * Called by the framework when your view for creating input needs to
    109      * be generated.  This will be called the first time your input method
    110      * is displayed, and every time it needs to be re-created such as due to
    111      * a configuration change.
    112      */
    113     @Override public View onCreateInputView() {
    114         mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
    115                 R.layout.input, null);
    116         mInputView.setOnKeyboardActionListener(this);
    117         mInputView.setKeyboard(mQwertyKeyboard);
    118         return mInputView;
    119     }
    120 
    121     /**
    122      * Called by the framework when your view for showing candidates needs to
    123      * be generated, like {@link #onCreateInputView}.
    124      */
    125     @Override public View onCreateCandidatesView() {
    126         mCandidateView = new CandidateView(this);
    127         mCandidateView.setService(this);
    128         return mCandidateView;
    129     }
    130 
    131     /**
    132      * This is the main point where we do our initialization of the input method
    133      * to begin operating on an application.  At this point we have been
    134      * bound to the client, and are now receiving all of the detailed information
    135      * about the target of our edits.
    136      */
    137     @Override public void onStartInput(EditorInfo attribute, boolean restarting) {
    138         super.onStartInput(attribute, restarting);
    139 
    140         // Reset our state.  We want to do this even if restarting, because
    141         // the underlying state of the text editor could have changed in any way.
    142         mComposing.setLength(0);
    143         updateCandidates();
    144 
    145         if (!restarting) {
    146             // Clear shift states.
    147             mMetaState = 0;
    148         }
    149 
    150         mPredictionOn = false;
    151         mCompletionOn = false;
    152         mCompletions = null;
    153 
    154         // We are now going to initialize our state based on the type of
    155         // text being edited.
    156         switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
    157             case InputType.TYPE_CLASS_NUMBER:
    158             case InputType.TYPE_CLASS_DATETIME:
    159                 // Numbers and dates default to the symbols keyboard, with
    160                 // no extra features.
    161                 mCurKeyboard = mSymbolsKeyboard;
    162                 break;
    163 
    164             case InputType.TYPE_CLASS_PHONE:
    165                 // Phones will also default to the symbols keyboard, though
    166                 // often you will want to have a dedicated phone keyboard.
    167                 mCurKeyboard = mSymbolsKeyboard;
    168                 break;
    169 
    170             case InputType.TYPE_CLASS_TEXT:
    171                 // This is general text editing.  We will default to the
    172                 // normal alphabetic keyboard, and assume that we should
    173                 // be doing predictive text (showing candidates as the
    174                 // user types).
    175                 mCurKeyboard = mQwertyKeyboard;
    176                 mPredictionOn = true;
    177 
    178                 // We now look for a few special variations of text that will
    179                 // modify our behavior.
    180                 int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION;
    181                 if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
    182                         variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
    183                     // Do not display predictions / what the user is typing
    184                     // when they are entering a password.
    185                     mPredictionOn = false;
    186                 }
    187 
    188                 if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
    189                         || variation == InputType.TYPE_TEXT_VARIATION_URI
    190                         || variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
    191                     // Our predictions are not useful for e-mail addresses
    192                     // or URIs.
    193                     mPredictionOn = false;
    194                 }
    195 
    196                 if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
    197                     // If this is an auto-complete text view, then our predictions
    198                     // will not be shown and instead we will allow the editor
    199                     // to supply their own.  We only show the editor's
    200                     // candidates when in fullscreen mode, otherwise relying
    201                     // own it displaying its own UI.
    202                     mPredictionOn = false;
    203                     mCompletionOn = isFullscreenMode();
    204                 }
    205 
    206                 // We also want to look at the current state of the editor
    207                 // to decide whether our alphabetic keyboard should start out
    208                 // shifted.
    209                 updateShiftKeyState(attribute);
    210                 break;
    211 
    212             default:
    213                 // For all unknown input types, default to the alphabetic
    214                 // keyboard with no special features.
    215                 mCurKeyboard = mQwertyKeyboard;
    216                 updateShiftKeyState(attribute);
    217         }
    218 
    219         // Update the label on the enter key, depending on what the application
    220         // says it will do.
    221         mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
    222     }
    223 
    224     /**
    225      * This is called when the user is done editing a field.  We can use
    226      * this to reset our state.
    227      */
    228     @Override public void onFinishInput() {
    229         super.onFinishInput();
    230 
    231         // Clear current composing text and candidates.
    232         mComposing.setLength(0);
    233         updateCandidates();
    234 
    235         // We only hide the candidates window when finishing input on
    236         // a particular editor, to avoid popping the underlying application
    237         // up and down if the user is entering text into the bottom of
    238         // its window.
    239         setCandidatesViewShown(false);
    240 
    241         mCurKeyboard = mQwertyKeyboard;
    242         if (mInputView != null) {
    243             mInputView.closing();
    244         }
    245     }
    246 
    247     @Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
    248         super.onStartInputView(attribute, restarting);
    249         // Apply the selected keyboard to the input view.
    250         mInputView.setKeyboard(mCurKeyboard);
    251         mInputView.closing();
    252         final InputMethodSubtype subtype = mInputMethodManager.getCurrentInputMethodSubtype();
    253         mInputView.setSubtypeOnSpaceKey(subtype);
    254     }
    255 
    256     @Override
    257     public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
    258         mInputView.setSubtypeOnSpaceKey(subtype);
    259     }
    260 
    261     /**
    262      * Deal with the editor reporting movement of its cursor.
    263      */
    264     @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd,
    265             int newSelStart, int newSelEnd,
    266             int candidatesStart, int candidatesEnd) {
    267         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
    268                 candidatesStart, candidatesEnd);
    269 
    270         // If the current selection in the text view changes, we should
    271         // clear whatever candidate text we have.
    272         if (mComposing.length() > 0 && (newSelStart != candidatesEnd
    273                 || newSelEnd != candidatesEnd)) {
    274             mComposing.setLength(0);
    275             updateCandidates();
    276             InputConnection ic = getCurrentInputConnection();
    277             if (ic != null) {
    278                 ic.finishComposingText();
    279             }
    280         }
    281     }
    282 
    283     /**
    284      * This tells us about completions that the editor has determined based
    285      * on the current text in it.  We want to use this in fullscreen mode
    286      * to show the completions ourself, since the editor can not be seen
    287      * in that situation.
    288      */
    289     @Override public void onDisplayCompletions(CompletionInfo[] completions) {
    290         if (mCompletionOn) {
    291             mCompletions = completions;
    292             if (completions == null) {
    293                 setSuggestions(null, false, false);
    294                 return;
    295             }
    296 
    297             List<String> stringList = new ArrayList<String>();
    298             for (int i = 0; i < completions.length; i++) {
    299                 CompletionInfo ci = completions[i];
    300                 if (ci != null) stringList.add(ci.getText().toString());
    301             }
    302             setSuggestions(stringList, true, true);
    303         }
    304     }
    305 
    306     /**
    307      * This translates incoming hard key events in to edit operations on an
    308      * InputConnection.  It is only needed when using the
    309      * PROCESS_HARD_KEYS option.
    310      */
    311     private boolean translateKeyDown(int keyCode, KeyEvent event) {
    312         mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState,
    313                 keyCode, event);
    314         int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState));
    315         mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState);
    316         InputConnection ic = getCurrentInputConnection();
    317         if (c == 0 || ic == null) {
    318             return false;
    319         }
    320 
    321         boolean dead = false;
    322 
    323         if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
    324             dead = true;
    325             c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
    326         }
    327 
    328         if (mComposing.length() > 0) {
    329             char accent = mComposing.charAt(mComposing.length() -1 );
    330             int composed = KeyEvent.getDeadChar(accent, c);
    331 
    332             if (composed != 0) {
    333                 c = composed;
    334                 mComposing.setLength(mComposing.length()-1);
    335             }
    336         }
    337 
    338         onKey(c, null);
    339 
    340         return true;
    341     }
    342 
    343     /**
    344      * Use this to monitor key events being delivered to the application.
    345      * We get first crack at them, and can either resume them or let them
    346      * continue to the app.
    347      */
    348     @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
    349         switch (keyCode) {
    350             case KeyEvent.KEYCODE_BACK:
    351                 // The InputMethodService already takes care of the back
    352                 // key for us, to dismiss the input method if it is shown.
    353                 // However, our keyboard could be showing a pop-up window
    354                 // that back should dismiss, so we first allow it to do that.
    355                 if (event.getRepeatCount() == 0 && mInputView != null) {
    356                     if (mInputView.handleBack()) {
    357                         return true;
    358                     }
    359                 }
    360                 break;
    361 
    362             case KeyEvent.KEYCODE_DEL:
    363                 // Special handling of the delete key: if we currently are
    364                 // composing text for the user, we want to modify that instead
    365                 // of let the application to the delete itself.
    366                 if (mComposing.length() > 0) {
    367                     onKey(Keyboard.KEYCODE_DELETE, null);
    368                     return true;
    369                 }
    370                 break;
    371 
    372             case KeyEvent.KEYCODE_ENTER:
    373                 // Let the underlying text editor always handle these.
    374                 return false;
    375 
    376             default:
    377                 // For all other keys, if we want to do transformations on
    378                 // text being entered with a hard keyboard, we need to process
    379                 // it and do the appropriate action.
    380                 if (PROCESS_HARD_KEYS) {
    381                     if (keyCode == KeyEvent.KEYCODE_SPACE
    382                             && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) {
    383                         // A silly example: in our input method, Alt+Space
    384                         // is a shortcut for 'android' in lower case.
    385                         InputConnection ic = getCurrentInputConnection();
    386                         if (ic != null) {
    387                             // First, tell the editor that it is no longer in the
    388                             // shift state, since we are consuming this.
    389                             ic.clearMetaKeyStates(KeyEvent.META_ALT_ON);
    390                             keyDownUp(KeyEvent.KEYCODE_A);
    391                             keyDownUp(KeyEvent.KEYCODE_N);
    392                             keyDownUp(KeyEvent.KEYCODE_D);
    393                             keyDownUp(KeyEvent.KEYCODE_R);
    394                             keyDownUp(KeyEvent.KEYCODE_O);
    395                             keyDownUp(KeyEvent.KEYCODE_I);
    396                             keyDownUp(KeyEvent.KEYCODE_D);
    397                             // And we consume this event.
    398                             return true;
    399                         }
    400                     }
    401                     if (mPredictionOn && translateKeyDown(keyCode, event)) {
    402                         return true;
    403                     }
    404                 }
    405         }
    406 
    407         return super.onKeyDown(keyCode, event);
    408     }
    409 
    410     /**
    411      * Use this to monitor key events being delivered to the application.
    412      * We get first crack at them, and can either resume them or let them
    413      * continue to the app.
    414      */
    415     @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
    416         // If we want to do transformations on text being entered with a hard
    417         // keyboard, we need to process the up events to update the meta key
    418         // state we are tracking.
    419         if (PROCESS_HARD_KEYS) {
    420             if (mPredictionOn) {
    421                 mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState,
    422                         keyCode, event);
    423             }
    424         }
    425 
    426         return super.onKeyUp(keyCode, event);
    427     }
    428 
    429     /**
    430      * Helper function to commit any text being composed in to the editor.
    431      */
    432     private void commitTyped(InputConnection inputConnection) {
    433         if (mComposing.length() > 0) {
    434             inputConnection.commitText(mComposing, mComposing.length());
    435             mComposing.setLength(0);
    436             updateCandidates();
    437         }
    438     }
    439 
    440     /**
    441      * Helper to update the shift state of our keyboard based on the initial
    442      * editor state.
    443      */
    444     private void updateShiftKeyState(EditorInfo attr) {
    445         if (attr != null
    446                 && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) {
    447             int caps = 0;
    448             EditorInfo ei = getCurrentInputEditorInfo();
    449             if (ei != null && ei.inputType != InputType.TYPE_NULL) {
    450                 caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
    451             }
    452             mInputView.setShifted(mCapsLock || caps != 0);
    453         }
    454     }
    455 
    456     /**
    457      * Helper to determine if a given character code is alphabetic.
    458      */
    459     private boolean isAlphabet(int code) {
    460         if (Character.isLetter(code)) {
    461             return true;
    462         } else {
    463             return false;
    464         }
    465     }
    466 
    467     /**
    468      * Helper to send a key down / key up pair to the current editor.
    469      */
    470     private void keyDownUp(int keyEventCode) {
    471         getCurrentInputConnection().sendKeyEvent(
    472                 new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
    473         getCurrentInputConnection().sendKeyEvent(
    474                 new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
    475     }
    476 
    477     /**
    478      * Helper to send a character to the editor as raw key events.
    479      */
    480     private void sendKey(int keyCode) {
    481         switch (keyCode) {
    482             case '\n':
    483                 keyDownUp(KeyEvent.KEYCODE_ENTER);
    484                 break;
    485             default:
    486                 if (keyCode >= '0' && keyCode <= '9') {
    487                     keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0);
    488                 } else {
    489                     getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1);
    490                 }
    491                 break;
    492         }
    493     }
    494 
    495     // Implementation of KeyboardViewListener
    496 
    497     public void onKey(int primaryCode, int[] keyCodes) {
    498         if (isWordSeparator(primaryCode)) {
    499             // Handle separator
    500             if (mComposing.length() > 0) {
    501                 commitTyped(getCurrentInputConnection());
    502             }
    503             sendKey(primaryCode);
    504             updateShiftKeyState(getCurrentInputEditorInfo());
    505         } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
    506             handleBackspace();
    507         } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
    508             handleShift();
    509         } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
    510             handleClose();
    511             return;
    512         } else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) {
    513             // Show a menu or somethin'
    514         } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
    515                 && mInputView != null) {
    516             Keyboard current = mInputView.getKeyboard();
    517             if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) {
    518                 current = mQwertyKeyboard;
    519             } else {
    520                 current = mSymbolsKeyboard;
    521             }
    522             mInputView.setKeyboard(current);
    523             if (current == mSymbolsKeyboard) {
    524                 current.setShifted(false);
    525             }
    526         } else {
    527             handleCharacter(primaryCode, keyCodes);
    528         }
    529     }
    530 
    531     public void onText(CharSequence text) {
    532         InputConnection ic = getCurrentInputConnection();
    533         if (ic == null) return;
    534         ic.beginBatchEdit();
    535         if (mComposing.length() > 0) {
    536             commitTyped(ic);
    537         }
    538         ic.commitText(text, 0);
    539         ic.endBatchEdit();
    540         updateShiftKeyState(getCurrentInputEditorInfo());
    541     }
    542 
    543     /**
    544      * Update the list of available candidates from the current composing
    545      * text.  This will need to be filled in by however you are determining
    546      * candidates.
    547      */
    548     private void updateCandidates() {
    549         if (!mCompletionOn) {
    550             if (mComposing.length() > 0) {
    551                 ArrayList<String> list = new ArrayList<String>();
    552                 list.add(mComposing.toString());
    553                 setSuggestions(list, true, true);
    554             } else {
    555                 setSuggestions(null, false, false);
    556             }
    557         }
    558     }
    559 
    560     public void setSuggestions(List<String> suggestions, boolean completions,
    561             boolean typedWordValid) {
    562         if (suggestions != null && suggestions.size() > 0) {
    563             setCandidatesViewShown(true);
    564         } else if (isExtractViewShown()) {
    565             setCandidatesViewShown(true);
    566         }
    567         if (mCandidateView != null) {
    568             mCandidateView.setSuggestions(suggestions, completions, typedWordValid);
    569         }
    570     }
    571 
    572     private void handleBackspace() {
    573         final int length = mComposing.length();
    574         if (length > 1) {
    575             mComposing.delete(length - 1, length);
    576             getCurrentInputConnection().setComposingText(mComposing, 1);
    577             updateCandidates();
    578         } else if (length > 0) {
    579             mComposing.setLength(0);
    580             getCurrentInputConnection().commitText("", 0);
    581             updateCandidates();
    582         } else {
    583             keyDownUp(KeyEvent.KEYCODE_DEL);
    584         }
    585         updateShiftKeyState(getCurrentInputEditorInfo());
    586     }
    587 
    588     private void handleShift() {
    589         if (mInputView == null) {
    590             return;
    591         }
    592 
    593         Keyboard currentKeyboard = mInputView.getKeyboard();
    594         if (mQwertyKeyboard == currentKeyboard) {
    595             // Alphabet keyboard
    596             checkToggleCapsLock();
    597             mInputView.setShifted(mCapsLock || !mInputView.isShifted());
    598         } else if (currentKeyboard == mSymbolsKeyboard) {
    599             mSymbolsKeyboard.setShifted(true);
    600             mInputView.setKeyboard(mSymbolsShiftedKeyboard);
    601             mSymbolsShiftedKeyboard.setShifted(true);
    602         } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
    603             mSymbolsShiftedKeyboard.setShifted(false);
    604             mInputView.setKeyboard(mSymbolsKeyboard);
    605             mSymbolsKeyboard.setShifted(false);
    606         }
    607     }
    608 
    609     private void handleCharacter(int primaryCode, int[] keyCodes) {
    610         if (isInputViewShown()) {
    611             if (mInputView.isShifted()) {
    612                 primaryCode = Character.toUpperCase(primaryCode);
    613             }
    614         }
    615         if (isAlphabet(primaryCode) && mPredictionOn) {
    616             mComposing.append((char) primaryCode);
    617             getCurrentInputConnection().setComposingText(mComposing, 1);
    618             updateShiftKeyState(getCurrentInputEditorInfo());
    619             updateCandidates();
    620         } else {
    621             getCurrentInputConnection().commitText(
    622                     String.valueOf((char) primaryCode), 1);
    623         }
    624     }
    625 
    626     private void handleClose() {
    627         commitTyped(getCurrentInputConnection());
    628         requestHideSelf(0);
    629         mInputView.closing();
    630     }
    631 
    632     private void checkToggleCapsLock() {
    633         long now = System.currentTimeMillis();
    634         if (mLastShiftTime + 800 > now) {
    635             mCapsLock = !mCapsLock;
    636             mLastShiftTime = 0;
    637         } else {
    638             mLastShiftTime = now;
    639         }
    640     }
    641 
    642     private String getWordSeparators() {
    643         return mWordSeparators;
    644     }
    645 
    646     public boolean isWordSeparator(int code) {
    647         String separators = getWordSeparators();
    648         return separators.contains(String.valueOf((char)code));
    649     }
    650 
    651     public void pickDefaultCandidate() {
    652         pickSuggestionManually(0);
    653     }
    654 
    655     public void pickSuggestionManually(int index) {
    656         if (mCompletionOn && mCompletions != null && index >= 0
    657                 && index < mCompletions.length) {
    658             CompletionInfo ci = mCompletions[index];
    659             getCurrentInputConnection().commitCompletion(ci);
    660             if (mCandidateView != null) {
    661                 mCandidateView.clear();
    662             }
    663             updateShiftKeyState(getCurrentInputEditorInfo());
    664         } else if (mComposing.length() > 0) {
    665             // If we were generating candidate suggestions for the current
    666             // text, we would commit one of them here.  But for this sample,
    667             // we will just commit the current text.
    668             commitTyped(getCurrentInputConnection());
    669         }
    670     }
    671 
    672     public void swipeRight() {
    673         if (mCompletionOn) {
    674             pickDefaultCandidate();
    675         }
    676     }
    677 
    678     public void swipeLeft() {
    679         handleBackspace();
    680     }
    681 
    682     public void swipeDown() {
    683         handleClose();
    684     }
    685 
    686     public void swipeUp() {
    687     }
    688 
    689     public void onPress(int primaryCode) {
    690     }
    691 
    692     public void onRelease(int primaryCode) {
    693     }
    694 }
    695