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