Created
October 12, 2020 23:19
-
-
Save matthewmorrone/7b9cd46c09d025ac319661f18710eb71 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright (C) 2008-2009 Google Inc. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
| * use this file except in compliance with the License. You may obtain a copy of | |
| * the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| * License for the specific language governing permissions and limitations under | |
| * the License. | |
| */ | |
| package com.example.android.softkeyboard; | |
| import android.content.Context; | |
| import android.inputmethodservice.InputMethodService; | |
| import android.inputmethodservice.Keyboard; | |
| import android.inputmethodservice.KeyboardView; | |
| import android.text.method.MetaKeyKeyListener; | |
| import android.view.KeyCharacterMap; | |
| import android.view.KeyEvent; | |
| import android.view.View; | |
| import android.view.WindowManager; | |
| import android.view.inputmethod.CompletionInfo; | |
| import android.view.inputmethod.EditorInfo; | |
| import android.view.inputmethod.InputConnection; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| /** | |
| * Example of writing an input method for a soft keyboard. This code is | |
| * focused on simplicity over completeness, so it should in no way be considered | |
| * to be a complete soft keyboard implementation. Its purpose is to provide | |
| * a basic example for how you would get started writing an input method, to | |
| * be fleshed out as appropriate. | |
| */ | |
| public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener { | |
| static final boolean DEBUG = false; | |
| private KeyboardView mInputView; | |
| private CandidateView mCandidateView; | |
| private CompletionInfo[] mCompletions; | |
| private StringBuilder mComposing = new StringBuilder(); | |
| private boolean mPredictionOn; | |
| private boolean mCompletionOn; | |
| private int mLastDisplayWidth; | |
| private boolean mCapsLock; | |
| private long mLastShiftTime; | |
| private long mMetaState; | |
| private Keyboard mSymbolsKeyboard; | |
| private Keyboard mSymbolsShiftedKeyboard; | |
| private Keyboard mQwertyKeyboard; | |
| private String mWordSeparators; | |
| private void makeKeyboards() { | |
| // Configuration change is coming after the keyboard gets recreated. So don't rely on that. | |
| // If keyboards have already been made, check if we have a screen width change and | |
| // create the keyboard layouts again at the correct orientation | |
| if (mQwertyKeyboard != null) { | |
| WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); | |
| int displayWidth = wm.getDefaultDisplay().getWidth(); | |
| if (displayWidth == mLastDisplayWidth) { | |
| return; | |
| } | |
| mLastDisplayWidth = displayWidth; | |
| } | |
| mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty); | |
| mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols); | |
| mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift); | |
| } | |
| @Override | |
| public void onCreate() { | |
| super.onCreate(); | |
| makeKeyboards(); | |
| mWordSeparators = getResources().getString(R.string.word_separators); | |
| } | |
| @Override | |
| public View onCreateInputView() { | |
| makeKeyboards(); | |
| mInputView = (KeyboardView)getLayoutInflater().inflate(R.layout.input, null); | |
| mInputView.setOnKeyboardActionListener(this); | |
| mInputView.setKeyboard(mQwertyKeyboard); | |
| return mInputView; | |
| } | |
| @Override | |
| public View onCreateCandidatesView() { | |
| mCandidateView = new CandidateView(this); | |
| mCandidateView.setService(this); | |
| return mCandidateView; | |
| } | |
| @Override | |
| public void onStartInputView(EditorInfo attribute, boolean restarting) { | |
| mComposing.setLength(0); | |
| updateCandidates(); | |
| if (!restarting) { | |
| // Clear shift states. | |
| mMetaState = 0; | |
| } | |
| mPredictionOn = false; | |
| mCompletionOn = false; | |
| mCompletions = null; | |
| Keyboard keyboard; | |
| switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) { | |
| case EditorInfo.TYPE_CLASS_NUMBER: | |
| case EditorInfo.TYPE_CLASS_DATETIME: | |
| keyboard = mSymbolsKeyboard; | |
| break; | |
| case EditorInfo.TYPE_CLASS_PHONE: | |
| keyboard = mSymbolsKeyboard; | |
| break; | |
| default: | |
| keyboard = mQwertyKeyboard; | |
| mPredictionOn = true; | |
| // Make sure that passwords are not displayed in candidate view | |
| int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; | |
| if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) { | |
| mPredictionOn = false; | |
| } | |
| if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { | |
| mPredictionOn = false; | |
| } | |
| if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { | |
| mPredictionOn = false; | |
| mCompletionOn = isFullscreenMode(); | |
| } | |
| updateShiftKeyState(attribute); | |
| break; | |
| } | |
| if (mInputView != null) { | |
| mInputView.setKeyboard(keyboard); | |
| mInputView.closing(); | |
| } | |
| mComposing.setLength(0); | |
| setSuggestions(null, false, false); | |
| } | |
| @Override | |
| public void onFinishInput() { | |
| super.onFinishInput(); | |
| mComposing.setLength(0); | |
| updateCandidates(); | |
| if (mInputView != null) { | |
| mInputView.closing(); | |
| } | |
| } | |
| @Override | |
| public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { | |
| // If the current selection in the text view changes, we should | |
| // clear whatever candidate text we have. | |
| if (mComposing.length() > 0 && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd)) { | |
| mComposing.setLength(0); | |
| updateCandidates(); | |
| InputConnection ic = getCurrentInputConnection(); | |
| if (ic != null) { | |
| ic.finishComposingText(); | |
| } | |
| } | |
| } | |
| @Override | |
| public void onDisplayCompletions(CompletionInfo[] completions) { | |
| if (mCompletionOn) { | |
| mCompletions = completions; | |
| if (completions == null) { | |
| setSuggestions(null, false, false); | |
| return; | |
| } | |
| List<String> stringList = new ArrayList<String>(); | |
| for (int i = 0; i < (completions != null ? completions.length : 0); i++) { | |
| CompletionInfo ci = completions[i]; | |
| if (ci != null) { | |
| stringList.add(ci.getText().toString()); | |
| } | |
| } | |
| setSuggestions(stringList, true, true); | |
| } | |
| } | |
| private boolean translateKeyDown(int keyCode, KeyEvent event) { | |
| mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, keyCode, event); | |
| int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState)); | |
| mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState); | |
| InputConnection ic = getCurrentInputConnection(); | |
| if (c == 0 || ic == null) { | |
| return false; | |
| } | |
| boolean dead = false; | |
| if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { | |
| dead = true; | |
| c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; | |
| } | |
| if (mComposing.length() > 0) { | |
| char accent = mComposing.charAt(mComposing.length() - 1); | |
| int composed = KeyEvent.getDeadChar(accent, c); | |
| if (composed != 0) { | |
| c = composed; | |
| mComposing.setLength(mComposing.length() - 1); | |
| } | |
| } | |
| onKey(c, null); | |
| return true; | |
| } | |
| @Override | |
| public boolean onKeyDown(int keyCode, KeyEvent event) { | |
| switch (keyCode) { | |
| case KeyEvent.KEYCODE_BACK: | |
| if (event.getRepeatCount() == 0 && mInputView != null) { | |
| if (mInputView.handleBack()) { | |
| return true; | |
| } | |
| } | |
| break; | |
| case KeyEvent.KEYCODE_DEL: | |
| if (mComposing.length() > 0) { | |
| onKey(Keyboard.KEYCODE_DELETE, null); | |
| return true; | |
| } | |
| break; | |
| default: | |
| if (mPredictionOn && translateKeyDown(keyCode, event)) { | |
| return true; | |
| } | |
| } | |
| return super.onKeyDown(keyCode, event); | |
| } | |
| @Override | |
| public boolean onKeyUp(int keyCode, KeyEvent event) { | |
| switch (keyCode) { | |
| case KeyEvent.KEYCODE_DPAD_DOWN: | |
| case KeyEvent.KEYCODE_DPAD_UP: | |
| case KeyEvent.KEYCODE_DPAD_LEFT: | |
| case KeyEvent.KEYCODE_DPAD_RIGHT: | |
| // Enable shift key and DPAD to do selections | |
| if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) { | |
| event = new KeyEvent(event.getDownTime(), event.getEventTime(), event.getAction(), event.getKeyCode(), event.getRepeatCount(), KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); | |
| getCurrentInputConnection().sendKeyEvent(event); | |
| return true; | |
| } | |
| break; | |
| default: | |
| if (mPredictionOn) { | |
| mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, keyCode, event); | |
| } | |
| } | |
| return super.onKeyUp(keyCode, event); | |
| } | |
| private void commitTyped(InputConnection inputConnection) { | |
| if (mComposing.length() > 0) { | |
| inputConnection.commitText(mComposing, mComposing.length()); | |
| mComposing.setLength(0); | |
| updateCandidates(); | |
| } | |
| } | |
| public void updateShiftKeyState(EditorInfo attr) { | |
| if (attr != null && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { | |
| int caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); | |
| mInputView.setShifted(mCapsLock || caps != 0); | |
| } | |
| } | |
| private boolean isAlphabet(int code) { | |
| if (Character.isLetter(code)) { | |
| return true; | |
| } | |
| else { | |
| return false; | |
| } | |
| } | |
| private void keyDownUp(int keyEventCode) { | |
| getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); | |
| getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); | |
| } | |
| private void sendKey(int keyCode) { | |
| switch (keyCode) { | |
| case '\n': | |
| keyDownUp(KeyEvent.KEYCODE_ENTER); | |
| break; | |
| default: | |
| if (keyCode >= '0' && keyCode <= '9') { | |
| keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0); | |
| } | |
| else { | |
| getCurrentInputConnection().commitText(String.valueOf((char)keyCode), 1); | |
| } | |
| break; | |
| } | |
| } | |
| // Implementation of KeyboardViewListener | |
| public void onKey(int primaryCode, int[] keyCodes) { | |
| if (isWordSeparator(primaryCode)) { | |
| // Handle separator | |
| if (mComposing.length() > 0) { | |
| commitTyped(getCurrentInputConnection()); | |
| } | |
| sendKey(primaryCode); | |
| updateShiftKeyState(getCurrentInputEditorInfo()); | |
| } | |
| else if (primaryCode == Keyboard.KEYCODE_DELETE) { | |
| handleBackspace(); | |
| } | |
| else if (primaryCode == Keyboard.KEYCODE_SHIFT) { | |
| handleShift(); | |
| } | |
| else if (primaryCode == Keyboard.KEYCODE_CANCEL) { | |
| handleClose(); | |
| return; | |
| } | |
| else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) { | |
| // Show a menu or somethin' | |
| } | |
| else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mInputView != null) { | |
| Keyboard current = mInputView.getKeyboard(); | |
| if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { | |
| current = mQwertyKeyboard; | |
| } | |
| else { | |
| current = mSymbolsKeyboard; | |
| } | |
| mInputView.setKeyboard(current); | |
| if (current == mSymbolsKeyboard) { | |
| current.setShifted(false); | |
| } | |
| } | |
| else { | |
| handleCharacter(primaryCode, keyCodes); | |
| } | |
| } | |
| /** | |
| * Update the list of available candidates from the current composing | |
| * text. This will need to be filled in by however you are determining | |
| * candidates. | |
| */ | |
| private void updateCandidates() { | |
| if (!mCompletionOn) { | |
| if (mComposing.length() > 0) { | |
| ArrayList<String> list = new ArrayList<String>(); | |
| list.add(mComposing.toString()); | |
| setSuggestions(list, true, true); | |
| } | |
| else { | |
| setSuggestions(null, false, false); | |
| } | |
| } | |
| } | |
| public void setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid) { | |
| mCandidateView.setSuggestions(suggestions, completions, typedWordValid); | |
| if (suggestions != null && suggestions.size() > 0) { | |
| setCandidatesViewShown(true); | |
| } | |
| else if (isFullscreenMode()) { | |
| setCandidatesViewShown(true); | |
| } | |
| else { | |
| setCandidatesViewShown(false); | |
| } | |
| } | |
| private void handleBackspace() { | |
| final int length = mComposing.length(); | |
| if (length > 1) { | |
| mComposing.delete(length - 1, length); | |
| getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); | |
| updateCandidates(); | |
| } | |
| else if (length > 0) { | |
| mComposing.setLength(0); | |
| getCurrentInputConnection().commitText("", 0); | |
| updateCandidates(); | |
| } | |
| else { | |
| //getCurrentInputConnection().deleteSurroundingText(1, 0); | |
| keyDownUp(KeyEvent.KEYCODE_DEL); | |
| } | |
| updateShiftKeyState(getCurrentInputEditorInfo()); | |
| } | |
| private void handleShift() { | |
| if (mInputView == null) { | |
| return; | |
| } | |
| Keyboard currentKeyboard = mInputView.getKeyboard(); | |
| if (mQwertyKeyboard == currentKeyboard) { | |
| // Alphabet keyboard | |
| checkToggleCapsLock(); | |
| mInputView.setShifted(mCapsLock || !mInputView.isShifted()); | |
| } | |
| else if (currentKeyboard == mSymbolsKeyboard) { | |
| mSymbolsKeyboard.setShifted(true); | |
| mInputView.setKeyboard(mSymbolsShiftedKeyboard); | |
| mSymbolsShiftedKeyboard.setShifted(true); | |
| } | |
| else if (currentKeyboard == mSymbolsShiftedKeyboard) { | |
| mSymbolsShiftedKeyboard.setShifted(false); | |
| mInputView.setKeyboard(mSymbolsKeyboard); | |
| mSymbolsKeyboard.setShifted(false); | |
| } | |
| } | |
| private void handleCharacter(int primaryCode, int[] keyCodes) { | |
| if (isInputViewShown()) { | |
| if (mInputView.isShifted()) { | |
| primaryCode = Character.toUpperCase(primaryCode); | |
| } | |
| } | |
| if (isAlphabet(primaryCode) && mPredictionOn) { | |
| mComposing.append((char)primaryCode); | |
| getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); | |
| updateShiftKeyState(getCurrentInputEditorInfo()); | |
| updateCandidates(); | |
| } | |
| else { | |
| getCurrentInputConnection().commitText(String.valueOf((char)primaryCode), 0); | |
| } | |
| } | |
| private void handleClose() { | |
| commitTyped(getCurrentInputConnection()); | |
| dismissSoftInput(0); | |
| mInputView.closing(); | |
| } | |
| private void checkToggleCapsLock() { | |
| long now = System.currentTimeMillis(); | |
| if (mLastShiftTime + 800 > now) { | |
| mCapsLock = !mCapsLock; | |
| mLastShiftTime = 0; | |
| } | |
| else { | |
| mLastShiftTime = now; | |
| } | |
| } | |
| protected String getWordSeparators() { | |
| return mWordSeparators; | |
| } | |
| public boolean isWordSeparator(int code) { | |
| String separators = getWordSeparators(); | |
| return separators.contains(String.valueOf((char)code)); | |
| } | |
| public void pickDefaultCandidate() { | |
| pickSuggestionManually(0); | |
| } | |
| public void pickSuggestionManually(int index) { | |
| if (mCompletionOn && mCompletions != null && index >= 0 && index < mCompletions.length) { | |
| CompletionInfo ci = mCompletions[index]; | |
| getCurrentInputConnection().commitCompletion(ci); | |
| if (mCandidateView != null) { | |
| mCandidateView.clear(); | |
| } | |
| updateShiftKeyState(getCurrentInputEditorInfo()); | |
| } | |
| else if (mComposing.length() > 0) { | |
| // If we were generating candidate suggestions for the current | |
| // text, we would commit one of them here. But for this sample, | |
| // we will just commit the current text. | |
| commitTyped(getCurrentInputConnection()); | |
| } | |
| } | |
| public void swipeRight() { | |
| if (mCompletionOn) { | |
| pickDefaultCandidate(); | |
| } | |
| } | |
| public void swipeLeft() { | |
| handleBackspace(); | |
| } | |
| public void swipeDown() { | |
| handleClose(); | |
| } | |
| public void swipeUp() { | |
| // ? | |
| } | |
| public void onPress(int primaryCode) { | |
| } | |
| public void onRelease(int primaryCode) { | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment