FileDocCategorySizeDatePackage
KeyboardView.javaAPI DocAndroid 1.5 API47014Wed May 06 22:41:54 BST 2009android.inputmethodservice

KeyboardView

public class KeyboardView extends android.view.View implements View.OnClickListener
A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key presses and touch movements.
attr
ref android.R.styleable#KeyboardView_keyBackground
attr
ref android.R.styleable#KeyboardView_keyPreviewLayout
attr
ref android.R.styleable#KeyboardView_keyPreviewOffset
attr
ref android.R.styleable#KeyboardView_labelTextSize
attr
ref android.R.styleable#KeyboardView_keyTextSize
attr
ref android.R.styleable#KeyboardView_keyTextColor
attr
ref android.R.styleable#KeyboardView_verticalCorrection
attr
ref android.R.styleable#KeyboardView_popupLayout

Fields Summary
private static final boolean
DEBUG
private static final int
NOT_A_KEY
private static final int[]
KEY_DELETE
private static final int[]
LONG_PRESSABLE_STATE_SET
private Keyboard
mKeyboard
private int
mCurrentKeyIndex
private int
mLabelTextSize
private int
mKeyTextSize
private int
mKeyTextColor
private float
mShadowRadius
private int
mShadowColor
private float
mBackgroundDimAmount
private android.widget.TextView
mPreviewText
private android.widget.PopupWindow
mPreviewPopup
private int
mPreviewTextSizeLarge
private int
mPreviewOffset
private int
mPreviewHeight
private int[]
mOffsetInWindow
private android.widget.PopupWindow
mPopupKeyboard
private android.view.View
mMiniKeyboardContainer
private KeyboardView
mMiniKeyboard
private boolean
mMiniKeyboardOnScreen
private android.view.View
mPopupParent
private int
mMiniKeyboardOffsetX
private int
mMiniKeyboardOffsetY
private Map
mMiniKeyboardCache
private int[]
mWindowOffset
private android.inputmethodservice.Keyboard.Key[]
mKeys
private OnKeyboardActionListener
mKeyboardActionListener
Listener for {@link OnKeyboardActionListener}.
private static final int
MSG_SHOW_PREVIEW
private static final int
MSG_REMOVE_PREVIEW
private static final int
MSG_REPEAT
private static final int
MSG_LONGPRESS
private static final int
DELAY_BEFORE_PREVIEW
private static final int
DELAY_AFTER_PREVIEW
private int
mVerticalCorrection
private int
mProximityThreshold
private boolean
mPreviewCentered
private boolean
mShowPreview
private boolean
mShowTouchPoints
private int
mPopupPreviewX
private int
mPopupPreviewY
private int
mLastX
private int
mLastY
private int
mStartX
private int
mStartY
private boolean
mProximityCorrectOn
private android.graphics.Paint
mPaint
private android.graphics.Rect
mPadding
private long
mDownTime
private long
mLastMoveTime
private int
mLastKey
private int
mLastCodeX
private int
mLastCodeY
private int
mCurrentKey
private long
mLastKeyTime
private long
mCurrentKeyTime
private int[]
mKeyIndices
private android.view.GestureDetector
mGestureDetector
private int
mPopupX
private int
mPopupY
private int
mRepeatKeyIndex
private int
mPopupLayout
private boolean
mAbortKey
private android.inputmethodservice.Keyboard.Key
mInvalidatedKey
private android.graphics.Rect
mClipRegion
private android.graphics.drawable.Drawable
mKeyBackground
private static final int
REPEAT_INTERVAL
private static final int
REPEAT_START_DELAY
private static final int
LONGPRESS_TIMEOUT
private static int
MAX_NEARBY_KEYS
private int[]
mDistances
private int
mLastSentIndex
private int
mTapCount
private long
mLastTapTime
private boolean
mInMultiTap
private static final int
MULTITAP_INTERVAL
private StringBuilder
mPreviewLabel
private boolean
mDrawPending
Whether the keyboard bitmap needs to be redrawn before it's blitted.
private android.graphics.Rect
mDirtyRect
The dirty region in the keyboard bitmap
private android.graphics.Bitmap
mBuffer
The keyboard bitmap for faster updates
private android.graphics.Canvas
mCanvas
The canvas for the above mutable keyboard bitmap
android.os.Handler
mHandler
Constructors Summary
public KeyboardView(android.content.Context context, android.util.AttributeSet attrs)


         
        this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
    
