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          */
onPress(int primaryCode)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          */
onRelease(int primaryCode)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          */
onKey(int primaryCode, int[] keyCodes)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          */
onText(CharSequence text)99         void onText(CharSequence text);
100 
101         /**
102          * Called when the user quickly moves the finger from right to left.
103          */
swipeLeft()104         void swipeLeft();
105 
106         /**
107          * Called when the user quickly moves the finger from left to right.
108          */
swipeRight()109         void swipeRight();
110 
111         /**
112          * Called when the user quickly moves the finger from up to down.
113          */
swipeDown()114         void swipeDown();
115 
116         /**
117          * Called when the user quickly moves the finger from down to up.
118          */
swipeUp()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          */
onLongPress(Keyboard.Key key)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 */
KeyboardView(Context context, AttributeSet attrs)274     public KeyboardView(Context context, AttributeSet attrs) {
275         this(context, attrs, 0);
276     }
277 
278     /** Constructor */
KeyboardView(Context context, AttributeSet attrs, int defStyle)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 
initGestureDetector()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      */
setOnKeyboardActionListener(OnKeyboardActionListener listener)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      */
getOnKeyboardActionListener()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      */
setKeyboard(Keyboard keyboard)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      */
getKeyboard()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      */
setShifted(boolean shifted)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      */
isShifted()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      */
setPreviewEnabled(boolean previewEnabled)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      */
isPreviewEnabled()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      */
isParentPreviewEnabled()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 
setVerticalCorrection(int verticalOffset)566     public void setVerticalCorrection(int verticalOffset) {
567 
568     }
569 
570     /**
571      * Set View on the PopupParent.
572      * @param v  The View to set.
573      */
setPopupParent(View v)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      */
setPopupOffset(int x, int y)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      */
setProximityCorrectionEnabled(boolean enabled)597     public void setProximityCorrectionEnabled(boolean enabled) {
598         mProximityCorrectOn = enabled;
599     }
600 
601     /**
602      * Returns true if proximity correction is enabled.
603      */
isProximityCorrectionEnabled()604     public boolean isProximityCorrectionEnabled() {
605         return mProximityCorrectOn;
606     }
607 
608     /**
609      * Popup keyboard close button clicked.
610      * @hide
611      */
onClick(View v)612     public void onClick(View v) {
613         dismissPopupKeyboard();
614     }
615 
adjustCase(CharSequence label)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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      */
computeProximityThreshold(Keyboard keyboard)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
onSizeChanged(int w, int h, int oldw, int oldh)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
onDraw(Canvas canvas)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 
onBufferDraw()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 
getKeyIndices(int x, int y, int[] allKeys)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 
detectAndSendKey(int index, int x, int y, long eventTime)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      */
getPreviewText(Key key)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 
showPreview(int keyIndex)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 
showKey(final int keyIndex)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      */
invalidateAllKeys()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      */
invalidateKey(int keyIndex)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 
openPopupIfRequired(MotionEvent me)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      */
onLongPress(Key popupKey)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 
repeatKey()1313     private boolean repeatKey() {
1314         Key key = mKeys[mRepeatKeyIndex];
1315         detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
1316         return true;
1317     }
1318 
swipeRight()1319     protected void swipeRight() {
1320         mKeyboardActionListener.swipeRight();
1321     }
1322 
swipeLeft()1323     protected void swipeLeft() {
1324         mKeyboardActionListener.swipeLeft();
1325     }
1326 
swipeUp()1327     protected void swipeUp() {
1328         mKeyboardActionListener.swipeUp();
1329     }
1330 
swipeDown()1331     protected void swipeDown() {
1332         mKeyboardActionListener.swipeDown();
1333     }
1334 
closing()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 
removeMessages()1347     private void removeMessages() {
1348         mHandler.removeMessages(MSG_REPEAT);
1349         mHandler.removeMessages(MSG_LONGPRESS);
1350         mHandler.removeMessages(MSG_SHOW_PREVIEW);
1351     }
1352 
1353     @Override
onDetachedFromWindow()1354     public void onDetachedFromWindow() {
1355         super.onDetachedFromWindow();
1356         closing();
1357     }
1358 
dismissPopupKeyboard()1359     private void dismissPopupKeyboard() {
1360         if (mPopupKeyboard.isShowing()) {
1361             mPopupKeyboard.dismiss();
1362             mMiniKeyboardOnScreen = false;
1363             invalidateAllKeys();
1364         }
1365     }
1366 
handleBack()1367     public boolean handleBack() {
1368         if (mPopupKeyboard.isShowing()) {
1369             dismissPopupKeyboard();
1370             return true;
1371         }
1372         return false;
1373     }
1374 
resetMultiTap()1375     private void resetMultiTap() {
1376         mLastSentIndex = NOT_A_KEY;
1377         mTapCount = 0;
1378         mLastTapTime = -1;
1379         mInMultiTap = false;
1380     }
1381 
checkMultiTap(long eventTime, int keyIndex)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 
clear()1413         public void clear() {
1414             mPastTime[0] = 0;
1415         }
1416 
addMovement(MotionEvent ev)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 
addPoint(float x, float y, long time)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 
computeCurrentVelocity(int units)1461         public void computeCurrentVelocity(int units) {
1462             computeCurrentVelocity(units, Float.MAX_VALUE);
1463         }
1464 
computeCurrentVelocity(int units, float maxVelocity)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