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