public KeyboardView(android.content.Context context, android.util.AttributeSet attrs, int defStyle)

        super(context, attrs, defStyle);

        TypedArray a =
            context.obtainStyledAttributes(
                attrs, android.R.styleable.KeyboardView, defStyle, 0);

        LayoutInflater inflate =
                (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        int previewLayout = 0;
        int keyTextSize = 0;

        int n = a.getIndexCount();
        
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);

            switch (attr) {
            case com.android.internal.R.styleable.KeyboardView_keyBackground:
                mKeyBackground = a.getDrawable(attr);
                break;
            case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
                break;
            case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
                previewLayout = a.getResourceId(attr, 0);
                break;
            case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
                break;
            case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
                mPreviewHeight = a.getDimensionPixelSize(attr, 80);
                break;
            case com.android.internal.R.styleable.KeyboardView_keyTextSize:
                mKeyTextSize = a.getDimensionPixelSize(attr, 18);
                break;
            case com.android.internal.R.styleable.KeyboardView_keyTextColor:
                mKeyTextColor = a.getColor(attr, 0xFF000000);
                break;
            case com.android.internal.R.styleable.KeyboardView_labelTextSize:
                mLabelTextSize = a.getDimensionPixelSize(attr, 14);
                break;
            case com.android.internal.R.styleable.KeyboardView_popupLayout:
                mPopupLayout = a.getResourceId(attr, 0);
                break;
            case com.android.internal.R.styleable.KeyboardView_shadowColor:
                mShadowColor = a.getColor(attr, 0);
                break;
            case com.android.internal.R.styleable.KeyboardView_shadowRadius:
                mShadowRadius = a.getFloat(attr, 0f);
                break;
            }
        }
        
        a = mContext.obtainStyledAttributes(
                com.android.internal.R.styleable.Theme);
        mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);

        mPreviewPopup = new PopupWindow(context);
        if (previewLayout != 0) {
            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
            mPreviewPopup.setContentView(mPreviewText);
            mPreviewPopup.setBackgroundDrawable(null);
        } else {
            mShowPreview = false;
        }
        
        mPreviewPopup.setTouchable(false);
        
        mPopupKeyboard = new PopupWindow(context);
        mPopupKeyboard.setBackgroundDrawable(null);
        //mPopupKeyboard.setClippingEnabled(false);
        
        mPopupParent = this;
        //mPredicting = true;
        
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(keyTextSize);
        mPaint.setTextAlign(Align.CENTER);

        mPadding = new Rect(0, 0, 0, 0);
        mMiniKeyboardCache = new HashMap<Key,View>();
        mKeyBackground.getPadding(mPadding);
        
        resetMultiTap();
        initGestureDetector();
    
Methods Summary
private java.lang.CharSequenceadjustCase(java.lang.CharSequence label)

        if (mKeyboard.isShifted() && label != null && label.length() < 3
                && Character.isLowerCase(label.charAt(0))) {
            label = label.toString().toUpperCase();
        }
        return label;
    
private voidcheckMultiTap(long eventTime, int keyIndex)

        if (keyIndex == NOT_A_KEY) return;
        Key key = mKeys[keyIndex];
        if (key.codes.length > 1) {
            mInMultiTap = true;
            if (eventTime < mLastTapTime + MULTITAP_INTERVAL
                    && keyIndex == mLastSentIndex) {
                mTapCount = (mTapCount + 1) % key.codes.length;
                return;
            } else {
                mTapCount = -1;
                return;
            }
        }
        if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
            resetMultiTap();
        }
    
public voidclosing()

        if (mPreviewPopup.isShowing()) {
            mPreviewPopup.dismiss();
        }
        mHandler.removeMessages(MSG_REPEAT);
        mHandler.removeMessages(MSG_LONGPRESS);
        mHandler.removeMessages(MSG_SHOW_PREVIEW);
        
        dismissPopupKeyboard();
        mBuffer = null;
        mCanvas = null;
        mMiniKeyboardCache.clear();
    
