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