Mega Code Archive

 
Categories / Android / User Event
 

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()[]*&amp;@{}/&lt;&gt;_+=|&quot;</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="&amp;"/>         <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="&quot;"/>         <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="&lt;"/>         <Key android:codes="62" android:keyLabel="&gt;"/>         <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>