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
17package com.example.android.softkeyboard;
18
19import android.app.Dialog;
20import android.inputmethodservice.InputMethodService;
21import android.inputmethodservice.Keyboard;
22import android.inputmethodservice.KeyboardView;
23import android.os.IBinder;
24import android.text.InputType;
25import android.text.method.MetaKeyKeyListener;
26import android.view.KeyCharacterMap;
27import android.view.KeyEvent;
28import android.view.View;
29import android.view.Window;
30import android.view.inputmethod.CompletionInfo;
31import android.view.inputmethod.EditorInfo;
32import android.view.inputmethod.InputConnection;
33import android.view.inputmethod.InputMethodManager;
34import android.view.inputmethod.InputMethodSubtype;
35
36import java.util.ArrayList;
37import 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 */
46public 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