【android开发】自定义数字软键盘的设计与实现(1)

转载 2016年08月30日 11:39:52

相信很多情况下我们看到过一些数字的输入,弹出来的并不是系统自带的键盘。这就是自定义的软键盘,软键盘的一个好处就是简单,操作方便。如何实现一个自定义的软键盘呢??其实这个过程是比较简单的,只要把几个关键的原理搞明白了,你就会发现真的很简单,很方便!看一下效果图:


这篇博客主要介绍一下实现的相关原理,下一节就会把具体实现的步骤和大家分享一下!

实现软键盘主要用到了系统的两个类Keyboard和KeyboardView:

Keyboard类源码的介绍是: Listener for virtual keyboard events.即用于监听虚拟键盘。

至于Keyboard类的映射机制,这里就不需要说了,本人感觉也没必要了解的太深,这里要介绍一些他,在接下来需要的几个重要的内容。我们看一下他的源码:

[java] view plain copy
  1. package com.android.inputmethod.keyboard;  
  2.   
  3. import android.util.Log;  
  4. import android.util.SparseArray;  
  5. import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;  
  6. import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;  
  7. import com.android.inputmethod.keyboard.internal.KeyboardParams;  
  8. import com.android.inputmethod.latin.CollectionUtils;  
  9.   
  10. /** 
  11.  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 
  12.  * consists of rows of keys. 
  13.  * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 
  14.  * <pre> 
  15.  * <Keyboard 
  16.  *         latin:keyWidth="%10p" 
  17.  *         latin:keyHeight="50px" 
  18.  *         latin:horizontalGap="2px" 
  19.  *         latin:verticalGap="2px" > 
  20.  *     <Row latin:keyWidth="32px" > 
  21.  *         <Key latin:keyLabel="A" /> 
  22.  *         ... 
  23.  *     </Row> 
  24.  *     ... 
  25.  * </Keyboard> 
  26.  * </pre> 
  27.  */  
  28. public class Keyboard {  
  29.     private static final String TAG = Keyboard.class.getSimpleName();  
  30.   
  31.   
  32.     /** Some common keys code. Must be positive. 
  33.      * These should be aligned with values/keycodes.xml 
  34.      */  
  35.     public static final int CODE_ENTER = '\n';  
  36.     public static final int CODE_TAB = '\t';  
  37.     public static final int CODE_SPACE = ' ';  
  38.     public static final int CODE_PERIOD = '.';  
  39.     public static final int CODE_DASH = '-';  
  40.     public static final int CODE_SINGLE_QUOTE = '\'';  
  41.     public static final int CODE_DOUBLE_QUOTE = '"';  
  42.     public static final int CODE_QUESTION_MARK = '?';  
  43.     public static final int CODE_EXCLAMATION_MARK = '!';  
  44.     // TODO: Check how this should work for right-to-left languages. It seems to stand  
  45.     // that for rtl languages, a closing parenthesis is a left parenthesis. Is this  
  46.     // managed by the font? Or is it a different char?  
  47.     public static final int CODE_CLOSING_PARENTHESIS = ')';  
  48.     public static final int CODE_CLOSING_SQUARE_BRACKET = ']';  
  49.     public static final int CODE_CLOSING_CURLY_BRACKET = '}';  
  50.     public static final int CODE_CLOSING_ANGLE_BRACKET = '>';  
  51.     /** Special keys code. Must be negative. 
  52.      * These should be aligned with KeyboardCodesSet.ID_TO_NAME[], 
  53.      * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[] 
  54.      */  
  55.     public static final int CODE_SHIFT = -1;  
  56.     public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;  
  57.     public static final int CODE_OUTPUT_TEXT = -3;  
  58.     public static final int CODE_DELETE = -4;  
  59.     public static final int CODE_SETTINGS = -5;  
  60.     public static final int CODE_SHORTCUT = -6;  
  61.     public static final int CODE_ACTION_ENTER = -7;  
  62.     public static final int CODE_ACTION_NEXT = -8;  
  63.     public static final int CODE_ACTION_PREVIOUS = -9;  
  64.     public static final int CODE_LANGUAGE_SWITCH = -10;  
  65.     public static final int CODE_RESEARCH = -11;  
  66.     // Code value representing the code is not specified.  
  67.     public static final int CODE_UNSPECIFIED = -12;  
  68.     public final KeyboardId mId;  
  69.     public final int mThemeId;  
  70.   
  71.     /** Total height of the keyboard, including the padding and keys */  
  72.     public final int mOccupiedHeight;  
  73.     /** Total width of the keyboard, including the padding and keys */  
  74.     public final int mOccupiedWidth;  
  75.     /** The padding above the keyboard */  
  76.     public final int mTopPadding;  
  77.     /** Default gap between rows */  
  78.     public final int mVerticalGap;  
  79.   
  80.     /** Per keyboard key visual parameters */  
  81.     public final KeyVisualAttributes mKeyVisualAttributes;  
  82.     public final int mMostCommonKeyHeight;  
  83.     public final int mMostCommonKeyWidth;  
  84.     /** More keys keyboard template */  
  85.     public final int mMoreKeysTemplate;  
  86.     /** Maximum column for more keys keyboard */  
  87.     public final int mMaxMoreKeysKeyboardColumn;  
  88.     /** Array of keys and icons in this keyboard */  
  89.     public final Key[] mKeys;  
  90.     public final Key[] mShiftKeys;  
  91.     public final Key[] mAltCodeKeysWhileTyping;  
  92.     public final KeyboardIconsSet mIconsSet;  
  93.     private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();  
  94.     private final ProximityInfo mProximityInfo;  
  95.     private final boolean mProximityCharsCorrectionEnabled;  
  96.     public Keyboard(final KeyboardParams params) {  
  97.     /// M: @{  
  98.         ProximityInfo tmp;  
  99.         /// @}  
  100.         mId = params.mId;  
  101.         mThemeId = params.mThemeId;  
  102.         mOccupiedHeight = params.mOccupiedHeight;  
  103.         mOccupiedWidth = params.mOccupiedWidth;  
  104.         mMostCommonKeyHeight = params.mMostCommonKeyHeight;  
  105.         mMostCommonKeyWidth = params.mMostCommonKeyWidth;  
  106.         mMoreKeysTemplate = params.mMoreKeysTemplate;  
  107.         mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;  
  108.         mKeyVisualAttributes = params.mKeyVisualAttributes;  
  109.         mTopPadding = params.mTopPadding;  
  110.         mVerticalGap = params.mVerticalGap;  
  111.         mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]);  
  112.         mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]);  
  113.         mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray(  
  114.                 new Key[params.mAltCodeKeysWhileTyping.size()]);  
  115.         mIconsSet = params.mIconsSet;  
  116.         /// M: Modified by MTK to avoid OOME thrown to DVM on LCA  
  117.         //     since there is an int array sized about 32KB allocated  
  118.         //     within ProximityInfo constructor. @{  
  119.         try {  
  120.             tmp = new ProximityInfo(params.mId.mLocale.toString(),  
  121.                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,  
  122.                 mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);  
  123.         } catch (OutOfMemoryError e) {  
  124.             Log.w(TAG, "OutOfMemoryError at ProximityInfo");  
  125.             /// M: It`s dangerous here, since this approach will generate  
  126.             //     a proximity useless, this will cause no responce on VK.  
  127.             // TODO: It`s better to unbind LatinIME here or provide another  
  128.             //       keyboard here.  
  129.             tmp = ProximityInfo.createDummyProximityInfo();  
  130.         }  
  131.         mProximityInfo = tmp;  
  132.         /// @}  
  133.         mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;  
  134.     }  
  135.     public boolean hasProximityCharsCorrection(final int code) {  
  136.         if (!mProximityCharsCorrectionEnabled) {  
  137.             return false;  
  138.         }  
  139.         // Note: The native code has the main keyboard layout only at this moment.  
  140.         // TODO: Figure out how to handle proximity characters information of all layouts.  
  141.         final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (  
  142.                 mId.mElementId == KeyboardId.ELEMENT_ALPHABET  
  143.                 || mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);  
  144.         return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);  
  145.     }  
  146.     public ProximityInfo getProximityInfo() {  
  147.         return mProximityInfo;  
  148.     }  
  149.     public Key getKey(final int code) {  
  150.         if (code == CODE_UNSPECIFIED) {  
  151.             return null;  
  152.         }  
  153.         synchronized (mKeyCache) {  
  154.             final int index = mKeyCache.indexOfKey(code);  
  155.             if (index >= 0) {  
  156.                 return mKeyCache.valueAt(index);  
  157.             }  
  158.             for (final Key key : mKeys) {  
  159.                 if (key.mCode == code) {  
  160.                     mKeyCache.put(code, key);  
  161.                     return key;  
  162.                 }  
  163.             }  
  164.             mKeyCache.put(code, null);  
  165.             return null;  
  166.         }  
  167.     }  
  168.     public boolean hasKey(final Key aKey) {  
  169.         if (mKeyCache.indexOfValue(aKey) >= 0) {  
  170.             return true;  
  171.         }  
  172.   
  173.   
  174.         for (final Key key : mKeys) {  
  175.             if (key == aKey) {  
  176.                 mKeyCache.put(key.mCode, key);  
  177.                 return true;  
  178.             }  
  179.         }  
  180.         return false;  
  181.     }  
  182.     public static boolean isLetterCode(final int code) {  
  183.         return code >= CODE_SPACE;  
  184.     }  
  185.     @Override  
  186.     public String toString() {  
  187.         return mId.toString();  
  188.     }  
  189.     /** 
  190.      * Returns the array of the keys that are closest to the given point. 
  191.      * @param x the x-coordinate of the point 
  192.      * @param y the y-coordinate of the point 
  193.      * @return the array of the nearest keys to the given point. If the given 
  194.      * point is out of range, then an array of size zero is returned. 
  195.      */  
  196.     public Key[] getNearestKeys(final int x, final int y) {  
  197.         // Avoid dead pixels at edges of the keyboard  
  198.         final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));  
  199.         final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));  
  200.         return mProximityInfo.getNearestKeys(adjustedX, adjustedY);  
  201.     }  
  202.     public static String printableCode(final int code) {  
  203.         switch (code) {  
  204.         case CODE_SHIFT: return "shift";  
  205.         case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";  
  206.         case CODE_OUTPUT_TEXT: return "text";  
  207.         case CODE_DELETE: return "delete";  
  208.         case CODE_SETTINGS: return "settings";  
  209.         case CODE_SHORTCUT: return "shortcut";  
  210.         case CODE_ACTION_ENTER: return "actionEnter";  
  211.         case CODE_ACTION_NEXT: return "actionNext";  
  212.         case CODE_ACTION_PREVIOUS: return "actionPrevious";  
  213.         case CODE_LANGUAGE_SWITCH: return "languageSwitch";  
  214.         case CODE_UNSPECIFIED: return "unspec";  
  215.         case CODE_TAB: return "tab";  
  216.         case CODE_ENTER: return "enter";  
  217.         default:  
  218.             if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);  
  219.             if (code < CODE_SPACE) return String.format("'\\u%02x'", code);  
  220.             if (code < 0x100return String.format("'%c'", code);  
  221.             return String.format("'\\u%04x'", code);  
  222.         }  
  223.     }  
  224. }  


