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