Example of writing an input method for a soft keyboard.
// //src\com\example\android\softkeyboard\CandidateView.java /* * 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.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.List; public class CandidateView extends View { private static final int OUT_OF_BOUNDS = -1; private SoftKeyboard mService; private List<String> mSuggestions; private int mSelectedIndex; private int mTouchX = OUT_OF_BOUNDS; private Drawable mSelectionHighlight; private boolean mTypedWordValid; private Rect mBgPadding; private static final int MAX_SUGGESTIONS = 32; private static final int SCROLL_PIXELS = 20; private int[] mWordWidth = new int[MAX_SUGGESTIONS]; private int[] mWordX = new int[MAX_SUGGESTIONS]; private static final int X_GAP = 10; private static final List<String> EMPTY_LIST = new ArrayList<String>(); private int mColorNormal; private int mColorRecommended; private int mColorOther; private int mVerticalPadding; private Paint mPaint; private boolean mScrolled; private int mTargetScrollX; private int mTotalWidth; private GestureDetector mGestureDetector; /** * Construct a CandidateView for showing suggested words for completion. * @param context * @param attrs */ public CandidateView(Context context) { super(context); mSelectionHighlight = context.getResources().getDrawable( android.R.drawable.list_selector_background); mSelectionHighlight.setState(new int[] { android.R.attr.state_enabled, android.R.attr.state_focused, android.R.attr.state_window_focused, android.R.attr.state_pressed }); Resources r = context.getResources(); setBackgroundColor(r.getColor(R.color.candidate_background)); mColorNormal = r.getColor(R.color.candidate_normal); mColorRecommended = r.getColor(R.color.candidate_recommended); mColorOther = r.getColor(R.color.candidate_other); mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); mPaint = new Paint(); mPaint.setColor(mColorNormal); mPaint.setAntiAlias(true); mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); mPaint.setStrokeWidth(0); mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mScrolled = true; int sx = getScrollX(); sx += distanceX; if (sx < 0) { sx = 0; } if (sx + getWidth() > mTotalWidth) { sx -= distanceX; } mTargetScrollX = sx; scrollTo(sx, getScrollY()); invalidate(); return true; } }); setHorizontalFadingEdgeEnabled(true); setWillNotDraw(false); setHorizontalScrollBarEnabled(false); setVerticalScrollBarEnabled(false); } /** * A connection back to the service to communicate with the text field * @param listener */ public void setService(SoftKeyboard listener) { mService = listener; } @Override public int computeHorizontalScrollRange() { return mTotalWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measuredWidth = resolveSize(50, widthMeasureSpec); // Get the desired height of the icon menu view (last row of items does // not have a divider below) Rect padding = new Rect(); mSelectionHighlight.getPadding(padding); final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding + padding.top + padding.bottom; // Maximum possible width and desired height setMeasuredDimension(measuredWidth, resolveSize(desiredHeight, heightMeasureSpec)); } /** * If the canvas is null, then only touch calculations are performed to pick the target * candidate. */ @Override protected void onDraw(Canvas canvas) { if (canvas != null) { super.onDraw(canvas); } mTotalWidth = 0; if (mSuggestions == null) return; if (mBgPadding == null) { mBgPadding = new Rect(0, 0, 0, 0); if (getBackground() != null) { getBackground().getPadding(mBgPadding); } } int x = 0; final int count = mSuggestions.size(); final int height = getHeight(); final Rect bgPadding = mBgPadding; final Paint paint = mPaint; final int touchX = mTouchX; final int scrollX = getScrollX(); final boolean scrolled = mScrolled; final boolean typedWordValid = mTypedWordValid; final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); for (int i = 0; i < count; i++) { String suggestion = mSuggestions.get(i); float textWidth = paint.measureText(suggestion); final int wordWidth = (int) textWidth + X_GAP * 2; mWordX[i] = x; mWordWidth[i] = wordWidth; paint.setColor(mColorNormal); if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { if (canvas != null) { canvas.translate(x, 0); mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); mSelectionHighlight.draw(canvas); canvas.translate(-x, 0); } mSelectedIndex = i; } if (canvas != null) { if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { paint.setFakeBoldText(true); paint.setColor(mColorRecommended); } else if (i != 0) { paint.setColor(mColorOther); } canvas.drawText(suggestion, x + X_GAP, y, paint); paint.setColor(mColorOther); canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, x + wordWidth + 0.5f, height + 1, paint); paint.setFakeBoldText(false); } x += wordWidth; } mTotalWidth = x; if (mTargetScrollX != getScrollX()) { scrollToTarget(); } } private void scrollToTarget() { int sx = getScrollX(); if (mTargetScrollX > sx) { sx += SCROLL_PIXELS; if (sx >= mTargetScrollX) { sx = mTargetScrollX; requestLayout(); } } else { sx -= SCROLL_PIXELS; if (sx <= mTargetScrollX) { sx = mTargetScrollX; requestLayout(); } } scrollTo(sx, getScrollY()); invalidate(); } public void setSuggestions(List<String> suggestions, boolean completions, boolean typedWordValid) { clear(); if (suggestions != null) { mSuggestions = new ArrayList<String>(suggestions); } mTypedWordValid = typedWordValid; scrollTo(0, 0); mTargetScrollX = 0; // Compute the total width onDraw(null); invalidate(); requestLayout(); } public void clear() { mSuggestions = EMPTY_LIST; mTouchX = OUT_OF_BOUNDS; mSelectedIndex = -1; invalidate(); } @Override public boolean onTouchEvent(MotionEvent me) { if (mGestureDetector.onTouchEvent(me)) { return true; } int action = me.getAction(); int x = (int) me.getX(); int y = (int) me.getY(); mTouchX = x; switch (action) { case MotionEvent.ACTION_DOWN: mScrolled = false; invalidate(); break; case MotionEvent.ACTION_MOVE: if (y <= 0) { // Fling up!? if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); mSelectedIndex = -1; } } invalidate(); break; case MotionEvent.ACTION_UP: if (!mScrolled) { if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); } } mSelectedIndex = -1; removeHighlight(); requestLayout(); break; } return true; } /** * For flick through from keyboard, call this method with the x coordinate of the flick * gesture. * @param x */ public void takeSuggestionAt(float x) { mTouchX = (int) x; // To detect candidate onDraw(null); if (mSelectedIndex >= 0) { mService.pickSuggestionManually(mSelectedIndex); } invalidate(); } private void removeHighlight() { mTouchX = OUT_OF_BOUNDS; invalidate(); } } //src\com\example\android\softkeyboard\LatinKeyboard.java /* * 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.content.res.Resources; import android.content.res.XmlResourceParser; import android.inputmethodservice.Keyboard; import android.inputmethodservice.Keyboard.Key; import android.inputmethodservice.Keyboard.Row; import android.view.inputmethod.EditorInfo; public class LatinKeyboard extends Keyboard { private Key mEnterKey; public LatinKeyboard(Context context, int xmlLayoutResId) { super(context, xmlLayoutResId); } public LatinKeyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) { super(context, layoutTemplateResId, characters, columns, horizontalPadding); } @Override protected Key createKeyFromXml(Resources res, Row parent, int x, int y, XmlResourceParser parser) { Key key = new LatinKey(res, parent, x, y, parser); if (key.codes[0] == 10) { mEnterKey = key; } return key; } /** * This looks at the ime options given by the current editor, to set the * appropriate label on the keyboard's enter key (if it has one). */ void setImeOptions(Resources res, int options) { if (mEnterKey == null) { return; } switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { case EditorInfo.IME_ACTION_GO: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_go_key); break; case EditorInfo.IME_ACTION_NEXT: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_next_key); break; case EditorInfo.IME_ACTION_SEARCH: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_search); mEnterKey.label = null; break; case EditorInfo.IME_ACTION_SEND: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_send_key); break; default: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_return); mEnterKey.label = null; break; } } static class LatinKey extends Keyboard.Key { public LatinKey(Resources res, Keyboard.Row parent, int x, int y, XmlResourceParser parser) { super(res, parent, x, y, parser); } /** * Overriding this method so that we can reduce the target area for the key that * closes the keyboard. */ @Override public boolean isInside(int x, int y) { return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y); } } } //src\com\example\android\softkeyboard\LatinKeyboardView.java /* * 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.Keyboard; import android.inputmethodservice.KeyboardView; import android.inputmethodservice.Keyboard.Key; import android.util.AttributeSet; public class LatinKeyboardView extends KeyboardView { static final int KEYCODE_OPTIONS = -100; public LatinKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); } public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected boolean onLongPress(Key key) { if (key.codes[0] == Keyboard.KEYCODE_CANCEL) { getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); return true; } else { return super.onLongPress(key); } } } //src\com\example\android\softkeyboard\SoftKeyboard.java /* * 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.inputmethodservice.InputMethodService; import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.text.method.MetaKeyKeyListener; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; 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; /** * This boolean indicates the optional example code for performing * processing of hard keys in addition to regular text generation * from on-screen interaction. It would be used for input methods that * perform language translations (such as converting text entered on * a QWERTY keyboard to Chinese), but may not be used for input methods * that are primarily intended to be used for on-screen text entry. */ static final boolean PROCESS_HARD_KEYS = true; 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 LatinKeyboard mSymbolsKeyboard; private LatinKeyboard mSymbolsShiftedKeyboard; private LatinKeyboard mQwertyKeyboard; private LatinKeyboard mCurKeyboard; private String mWordSeparators; /** * Main initialization of the input method component. Be sure to call * to super class. */ @Override public void onCreate() { super.onCreate(); mWordSeparators = getResources().getString(R.string.word_separators); } /** * This is the point where you can do all of your UI initialization. It * is called after creation and any configuration change. */ @Override public void onInitializeInterface() { if (mQwertyKeyboard != null) { // Configuration changes can happen after the keyboard gets recreated, // so we need to be able to re-build the keyboards if the available // space has changed. int displayWidth = getMaxWidth(); 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); } /** * Called by the framework when your view for creating input needs to * be generated. This will be called the first time your input method * is displayed, and every time it needs to be re-created such as due to * a configuration change. */ @Override public View onCreateInputView() { mInputView = (KeyboardView) getLayoutInflater().inflate( R.layout.input, null); mInputView.setOnKeyboardActionListener(this); mInputView.setKeyboard(mQwertyKeyboard); return mInputView; } /** * Called by the framework when your view for showing candidates needs to * be generated, like {@link #onCreateInputView}. */ @Override public View onCreateCandidatesView() { mCandidateView = new CandidateView(this); mCandidateView.setService(this); return mCandidateView; } /** * This is the main point where we do our initialization of the input method * to begin operating on an application. At this point we have been * bound to the client, and are now receiving all of the detailed information * about the target of our edits. */ @Override public void onStartInput(EditorInfo attribute, boolean restarting) { super.onStartInput(attribute, restarting); // Reset our state. We want to do this even if restarting, because // the underlying state of the text editor could have changed in any way. mComposing.setLength(0); updateCandidates(); if (!restarting) { // Clear shift states. mMetaState = 0; } mPredictionOn = false; mCompletionOn = false; mCompletions = null; // We are now going to initialize our state based on the type of // text being edited. switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { case EditorInfo.TYPE_CLASS_NUMBER: case EditorInfo.TYPE_CLASS_DATETIME: // Numbers and dates default to the symbols keyboard, with // no extra features. mCurKeyboard = mSymbolsKeyboard; break; case EditorInfo.TYPE_CLASS_PHONE: // Phones will also default to the symbols keyboard, though // often you will want to have a dedicated phone keyboard. mCurKeyboard = mSymbolsKeyboard; break; case EditorInfo.TYPE_CLASS_TEXT: // This is general text editing. We will default to the // normal alphabetic keyboard, and assume that we should // be doing predictive text (showing candidates as the // user types). mCurKeyboard = mQwertyKeyboard; mPredictionOn = true; // We now look for a few special variations of text that will // modify our behavior. int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { // Do not display predictions / what the user is typing // when they are entering a password. mPredictionOn = false; } if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == EditorInfo.TYPE_TEXT_VARIATION_URI || variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { // Our predictions are not useful for e-mail addresses // or URIs. mPredictionOn = false; } if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { // If this is an auto-complete text view, then our predictions // will not be shown and instead we will allow the editor // to supply their own. We only show the editor's // candidates when in fullscreen mode, otherwise relying // own it displaying its own UI. mPredictionOn = false; mCompletionOn = isFullscreenMode(); } // We also want to look at the current state of the editor // to decide whether our alphabetic keyboard should start out // shifted. updateShiftKeyState(attribute); break; default: // For all unknown input types, default to the alphabetic // keyboard with no special features. mCurKeyboard = mQwertyKeyboard; updateShiftKeyState(attribute); } // Update the label on the enter key, depending on what the application // says it will do. mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions); } /** * This is called when the user is done editing a field. We can use * this to reset our state. */ @Override public void onFinishInput() { super.onFinishInput(); // Clear current composing text and candidates. mComposing.setLength(0); updateCandidates(); // We only hide the candidates window when finishing input on // a particular editor, to avoid popping the underlying application // up and down if the user is entering text into the bottom of // its window. setCandidatesViewShown(false); mCurKeyboard = mQwertyKeyboard; if (mInputView != null) { mInputView.closing(); } } @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { super.onStartInputView(attribute, restarting); // Apply the selected keyboard to the input view. mInputView.setKeyboard(mCurKeyboard); mInputView.closing(); } /** * Deal with the editor reporting movement of its cursor. */ @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, 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(); } } } /** * This tells us about completions that the editor has determined based * on the current text in it. We want to use this in fullscreen mode * to show the completions ourself, since the editor can not be seen * in that situation. */ @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); } } /** * This translates incoming hard key events in to edit operations on an * InputConnection. It is only needed when using the * PROCESS_HARD_KEYS option. */ 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; } /** * Use this to monitor key events being delivered to the application. * We get first crack at them, and can either resume them or let them * continue to the app. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: // The InputMethodService already takes care of the back // key for us, to dismiss the input method if it is shown. // However, our keyboard could be showing a pop-up window // that back should dismiss, so we first allow it to do that. if (event.getRepeatCount() == 0 && mInputView != null) { if (mInputView.handleBack()) { return true; } } break; case KeyEvent.KEYCODE_DEL: // Special handling of the delete key: if we currently are // composing text for the user, we want to modify that instead // of let the application to the delete itself. if (mComposing.length() > 0) { onKey(Keyboard.KEYCODE_DELETE, null); return true; } break; case KeyEvent.KEYCODE_ENTER: // Let the underlying text editor always handle these. return false; default: // For all other keys, if we want to do transformations on // text being entered with a hard keyboard, we need to process // it and do the appropriate action. if (PROCESS_HARD_KEYS) { if (keyCode == KeyEvent.KEYCODE_SPACE && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) { // A silly example: in our input method, Alt+Space // is a shortcut for 'android' in lower case. InputConnection ic = getCurrentInputConnection(); if (ic != null) { // First, tell the editor that it is no longer in the // shift state, since we are consuming this. ic.clearMetaKeyStates(KeyEvent.META_ALT_ON); keyDownUp(KeyEvent.KEYCODE_A); keyDownUp(KeyEvent.KEYCODE_N); keyDownUp(KeyEvent.KEYCODE_D); keyDownUp(KeyEvent.KEYCODE_R); keyDownUp(KeyEvent.KEYCODE_O); keyDownUp(KeyEvent.KEYCODE_I); keyDownUp(KeyEvent.KEYCODE_D); // And we consume this event. return true; } } if (mPredictionOn && translateKeyDown(keyCode, event)) { return true; } } } return super.onKeyDown(keyCode, event); } /** * Use this to monitor key events being delivered to the application. * We get first crack at them, and can either resume them or let them * continue to the app. */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { // If we want to do transformations on text being entered with a hard // keyboard, we need to process the up events to update the meta key // state we are tracking. if (PROCESS_HARD_KEYS) { if (mPredictionOn) { mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, keyCode, event); } } return super.onKeyUp(keyCode, event); } /** * Helper function to commit any text being composed in to the editor. */ private void commitTyped(InputConnection inputConnection) { if (mComposing.length() > 0) { inputConnection.commitText(mComposing, mComposing.length()); mComposing.setLength(0); updateCandidates(); } } /** * Helper to update the shift state of our keyboard based on the initial * editor state. */ private void updateShiftKeyState(EditorInfo attr) { if (attr != null && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { int caps = 0; EditorInfo ei = getCurrentInputEditorInfo(); if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) { caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); } mInputView.setShifted(mCapsLock || caps != 0); } } /** * Helper to determine if a given character code is alphabetic. */ private boolean isAlphabet(int code) { if (Character.isLetter(code)) { return true; } else { return false; } } /** * Helper to send a key down / key up pair to the current editor. */ private void keyDownUp(int keyEventCode) { getCurrentInputConnection().sendKeyEvent( new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); getCurrentInputConnection().sendKeyEvent( new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); } /** * Helper to send a character to the editor as raw key events. */ 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); } } public void onText(CharSequence text) { InputConnection ic = getCurrentInputConnection(); if (ic == null) return; ic.beginBatchEdit(); if (mComposing.length() > 0) { commitTyped(ic); } ic.commitText(text, 0); ic.endBatchEdit(); updateShiftKeyState(getCurrentInputEditorInfo()); } /** * 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) { if (suggestions != null && suggestions.size() > 0) { setCandidatesViewShown(true); } else if (isExtractViewShown()) { setCandidatesViewShown(true); } if (mCandidateView != null) { mCandidateView.setSuggestions(suggestions, completions, typedWordValid); } } private void handleBackspace() { final int length = mComposing.length(); if (length > 1) { mComposing.delete(length - 1, length); getCurrentInputConnection().setComposingText(mComposing, 1); updateCandidates(); } else if (length > 0) { mComposing.setLength(0); getCurrentInputConnection().commitText("", 0); updateCandidates(); } else { 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, 1); updateShiftKeyState(getCurrentInputEditorInfo()); updateCandidates(); } else { getCurrentInputConnection().commitText( String.valueOf((char) primaryCode), 1); } } private void handleClose() { commitTyped(getCurrentInputConnection()); requestHideSelf(0); mInputView.closing(); } private void checkToggleCapsLock() { long now = System.currentTimeMillis(); if (mLastShiftTime + 800 > now) { mCapsLock = !mCapsLock; mLastShiftTime = 0; } else { mLastShiftTime = now; } } private 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) { } } // //res\layout\input.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <com.example.android.softkeyboard.LatinKeyboardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/keyboard" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content" /> // //res\values\colors.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <resources> <color name="candidate_normal">#FF000000</color> <color name="candidate_recommended">#FFE35900</color> <color name="candidate_other">#ff808080</color> <color name="candidate_background">#bbffffff</color> </resources> //res\values\dimens.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <resources> <dimen name="key_height">50dip</dimen> <dimen name="candidate_font_height">16sp</dimen> <dimen name="candidate_vertical_padding">6sp</dimen> </resources> //res\values\strings.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Title for Latin keyboard --> <string name="ime_name">Sample Soft Keyboard</string> <!-- Symbols that are commonly considered word separators in this language --> <string name="word_separators">\u0020.,;:!?\n()[]*&@{}/<>_+=|"</string> <!-- Labels on soft keys --> <string name="label_go_key">Go</string> <string name="label_next_key">Next</string> <string name="label_send_key">Send</string> </resources> // //res\values-land\dimens.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <resources> <dimen name="key_height">46dip</dimen> </resources> // //res\xml\method.xml <?xml version="1.0" encoding="utf-8"?> <!-- /** * Copyright (c) 2008, The Android Open Source Project * * 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. */ --> <!-- The attributes in this XML file provide configuration information --> <!-- for the Search Manager. --> <input-method xmlns:android="http://schemas.android.com/apk/res/android" /> //res\xml\qwerty.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="10%p" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="@dimen/key_height" > <Row> <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/> <Key android:codes="119" android:keyLabel="w"/> <Key android:codes="101" android:keyLabel="e"/> <Key android:codes="114" android:keyLabel="r"/> <Key android:codes="116" android:keyLabel="t"/> <Key android:codes="121" android:keyLabel="y"/> <Key android:codes="117" android:keyLabel="u"/> <Key android:codes="105" android:keyLabel="i"/> <Key android:codes="111" android:keyLabel="o"/> <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p" android:keyEdgeFlags="left"/> <Key android:codes="115" android:keyLabel="s"/> <Key android:codes="100" android:keyLabel="d"/> <Key android:codes="102" android:keyLabel="f"/> <Key android:codes="103" android:keyLabel="g"/> <Key android:codes="104" android:keyLabel="h"/> <Key android:codes="106" android:keyLabel="j"/> <Key android:codes="107" android:keyLabel="k"/> <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" android:keyWidth="15%p" android:isModifier="true" android:isSticky="true" android:keyEdgeFlags="left"/> <Key android:codes="122" android:keyLabel="z"/> <Key android:codes="120" android:keyLabel="x"/> <Key android:codes="99" android:keyLabel="c"/> <Key android:codes="118" android:keyLabel="v"/> <Key android:codes="98" android:keyLabel="b"/> <Key android:codes="110" android:keyLabel="n"/> <Key android:codes="109" android:keyLabel="m"/> <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> </Row> <Row android:rowEdgeFlags="bottom"> <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" android:keyWidth="20%p" android:keyEdgeFlags="left"/> <Key android:codes="-2" android:keyLabel="123" android:keyWidth="15%p"/> <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" android:isRepeatable="true"/> <Key android:codes="46,44" android:keyLabel=". ," android:keyWidth="15%p"/> <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right"/> </Row> </Keyboard> //res\xml\symbols.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="10%p" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="@dimen/key_height" > <Row> <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/> <Key android:codes="50" android:keyLabel="2"/> <Key android:codes="51" android:keyLabel="3"/> <Key android:codes="52" android:keyLabel="4"/> <Key android:codes="53" android:keyLabel="5"/> <Key android:codes="54" android:keyLabel="6"/> <Key android:codes="55" android:keyLabel="7"/> <Key android:codes="56" android:keyLabel="8"/> <Key android:codes="57" android:keyLabel="9"/> <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/> <Key android:codes="35" android:keyLabel="\#"/> <Key android:codes="36" android:keyLabel="$"/> <Key android:codes="37" android:keyLabel="%"/> <Key android:codes="38" android:keyLabel="&"/> <Key android:codes="42" android:keyLabel="*"/> <Key android:codes="45" android:keyLabel="-"/> <Key android:codes="61" android:keyLabel="="/> <Key android:codes="40" android:keyLabel="("/> <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" android:keyWidth="15%p" android:isModifier="true" android:isSticky="true" android:keyEdgeFlags="left"/> <Key android:codes="33" android:keyLabel="!" /> <Key android:codes="34" android:keyLabel="""/> <Key android:codes="39" android:keyLabel="\'"/> <Key android:codes="58" android:keyLabel=":"/> <Key android:codes="59" android:keyLabel=";"/> <Key android:codes="47" android:keyLabel="/" /> <Key android:codes="63" android:keyLabel="\?"/> <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> </Row> <Row android:rowEdgeFlags="bottom"> <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" android:keyWidth="20%p" android:keyEdgeFlags="left" /> <Key android:codes="-2" android:keyLabel="ABC" android:keyWidth="15%p" /> <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" android:isRepeatable="true"/> <Key android:codes="44" android:keyLabel="," android:keyWidth="15%p" /> <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right" /> </Row> </Keyboard> //res\xml\symbols_shift.xml <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 2008, The Android Open Source Project ** ** 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. */ --> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="10%p" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="@dimen/key_height" > <Row> <Key android:codes="126" android:keyLabel="" android:keyEdgeFlags="left"/> <Key android:codes="177" android:keyLabel=""/> <Key android:codes="215" android:keyLabel=""/> <Key android:codes="247" android:keyLabel=""/> <Key android:codes="8226" android:keyLabel=""/> <Key android:codes="176" android:keyLabel=""/> <Key android:codes="96" android:keyLabel="`"/> <Key android:codes="180" android:keyLabel=""/> <Key android:codes="123" android:keyLabel="{"/> <Key android:codes="125" android:keyLabel="}" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="169" android:keyLabel="" android:keyEdgeFlags="left"/> <Key android:codes="163" android:keyLabel=""/> <Key android:codes="8364" android:keyLabel=""/> <Key android:codes="94" android:keyLabel="^"/> <Key android:codes="174" android:keyLabel=""/> <Key android:codes="165" android:keyLabel=""/> <Key android:codes="95" android:keyLabel="_"/> <Key android:codes="43" android:keyLabel="+"/> <Key android:codes="91" android:keyLabel="["/> <Key android:codes="93" android:keyLabel="]" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" android:keyWidth="15%p" android:isModifier="true" android:isSticky="true" android:keyEdgeFlags="left"/> <Key android:codes="161" android:keyLabel="" /> <Key android:codes="60" android:keyLabel="<"/> <Key android:codes="62" android:keyLabel=">"/> <Key android:codes="162" android:keyLabel=""/> <Key android:codes="124" android:keyLabel="|"/> <Key android:codes="92" android:keyLabel="\\" /> <Key android:codes="191" android:keyLabel=""/> <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> </Row> <Row android:rowEdgeFlags="bottom"> <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" android:keyWidth="20%p" android:keyEdgeFlags="left" /> <Key android:codes="-2" android:keyLabel="ABC" android:keyWidth="15%p" /> <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" android:isRepeatable="true"/> <Key android:codes="8230" android:keyLabel="" android:keyWidth="15%p" /> <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right" /> </Row> </Keyboard>
1. | Key event listener | ||
2. | Soft keyboard get Char | ||
3. | show hide virtual keyboard | ||
4. | has Physical Keyboard | ||
5. | Show Keyboard | ||
6. | Hide Keyboard | ||
7. | intrepret Direction Key |