Home | History | Annotate | Download | only in openwnn
      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(0, 0, 0, 0);
    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, 0, 0);
    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(0, 0, 0, 0);
    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 == null) return;
    645         final Key[] keys = mKeys;
    646         if (keys == null) return;
    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 == 0) return;
    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, 0, 0, null);
    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 == null) return;
    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 == null? null : 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(0, 0, 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, 0, 0, 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(0, 0, 0, 0);
    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(0, 0,
    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(0, 0, 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 == null) continue;
    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(null, null, null,
    935                     key.iconPreview != null ? key.iconPreview : key.icon);
    936             mPreviewText.setText(null);
    937             mPreviewText.setPadding(5, 0, 5, 20);
    938         } else {
    939             mPreviewText.setCompoundDrawables(null, null, null, null);
    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(0, 0, 0, 10);
    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(0, 0, 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 == null) return;
   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;
   1284                     mCurrentKeyTime = 0;
   1285                 }
   1286                 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
   1287                         && mLastKey != NOT_A_KEY) {
   1288                     mCurrentKey = mLastKey;
   1289                     touchX = mLastCodeX;
   1290                     touchY = mLastCodeY;
   1291                 }
   1292                 showPreview(NOT_A_KEY);
   1293                 Arrays.fill(mKeyIndices, NOT_A_KEY);
   1294                 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
   1295                     detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
   1296                 }
   1297                 invalidateKey(keyIndex);
   1298                 mRepeatKeyIndex = NOT_A_KEY;
   1299                 break;
   1300             case MotionEvent.ACTION_CANCEL:
   1301                 removeMessages();
   1302                 dismissPopupKeyboard();
   1303                 mAbortKey = true;
   1304                 showPreview(NOT_A_KEY);
   1305                 invalidateKey(mCurrentKey);
   1306                 break;
   1307         }
   1308         mLastX = touchX;
   1309         mLastY = touchY;
   1310         return true;
   1311     }
   1312 
   1313     private boolean repeatKey() {
   1314         Key key = mKeys[mRepeatKeyIndex];
   1315         detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
   1316         return true;
   1317     }
   1318 
   1319     protected void swipeRight() {
   1320         mKeyboardActionListener.swipeRight();
   1321     }
   1322 
   1323     protected void swipeLeft() {
   1324         mKeyboardActionListener.swipeLeft();
   1325     }
   1326 
   1327     protected void swipeUp() {
   1328         mKeyboardActionListener.swipeUp();
   1329     }
   1330 
   1331     protected void swipeDown() {
   1332         mKeyboardActionListener.swipeDown();
   1333     }
   1334 
   1335     public void closing() {
   1336         if (mPreviewPopup.isShowing()) {
   1337             mPreviewPopup.dismiss();
   1338         }
   1339         removeMessages();
   1340 
   1341         dismissPopupKeyboard();
   1342         mBuffer = null;
   1343         mCanvas = null;
   1344         mMiniKeyboardCache.clear();
   1345     }
   1346 
   1347     private void removeMessages() {
   1348         mHandler.removeMessages(MSG_REPEAT);
   1349         mHandler.removeMessages(MSG_LONGPRESS);
   1350         mHandler.removeMessages(MSG_SHOW_PREVIEW);
   1351     }
   1352 
   1353     @Override
   1354     public void onDetachedFromWindow() {
   1355         super.onDetachedFromWindow();
   1356         closing();
   1357     }
   1358 
   1359     private void dismissPopupKeyboard() {
   1360         if (mPopupKeyboard.isShowing()) {
   1361             mPopupKeyboard.dismiss();
   1362             mMiniKeyboardOnScreen = false;
   1363             invalidateAllKeys();
   1364         }
   1365     }
   1366 
   1367     public boolean handleBack() {
   1368         if (mPopupKeyboard.isShowing()) {
   1369             dismissPopupKeyboard();
   1370             return true;
   1371         }
   1372         return false;
   1373     }
   1374 
   1375     private void resetMultiTap() {
   1376         mLastSentIndex = NOT_A_KEY;
   1377         mTapCount = 0;
   1378         mLastTapTime = -1;
   1379         mInMultiTap = false;
   1380     }
   1381 
   1382     private void checkMultiTap(long eventTime, int keyIndex) {
   1383         if (keyIndex == NOT_A_KEY) return;
   1384         Key key = mKeys[keyIndex];
   1385         if (key.codes.length > 1) {
   1386             mInMultiTap = true;
   1387             if (eventTime < mLastTapTime + MULTITAP_INTERVAL
   1388                     && keyIndex == mLastSentIndex) {
   1389                 mTapCount = (mTapCount + 1) % key.codes.length;
   1390                 return;
   1391             } else {
   1392                 mTapCount = -1;
   1393                 return;
   1394             }
   1395         }
   1396         if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
   1397             resetMultiTap();
   1398         }
   1399     }
   1400 
   1401     private static class SwipeTracker {
   1402 
   1403         static final int NUM_PAST = 4;
   1404         static final int LONGEST_PAST_TIME = 200;
   1405 
   1406         final float mPastX[] = new float[NUM_PAST];
   1407         final float mPastY[] = new float[NUM_PAST];
   1408         final long mPastTime[] = new long[NUM_PAST];
   1409 
   1410         float mYVelocity;
   1411         float mXVelocity;
   1412 
   1413         public void clear() {
   1414             mPastTime[0] = 0;
   1415         }
   1416 
   1417         public void addMovement(MotionEvent ev) {
   1418             long time = ev.getEventTime();
   1419             final int N = ev.getHistorySize();
   1420             for (int i=0; i<N; i++) {
   1421                 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
   1422                         ev.getHistoricalEventTime(i));
   1423             }
   1424             addPoint(ev.getX(), ev.getY(), time);
   1425         }
   1426 
   1427         private void addPoint(float x, float y, long time) {
   1428             int drop = -1;
   1429             int i;
   1430             final long[] pastTime = mPastTime;
   1431             for (i=0; i<NUM_PAST; i++) {
   1432                 if (pastTime[i] == 0) {
   1433                     break;
   1434                 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
   1435                     drop = i;
   1436                 }
   1437             }
   1438             if (i == NUM_PAST && drop < 0) {
   1439                 drop = 0;
   1440             }
   1441             if (drop == i) drop--;
   1442             final float[] pastX = mPastX;
   1443             final float[] pastY = mPastY;
   1444             if (drop >= 0) {
   1445                 final int start = drop+1;
   1446                 final int count = NUM_PAST-drop-1;
   1447                 System.arraycopy(pastX, start, pastX, 0, count);
   1448                 System.arraycopy(pastY, start, pastY, 0, count);
   1449                 System.arraycopy(pastTime, start, pastTime, 0, count);
   1450                 i -= (drop+1);
   1451             }
   1452             pastX[i] = x;
   1453             pastY[i] = y;
   1454             pastTime[i] = time;
   1455             i++;
   1456             if (i < NUM_PAST) {
   1457                 pastTime[i] = 0;
   1458             }
   1459         }
   1460 
   1461         public void computeCurrentVelocity(int units) {
   1462             computeCurrentVelocity(units, Float.MAX_VALUE);
   1463         }
   1464 
   1465         public void computeCurrentVelocity(int units, float maxVelocity) {
   1466             final float[] pastX = mPastX;
   1467             final float[] pastY = mPastY;
   1468             final long[] pastTime = mPastTime;
   1469 
   1470             final float oldestX = pastX[0];
   1471             final float oldestY = pastY[0];
   1472             final long oldestTime = pastTime[0];
   1473             float accumX = 0;
   1474             float accumY = 0;
   1475             int N=0;
   1476             while (N < NUM_PAST) {
   1477                 if (pastTime[N] == 0) {
   1478                     break;
   1479                 }
   1480                 N++;
   1481             }
   1482 
   1483             for (int i=1; i < N; i++) {
   1484                 final int dur = (int)(pastTime[i] - oldestTime);
   1485                 if (dur == 0) continue;
   1486                 float dist = pastX[i] - oldestX;
   1487                 float vel = (dist/dur) * units;
   1488                 if (accumX == 0) accumX = vel;
   1489                 else accumX = (accumX + vel) * .5f;
   1490 
   1491                 dist = pastY[i] - oldestY;
   1492                 vel = (dist/dur) * units;
   1493                 if (accumY == 0) accumY = vel;
   1494                 else accumY = (accumY + vel) * .5f;
   1495             }
   1496             mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
   1497                     : Math.min(accumX, maxVelocity);
   1498             mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
   1499                     : Math.min(accumY, maxVelocity);
   1500         }
   1501 
   1502         public float getXVelocity() {
   1503             return mXVelocity;
   1504         }
   1505 
   1506         public float getYVelocity() {
   1507             return mYVelocity;
   1508         }
   1509     }
   1510 
   1511     /**
   1512      * Clear window info.
   1513      */
   1514     public void clearWindowInfo() {
   1515         mOffsetInWindow = null;
   1516     }
   1517 }
   1518