• 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.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      */
onCreate()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      */
onInitializeInterface()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      */
onCreateInputView()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 
setLatinKeyboard(LatinKeyboard nextKeyboard)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      */
onCreateCandidatesView()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      */
onStartInput(EditorInfo attribute, boolean restarting)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      */
onFinishInput()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 
onStartInputView(EditorInfo attribute, boolean restarting)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
onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype)267     public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
268         mInputView.setSubtypeOnSpaceKey(subtype);
269     }
270 
271     /**
272      * Deal with the editor reporting movement of its cursor.
273      */
onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)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      */
onDisplayCompletions(CompletionInfo[] completions)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      */
translateKeyDown(int keyCode, KeyEvent event)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      */
onKeyDown(int keyCode, KeyEvent event)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      */
onKeyUp(int keyCode, KeyEvent event)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      */
commitTyped(InputConnection inputConnection)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      */
updateShiftKeyState(EditorInfo attr)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      */
isAlphabet(int code)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      */
keyDownUp(int keyEventCode)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      */
sendKey(int keyCode)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 
onKey(int primaryCode, int[] keyCodes)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 
onText(CharSequence text)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      */
updateCandidates()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 
setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid)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 
handleBackspace()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 
handleShift()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 
handleCharacter(int primaryCode, int[] keyCodes)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 
handleClose()636     private void handleClose() {
637         commitTyped(getCurrentInputConnection());
638         requestHideSelf(0);
639         mInputView.closing();
640     }
641 
getToken()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 
handleLanguageSwitch()654     private void handleLanguageSwitch() {
655         mInputMethodManager.switchToNextInputMethod(getToken(), false /* onlyCurrentIme */);
656     }
657 
checkToggleCapsLock()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 
getWordSeparators()668     private String getWordSeparators() {
669         return mWordSeparators;
670     }
671 
isWordSeparator(int code)672     public boolean isWordSeparator(int code) {
673         String separators = getWordSeparators();
674         return separators.contains(String.valueOf((char)code));
675     }
676 
pickDefaultCandidate()677     public void pickDefaultCandidate() {
678         pickSuggestionManually(0);
679     }
680 
pickSuggestionManually(int index)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 
swipeRight()698     public void swipeRight() {
699         if (mCompletionOn) {
700             pickDefaultCandidate();
701         }
702     }
703 
swipeLeft()704     public void swipeLeft() {
705         handleBackspace();
706     }
707 
swipeDown()708     public void swipeDown() {
709         handleClose();
710     }
711 
swipeUp()712     public void swipeUp() {
713     }
714 
onPress(int primaryCode)715     public void onPress(int primaryCode) {
716     }
717 
onRelease(int primaryCode)718     public void onRelease(int primaryCode) {
719     }
720 }
721