private voidcomputeProximityThreshold(Keyboard keyboard)
Compute the average distance between adjacent keys (horizontally and vertically) and square it to get the proximity threshold. We use a square here and in computing the touch distance from a key's center to avoid taking a square root.

param
keyboard

        if (keyboard == null) return;
        final Key[] keys = mKeys;
        if (keys == null) return;
        int length = keys.length;
        int dimensionSum = 0;
        for (int i = 0; i < length; i++) {
            Key key = keys[i];
            dimensionSum += Math.min(key.width, key.height) + key.gap;
        }
        if (dimensionSum < 0 || length == 0) return;
        mProximityThreshold = (int) (dimensionSum * 1.4f / length);
        mProximityThreshold *= mProximityThreshold; // Square it
    
private voiddetectAndSendKey(int x, int y, long eventTime)

        int index = mCurrentKey;
        if (index != NOT_A_KEY && index < mKeys.length) {
            final Key key = mKeys[index];
            if (key.text != null) {
                mKeyboardActionListener.onText(key.text);
                mKeyboardActionListener.onRelease(NOT_A_KEY);
            } else {
                int code = key.codes[0];
                //TextEntryState.keyPressedAt(key, x, y);
                int[] codes = new int[MAX_NEARBY_KEYS];
                Arrays.fill(codes, NOT_A_KEY);
                getKeyIndices(x, y, codes);
                // Multi-tap
                if (mInMultiTap) {
                    if (mTapCount != -1) {
                        mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
                    } else {
                        mTapCount = 0;
                    }
                    code = key.codes[mTapCount];
                }
                mKeyboardActionListener.onKey(code, codes);
                mKeyboardActionListener.onRelease(code);
            }
            mLastSentIndex = index;
            mLastTapTime = eventTime;
        }
    
private voiddismissPopupKeyboard()

        if (mPopupKeyboard.isShowing()) {
            mPopupKeyboard.dismiss();
            mMiniKeyboardOnScreen = false;
            invalidateAll();
        }
    
private intgetKeyIndices(int x, int y, int[] allKeys)

        final Key[] keys = mKeys;
        final boolean shifted = mKeyboard.isShifted();
        int primaryIndex = NOT_A_KEY;
        int closestKey = NOT_A_KEY;
        int closestKeyDist = mProximityThreshold + 1;
        java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
        int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
        final int keyCount = nearestKeyIndices.length;
        for (int i = 0; i < keyCount; i++) {
            final Key key = keys[nearestKeyIndices[i]];
            int dist = 0;
            boolean isInside = key.isInside(x,y);
            if (((mProximityCorrectOn 
                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) 
                    || isInside)
                    && key.codes[0] > 32) {
                // Find insertion point
                final int nCodes = key.codes.length;
                if (dist < closestKeyDist) {
                    closestKeyDist = dist;
                    closestKey = nearestKeyIndices[i];
                }
                
                if (allKeys == null) continue;
                
                for (int j = 0; j < mDistances.length; j++) {
                    if (mDistances[j] > dist) {
                        // Make space for nCodes codes
                        System.arraycopy(mDistances, j, mDistances, j + nCodes,
                                mDistances.length - j - nCodes);
                        System.arraycopy(allKeys, j, allKeys, j + nCodes,
                                allKeys.length - j - nCodes);
                        for (int c = 0; c < nCodes; c++) {
                            allKeys[j + c] = key.codes[c];
                            mDistances[j + c] = dist;
                        }
                        break;
                    }
                }
            }
            
            if (isInside) {
                primaryIndex = nearestKeyIndices[i];
            }
        }
        if (primaryIndex == NOT_A_KEY) {
            primaryIndex = closestKey;
        }
        return primaryIndex;
    
public KeyboardgetKeyboard()
Returns the current keyboard being displayed by this view.

return
the currently attached keyboard
see
#setKeyboard(Keyboard)

        return mKeyboard;
    
