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