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