protected android.inputmethodservice.KeyboardView$OnKeyboardActionListenergetOnKeyboardActionListener()
Returns the {@link OnKeyboardActionListener} object.

return
the listener attached to this keyboard

        return mKeyboardActionListener;
    
private java.lang.CharSequencegetPreviewText(android.inputmethodservice.Keyboard.Key key)
Handle multi-tap keys by producing the key label for the current multi-tap state.

        if (mInMultiTap) {
            // Multi-tap
            mPreviewLabel.setLength(0);
            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
            return adjustCase(mPreviewLabel);
        } else {
            return adjustCase(key.label);
        }
    
public booleanhandleBack()

        if (mPopupKeyboard.isShowing()) {
            dismissPopupKeyboard();
            return true;
        }
        return false;
    
private voidinitGestureDetector()

        mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onFling(MotionEvent me1, MotionEvent me2, 
                    float velocityX, float velocityY) {
                final float absX = Math.abs(velocityX);
                final float absY = Math.abs(velocityY);
                if (velocityX > 500 && absY < absX) {
                    swipeRight();
                    return true;
                } else if (velocityX < -500 && absY < absX) {
                    swipeLeft();
                    return true;
                } else if (velocityY < -500 && absX < absY) {
                    swipeUp();
                    return true;
                } else if (velocityY > 500 && absX < 200) {
                    swipeDown();
                    return true;
                } else if (absX > 800 || absY > 800) {
                    return true;
                }
                return false;
            }
        });

        mGestureDetector.setIsLongpressEnabled(false);
    
private voidinvalidateAll()

        mDirtyRect.union(0, 0, getWidth(), getHeight());
        mDrawPending = true;
        invalidate();
    
private voidinvalidateKey(int keyIndex)

        if (keyIndex < 0 || keyIndex >= mKeys.length) {
            return;
        }
        final Key key = mKeys[keyIndex];
        mInvalidatedKey = key;
        mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop, 
                key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
        onBufferDraw();
        invalidate(key.x + mPaddingLeft, key.y + mPaddingTop, 
                key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
    
public booleanisPreviewEnabled()
Returns the enabled state of the key feedback popup.

return
whether or not the key feedback popup is enabled
see
#setPreviewEnabled(boolean)

        return mShowPreview;
    
public booleanisProximityCorrectionEnabled()
Returns true if proximity correction is enabled.

        return mProximityCorrectOn;
    
public booleanisShifted()
Returns the state of the shift key of the keyboard, if any.

return
true if the shift is in a pressed state, false otherwise. If there is no shift key on the keyboard or there is no keyboard attached, it returns false.
see
KeyboardView#setShifted(boolean)

        if (mKeyboard != null) {
            return mKeyboard.isShifted();
        }
        return false;
    
private voidonBufferDraw()

        if (mBuffer == null) {
            mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            mCanvas = new Canvas(mBuffer);
            invalidateAll();
        }
        final Canvas canvas = mCanvas;
        canvas.clipRect(mDirtyRect, Op.REPLACE);
        
        if (mKeyboard == null) return;
        
        final Paint paint = mPaint;
        final Drawable keyBackground = mKeyBackground;
        final Rect clipRegion = mClipRegion;
        final Rect padding = mPadding;
        final int kbdPaddingLeft = mPaddingLeft;
        final int kbdPaddingTop = mPaddingTop;
        final Key[] keys = mKeys;
        final Key invalidKey = mInvalidatedKey;

        paint.setAlpha(255);
        paint.setColor(mKeyTextColor);
        boolean drawSingleKey = false;
        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
          // Is clipRegion completely contained within the invalidated key?
          if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
                  invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
                  invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
                  invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
              drawSingleKey = true;
          }
        }
        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
        final int keyCount = keys.length;
        for (int i = 0; i < keyCount; i++) {
            final Key key = keys[i];
            if (drawSingleKey && invalidKey != key) {
                continue;
            }
            int[] drawableState = key.getCurrentDrawableState();
            keyBackground.setState(drawableState);
            
            // Switch the character to uppercase if shift is pressed
            String label = key.label == null? null : adjustCase(key.label).toString();
            
            final Rect bounds = keyBackground.getBounds();
            if (key.width != bounds.right || 
                    key.height != bounds.bottom) {
                keyBackground.setBounds(0, 0, key.width, key.height);
            }
            canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
            keyBackground.draw(canvas);
            
            if (label != null) {
                // For characters, use large font. For labels like "Done", use small font.
                if (label.length() > 1 && key.codes.length < 2) {
                    paint.setTextSize(mLabelTextSize);
                    paint.setTypeface(Typeface.DEFAULT_BOLD);
                } else {
                    paint.setTextSize(mKeyTextSize);
                    paint.setTypeface(Typeface.DEFAULT);
                }
                // Draw a drop shadow for the text
                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
                // Draw the text
                canvas.drawText(label,
                    (key.width - padding.left - padding.right) / 2
                            + padding.left,
                    (key.height - padding.top - padding.bottom) / 2
                            + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
                    paint);
                // Turn off drop shadow
                paint.setShadowLayer(0, 0, 0, 0);
            } else if (key.icon != null) {
                final int drawableX = (key.width - padding.left - padding.right 
                                - key.icon.getIntrinsicWidth()) / 2 + padding.left;
                final int drawableY = (key.height - padding.top - padding.bottom 
                        - key.icon.getIntrinsicHeight()) / 2 + padding.top;
                canvas.translate(drawableX, drawableY);
                key.icon.setBounds(0, 0, 
                        key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
                key.icon.draw(canvas);
                canvas.translate(-drawableX, -drawableY);
            }
            canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
        }
        mInvalidatedKey = null;
        // Overlay a dark rectangle to dim the keyboard
        if (mMiniKeyboardOnScreen) {
            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        }

        if (DEBUG && mShowTouchPoints) {
            paint.setAlpha(128);
            paint.setColor(0xFFFF0000);
            canvas.drawCircle(mStartX, mStartY, 3, paint);
            canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
            paint.setColor(0xFF0000FF);
            canvas.drawCircle(mLastX, mLastY, 3, paint);
            paint.setColor(0xFF00FF00);
            canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
        }
        
        mDrawPending = false;
        mDirtyRect.setEmpty();
    
public voidonClick(android.view.View v)
Popup keyboard close button clicked.

hide

        dismissPopupKeyboard();
    
public voidonDetachedFromWindow()

        super.onDetachedFromWindow();
        closing();
    
public voidonDraw(android.graphics.Canvas canvas)

        super.onDraw(canvas);
        if (mDrawPending || mBuffer == null) {
            onBufferDraw();
        }
        canvas.drawBitmap(mBuffer, 0, 0, null);
    
protected booleanonLongPress(android.inputmethodservice.Keyboard.Key popupKey)
Called when a key is long pressed. By default this will open any popup keyboard associated with this key through the attributes popupLayout and popupCharacters.

param
popupKey the key that was long pressed
return
true if the long press is handled, false otherwise. Subclasses should call the method on the base class if the subclass doesn't wish to handle the call.

        int popupKeyboardId = popupKey.popupResId;

        if (popupKeyboardId != 0) {
            mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
            if (mMiniKeyboardContainer == null) {
                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
                        com.android.internal.R.id.keyboardView);
                View closeButton = mMiniKeyboardContainer.findViewById(
                        com.android.internal.R.id.closeButton);
                if (closeButton != null) closeButton.setOnClickListener(this);
                mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
                    public void onKey(int primaryCode, int[] keyCodes) {
                        mKeyboardActionListener.onKey(primaryCode, keyCodes);
                        dismissPopupKeyboard();
                    }
                    
                    public void onText(CharSequence text) {
                        mKeyboardActionListener.onText(text);
                        dismissPopupKeyboard();
                    }
                    
                    public void swipeLeft() { }
                    public void swipeRight() { }
                    public void swipeUp() { }
                    public void swipeDown() { }
                    public void onPress(int primaryCode) {
                        mKeyboardActionListener.onPress(primaryCode);
                    }
                    public void onRelease(int primaryCode) {
                        mKeyboardActionListener.onRelease(primaryCode);
                    }
                });
                //mInputView.setSuggest(mSuggest);
                Keyboard keyboard;
                if (popupKey.popupCharacters != null) {
                    keyboard = new Keyboard(getContext(), popupKeyboardId, 
                            popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
                } else {
                    keyboard = new Keyboard(getContext(), popupKeyboardId);
                }
                mMiniKeyboard.setKeyboard(keyboard);
                mMiniKeyboard.setPopupParent(this);
                mMiniKeyboardContainer.measure(
                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), 
                        MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
                
                mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
            } else {
                mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
                        com.android.internal.R.id.keyboardView);
            }
            if (mWindowOffset == null) {
                mWindowOffset = new int[2];
                getLocationInWindow(mWindowOffset);
            }
            mPopupX = popupKey.x + mPaddingLeft;
            mPopupY = popupKey.y + mPaddingTop;
            mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
            mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
            final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
            final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
            mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
            mMiniKeyboard.setShifted(isShifted());
            mPopupKeyboard.setContentView(mMiniKeyboardContainer);
            mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
            mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
            mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
            mMiniKeyboardOnScreen = true;
            //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
            invalidateAll();
            return true;
        }
        return false;
    
public voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        // Round up a little
        if (mKeyboard == null) {
            setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
        } else {
            int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
                width = MeasureSpec.getSize(widthMeasureSpec);
            }
            setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
        }
    
public voidonSizeChanged(int w, int h, int oldw, int oldh)

        super.onSizeChanged(w, h, oldw, oldh);
        // Release the buffer, if any and it will be reallocated on the next draw
        mBuffer = null;
    
public booleanonTouchEvent(android.view.MotionEvent me)

        int touchX = (int) me.getX() - mPaddingLeft;
        int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;
        int action = me.getAction();
        long eventTime = me.getEventTime();
        int keyIndex = getKeyIndices(touchX, touchY, null);
        
        if (mGestureDetector.onTouchEvent(me)) {
            showPreview(NOT_A_KEY);
            mHandler.removeMessages(MSG_REPEAT);
            mHandler.removeMessages(MSG_LONGPRESS);            
            return true;
        }
        
        // Needs to be called after the gesture detector gets a turn, as it may have
        // displayed the mini keyboard
        if (mMiniKeyboardOnScreen) {
            return true;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mAbortKey = false;
                mStartX = touchX;
                mStartY = touchY;
                mLastCodeX = touchX;
                mLastCodeY = touchY;
                mLastKeyTime = 0;
                mCurrentKeyTime = 0;
                mLastKey = NOT_A_KEY;
                mCurrentKey = keyIndex;
                mDownTime = me.getEventTime();
                mLastMoveTime = mDownTime;
                checkMultiTap(eventTime, keyIndex);
                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? 
                        mKeys[keyIndex].codes[0] : 0);
                if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
                    mRepeatKeyIndex = mCurrentKey;
                    repeatKey();
                    Message msg = mHandler.obtainMessage(MSG_REPEAT);
                    mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
                }
                if (mCurrentKey != NOT_A_KEY) {
                    Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
                    mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
                }
                showPreview(keyIndex);
                break;

            case MotionEvent.ACTION_MOVE:
                boolean continueLongPress = false;
                if (keyIndex != NOT_A_KEY) {
                    if (mCurrentKey == NOT_A_KEY) {
                        mCurrentKey = keyIndex;
                        mCurrentKeyTime = eventTime - mDownTime;
                    } else {
                        if (keyIndex == mCurrentKey) {
                            mCurrentKeyTime += eventTime - mLastMoveTime;
                            continueLongPress = true;
                        } else {
                            resetMultiTap();
                            mLastKey = mCurrentKey;
                            mLastCodeX = mLastX;
                            mLastCodeY = mLastY;
                            mLastKeyTime =
                                    mCurrentKeyTime + eventTime - mLastMoveTime;
                            mCurrentKey = keyIndex;
                            mCurrentKeyTime = 0;
                        }
                    }
                    if (keyIndex != mRepeatKeyIndex) {
                        mHandler.removeMessages(MSG_REPEAT);
                        mRepeatKeyIndex = NOT_A_KEY;
                    }
                }
                if (!continueLongPress) {
                    // Cancel old longpress
                    mHandler.removeMessages(MSG_LONGPRESS);
                    // Start new longpress if key has changed
                    if (keyIndex != NOT_A_KEY) {
                        Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
                        mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
                    }
                }
                showPreview(keyIndex);
                break;

            case MotionEvent.ACTION_UP:
                mHandler.removeMessages(MSG_SHOW_PREVIEW);
                mHandler.removeMessages(MSG_REPEAT);
                mHandler.removeMessages(MSG_LONGPRESS);
                if (keyIndex == mCurrentKey) {
                    mCurrentKeyTime += eventTime - mLastMoveTime;
                } else {
                    resetMultiTap();
                    mLastKey = mCurrentKey;
                    mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
                    mCurrentKey = keyIndex;
                    mCurrentKeyTime = 0;
                }
                if (mCurrentKeyTime < mLastKeyTime && mLastKey != NOT_A_KEY) {
                    mCurrentKey = mLastKey;
                    touchX = mLastCodeX;
                    touchY = mLastCodeY;
                }
                showPreview(NOT_A_KEY);
                Arrays.fill(mKeyIndices, NOT_A_KEY);
                // If we're not on a repeating key (which sends on a DOWN event)
                if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
                    detectAndSendKey(touchX, touchY, eventTime);
                }
                invalidateKey(keyIndex);
                mRepeatKeyIndex = NOT_A_KEY;
                break;
        }
        mLastX = touchX;
        mLastY = touchY;
        return true;
    
private booleanopenPopupIfRequired(android.view.MotionEvent me)

        // Check if we have a popup layout specified first.
        if (mPopupLayout == 0) {
            return false;
        }
        if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
            return false;
        }

        Key popupKey = mKeys[mCurrentKey];        
        boolean result = onLongPress(popupKey);
        if (result) {
            mAbortKey = true;
            showPreview(NOT_A_KEY);
        }
        return result;
    
private booleanrepeatKey()

        Key key = mKeys[mRepeatKeyIndex];
        detectAndSendKey(key.x, key.y, mLastTapTime);
        return true;
    
private voidresetMultiTap()

        mLastSentIndex = NOT_A_KEY;
        mTapCount = 0;
        mLastTapTime = -1;
        mInMultiTap = false;
    
public voidsetKeyboard(Keyboard keyboard)
Attaches a keyboard to this view. The keyboard can be switched at any time and the view will re-layout itself to accommodate the keyboard.

see
Keyboard
see
#getKeyboard()
param
keyboard the keyboard to display in this view

        if (mKeyboard != null) {
            showPreview(NOT_A_KEY);
        }
        mKeyboard = keyboard;
        List<Key> keys = mKeyboard.getKeys();
        mKeys = keys.toArray(new Key[keys.size()]);
        requestLayout();
        // Release buffer, just in case the new keyboard has a different size. 
        // It will be reallocated on the next draw.
        mBuffer = null;
        invalidateAll();
        computeProximityThreshold(keyboard);
        mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
    
public voidsetOnKeyboardActionListener(android.inputmethodservice.KeyboardView$OnKeyboardActionListener listener)

        mKeyboardActionListener = listener;
    
public voidsetPopupOffset(int x, int y)

        mMiniKeyboardOffsetX = x;
        mMiniKeyboardOffsetY = y;
        if (mPreviewPopup.isShowing()) {
            mPreviewPopup.dismiss();
        }
    
public voidsetPopupParent(android.view.View v)

        mPopupParent = v;
    
public voidsetPreviewEnabled(boolean previewEnabled)
Enables or disables the key feedback popup. This is a popup that shows a magnified version of the depressed key. By default the preview is enabled.

param
previewEnabled whether or not to enable the key feedback popup
see
#isPreviewEnabled()

        mShowPreview = previewEnabled;
    
public voidsetProximityCorrectionEnabled(boolean enabled)
When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key codes for adjacent keys. When disabled, only the primary key code will be reported.

param
enabled whether or not the proximity correction is enabled

        mProximityCorrectOn = enabled;
    
public booleansetShifted(boolean shifted)
Sets the state of the shift key of the keyboard, if any.

param
shifted whether or not to enable the state of the shift key
return
true if the shift key state changed, false if there was no change
see
KeyboardView#isShifted()

        if (mKeyboard != null) {
            if (mKeyboard.setShifted(shifted)) {
                // The whole keyboard probably needs to be redrawn
                invalidateAll();
                return true;
            }
        }
        return false;
    
public voidsetVerticalCorrection(int verticalOffset)

        
    
private voidshowKey(int keyIndex)

        final PopupWindow previewPopup = mPreviewPopup;
        final Key[] keys = mKeys;
        Key key = keys[keyIndex];
        if (key.icon != null) {
            mPreviewText.setCompoundDrawables(null, null, null, 
                    key.iconPreview != null ? key.iconPreview : key.icon);
            mPreviewText.setText(null);
        } else {
            mPreviewText.setCompoundDrawables(null, null, null, null);
            mPreviewText.setText(getPreviewText(key));
            if (key.label.length() > 1 && key.codes.length < 2) {
                mPreviewText.setTextSize(mKeyTextSize);
                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
            } else {
                mPreviewText.setTextSize(mPreviewTextSizeLarge);
                mPreviewText.setTypeface(Typeface.DEFAULT);
            }
        }
        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width 
                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
        final int popupHeight = mPreviewHeight;
        LayoutParams lp = mPreviewText.getLayoutParams();
        if (lp != null) {
            lp.width = popupWidth;
            lp.height = popupHeight;
        }
        if (!mPreviewCentered) {
            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
        } else {
            // TODO: Fix this if centering is brought back
            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
            mPopupPreviewY = - mPreviewText.getMeasuredHeight();
        }
        mHandler.removeMessages(MSG_REMOVE_PREVIEW);
        if (mOffsetInWindow == null) {
            mOffsetInWindow = new int[2];
            getLocationInWindow(mOffsetInWindow);
            mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
            mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
        }
        // Set the preview background state
        mPreviewText.getBackground().setState(
                key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
        if (previewPopup.isShowing()) {
            previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
                    mPopupPreviewY + mOffsetInWindow[1], 
                    popupWidth, popupHeight);
        } else {
            previewPopup.setWidth(popupWidth);
            previewPopup.setHeight(popupHeight);
            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, 
                    mPopupPreviewX + mOffsetInWindow[0], 
                    mPopupPreviewY + mOffsetInWindow[1]);
        }
        mPreviewText.setVisibility(VISIBLE);
    
private voidshowPreview(int keyIndex)

        int oldKeyIndex = mCurrentKeyIndex;
        final PopupWindow previewPopup = mPreviewPopup;
        
        mCurrentKeyIndex = keyIndex;
        // Release the old key and press the new key
        final Key[] keys = mKeys;
        if (oldKeyIndex != mCurrentKeyIndex) {
            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
                keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
                invalidateKey(oldKeyIndex);
            }
            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
                keys[mCurrentKeyIndex].onPressed();
                invalidateKey(mCurrentKeyIndex);
            }
        }
        // If key changed and preview is on ...
        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
            mHandler.removeMessages(MSG_SHOW_PREVIEW);
            if (previewPopup.isShowing()) {
                if (keyIndex == NOT_A_KEY) {
                    mHandler.sendMessageDelayed(mHandler
                            .obtainMessage(MSG_REMOVE_PREVIEW), 
                            DELAY_AFTER_PREVIEW);
                }
            }
            if (keyIndex != NOT_A_KEY) {
                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
                    // Show right away, if it's already visible and finger is moving around
                    showKey(keyIndex);
                } else {
                    mHandler.sendMessageDelayed(
                            mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), 
                            DELAY_BEFORE_PREVIEW);
                }
            }
        }
    
protected voidswipeDown()

        mKeyboardActionListener.swipeDown();
    
protected voidswipeLeft()

        mKeyboardActionListener.swipeLeft();
    
protected voidswipeRight()

        mKeyboardActionListener.swipeRight();
    
protected voidswipeUp()

        mKeyboardActionListener.swipeUp();