我们在设置每一个按键的code时,就是根据keyboard类中定义的一些属性,比如回退,删除,清空等都是固定。

知道了

    public static final int CODE_SHIFT = -1;
    public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
    public static final int CODE_OUTPUT_TEXT = -3;
    public static final int CODE_DELETE = -4;
    public static final int CODE_SETTINGS = -5;
    public static final int CODE_SHORTCUT = -6;
    public static final int CODE_ACTION_ENTER = -7;
    public static final int CODE_ACTION_NEXT = -8;
    public static final int CODE_ACTION_PREVIOUS = -9;
    public static final int CODE_LANGUAGE_SWITCH = -10;
    public static final int CODE_RESEARCH = -11;
    // Code value representing the code is not specified.
    public static final int CODE_UNSPECIFIED = -12;

我们就不会有太多的疑惑了!也是说,我们自定义的每一个按键都将会有一个codes值,比如回退我们就写成:<Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"/>

在监听就是:if (primaryCode == Keyboard.KEYCODE_DELETE){}.这就表示,监听回退事件了!


KeyboardView类源码的介绍是: A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key presses and touch movements.即它处理绘制钥匙和检测按键和触摸动作。源代码如下:

[java] view plain copy
  1. /* 
  2.  * Copyright (C) 2008-2012  OMRON SOFTWARE Co., Ltd. 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. /* This file is porting from Android framework. 
  17.  *   frameworks/base/core/java/android/inputmethodservice/KeyboardView.java 
  18.  * 
  19.  *package android.inputmethodservice; 
  20.  */  
  21.   
  22. package jp.co.omronsoft.openwnn;  
  23.   
  24. import android.content.Context;  
  25. import android.content.res.TypedArray;  
  26. import android.graphics.Bitmap;  
  27. import android.graphics.Canvas;  
  28. import android.graphics.Paint;  
  29. import android.graphics.PorterDuff;  
  30. import android.graphics.Rect;  
  31. import android.graphics.Typeface;  
  32. import android.graphics.Paint.Align;  
  33. import android.graphics.Region.Op;  
  34. import android.graphics.drawable.Drawable;  
  35. import android.os.Handler;  
  36. import android.os.Message;  
  37. import android.util.AttributeSet;  
  38. import android.util.TypedValue;  
  39. import android.view.GestureDetector;  
  40. import android.view.Gravity;  
  41. import android.view.LayoutInflater;  
  42. import android.view.MotionEvent;  
  43. import android.view.View;  
  44. import android.view.ViewConfiguration;  
  45. import android.view.ViewGroup.LayoutParams;  
  46. import android.widget.PopupWindow;  
  47. import android.widget.TextView;  
  48.   
  49. import jp.co.omronsoft.openwnn.Keyboard;  
  50. import jp.co.omronsoft.openwnn.Keyboard.Key;  
  51.   
  52. import java.util.Arrays;  
  53. import java.util.HashMap;  
  54. import java.util.List;  
  55. import java.util.Map;  
  56.   
  57. /** 
  58.  * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and 
  59.  * detecting key presses and touch movements. 
  60.  */  
  61. public class KeyboardView extends View implements View.OnClickListener {  
  62.   
  63.     /** 
  64.      * Listener for virtual keyboard events. 
  65.      */  
  66.     public interface OnKeyboardActionListener {  
  67.    
  68.         /** 
  69.          * Called when the user presses a key. This is sent before the {@link #onKey} is called. 
  70.          * For keys that repeat, this is only called once. 
  71.          * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid 
  72.          * key, the value will be zero. 
  73.          */  
  74.         void onPress(int primaryCode);  
  75.    
  76.         /** 
  77.          * Called when the user releases a key. This is sent after the {@link #onKey} is called. 
  78.          * For keys that repeat, this is only called once. 
  79.          * @param primaryCode the code of the key that was released 
  80.          */  
  81.         void onRelease(int primaryCode);  
  82.   
  83.         /** 
  84.          * Send a key press to the listener. 
  85.          * @param primaryCode this is the key that was pressed 
  86.          * @param keyCodes the codes for all the possible alternative keys 
  87.          * with the primary code being the first. If the primary key code is 
  88.          * a single character such as an alphabet or number or symbol, the alternatives 
  89.          * will include other characters that may be on the same key or adjacent keys. 
  90.          * These codes are useful to correct for accidental presses of a key adjacent to 
  91.          * the intended key. 
  92.          */  
  93.         void onKey(int primaryCode, int[] keyCodes);  
  94.   
  95.         /** 
  96.          * Sends a sequence of characters to the listener. 
  97.          * @param text the sequence of characters to be displayed. 
  98.          */  
  99.         void onText(CharSequence text);  
  100.   
  101.         /** 
  102.          * Called when the user quickly moves the finger from right to left. 
  103.          */  
  104.         void swipeLeft();  
  105.   
  106.         /** 
  107.          * Called when the user quickly moves the finger from left to right. 
  108.          */  
  109.         void swipeRight();  
  110.   
  111.         /** 
  112.          * Called when the user quickly moves the finger from up to down. 
  113.          */  
  114.         void swipeDown();  
  115.   
  116.         /** 
  117.          * Called when the user quickly moves the finger from down to up. 
  118.          */  
  119.         void swipeUp();  
  120.   
  121.         /** 
  122.          * Called when the user long presses a key. 
  123.          * @param popupKey the key that was long pressed 
  124.          * @return true if the long press is handled, false otherwise. 
  125.          */  
  126.         boolean onLongPress(Keyboard.Key key);  
  127.     }  
  128.   
  129.     private static final int NOT_A_KEY = -1;  
  130.     private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };  
  131.     private static final int[] LONG_PRESSABLE_STATE_SET = {  
  132.         android.R.attr.state_long_pressable  
  133.     };  
  134.   
  135.     private Keyboard mKeyboard;  
  136.     private int mCurrentKeyIndex = NOT_A_KEY;  
  137.     private int mLabelTextSize;  
  138.     private int mKeyTextSize;  
  139.     private int mKeyTextColor;  
  140.     private int mKeyTextColor2nd;  
  141.     private float mShadowRadius;  
  142.     private int mShadowColor;  
  143.     private float mBackgroundDimAmount;  
  144.    
  145.     private TextView mPreviewText;  
  146.     private PopupWindow mPreviewPopup;  
  147.     private int mPreviewTextSizeLarge;  
  148.     private int mPreviewOffset;  
  149.     private int mPreviewHeight;  
  150.     private int[] mOffsetInWindow;  
  151.   
  152.     private PopupWindow mPopupKeyboard;  
  153.     private View mMiniKeyboardContainer;  
  154.     private KeyboardView mMiniKeyboard;  
  155.     private boolean mMiniKeyboardOnScreen;  
  156.     private View mPopupParent;  
  157.     private int mMiniKeyboardOffsetX;  
  158.     private int mMiniKeyboardOffsetY;  
  159.     private Map<Key,View> mMiniKeyboardCache;  
  160.     private int[] mWindowOffset;  
  161.     private Key[] mKeys;  
  162.   
  163.     /** Listener for {@link OnKeyboardActionListener}. */  
  164.     private OnKeyboardActionListener mKeyboardActionListener;  
  165.   
  166.     private static final int MSG_SHOW_PREVIEW = 1;  
  167.     private static final int MSG_REMOVE_PREVIEW = 2;  
  168.     private static final int MSG_REPEAT = 3;  
  169.     private static final int MSG_LONGPRESS = 4;  
  170.   
  171.     private static final int DELAY_BEFORE_PREVIEW = 0;  
  172.     private static final int DELAY_AFTER_PREVIEW = 70;  
  173.     private static final int DEBOUNCE_TIME = 70;  
  174.   
  175.     private int mVerticalCorrection;  
  176.     private int mProximityThreshold;  
  177.   
  178.     private boolean mPreviewCentered = false;  
  179.     private boolean mShowPreview = true;  
  180.     private boolean mShowTouchPoints = true;  
  181.     private int mPopupPreviewX;  
  182.     private int mPopupPreviewY;  
  183.     private int mWindowY;  
  184.   
  185.     private int mLastX;  
  186.     private int mLastY;  
  187.     private int mStartX;  
  188.     private int mStartY;  
  189.   
  190.     private boolean mProximityCorrectOn;  
  191.   
  192.     private Paint mPaint;  
  193.     private Rect mPadding;  
  194.    
  195.     private long mDownTime;  
  196.     private long mLastMoveTime;  
  197.     private int mLastKey;  
  198.     private int mLastCodeX;  
  199.     private int mLastCodeY;  
  200.     private int mCurrentKey = NOT_A_KEY;  
  201.     private int mDownKey = NOT_A_KEY;  
  202.     private long mLastKeyTime;  
  203.     private long mCurrentKeyTime;  
  204.     private int[] mKeyIndices = new int[12];  
  205.     private GestureDetector mGestureDetector;  
  206.     private int mPopupX;  
  207.     private int mPopupY;  
  208.     private int mRepeatKeyIndex = NOT_A_KEY;  
  209.     private int mPopupLayout;  
  210.     private boolean mAbortKey;  
  211.     private Key mInvalidatedKey;  
  212.     private Rect mClipRegion = new Rect(0000);  
  213.     private boolean mPossiblePoly;  
  214.     private SwipeTracker mSwipeTracker = new SwipeTracker();  
  215.     private int mSwipeThreshold;  
  216.     private boolean mDisambiguateSwipe;  
  217.   
  218.     private int mOldPointerCount = 1;  
  219.     private float mOldPointerX;  
  220.     private float mOldPointerY;  
  221.   
  222.     private Drawable mKeyBackground;  
  223.     private Drawable mKeyBackground2nd;  
  224.   
  225.     private static final int REPEAT_INTERVAL = 50;  
  226.     private static final int REPEAT_START_DELAY = 400;  
  227.     private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();  
  228.   
  229.     private static int MAX_NEARBY_KEYS = 12;  
  230.     private int[] mDistances = new int[MAX_NEARBY_KEYS];  
  231.   
  232.     private int mLastSentIndex;  
  233.     private int mTapCount;  
  234.     private long mLastTapTime;  
  235.     private boolean mInMultiTap;  
  236.     private static final int MULTITAP_INTERVAL = 800;  
  237.     private StringBuilder mPreviewLabel = new StringBuilder(1);  
  238.   
  239.     /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/  
  240.     private boolean mDrawPending;  
  241.     /** The dirty region in the keyboard bitmap */  
  242.     private Rect mDirtyRect = new Rect();  
  243.     /** The keyboard bitmap for faster updates */  
  244.     private Bitmap mBuffer;  
  245.     /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */  
  246.     private boolean mKeyboardChanged;  
  247.     /** The canvas for the above mutable keyboard bitmap */  
  248.     private Canvas mCanvas;  
  249.    
  250.     Handler mHandler = new Handler() {  
  251.         @Override  
  252.         public void handleMessage(Message msg) {  
  253.             switch (msg.what) {  
  254.                 case MSG_SHOW_PREVIEW:  
  255.                     showKey(msg.arg1);  
  256.                     break;  
  257.                 case MSG_REMOVE_PREVIEW:  
  258.                     mPreviewText.setVisibility(INVISIBLE);  
  259.                     break;  
  260.                 case MSG_REPEAT:  
  261.                     if (repeatKey()) {  
  262.                         Message repeat = Message.obtain(this, MSG_REPEAT);  
  263.                         sendMessageDelayed(repeat, REPEAT_INTERVAL);  
  264.                     }  
  265.                     break;  
  266.                 case MSG_LONGPRESS:  
  267.                     openPopupIfRequired((MotionEvent) msg.obj);  
  268.                     break;  
  269.             }  
  270.         }  
  271.     };  
  272.   
  273.     /** Constructor */  
  274.     public KeyboardView(Context context, AttributeSet attrs) {  
  275.         this(context, attrs, 0);  
  276.     }  
  277.   
  278.     /** Constructor */  
  279.     public KeyboardView(Context context, AttributeSet attrs, int defStyle) {  
  280.         super(context, attrs, defStyle);  
  281.   
  282.         TypedArray a =  
  283.             context.obtainStyledAttributes(  
  284.                 attrs, android.R.styleable.KeyboardView, defStyle, R.style.WnnKeyboardView);  
  285.   
  286.         LayoutInflater inflate =  
  287.                 (LayoutInflater) context  
  288.                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  289.   
  290.         int previewLayout = 0;  
  291.         int keyTextSize = 0;  
  292.   
  293.         int n = a.getIndexCount();  
  294.    
  295.         for (int i = 0; i < n; i++) {  
  296.             int attr = a.getIndex(i);  
  297.   
  298.             switch (attr) {  
  299.             case android.R.styleable.KeyboardView_keyBackground:  
  300.                 mKeyBackground = a.getDrawable(attr);  
  301.                 break;  
  302.             case android.R.styleable.KeyboardView_verticalCorrection:  
  303.                 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);  
  304.                 break;  
  305.             case android.R.styleable.KeyboardView_keyPreviewLayout:  
  306.                 previewLayout = a.getResourceId(attr, 0);  
  307.                 break;  
  308.             case android.R.styleable.KeyboardView_keyPreviewOffset:  
  309.                 mPreviewOffset = a.getDimensionPixelOffset(attr, 0);  
  310.                 break;  
  311.             case android.R.styleable.KeyboardView_keyPreviewHeight:  
  312.                 mPreviewHeight = a.getDimensionPixelSize(attr, 80);  
  313.                 break;  
  314.             case android.R.styleable.KeyboardView_keyTextSize:  
  315.                 mKeyTextSize = a.getDimensionPixelSize(attr, 18);  
  316.                 break;  
  317.             case android.R.styleable.KeyboardView_keyTextColor:  
  318.                 mKeyTextColor = a.getColor(attr, 0xFF000000);  
  319.                 break;  
  320.             case android.R.styleable.KeyboardView_labelTextSize:  
  321.                 mLabelTextSize = a.getDimensionPixelSize(attr, 14);  
  322.                 break;  
  323.             case android.R.styleable.KeyboardView_popupLayout:  
  324.                 mPopupLayout = a.getResourceId(attr, 0);  
  325.                 break;  
  326.             case android.R.styleable.KeyboardView_shadowColor:  
  327.                 mShadowColor = a.getColor(attr, 0);  
  328.                 break;  
  329.             case android.R.styleable.KeyboardView_shadowRadius:  
  330.                 mShadowRadius = a.getFloat(attr, 0f);  
  331.                 break;  
  332.             }  
  333.         }  
  334.   
  335.         a.recycle();  
  336.         a = context.obtainStyledAttributes(attrs, R.styleable.WnnKeyboardView, 00);  
  337.         mKeyBackground2nd = a.getDrawable(R.styleable.WnnKeyboardView_keyBackground2nd);  
  338.         mKeyTextColor2nd = a.getColor(R.styleable.WnnKeyboardView_keyTextColor2nd, 0xFF000000);  
  339.    
  340.         a.recycle();  
  341.         a = mContext.obtainStyledAttributes(  
  342.                 android.R.styleable.Theme);  
  343.         mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);  
  344.   
  345.         mPreviewPopup = new PopupWindow(context);  
  346.         if (previewLayout != 0) {  
  347.             mPreviewText = (TextView) inflate.inflate(previewLayout, null);  
  348.             mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();  
  349.             mPreviewPopup.setContentView(mPreviewText);  
  350.             mPreviewPopup.setBackgroundDrawable(null);  
  351.         } else {  
  352.             mShowPreview = false;  
  353.         }  
  354.    
  355.         mPreviewPopup.setTouchable(false);  
  356.    
  357.         mPopupKeyboard = new PopupWindow(context);  
  358.         mPopupKeyboard.setBackgroundDrawable(null);  
  359.   
  360.         mPopupParent = this;  
  361.           
  362.         mPaint = new Paint();  
  363.         mPaint.setAntiAlias(true);  
  364.         mPaint.setTextSize(keyTextSize);  
  365.         mPaint.setTextAlign(Align.CENTER);  
  366.         mPaint.setAlpha(255);  
  367.   
  368.         mPadding = new Rect(0000);  
  369.         mMiniKeyboardCache = new HashMap<Key,View>();  
  370.         mKeyBackground.getPadding(mPadding);  
  371.   
  372.         mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);  
  373.   
  374.         mDisambiguateSwipe = true;  
  375.   
  376.         resetMultiTap();  
  377.         initGestureDetector();  
  378.     }  
  379.   
  380.     private void initGestureDetector() {  
  381.         mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {  
  382.             @Override  
  383.             public boolean onFling(MotionEvent me1, MotionEvent me2,  
  384.                     float velocityX, float velocityY) {  
  385.                 if (mPossiblePoly) return false;  
  386.                 final float absX = Math.abs(velocityX);  
  387.                 final float absY = Math.abs(velocityY);  
  388.                 float deltaX = me2.getX() - me1.getX();  
  389.                 float deltaY = me2.getY() - me1.getY();  
  390.                 int travelX = getWidth() / 2;  
  391.                 int travelY = getHeight() / 2;  
  392.                 mSwipeTracker.computeCurrentVelocity(1000);  
  393.                 final float endingVelocityX = mSwipeTracker.getXVelocity();  
  394.                 final float endingVelocityY = mSwipeTracker.getYVelocity();  
  395.                 boolean sendDownKey = false;  
  396.                 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {  
  397.                     if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {  
  398.                         sendDownKey = true;  
  399.                     } else {  
  400.                         swipeRight();  
  401.                         return true;  
  402.                     }  
  403.                 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {  
  404.                     if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {  
  405.                         sendDownKey = true;  
  406.                     } else {  
  407.                         swipeLeft();  
  408.                         return true;  
  409.                     }  
  410.                 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {  
  411.                     if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {  
  412.                         sendDownKey = true;  
  413.                     } else {  
  414.                         swipeUp();  
  415.                         return true;  
  416.                     }  
  417.                 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {  
  418.                     if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {  
  419.                         sendDownKey = true;  
  420.                     } else {  
  421.                         swipeDown();  
  422.                         return true;  
  423.                     }  
  424.                 }  
  425.   
  426.                 if (sendDownKey) {  
  427.                     detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());  
  428.                 }  
  429.                 return false;  
  430.             }  
  431.         });  
  432.   
  433.         mGestureDetector.setIsLongpressEnabled(false);  
  434.     }  
  435.   
  436.     /** 
  437.      * Set the {@link OnKeyboardActionListener} object. 
  438.      * @param listener  The OnKeyboardActionListener to set. 
  439.      */  
  440.     public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {  
  441.         mKeyboardActionListener = listener;  
  442.     }  
  443.   
  444.     /** 
  445.      * Returns the {@link OnKeyboardActionListener} object. 
  446.      * @return the listener attached to this keyboard 
  447.      */  
  448.     protected OnKeyboardActionListener getOnKeyboardActionListener() {  
  449.         return mKeyboardActionListener;  
  450.     }  
  451.   
  452.     /** 
  453.      * Attaches a keyboard to this view. The keyboard can be switched at any time and the 
  454.      * view will re-layout itself to accommodate the keyboard. 
  455.      * @see Keyboard 
  456.      * @see #getKeyboard() 
  457.      * @param keyboard the keyboard to display in this view 
  458.      */  
  459.     public void setKeyboard(Keyboard keyboard) {  
  460.         if (!keyboard.equals(mKeyboard)) {  
  461.             clearWindowInfo();  
  462.         }  
  463.         int oldRepeatKeyCode = NOT_A_KEY;  
  464.         if (mKeyboard != null) {  
  465.             showPreview(NOT_A_KEY);  
  466.             if ((mRepeatKeyIndex != NOT_A_KEY) && (mRepeatKeyIndex < mKeys.length)) {  
  467.                 oldRepeatKeyCode = mKeys[mRepeatKeyIndex].codes[0];  
  468.             }  
  469.         }  
  470.         removeMessages();  
  471.         mKeyboard = keyboard;  
  472.         List<Key> keys = mKeyboard.getKeys();  
  473.         mKeys = keys.toArray(new Key[keys.size()]);  
  474.         requestLayout();  
  475.         mKeyboardChanged = true;  
  476.         invalidateAllKeys();  
  477.         computeProximityThreshold(keyboard);  
  478.         mMiniKeyboardCache.clear();  
  479.         boolean abort = true;  
  480.         if (oldRepeatKeyCode != NOT_A_KEY) {  
  481.             int keyIndex = getKeyIndices(mStartX, mStartY, null);  
  482.             if ((keyIndex != NOT_A_KEY)  
  483.                 && (keyIndex < mKeys.length)  
  484.                 && (oldRepeatKeyCode == mKeys[keyIndex].codes[0])) {  
  485.                 abort = false;  
  486.                 mRepeatKeyIndex = keyIndex;  
  487.             }  
  488.         }  
  489.         if (abort) {  
  490.             mHandler.removeMessages(MSG_REPEAT);  
  491.         }  
  492.         mAbortKey = abort;  
  493.     }  
  494.   
  495.     /** 
  496.      * Returns the current keyboard being displayed by this view. 
  497.      * @return the currently attached keyboard 
  498.      * @see #setKeyboard(Keyboard) 
  499.      */  
  500.     public Keyboard getKeyboard() {  
  501.         return mKeyboard;  
  502.     }  
  503.    
  504.     /** 
  505.      * Sets the state of the shift key of the keyboard, if any. 
  506.      * @param shifted whether or not to enable the state of the shift key 
  507.      * @return true if the shift key state changed, false if there was no change 
  508.      * @see KeyboardView#isShifted() 
  509.      */  
  510.     public boolean setShifted(boolean shifted) {  
  511.         if (mKeyboard != null) {  
  512.             if (mKeyboard.setShifted(shifted)) {  
  513.                 invalidateAllKeys();  
  514.                 return true;  
  515.             }  
  516.         }  
  517.         return false;  
  518.     }  
  519.   
  520.     /** 
  521.      * Returns the state of the shift key of the keyboard, if any. 
  522.      * @return true if the shift is in a pressed state, false otherwise. If there is 
  523.      * no shift key on the keyboard or there is no keyboard attached, it returns false. 
  524.      * @see KeyboardView#setShifted(boolean) 
  525.      */  
  526.     public boolean isShifted() {  
  527.         if (mKeyboard != null) {  
  528.             return mKeyboard.isShifted();  
  529.         }  
  530.         return false;  
  531.     }  
  532.   
  533.     /** 
  534.      * Enables or disables the key feedback popup. This is a popup that shows a magnified 
  535.      * version of the depressed key. By default the preview is enabled. 
  536.      * @param previewEnabled whether or not to enable the key feedback popup 
  537.      * @see #isPreviewEnabled() 
  538.      */  
  539.     public void setPreviewEnabled(boolean previewEnabled) {  
  540.         mShowPreview = previewEnabled;  
  541.     }  
  542.   
  543.     /** 
  544.      * Returns the enabled state of the key feedback popup. 
  545.      * @return whether or not the key feedback popup is enabled 
  546.      * @see #setPreviewEnabled(boolean) 
  547.      */  
  548.     public boolean isPreviewEnabled() {  
  549.         return mShowPreview;  
  550.     }  
  551.   
  552.     /** 
  553.      * Returns the root parent has the enabled state of the key feedback popup. 
  554.      * @return whether or not the key feedback popup is enabled 
  555.      * @see #setPreviewEnabled(boolean) 
  556.      */  
  557.     public boolean isParentPreviewEnabled() {  
  558.         if ((mPopupParent != null) && (mPopupParent != this)  
  559.                 && (mPopupParent instanceof KeyboardView)) {  
  560.             return ((KeyboardView)mPopupParent).isParentPreviewEnabled();  
  561.         } else {  
  562.             return mShowPreview;  
  563.         }  
  564.     }  
  565.   
  566.     public void setVerticalCorrection(int verticalOffset) {  
  567.    
  568.     }  
  569.   
  570.     /** 
  571.      * Set View on the PopupParent. 
  572.      * @param v  The View to set. 
  573.      */  
  574.     public void setPopupParent(View v) {  
  575.         mPopupParent = v;  
  576.     }  
  577.   
  578.     /** 
  579.      * Set parameters on the KeyboardOffset. 
  580.      * @param x  The value of KeyboardOffset. 
  581.      * @param y  The value of KeyboardOffset. 
  582.      */  
  583.     public void setPopupOffset(int x, int y) {  
  584.         mMiniKeyboardOffsetX = x;  
  585.         mMiniKeyboardOffsetY = y;  
  586.         if (mPreviewPopup.isShowing()) {  
  587.             mPreviewPopup.dismiss();  
  588.         }  
  589.     }  
  590.   
  591.     /** 
  592.      * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key 
  593.      * codes for adjacent keys.  When disabled, only the primary key code will be 
  594.      * reported. 
  595.      * @param enabled whether or not the proximity correction is enabled 
  596.      */  
  597.     public void setProximityCorrectionEnabled(boolean enabled) {  
  598.         mProximityCorrectOn = enabled;  
  599.     }  
  600.   
  601.     /** 
  602.      * Returns true if proximity correction is enabled. 
  603.      */  
  604.     public boolean isProximityCorrectionEnabled() {  
  605.         return mProximityCorrectOn;  
  606.     }  
  607.   
  608.     /** 
  609.      * Popup keyboard close button clicked. 
  610.      * @hide 
  611.      */  
  612.     public void onClick(View v) {  
  613.         dismissPopupKeyboard();  
  614.     }  
  615.   
  616.     private CharSequence adjustCase(CharSequence label) {  
  617.         if (mKeyboard.isShifted() && label != null && label.length() < 3  
  618.                 && Character.isLowerCase(label.charAt(0))) {  
  619.             label = label.toString().toUpperCase();  
  620.         }  
  621.         return label;  
  622.     }  
  623.   
  624.     @Override  
  625.     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  626.         if (mKeyboard == null) {  
  627.             setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);  
  628.         } else {  
  629.             int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;  
  630.             if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {  
  631.                 width = MeasureSpec.getSize(widthMeasureSpec);  
  632.             }  
  633.             setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);  
  634.         }  
  635.     }  
  636.   
  637.     /** 
  638.      * Compute the average distance between adjacent keys (horizontally and vertically) 
  639.      * and square it to get the proximity threshold. We use a square here and in computing 
  640.      * the touch distance from a key's center to avoid taking a square root. 
  641.      * @param keyboard 
  642.      */  
  643.     private void computeProximityThreshold(Keyboard keyboard) {  
  644.         if (keyboard == nullreturn;  
  645.         final Key[] keys = mKeys;  
  646.         if (keys == nullreturn;  
  647.         int length = keys.length;  
  648.         int dimensionSum = 0;  
  649.         for (int i = 0; i < length; i++) {  
  650.             Key key = keys[i];  
  651.             dimensionSum += Math.min(key.width, key.height) + key.gap;  
  652.         }  
  653.         if (dimensionSum < 0 || length == 0return;  
  654.         mProximityThreshold = (int) (dimensionSum * 1.4f / length);  
  655.         mProximityThreshold *= mProximityThreshold;  
  656.     }  
  657.   
  658.     @Override  
  659.     public void onSizeChanged(int w, int h, int oldw, int oldh) {  
  660.         super.onSizeChanged(w, h, oldw, oldh);  
  661.         mBuffer = null;  
  662.     }  
  663.   
  664.     @Override  
  665.     public void onDraw(Canvas canvas) {  
  666.         super.onDraw(canvas);  
  667.         if (mDrawPending || mBuffer == null || mKeyboardChanged) {  
  668.             onBufferDraw();  
  669.         }  
  670.         canvas.drawBitmap(mBuffer, 00null);  
  671.     }  
  672.   
  673.     private void onBufferDraw() {  
  674.         boolean isBufferNull = (mBuffer == null);  
  675.         if (isBufferNull || mKeyboardChanged) {  
  676.             if (isBufferNull || mKeyboardChanged &&  
  677.                     (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {  
  678.                 final int width = Math.max(1, getWidth());  
  679.                 final int height = Math.max(1, getHeight());  
  680.                 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  
  681.                 mCanvas = new Canvas(mBuffer);  
  682.             }  
  683.             invalidateAllKeys();  
  684.             mKeyboardChanged = false;  
  685.         }  
  686.         final Canvas canvas = mCanvas;  
  687.         canvas.clipRect(mDirtyRect, Op.REPLACE);  
  688.   
  689.         if (mKeyboard == nullreturn;  
  690.    
  691.         final Paint paint = mPaint;  
  692.         final Rect clipRegion = mClipRegion;  
  693.         final Rect padding = mPadding;  
  694.         final int kbdPaddingLeft = mPaddingLeft;  
  695.         final int kbdPaddingTop = mPaddingTop;  
  696.         final Key[] keys = mKeys;  
  697.         final Key invalidKey = mInvalidatedKey;  
  698.   
  699.         paint.setColor(mKeyTextColor);  
  700.         boolean drawSingleKey = false;  
  701.         if (invalidKey != null && canvas.getClipBounds(clipRegion)) {  
  702.           if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&  
  703.                   invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&  
  704.                   invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&  
  705.                   invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {  
  706.               drawSingleKey = true;  
  707.           }  
  708.         }  
  709.         canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);  
  710.         final int keyCount = keys.length;  
  711.         for (int i = 0; i < keyCount; i++) {  
  712.             final Key key = keys[i];  
  713.             if (drawSingleKey && invalidKey != key) {  
  714.                 continue;  
  715.             }  
  716.   
  717.             paint.setColor(key.isSecondKey ? mKeyTextColor2nd : mKeyTextColor);  
  718.             Drawable keyBackground = key.isSecondKey ? mKeyBackground2nd : mKeyBackground;  
  719.             int[] drawableState = key.getCurrentDrawableState();  
  720.             keyBackground.setState(drawableState);  
  721.   
  722.             String label = key.label == nullnull : adjustCase(key.label).toString();  
  723.   
  724.             final Rect bounds = keyBackground.getBounds();  
  725.             if (key.width != bounds.right ||  
  726.                     key.height != bounds.bottom) {  
  727.                 keyBackground.setBounds(00, key.width, key.height);  
  728.             }  
  729.             canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);  
  730.             keyBackground.draw(canvas);  
  731.    
  732.             if (label != null) {  
  733.                 if (OpenWnn.isXLarge()) {  
  734.                     if (label.length() > 1 && key.codes.length < 2) {  
  735.                         paint.setTextSize(mLabelTextSize);  
  736.                         paint.setTypeface(Typeface.DEFAULT);  
  737.                     } else {  
  738.                         paint.setTextSize(mKeyTextSize);  
  739.                         paint.setTypeface(Typeface.DEFAULT_BOLD);  
  740.                     }  
  741.                 } else {  
  742.                     if (label.length() > 1 && key.codes.length < 2) {  
  743.                         paint.setTextSize(mLabelTextSize);  
  744.                         paint.setTypeface(Typeface.DEFAULT_BOLD);  
  745.                     } else {  
  746.                         paint.setTextSize(mKeyTextSize);  
  747.                         paint.setTypeface(Typeface.DEFAULT_BOLD);  
  748.                     }  
  749.                 }  
  750.                 paint.setShadowLayer(mShadowRadius, 00, mShadowColor);  
  751.                 if (OpenWnn.isXLarge()) {  
  752.                     canvas.drawText(label,  
  753.                         (key.width - padding.left + 7 - padding.right) / 2  
  754.                                 + padding.left,  
  755.                         (key.height - padding.top + 7 - padding.bottom) / 2  
  756.                                 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,  
  757.                         paint);  
  758.                 } else {  
  759.                     canvas.drawText(label,  
  760.                         (key.width - padding.left - padding.right) / 2  
  761.                                 + padding.left,  
  762.                         (key.height - padding.top - padding.bottom) / 2  
  763.                                 + (paint.getTextSize() - paint.descent()) / 2 + padding.top,  
  764.                         paint);  
  765.                 }  
  766.                 paint.setShadowLayer(0000);  
  767.             } else if (key.icon != null) {  
  768.                 int drawableX;  
  769.                 int drawableY;  
  770.                 if (OpenWnn.isXLarge()) {  
  771.                     drawableX = (key.width - padding.left + 12 - padding.right  
  772.                                     - key.icon.getIntrinsicWidth()) / 2 + padding.left;  
  773.                     drawableY = (key.height - padding.top + 9 - padding.bottom  
  774.                             - key.icon.getIntrinsicHeight()) / 2 + padding.top;  
  775.                 } else {  
  776.                     drawableX = (key.width - padding.left - padding.right   
  777.                                     - key.icon.getIntrinsicWidth()) / 2 + padding.left;  
  778.                     drawableY = (key.height - padding.top - padding.bottom   
  779.                             - key.icon.getIntrinsicHeight()) / 2 + padding.top;  
  780.                 }  
  781.                 canvas.translate(drawableX, drawableY);  
  782.                 key.icon.setBounds(00,  
  783.                         key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());  
  784.                 key.icon.draw(canvas);  
  785.                 canvas.translate(-drawableX, -drawableY);  
  786.             }  
  787.             canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);  
  788.         }  
  789.         mInvalidatedKey = null;  
  790.         if (mMiniKeyboardOnScreen) {  
  791.             paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);  
  792.             canvas.drawRect(00, getWidth(), getHeight(), paint);  
  793.         }  
  794.   
  795.         mDrawPending = false;  
  796.         mDirtyRect.setEmpty();  
  797.     }  
  798.   
  799.     private int getKeyIndices(int x, int y, int[] allKeys) {  
  800.         final Key[] keys = mKeys;  
  801.         int primaryIndex = NOT_A_KEY;  
  802.         int closestKey = NOT_A_KEY;  
  803.         int closestKeyDist = mProximityThreshold + 1;  
  804.         java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);  
  805.         int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);  
  806.         final int keyCount = nearestKeyIndices.length;  
  807.         for (int i = 0; i < keyCount; i++) {  
  808.             final Key key = keys[nearestKeyIndices[i]];  
  809.             int dist = 0;  
  810.             boolean isInside = key.isInside(x,y);  
  811.             if (isInside) {  
  812.                 primaryIndex = nearestKeyIndices[i];  
  813.             }  
  814.   
  815.             if (((mProximityCorrectOn  
  816.                     && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)  
  817.                     || isInside)  
  818.                     && key.codes[0] > 32) {  
  819.                 final int nCodes = key.codes.length;  
  820.                 if (dist < closestKeyDist) {  
  821.                     closestKeyDist = dist;  
  822.                     closestKey = nearestKeyIndices[i];  
  823.                 }  
  824.   
  825.                 if (allKeys == nullcontinue;  
  826.    
  827.                 for (int j = 0; j < mDistances.length; j++) {  
  828.                     if (mDistances[j] > dist) {  
  829.                         System.arraycopy(mDistances, j, mDistances, j + nCodes,  
  830.                                 mDistances.length - j - nCodes);  
  831.                         System.arraycopy(allKeys, j, allKeys, j + nCodes,  
  832.                                 allKeys.length - j - nCodes);  
  833.                         for (int c = 0; c < nCodes; c++) {  
  834.                             allKeys[j + c] = key.codes[c];  
  835.                             mDistances[j + c] = dist;  
  836.                         }  
  837.                         break;  
  838.                     }  
  839.                 }  
  840.             }  
  841.         }  
  842.         if (primaryIndex == NOT_A_KEY) {  
  843.             primaryIndex = closestKey;  
  844.         }  
  845.         return primaryIndex;  
  846.     }  
  847.   
  848.     private void detectAndSendKey(int index, int x, int y, long eventTime) {  
  849.         if (index != NOT_A_KEY && index < mKeys.length) {  
  850.             final Key key = mKeys[index];  
  851.             if (key.text != null) {  
  852.                 mKeyboardActionListener.onText(key.text);  
  853.                 mKeyboardActionListener.onRelease(NOT_A_KEY);  
  854.             } else {  
  855.                 int code = key.codes[0];  
  856.                 int[] codes = new int[MAX_NEARBY_KEYS];  
  857.                 Arrays.fill(codes, NOT_A_KEY);  
  858.                 getKeyIndices(x, y, codes);  
  859.                 if (mInMultiTap) {  
  860.                     if (mTapCount != -1) {  
  861.                         mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);  
  862.                     } else {  
  863.                         mTapCount = 0;  
  864.                     }  
  865.                     code = key.codes[mTapCount];  
  866.                 }  
  867.                 mKeyboardActionListener.onKey(code, codes);  
  868.                 mKeyboardActionListener.onRelease(code);  
  869.             }  
  870.             mLastSentIndex = index;  
  871.             mLastTapTime = eventTime;  
  872.         }  
  873.     }  
  874.   
  875.     /** 
  876.      * Handle multi-tap keys by producing the key label for the current multi-tap state. 
  877.      */  
  878.     private CharSequence getPreviewText(Key key) {  
  879.         if (mInMultiTap) {  
  880.             mPreviewLabel.setLength(0);  
  881.             mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);  
  882.             return adjustCase(mPreviewLabel);  
  883.         } else {  
  884.             return adjustCase(key.label);  
  885.         }  
  886.     }  
  887.   
  888.     private void showPreview(int keyIndex) {  
  889.         int oldKeyIndex = mCurrentKeyIndex;  
  890.         final PopupWindow previewPopup = mPreviewPopup;  
  891.    
  892.         mCurrentKeyIndex = keyIndex;  
  893.         final Key[] keys = mKeys;  
  894.         if (oldKeyIndex != mCurrentKeyIndex) {  
  895.             if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {  
  896.                 keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);  
  897.                 invalidateKey(oldKeyIndex);  
  898.             }  
  899.             if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {  
  900.                 keys[mCurrentKeyIndex].onPressed();  
  901.                 invalidateKey(mCurrentKeyIndex);  
  902.             }  
  903.         }  
  904.         if (oldKeyIndex != mCurrentKeyIndex && mShowPreview && isParentPreviewEnabled()) {  
  905.             mHandler.removeMessages(MSG_SHOW_PREVIEW);  
  906.             if (previewPopup.isShowing()) {  
  907.                 if (keyIndex == NOT_A_KEY) {  
  908.                     mHandler.sendMessageDelayed(mHandler  
  909.                             .obtainMessage(MSG_REMOVE_PREVIEW),  
  910.                             DELAY_AFTER_PREVIEW);  
  911.                 }  
  912.             }  
  913.             if (keyIndex != NOT_A_KEY) {  
  914.                 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {  
  915.                     showKey(keyIndex);  
  916.                 } else {  
  917.                     mHandler.sendMessageDelayed(  
  918.                             mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),  
  919.                             DELAY_BEFORE_PREVIEW);  
  920.                 }  
  921.             }  
  922.         }  
  923.     }  
  924.    
  925.     private void showKey(final int keyIndex) {  
  926.         final PopupWindow previewPopup = mPreviewPopup;  
  927.         final Key[] keys = mKeys;  
  928.         if (keyIndex < 0 || keyIndex >= mKeys.length) return;  
  929.         Key key = keys[keyIndex];  
  930.   
  931.         mPreviewText.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.keyboard_key_feedback));  
  932.   
  933.         if (key.icon != null) {  
  934.             mPreviewText.setCompoundDrawables(nullnullnull,  
  935.                     key.iconPreview != null ? key.iconPreview : key.icon);  
  936.             mPreviewText.setText(null);  
  937.             mPreviewText.setPadding(50520);  
  938.         } else {  
  939.             mPreviewText.setCompoundDrawables(nullnullnullnull);  
  940.             mPreviewText.setText(getPreviewText(key));  
  941.             if (key.label.length() > 1 && key.codes.length < 2) {  
  942.                 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);  
  943.                 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);  
  944.             } else {  
  945.                 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);  
  946.                 mPreviewText.setTypeface(Typeface.DEFAULT);  
  947.             }  
  948.             mPreviewText.setPadding(00010);  
  949.         }  
  950.         mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),  
  951.                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));  
  952.         int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width  
  953.                 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());  
  954.         final int popupHeight = mPreviewHeight;  
  955.         LayoutParams lp = mPreviewText.getLayoutParams();  
  956.         if (lp != null) {  
  957.             lp.width = popupWidth;  
  958.             lp.height = popupHeight;  
  959.         }  
  960.         if (!mPreviewCentered) {  
  961.             mPopupPreviewX = key.x - (Math.abs(popupWidth - key.width) / 2 );  
  962.             mPopupPreviewY = key.y - popupHeight + mPreviewOffset;  
  963.         } else {  
  964.             mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;  
  965.             mPopupPreviewY = - mPreviewText.getMeasuredHeight();  
  966.         }  
  967.         mPopupPreviewY = mPopupPreviewY + 20;  
  968.         mHandler.removeMessages(MSG_REMOVE_PREVIEW);  
  969.         if (mOffsetInWindow == null) {  
  970.             mOffsetInWindow = new int[2];  
  971.             getLocationInWindow(mOffsetInWindow);  
  972.             mOffsetInWindow[0] += mMiniKeyboardOffsetX;  
  973.             mOffsetInWindow[1] += mMiniKeyboardOffsetY;  
  974.             int[] mWindowLocation = new int[2];  
  975.             getLocationOnScreen(mWindowLocation);  
  976.             mWindowY = mWindowLocation[1];  
  977.         }  
  978.         mPreviewText.getBackground().setState(  
  979.                 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);  
  980.         mPopupPreviewX += mOffsetInWindow[0];  
  981.         mPopupPreviewY += mOffsetInWindow[1];  
  982.   
  983.         if (mPopupPreviewY + mWindowY < 0) {  
  984.             if (key.x + key.width <= getWidth() / 2) {  
  985.                 mPopupPreviewX += (int) (key.width * 2.5);  
  986.             } else {  
  987.                 mPopupPreviewX -= (int) (key.width * 2.5);  
  988.             }  
  989.             mPopupPreviewY += popupHeight;  
  990.         }  
  991.   
  992.         if (previewPopup.isShowing()) {  
  993.             previewPopup.update(mPopupPreviewX, mPopupPreviewY,  
  994.                     popupWidth, popupHeight);  
  995.         } else {  
  996.             previewPopup.setWidth(popupWidth);  
  997.             previewPopup.setHeight(popupHeight);  
  998.             previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,  
  999.                     mPopupPreviewX, mPopupPreviewY);  
  1000.         }  
  1001.         mPreviewText.setVisibility(VISIBLE);  
  1002.     }  
  1003.   
  1004.     /** 
  1005.      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 
  1006.      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 
  1007.      * draws the cached buffer. 
  1008.      * @see #invalidateKey(int) 
  1009.      */  
  1010.     public void invalidateAllKeys() {  
  1011.         mDirtyRect.union(00, getWidth(), getHeight());  
  1012.         mDrawPending = true;  
  1013.         invalidate();  
  1014.     }  
  1015.   
  1016.     /** 
  1017.      * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 
  1018.      * one key is changing it's content. Any changes that affect the position or size of the key 
  1019.      * may not be honored. 
  1020.      * @param keyIndex the index of the key in the attached {@link Keyboard}. 
  1021.      * @see #invalidateAllKeys 
  1022.      */  
  1023.     public void invalidateKey(int keyIndex) {  
  1024.         if (mKeys == nullreturn;  
  1025.         if (keyIndex < 0 || keyIndex >= mKeys.length) {  
  1026.             return;  
  1027.         }  
  1028.         final Key key = mKeys[keyIndex];  
  1029.         mInvalidatedKey = key;  
  1030.         mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,  
  1031.                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);  
  1032.         onBufferDraw();  
  1033.         invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,  
  1034.                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);  
  1035.     }  
  1036.   
  1037.     private boolean openPopupIfRequired(MotionEvent me) {  
  1038.         if (mPopupLayout == 0) {  
  1039.             return false;  
  1040.         }  
  1041.         if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {  
  1042.             return false;  
  1043.         }  
  1044.   
  1045.         Key popupKey = mKeys[mCurrentKey];  
  1046.         boolean result = onLongPress(popupKey);  
  1047.         if (result) {  
  1048.             mAbortKey = true;  
  1049.             showPreview(NOT_A_KEY);  
  1050.         }  
  1051.         return result;  
  1052.     }  
  1053.   
  1054.     /** 
  1055.      * Called when a key is long pressed. By default this will open any popup keyboard associated 
  1056.      * with this key through the attributes popupLayout and popupCharacters. 
  1057.      * @param popupKey the key that was long pressed 
  1058.      * @return true if the long press is handled, false otherwise. Subclasses should call the 
  1059.      * method on the base class if the subclass doesn't wish to handle the call. 
  1060.      */  
  1061.     protected boolean onLongPress(Key popupKey) {  
  1062.         if (mKeyboardActionListener.onLongPress(popupKey)) {  
  1063.             return true;  
  1064.         }  
  1065.         int popupKeyboardId = popupKey.popupResId;  
  1066.         if (popupKeyboardId != 0) {  
  1067.             mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);  
  1068.             if (mMiniKeyboardContainer == null) {  
  1069.                 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(  
  1070.                         Context.LAYOUT_INFLATER_SERVICE);  
  1071.                 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);  
  1072.                 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(R.id.keyboardView);  
  1073.                 View closeButton = mMiniKeyboardContainer.findViewById(R.id.closeButton);  
  1074.                 if (closeButton != null) closeButton.setOnClickListener(this);  
  1075.                 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {  
  1076.                     public void onKey(int primaryCode, int[] keyCodes) {  
  1077.                         mKeyboardActionListener.onKey(primaryCode, keyCodes);  
  1078.                         dismissPopupKeyboard();  
  1079.                     }  
  1080.    
  1081.                     public void onText(CharSequence text) {  
  1082.                         mKeyboardActionListener.onText(text);  
  1083.                         dismissPopupKeyboard();  
  1084.                     }  
  1085.    
  1086.                     public void swipeLeft() { }  
  1087.                     public void swipeRight() { }  
  1088.                     public void swipeUp() { }  
  1089.                     public void swipeDown() { }  
  1090.                     public void onPress(int primaryCode) {  
  1091.                         mKeyboardActionListener.onPress(primaryCode);  
  1092.                     }  
  1093.                     public void onRelease(int primaryCode) {  
  1094.                         mKeyboardActionListener.onRelease(primaryCode);  
  1095.                     }  
  1096.                     public boolean onLongPress(Keyboard.Key key) {  
  1097.                         return false;  
  1098.                     }  
  1099.                 });  
  1100.                 Keyboard keyboard;  
  1101.                 if (popupKey.popupCharacters != null) {  
  1102.                     keyboard = new Keyboard(getContext(), popupKeyboardId,   
  1103.                             popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());  
  1104.                 } else {  
  1105.                     keyboard = new Keyboard(getContext(), popupKeyboardId);  
  1106.                 }  
  1107.                 mMiniKeyboard.setKeyboard(keyboard);  
  1108.                 mMiniKeyboard.setPopupParent(this);  
  1109.                 mMiniKeyboardContainer.measure(  
  1110.                         MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),  
  1111.                         MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));  
  1112.    
  1113.                 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);  
  1114.             } else {  
  1115.                 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(R.id.keyboardView);  
  1116.             }  
  1117.             if (mWindowOffset == null) {  
  1118.                 mWindowOffset = new int[2];  
  1119.                 getLocationInWindow(mWindowOffset);  
  1120.             }  
  1121.             mPopupX = popupKey.x + mPaddingLeft;  
  1122.             mPopupY = popupKey.y + mPaddingTop;  
  1123.             mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();  
  1124.             mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();  
  1125.             final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];  
  1126.             final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];  
  1127.             mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);  
  1128.             mMiniKeyboard.setShifted(isShifted());  
  1129.             mPopupKeyboard.setContentView(mMiniKeyboardContainer);  
  1130.             mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());  
  1131.             mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());  
  1132.             mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);  
  1133.             mMiniKeyboardOnScreen = true;  
  1134.             invalidateAllKeys();  
  1135.             return true;  
  1136.         }  
  1137.         return false;  
  1138.     }  
  1139.   
  1140.     private long mOldEventTime;  
  1141.     private boolean mUsedVelocity;  
  1142.   
  1143.     @Override  
  1144.     public boolean onTouchEvent(MotionEvent me) {  
  1145.         final int pointerCount = me.getPointerCount();  
  1146.         final int action = me.getAction();  
  1147.         boolean result = false;  
  1148.         final long now = me.getEventTime();  
  1149.         final boolean isPointerCountOne = (pointerCount == 1);  
  1150.   
  1151.         if (pointerCount != mOldPointerCount) {  
  1152.             if (isPointerCountOne) {  
  1153.                 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,  
  1154.                         me.getX(), me.getY(), me.getMetaState());  
  1155.                 result = onModifiedTouchEvent(down, false);  
  1156.                 down.recycle();  
  1157.                 if (action == MotionEvent.ACTION_UP) {  
  1158.                     result = onModifiedTouchEvent(me, true);  
  1159.                 }  
  1160.             } else {  
  1161.                 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,  
  1162.                         mOldPointerX, mOldPointerY, me.getMetaState());  
  1163.                 result = onModifiedTouchEvent(up, true);  
  1164.                 up.recycle();  
  1165.             }  
  1166.         } else {  
  1167.             if (isPointerCountOne) {  
  1168.                 result = onModifiedTouchEvent(me, false);  
  1169.                 mOldPointerX = me.getX();  
  1170.                 mOldPointerY = me.getY();  
  1171.             } else {  
  1172.                 result = true;  
  1173.             }  
  1174.         }  
  1175.         mOldPointerCount = pointerCount;  
  1176.   
  1177.         return result;  
  1178.     }  
  1179.   
  1180.     private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {  
  1181.         int touchX = (int) me.getX() - mPaddingLeft;  
  1182.         int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;  
  1183.         final int action = me.getAction();  
  1184.         final long eventTime = me.getEventTime();  
  1185.         mOldEventTime = eventTime;  
  1186.         int keyIndex = getKeyIndices(touchX, touchY, null);  
  1187.         mPossiblePoly = possiblePoly;  
  1188.   
  1189.         if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();  
  1190.         mSwipeTracker.addMovement(me);  
  1191.   
  1192.         if (mAbortKey  
  1193.                 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {  
  1194.             return true;  
  1195.         }  
  1196.   
  1197.         if (mGestureDetector.onTouchEvent(me)) {  
  1198.             showPreview(NOT_A_KEY);  
  1199.             mHandler.removeMessages(MSG_REPEAT);  
  1200.             mHandler.removeMessages(MSG_LONGPRESS);  
  1201.             return true;  
  1202.         }  
  1203.    
  1204.         if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {  
  1205.             return true;  
  1206.         }  
  1207.   
  1208.         switch (action) {  
  1209.             case MotionEvent.ACTION_DOWN:  
  1210.                 mAbortKey = false;  
  1211.                 mStartX = touchX;  
  1212.                 mStartY = touchY;  
  1213.                 mLastCodeX = touchX;  
  1214.                 mLastCodeY = touchY;  
  1215.                 mLastKeyTime = 0;  
  1216.                 mCurrentKeyTime = 0;  
  1217.                 mLastKey = NOT_A_KEY;  
  1218.                 mCurrentKey = keyIndex;  
  1219.                 mDownKey = keyIndex;  
  1220.                 mDownTime = me.getEventTime();  
  1221.                 mLastMoveTime = mDownTime;  
  1222.                 checkMultiTap(eventTime, keyIndex);  
  1223.                 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?   
  1224.                         mKeys[keyIndex].codes[0] : 0);  
  1225.                 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {  
  1226.                     mRepeatKeyIndex = mCurrentKey;  
  1227.                     Message msg = mHandler.obtainMessage(MSG_REPEAT);  
  1228.                     mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);  
  1229.                     repeatKey();  
  1230.                     if (mAbortKey) {  
  1231.                         mRepeatKeyIndex = NOT_A_KEY;  
  1232.                         break;  
  1233.                     }  
  1234.                 }  
  1235.                 if (mCurrentKey != NOT_A_KEY) {  
  1236.                     Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);  
  1237.                     mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);  
  1238.                 }  
  1239.                 showPreview(keyIndex);  
  1240.                 break;  
  1241.   
  1242.             case MotionEvent.ACTION_MOVE:  
  1243.                 boolean continueLongPress = false;  
  1244.                 if (keyIndex != NOT_A_KEY) {  
  1245.                     if (mCurrentKey == NOT_A_KEY) {  
  1246.                         mCurrentKey = keyIndex;  
  1247.                         mCurrentKeyTime = eventTime - mDownTime;  
  1248.                     } else {  
  1249.                         if (keyIndex == mCurrentKey) {  
  1250.                             mCurrentKeyTime += eventTime - mLastMoveTime;  
  1251.                             continueLongPress = true;  
  1252.                         } else if (mRepeatKeyIndex == NOT_A_KEY) {  
  1253.                             resetMultiTap();  
  1254.                             mLastKey = mCurrentKey;  
  1255.                             mLastCodeX = mLastX;  
  1256.                             mLastCodeY = mLastY;  
  1257.                             mLastKeyTime =  
  1258.                                     mCurrentKeyTime + eventTime - mLastMoveTime;  
  1259.                             mCurrentKey = keyIndex;  
  1260.                             mCurrentKeyTime = 0;  
  1261.                         }  
  1262.                     }  
  1263.                 }  
  1264.                 if (!continueLongPress) {  
  1265.                     mHandler.removeMessages(MSG_LONGPRESS);  
  1266.                     if (keyIndex != NOT_A_KEY) {  
  1267.                         Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);  
  1268.                         mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);  
  1269.                     }  
  1270.                 }  
  1271.                 showPreview(mCurrentKey);  
  1272.                 mLastMoveTime = eventTime;  
  1273.                 break;  
  1274.   
  1275.             case MotionEvent.ACTION_UP:  
  1276.                 removeMessages();  
  1277.                 if (keyIndex == mCurrentKey) {  
  1278.                     mCurrentKeyTime += eventTime - mLastMoveTime;  
  1279.                 } else {  
  1280.                     resetMultiTap();  
  1281.                     mLastKey = mCurrentKey;  
  1282.                     mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;  
  1283.                     mCurrentKey = keyIndex;