• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008-2009 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.inputmethodservice;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.PorterDuff;
25 import android.graphics.Rect;
26 import android.graphics.Typeface;
27 import android.graphics.Paint.Align;
28 import android.graphics.Region.Op;
29 import android.graphics.drawable.Drawable;
30 import android.inputmethodservice.Keyboard.Key;
31 import android.media.AudioManager;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.provider.Settings;
35 import android.util.AttributeSet;
36 import android.util.TypedValue;
37 import android.view.GestureDetector;
38 import android.view.Gravity;
39 import android.view.LayoutInflater;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.ViewConfiguration;
43 import android.view.ViewGroup.LayoutParams;
44 import android.view.accessibility.AccessibilityEvent;
45 import android.view.accessibility.AccessibilityManager;
46 import android.widget.PopupWindow;
47 import android.widget.TextView;
48 
49 import com.android.internal.R;
50 
51 import java.util.Arrays;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
55 
56 /**
57  * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
58  * detecting key presses and touch movements.
59  *
60  * @attr ref android.R.styleable#KeyboardView_keyBackground
61  * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
62  * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
63  * @attr ref android.R.styleable#KeyboardView_labelTextSize
64  * @attr ref android.R.styleable#KeyboardView_keyTextSize
65  * @attr ref android.R.styleable#KeyboardView_keyTextColor
66  * @attr ref android.R.styleable#KeyboardView_verticalCorrection
67  * @attr ref android.R.styleable#KeyboardView_popupLayout
68  */
69 public class KeyboardView extends View implements View.OnClickListener {
70 
71     /**
72      * Listener for virtual keyboard events.
73      */
74     public interface OnKeyboardActionListener {
75 
76         /**
77          * Called when the user presses a key. This is sent before the {@link #onKey} is called.
78          * For keys that repeat, this is only called once.
79          * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
80          * key, the value will be zero.
81          */
onPress(int primaryCode)82         void onPress(int primaryCode);
83 
84         /**
85          * Called when the user releases a key. This is sent after the {@link #onKey} is called.
86          * For keys that repeat, this is only called once.
87          * @param primaryCode the code of the key that was released
88          */
onRelease(int primaryCode)89         void onRelease(int primaryCode);
90 
91         /**
92          * Send a key press to the listener.
93          * @param primaryCode this is the key that was pressed
94          * @param keyCodes the codes for all the possible alternative keys
95          * with the primary code being the first. If the primary key code is
96          * a single character such as an alphabet or number or symbol, the alternatives
97          * will include other characters that may be on the same key or adjacent keys.
98          * These codes are useful to correct for accidental presses of a key adjacent to
99          * the intended key.
100          */
onKey(int primaryCode, int[] keyCodes)101         void onKey(int primaryCode, int[] keyCodes);
102 
103         /**
104          * Sends a sequence of characters to the listener.
105          * @param text the sequence of characters to be displayed.
106          */
onText(CharSequence text)107         void onText(CharSequence text);
108 
109         /**
110          * Called when the user quickly moves the finger from right to left.
111          */
swipeLeft()112         void swipeLeft();
113 
114         /**
115          * Called when the user quickly moves the finger from left to right.
116          */
swipeRight()117         void swipeRight();
118 
119         /**
120          * Called when the user quickly moves the finger from up to down.
121          */
swipeDown()122         void swipeDown();
123 
124         /**
125          * Called when the user quickly moves the finger from down to up.
126          */
swipeUp()127         void swipeUp();
128     }
129 
130     private static final boolean DEBUG = false;
131     private static final int NOT_A_KEY = -1;
132     private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
133     private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
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 float mShadowRadius;
141     private int mShadowColor;
142     private float mBackgroundDimAmount;
143 
144     private TextView mPreviewText;
145     private PopupWindow mPreviewPopup;
146     private int mPreviewTextSizeLarge;
147     private int mPreviewOffset;
148     private int mPreviewHeight;
149     // Working variable
150     private final int[] mCoordinates = new int[2];
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 Key[] mKeys;
161 
162     /** Listener for {@link OnKeyboardActionListener}. */
163     private OnKeyboardActionListener mKeyboardActionListener;
164 
165     private static final int MSG_SHOW_PREVIEW = 1;
166     private static final int MSG_REMOVE_PREVIEW = 2;
167     private static final int MSG_REPEAT = 3;
168     private static final int MSG_LONGPRESS = 4;
169 
170     private static final int DELAY_BEFORE_PREVIEW = 0;
171     private static final int DELAY_AFTER_PREVIEW = 70;
172     private static final int DEBOUNCE_TIME = 70;
173 
174     private int mVerticalCorrection;
175     private int mProximityThreshold;
176 
177     private boolean mPreviewCentered = false;
178     private boolean mShowPreview = true;
179     private boolean mShowTouchPoints = true;
180     private int mPopupPreviewX;
181     private int mPopupPreviewY;
182 
183     private int mLastX;
184     private int mLastY;
185     private int mStartX;
186     private int mStartY;
187 
188     private boolean mProximityCorrectOn;
189 
190     private Paint mPaint;
191     private Rect mPadding;
192 
193     private long mDownTime;
194     private long mLastMoveTime;
195     private int mLastKey;
196     private int mLastCodeX;
197     private int mLastCodeY;
198     private int mCurrentKey = NOT_A_KEY;
199     private int mDownKey = NOT_A_KEY;
200     private long mLastKeyTime;
201     private long mCurrentKeyTime;
202     private int[] mKeyIndices = new int[12];
203     private GestureDetector mGestureDetector;
204     private int mPopupX;
205     private int mPopupY;
206     private int mRepeatKeyIndex = NOT_A_KEY;
207     private int mPopupLayout;
208     private boolean mAbortKey;
209     private Key mInvalidatedKey;
210     private Rect mClipRegion = new Rect(0, 0, 0, 0);
211     private boolean mPossiblePoly;
212     private SwipeTracker mSwipeTracker = new SwipeTracker();
213     private int mSwipeThreshold;
214     private boolean mDisambiguateSwipe;
215 
216     // Variables for dealing with multiple pointers
217     private int mOldPointerCount = 1;
218     private float mOldPointerX;
219     private float mOldPointerY;
220 
221     private Drawable mKeyBackground;
222 
223     private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
224     private static final int REPEAT_START_DELAY = 400;
225     private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
226 
227     private static int MAX_NEARBY_KEYS = 12;
228     private int[] mDistances = new int[MAX_NEARBY_KEYS];
229 
230     // For multi-tap
231     private int mLastSentIndex;
232     private int mTapCount;
233     private long mLastTapTime;
234     private boolean mInMultiTap;
235     private static final int MULTITAP_INTERVAL = 800; // milliseconds
236     private StringBuilder mPreviewLabel = new StringBuilder(1);
237 
238     /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
239     private boolean mDrawPending;
240     /** The dirty region in the keyboard bitmap */
241     private Rect mDirtyRect = new Rect();
242     /** The keyboard bitmap for faster updates */
243     private Bitmap mBuffer;
244     /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
245     private boolean mKeyboardChanged;
246     /** The canvas for the above mutable keyboard bitmap */
247     private Canvas mCanvas;
248     /** The accessibility manager for accessibility support */
249     private AccessibilityManager mAccessibilityManager;
250     /** The audio manager for accessibility support */
251     private AudioManager mAudioManager;
252     /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
253     private boolean mHeadsetRequiredToHearPasswordsAnnounced;
254 
255     Handler mHandler = new Handler() {
256         @Override
257         public void handleMessage(Message msg) {
258             switch (msg.what) {
259                 case MSG_SHOW_PREVIEW:
260                     showKey(msg.arg1);
261                     break;
262                 case MSG_REMOVE_PREVIEW:
263                     mPreviewText.setVisibility(INVISIBLE);
264                     break;
265                 case MSG_REPEAT:
266                     if (repeatKey()) {
267                         Message repeat = Message.obtain(this, MSG_REPEAT);
268                         sendMessageDelayed(repeat, REPEAT_INTERVAL);
269                     }
270                     break;
271                 case MSG_LONGPRESS:
272                     openPopupIfRequired((MotionEvent) msg.obj);
273                     break;
274             }
275         }
276     };
277 
KeyboardView(Context context, AttributeSet attrs)278     public KeyboardView(Context context, AttributeSet attrs) {
279         this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
280     }
281 
KeyboardView(Context context, AttributeSet attrs, int defStyleAttr)282     public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
283         this(context, attrs, defStyleAttr, 0);
284     }
285 
KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)286     public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
287         super(context, attrs, defStyleAttr, defStyleRes);
288 
289         TypedArray a = context.obtainStyledAttributes(
290                 attrs, android.R.styleable.KeyboardView, defStyleAttr, defStyleRes);
291 
292         LayoutInflater inflate =
293                 (LayoutInflater) context
294                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
295 
296         int previewLayout = 0;
297         int keyTextSize = 0;
298 
299         int n = a.getIndexCount();
300 
301         for (int i = 0; i < n; i++) {
302             int attr = a.getIndex(i);
303 
304             switch (attr) {
305             case com.android.internal.R.styleable.KeyboardView_keyBackground:
306                 mKeyBackground = a.getDrawable(attr);
307                 break;
308             case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
309                 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
310                 break;
311             case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
312                 previewLayout = a.getResourceId(attr, 0);
313                 break;
314             case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
315                 mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
316                 break;
317             case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
318                 mPreviewHeight = a.getDimensionPixelSize(attr, 80);
319                 break;
320             case com.android.internal.R.styleable.KeyboardView_keyTextSize:
321                 mKeyTextSize = a.getDimensionPixelSize(attr, 18);
322                 break;
323             case com.android.internal.R.styleable.KeyboardView_keyTextColor:
324                 mKeyTextColor = a.getColor(attr, 0xFF000000);
325                 break;
326             case com.android.internal.R.styleable.KeyboardView_labelTextSize:
327                 mLabelTextSize = a.getDimensionPixelSize(attr, 14);
328                 break;
329             case com.android.internal.R.styleable.KeyboardView_popupLayout:
330                 mPopupLayout = a.getResourceId(attr, 0);
331                 break;
332             case com.android.internal.R.styleable.KeyboardView_shadowColor:
333                 mShadowColor = a.getColor(attr, 0);
334                 break;
335             case com.android.internal.R.styleable.KeyboardView_shadowRadius:
336                 mShadowRadius = a.getFloat(attr, 0f);
337                 break;
338             }
339         }
340 
341         a = mContext.obtainStyledAttributes(
342                 com.android.internal.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         //mPopupKeyboard.setClippingEnabled(false);
360 
361         mPopupParent = this;
362         //mPredicting = true;
363 
364         mPaint = new Paint();
365         mPaint.setAntiAlias(true);
366         mPaint.setTextSize(keyTextSize);
367         mPaint.setTextAlign(Align.CENTER);
368         mPaint.setAlpha(255);
369 
370         mPadding = new Rect(0, 0, 0, 0);
371         mMiniKeyboardCache = new HashMap<Key,View>();
372         mKeyBackground.getPadding(mPadding);
373 
374         mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
375         mDisambiguateSwipe = getResources().getBoolean(
376                 com.android.internal.R.bool.config_swipeDisambiguation);
377 
378         mAccessibilityManager = AccessibilityManager.getInstance(context);
379         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
380 
381         resetMultiTap();
382         initGestureDetector();
383     }
384 
385 
initGestureDetector()386     private void initGestureDetector() {
387         mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
388             @Override
389             public boolean onFling(MotionEvent me1, MotionEvent me2,
390                     float velocityX, float velocityY) {
391                 if (mPossiblePoly) return false;
392                 final float absX = Math.abs(velocityX);
393                 final float absY = Math.abs(velocityY);
394                 float deltaX = me2.getX() - me1.getX();
395                 float deltaY = me2.getY() - me1.getY();
396                 int travelX = getWidth() / 2; // Half the keyboard width
397                 int travelY = getHeight() / 2; // Half the keyboard height
398                 mSwipeTracker.computeCurrentVelocity(1000);
399                 final float endingVelocityX = mSwipeTracker.getXVelocity();
400                 final float endingVelocityY = mSwipeTracker.getYVelocity();
401                 boolean sendDownKey = false;
402                 if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
403                     if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
404                         sendDownKey = true;
405                     } else {
406                         swipeRight();
407                         return true;
408                     }
409                 } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
410                     if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
411                         sendDownKey = true;
412                     } else {
413                         swipeLeft();
414                         return true;
415                     }
416                 } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
417                     if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
418                         sendDownKey = true;
419                     } else {
420                         swipeUp();
421                         return true;
422                     }
423                 } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
424                     if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
425                         sendDownKey = true;
426                     } else {
427                         swipeDown();
428                         return true;
429                     }
430                 }
431 
432                 if (sendDownKey) {
433                     detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
434                 }
435                 return false;
436             }
437         });
438 
439         mGestureDetector.setIsLongpressEnabled(false);
440     }
441 
setOnKeyboardActionListener(OnKeyboardActionListener listener)442     public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
443         mKeyboardActionListener = listener;
444     }
445 
446     /**
447      * Returns the {@link OnKeyboardActionListener} object.
448      * @return the listener attached to this keyboard
449      */
getOnKeyboardActionListener()450     protected OnKeyboardActionListener getOnKeyboardActionListener() {
451         return mKeyboardActionListener;
452     }
453 
454     /**
455      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
456      * view will re-layout itself to accommodate the keyboard.
457      * @see Keyboard
458      * @see #getKeyboard()
459      * @param keyboard the keyboard to display in this view
460      */
setKeyboard(Keyboard keyboard)461     public void setKeyboard(Keyboard keyboard) {
462         if (mKeyboard != null) {
463             showPreview(NOT_A_KEY);
464         }
465         // Remove any pending messages
466         removeMessages();
467         mKeyboard = keyboard;
468         List<Key> keys = mKeyboard.getKeys();
469         mKeys = keys.toArray(new Key[keys.size()]);
470         requestLayout();
471         // Hint to reallocate the buffer if the size changed
472         mKeyboardChanged = true;
473         invalidateAllKeys();
474         computeProximityThreshold(keyboard);
475         mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
476         // Switching to a different keyboard should abort any pending keys so that the key up
477         // doesn't get delivered to the old or new keyboard
478         mAbortKey = true; // Until the next ACTION_DOWN
479     }
480 
481     /**
482      * Returns the current keyboard being displayed by this view.
483      * @return the currently attached keyboard
484      * @see #setKeyboard(Keyboard)
485      */
getKeyboard()486     public Keyboard getKeyboard() {
487         return mKeyboard;
488     }
489 
490     /**
491      * Sets the state of the shift key of the keyboard, if any.
492      * @param shifted whether or not to enable the state of the shift key
493      * @return true if the shift key state changed, false if there was no change
494      * @see KeyboardView#isShifted()
495      */
setShifted(boolean shifted)496     public boolean setShifted(boolean shifted) {
497         if (mKeyboard != null) {
498             if (mKeyboard.setShifted(shifted)) {
499                 // The whole keyboard probably needs to be redrawn
500                 invalidateAllKeys();
501                 return true;
502             }
503         }
504         return false;
505     }
506 
507     /**
508      * Returns the state of the shift key of the keyboard, if any.
509      * @return true if the shift is in a pressed state, false otherwise. If there is
510      * no shift key on the keyboard or there is no keyboard attached, it returns false.
511      * @see KeyboardView#setShifted(boolean)
512      */
isShifted()513     public boolean isShifted() {
514         if (mKeyboard != null) {
515             return mKeyboard.isShifted();
516         }
517         return false;
518     }
519 
520     /**
521      * Enables or disables the key feedback popup. This is a popup that shows a magnified
522      * version of the depressed key. By default the preview is enabled.
523      * @param previewEnabled whether or not to enable the key feedback popup
524      * @see #isPreviewEnabled()
525      */
setPreviewEnabled(boolean previewEnabled)526     public void setPreviewEnabled(boolean previewEnabled) {
527         mShowPreview = previewEnabled;
528     }
529 
530     /**
531      * Returns the enabled state of the key feedback popup.
532      * @return whether or not the key feedback popup is enabled
533      * @see #setPreviewEnabled(boolean)
534      */
isPreviewEnabled()535     public boolean isPreviewEnabled() {
536         return mShowPreview;
537     }
538 
setVerticalCorrection(int verticalOffset)539     public void setVerticalCorrection(int verticalOffset) {
540 
541     }
setPopupParent(View v)542     public void setPopupParent(View v) {
543         mPopupParent = v;
544     }
545 
setPopupOffset(int x, int y)546     public void setPopupOffset(int x, int y) {
547         mMiniKeyboardOffsetX = x;
548         mMiniKeyboardOffsetY = y;
549         if (mPreviewPopup.isShowing()) {
550             mPreviewPopup.dismiss();
551         }
552     }
553 
554     /**
555      * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
556      * codes for adjacent keys.  When disabled, only the primary key code will be
557      * reported.
558      * @param enabled whether or not the proximity correction is enabled
559      */
setProximityCorrectionEnabled(boolean enabled)560     public void setProximityCorrectionEnabled(boolean enabled) {
561         mProximityCorrectOn = enabled;
562     }
563 
564     /**
565      * Returns true if proximity correction is enabled.
566      */
isProximityCorrectionEnabled()567     public boolean isProximityCorrectionEnabled() {
568         return mProximityCorrectOn;
569     }
570 
571     /**
572      * Popup keyboard close button clicked.
573      * @hide
574      */
onClick(View v)575     public void onClick(View v) {
576         dismissPopupKeyboard();
577     }
578 
adjustCase(CharSequence label)579     private CharSequence adjustCase(CharSequence label) {
580         if (mKeyboard.isShifted() && label != null && label.length() < 3
581                 && Character.isLowerCase(label.charAt(0))) {
582             label = label.toString().toUpperCase();
583         }
584         return label;
585     }
586 
587     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)588     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
589         // Round up a little
590         if (mKeyboard == null) {
591             setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
592         } else {
593             int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
594             if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
595                 width = MeasureSpec.getSize(widthMeasureSpec);
596             }
597             setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
598         }
599     }
600 
601     /**
602      * Compute the average distance between adjacent keys (horizontally and vertically)
603      * and square it to get the proximity threshold. We use a square here and in computing
604      * the touch distance from a key's center to avoid taking a square root.
605      * @param keyboard
606      */
computeProximityThreshold(Keyboard keyboard)607     private void computeProximityThreshold(Keyboard keyboard) {
608         if (keyboard == null) return;
609         final Key[] keys = mKeys;
610         if (keys == null) return;
611         int length = keys.length;
612         int dimensionSum = 0;
613         for (int i = 0; i < length; i++) {
614             Key key = keys[i];
615             dimensionSum += Math.min(key.width, key.height) + key.gap;
616         }
617         if (dimensionSum < 0 || length == 0) return;
618         mProximityThreshold = (int) (dimensionSum * 1.4f / length);
619         mProximityThreshold *= mProximityThreshold; // Square it
620     }
621 
622     @Override
onSizeChanged(int w, int h, int oldw, int oldh)623     public void onSizeChanged(int w, int h, int oldw, int oldh) {
624         super.onSizeChanged(w, h, oldw, oldh);
625         if (mKeyboard != null) {
626             mKeyboard.resize(w, h);
627         }
628         // Release the buffer, if any and it will be reallocated on the next draw
629         mBuffer = null;
630     }
631 
632     @Override
onDraw(Canvas canvas)633     public void onDraw(Canvas canvas) {
634         super.onDraw(canvas);
635         if (mDrawPending || mBuffer == null || mKeyboardChanged) {
636             onBufferDraw();
637         }
638         canvas.drawBitmap(mBuffer, 0, 0, null);
639     }
640 
onBufferDraw()641     private void onBufferDraw() {
642         if (mBuffer == null || mKeyboardChanged) {
643             if (mBuffer == null || mKeyboardChanged &&
644                     (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
645                 // Make sure our bitmap is at least 1x1
646                 final int width = Math.max(1, getWidth());
647                 final int height = Math.max(1, getHeight());
648                 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
649                 mCanvas = new Canvas(mBuffer);
650             }
651             invalidateAllKeys();
652             mKeyboardChanged = false;
653         }
654         final Canvas canvas = mCanvas;
655         canvas.clipRect(mDirtyRect, Op.REPLACE);
656 
657         if (mKeyboard == null) return;
658 
659         final Paint paint = mPaint;
660         final Drawable keyBackground = mKeyBackground;
661         final Rect clipRegion = mClipRegion;
662         final Rect padding = mPadding;
663         final int kbdPaddingLeft = mPaddingLeft;
664         final int kbdPaddingTop = mPaddingTop;
665         final Key[] keys = mKeys;
666         final Key invalidKey = mInvalidatedKey;
667 
668         paint.setColor(mKeyTextColor);
669         boolean drawSingleKey = false;
670         if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
671           // Is clipRegion completely contained within the invalidated key?
672           if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
673                   invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
674                   invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
675                   invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
676               drawSingleKey = true;
677           }
678         }
679         canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
680         final int keyCount = keys.length;
681         for (int i = 0; i < keyCount; i++) {
682             final Key key = keys[i];
683             if (drawSingleKey && invalidKey != key) {
684                 continue;
685             }
686             int[] drawableState = key.getCurrentDrawableState();
687             keyBackground.setState(drawableState);
688 
689             // Switch the character to uppercase if shift is pressed
690             String label = key.label == null? null : adjustCase(key.label).toString();
691 
692             final Rect bounds = keyBackground.getBounds();
693             if (key.width != bounds.right ||
694                     key.height != bounds.bottom) {
695                 keyBackground.setBounds(0, 0, key.width, key.height);
696             }
697             canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
698             keyBackground.draw(canvas);
699 
700             if (label != null) {
701                 // For characters, use large font. For labels like "Done", use small font.
702                 if (label.length() > 1 && key.codes.length < 2) {
703                     paint.setTextSize(mLabelTextSize);
704                     paint.setTypeface(Typeface.DEFAULT_BOLD);
705                 } else {
706                     paint.setTextSize(mKeyTextSize);
707                     paint.setTypeface(Typeface.DEFAULT);
708                 }
709                 // Draw a drop shadow for the text
710                 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
711                 // Draw the text
712                 canvas.drawText(label,
713                     (key.width - padding.left - padding.right) / 2
714                             + padding.left,
715                     (key.height - padding.top - padding.bottom) / 2
716                             + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
717                     paint);
718                 // Turn off drop shadow
719                 paint.setShadowLayer(0, 0, 0, 0);
720             } else if (key.icon != null) {
721                 final int drawableX = (key.width - padding.left - padding.right
722                                 - key.icon.getIntrinsicWidth()) / 2 + padding.left;
723                 final int drawableY = (key.height - padding.top - padding.bottom
724                         - key.icon.getIntrinsicHeight()) / 2 + padding.top;
725                 canvas.translate(drawableX, drawableY);
726                 key.icon.setBounds(0, 0,
727                         key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
728                 key.icon.draw(canvas);
729                 canvas.translate(-drawableX, -drawableY);
730             }
731             canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
732         }
733         mInvalidatedKey = null;
734         // Overlay a dark rectangle to dim the keyboard
735         if (mMiniKeyboardOnScreen) {
736             paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
737             canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
738         }
739 
740         if (DEBUG && mShowTouchPoints) {
741             paint.setAlpha(128);
742             paint.setColor(0xFFFF0000);
743             canvas.drawCircle(mStartX, mStartY, 3, paint);
744             canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
745             paint.setColor(0xFF0000FF);
746             canvas.drawCircle(mLastX, mLastY, 3, paint);
747             paint.setColor(0xFF00FF00);
748             canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
749         }
750 
751         mDrawPending = false;
752         mDirtyRect.setEmpty();
753     }
754 
getKeyIndices(int x, int y, int[] allKeys)755     private int getKeyIndices(int x, int y, int[] allKeys) {
756         final Key[] keys = mKeys;
757         int primaryIndex = NOT_A_KEY;
758         int closestKey = NOT_A_KEY;
759         int closestKeyDist = mProximityThreshold + 1;
760         java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
761         int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
762         final int keyCount = nearestKeyIndices.length;
763         for (int i = 0; i < keyCount; i++) {
764             final Key key = keys[nearestKeyIndices[i]];
765             int dist = 0;
766             boolean isInside = key.isInside(x,y);
767             if (isInside) {
768                 primaryIndex = nearestKeyIndices[i];
769             }
770 
771             if (((mProximityCorrectOn
772                     && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
773                     || isInside)
774                     && key.codes[0] > 32) {
775                 // Find insertion point
776                 final int nCodes = key.codes.length;
777                 if (dist < closestKeyDist) {
778                     closestKeyDist = dist;
779                     closestKey = nearestKeyIndices[i];
780                 }
781 
782                 if (allKeys == null) continue;
783 
784                 for (int j = 0; j < mDistances.length; j++) {
785                     if (mDistances[j] > dist) {
786                         // Make space for nCodes codes
787                         System.arraycopy(mDistances, j, mDistances, j + nCodes,
788                                 mDistances.length - j - nCodes);
789                         System.arraycopy(allKeys, j, allKeys, j + nCodes,
790                                 allKeys.length - j - nCodes);
791                         for (int c = 0; c < nCodes; c++) {
792                             allKeys[j + c] = key.codes[c];
793                             mDistances[j + c] = dist;
794                         }
795                         break;
796                     }
797                 }
798             }
799         }
800         if (primaryIndex == NOT_A_KEY) {
801             primaryIndex = closestKey;
802         }
803         return primaryIndex;
804     }
805 
detectAndSendKey(int index, int x, int y, long eventTime)806     private void detectAndSendKey(int index, int x, int y, long eventTime) {
807         if (index != NOT_A_KEY && index < mKeys.length) {
808             final Key key = mKeys[index];
809             if (key.text != null) {
810                 mKeyboardActionListener.onText(key.text);
811                 mKeyboardActionListener.onRelease(NOT_A_KEY);
812             } else {
813                 int code = key.codes[0];
814                 //TextEntryState.keyPressedAt(key, x, y);
815                 int[] codes = new int[MAX_NEARBY_KEYS];
816                 Arrays.fill(codes, NOT_A_KEY);
817                 getKeyIndices(x, y, codes);
818                 // Multi-tap
819                 if (mInMultiTap) {
820                     if (mTapCount != -1) {
821                         mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
822                     } else {
823                         mTapCount = 0;
824                     }
825                     code = key.codes[mTapCount];
826                 }
827                 mKeyboardActionListener.onKey(code, codes);
828                 mKeyboardActionListener.onRelease(code);
829             }
830             mLastSentIndex = index;
831             mLastTapTime = eventTime;
832         }
833     }
834 
835     /**
836      * Handle multi-tap keys by producing the key label for the current multi-tap state.
837      */
getPreviewText(Key key)838     private CharSequence getPreviewText(Key key) {
839         if (mInMultiTap) {
840             // Multi-tap
841             mPreviewLabel.setLength(0);
842             mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
843             return adjustCase(mPreviewLabel);
844         } else {
845             return adjustCase(key.label);
846         }
847     }
848 
showPreview(int keyIndex)849     private void showPreview(int keyIndex) {
850         int oldKeyIndex = mCurrentKeyIndex;
851         final PopupWindow previewPopup = mPreviewPopup;
852 
853         mCurrentKeyIndex = keyIndex;
854         // Release the old key and press the new key
855         final Key[] keys = mKeys;
856         if (oldKeyIndex != mCurrentKeyIndex) {
857             if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
858                 Key oldKey = keys[oldKeyIndex];
859                 oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
860                 invalidateKey(oldKeyIndex);
861                 final int keyCode = oldKey.codes[0];
862                 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
863                         keyCode);
864                 // TODO: We need to implement AccessibilityNodeProvider for this view.
865                 sendAccessibilityEventForUnicodeCharacter(
866                         AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
867             }
868             if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
869                 Key newKey = keys[mCurrentKeyIndex];
870                 newKey.onPressed();
871                 invalidateKey(mCurrentKeyIndex);
872                 final int keyCode = newKey.codes[0];
873                 sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
874                         keyCode);
875                 // TODO: We need to implement AccessibilityNodeProvider for this view.
876                 sendAccessibilityEventForUnicodeCharacter(
877                         AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
878             }
879         }
880         // If key changed and preview is on ...
881         if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
882             mHandler.removeMessages(MSG_SHOW_PREVIEW);
883             if (previewPopup.isShowing()) {
884                 if (keyIndex == NOT_A_KEY) {
885                     mHandler.sendMessageDelayed(mHandler
886                             .obtainMessage(MSG_REMOVE_PREVIEW),
887                             DELAY_AFTER_PREVIEW);
888                 }
889             }
890             if (keyIndex != NOT_A_KEY) {
891                 if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
892                     // Show right away, if it's already visible and finger is moving around
893                     showKey(keyIndex);
894                 } else {
895                     mHandler.sendMessageDelayed(
896                             mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
897                             DELAY_BEFORE_PREVIEW);
898                 }
899             }
900         }
901     }
902 
showKey(final int keyIndex)903     private void showKey(final int keyIndex) {
904         final PopupWindow previewPopup = mPreviewPopup;
905         final Key[] keys = mKeys;
906         if (keyIndex < 0 || keyIndex >= mKeys.length) return;
907         Key key = keys[keyIndex];
908         if (key.icon != null) {
909             mPreviewText.setCompoundDrawables(null, null, null,
910                     key.iconPreview != null ? key.iconPreview : key.icon);
911             mPreviewText.setText(null);
912         } else {
913             mPreviewText.setCompoundDrawables(null, null, null, null);
914             mPreviewText.setText(getPreviewText(key));
915             if (key.label.length() > 1 && key.codes.length < 2) {
916                 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
917                 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
918             } else {
919                 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
920                 mPreviewText.setTypeface(Typeface.DEFAULT);
921             }
922         }
923         mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
924                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
925         int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
926                 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
927         final int popupHeight = mPreviewHeight;
928         LayoutParams lp = mPreviewText.getLayoutParams();
929         if (lp != null) {
930             lp.width = popupWidth;
931             lp.height = popupHeight;
932         }
933         if (!mPreviewCentered) {
934             mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
935             mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
936         } else {
937             // TODO: Fix this if centering is brought back
938             mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
939             mPopupPreviewY = - mPreviewText.getMeasuredHeight();
940         }
941         mHandler.removeMessages(MSG_REMOVE_PREVIEW);
942         getLocationInWindow(mCoordinates);
943         mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
944         mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
945 
946         // Set the preview background state
947         mPreviewText.getBackground().setState(
948                 key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
949         mPopupPreviewX += mCoordinates[0];
950         mPopupPreviewY += mCoordinates[1];
951 
952         // If the popup cannot be shown above the key, put it on the side
953         getLocationOnScreen(mCoordinates);
954         if (mPopupPreviewY + mCoordinates[1] < 0) {
955             // If the key you're pressing is on the left side of the keyboard, show the popup on
956             // the right, offset by enough to see at least one key to the left/right.
957             if (key.x + key.width <= getWidth() / 2) {
958                 mPopupPreviewX += (int) (key.width * 2.5);
959             } else {
960                 mPopupPreviewX -= (int) (key.width * 2.5);
961             }
962             mPopupPreviewY += popupHeight;
963         }
964 
965         if (previewPopup.isShowing()) {
966             previewPopup.update(mPopupPreviewX, mPopupPreviewY,
967                     popupWidth, popupHeight);
968         } else {
969             previewPopup.setWidth(popupWidth);
970             previewPopup.setHeight(popupHeight);
971             previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
972                     mPopupPreviewX, mPopupPreviewY);
973         }
974         mPreviewText.setVisibility(VISIBLE);
975     }
976 
sendAccessibilityEventForUnicodeCharacter(int eventType, int code)977     private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
978         if (mAccessibilityManager.isEnabled()) {
979             AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
980             onInitializeAccessibilityEvent(event);
981             String text = null;
982             // This is very efficient since the properties are cached.
983             final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),
984                     Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
985             // Add text only if password announcement is enabled or if headset is
986             // used to avoid leaking passwords.
987             if (speakPassword || mAudioManager.isBluetoothA2dpOn()
988                     || mAudioManager.isWiredHeadsetOn()) {
989                 switch (code) {
990                     case Keyboard.KEYCODE_ALT:
991                         text = mContext.getString(R.string.keyboardview_keycode_alt);
992                         break;
993                     case Keyboard.KEYCODE_CANCEL:
994                         text = mContext.getString(R.string.keyboardview_keycode_cancel);
995                         break;
996                     case Keyboard.KEYCODE_DELETE:
997                         text = mContext.getString(R.string.keyboardview_keycode_delete);
998                         break;
999                     case Keyboard.KEYCODE_DONE:
1000                         text = mContext.getString(R.string.keyboardview_keycode_done);
1001                         break;
1002                     case Keyboard.KEYCODE_MODE_CHANGE:
1003                         text = mContext.getString(R.string.keyboardview_keycode_mode_change);
1004                         break;
1005                     case Keyboard.KEYCODE_SHIFT:
1006                         text = mContext.getString(R.string.keyboardview_keycode_shift);
1007                         break;
1008                     case '\n':
1009                         text = mContext.getString(R.string.keyboardview_keycode_enter);
1010                         break;
1011                     default:
1012                         text = String.valueOf((char) code);
1013                 }
1014             } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {
1015                 // We want the waring for required head set to be send with both the
1016                 // hover enter and hover exit event, so set the flag after the exit.
1017                 if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
1018                     mHeadsetRequiredToHearPasswordsAnnounced = true;
1019                 }
1020                 text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);
1021             } else {
1022                 text = mContext.getString(R.string.keyboard_password_character_no_headset);
1023             }
1024             event.getText().add(text);
1025             mAccessibilityManager.sendAccessibilityEvent(event);
1026         }
1027     }
1028 
1029     /**
1030      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
1031      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
1032      * draws the cached buffer.
1033      * @see #invalidateKey(int)
1034      */
invalidateAllKeys()1035     public void invalidateAllKeys() {
1036         mDirtyRect.union(0, 0, getWidth(), getHeight());
1037         mDrawPending = true;
1038         invalidate();
1039     }
1040 
1041     /**
1042      * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1043      * one key is changing it's content. Any changes that affect the position or size of the key
1044      * may not be honored.
1045      * @param keyIndex the index of the key in the attached {@link Keyboard}.
1046      * @see #invalidateAllKeys
1047      */
invalidateKey(int keyIndex)1048     public void invalidateKey(int keyIndex) {
1049         if (mKeys == null) return;
1050         if (keyIndex < 0 || keyIndex >= mKeys.length) {
1051             return;
1052         }
1053         final Key key = mKeys[keyIndex];
1054         mInvalidatedKey = key;
1055         mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
1056                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1057         onBufferDraw();
1058         invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
1059                 key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
1060     }
1061 
openPopupIfRequired(MotionEvent me)1062     private boolean openPopupIfRequired(MotionEvent me) {
1063         // Check if we have a popup layout specified first.
1064         if (mPopupLayout == 0) {
1065             return false;
1066         }
1067         if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
1068             return false;
1069         }
1070 
1071         Key popupKey = mKeys[mCurrentKey];
1072         boolean result = onLongPress(popupKey);
1073         if (result) {
1074             mAbortKey = true;
1075             showPreview(NOT_A_KEY);
1076         }
1077         return result;
1078     }
1079 
1080     /**
1081      * Called when a key is long pressed. By default this will open any popup keyboard associated
1082      * with this key through the attributes popupLayout and popupCharacters.
1083      * @param popupKey the key that was long pressed
1084      * @return true if the long press is handled, false otherwise. Subclasses should call the
1085      * method on the base class if the subclass doesn't wish to handle the call.
1086      */
onLongPress(Key popupKey)1087     protected boolean onLongPress(Key popupKey) {
1088         int popupKeyboardId = popupKey.popupResId;
1089 
1090         if (popupKeyboardId != 0) {
1091             mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
1092             if (mMiniKeyboardContainer == null) {
1093                 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
1094                         Context.LAYOUT_INFLATER_SERVICE);
1095                 mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
1096                 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1097                         com.android.internal.R.id.keyboardView);
1098                 View closeButton = mMiniKeyboardContainer.findViewById(
1099                         com.android.internal.R.id.closeButton);
1100                 if (closeButton != null) closeButton.setOnClickListener(this);
1101                 mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
1102                     public void onKey(int primaryCode, int[] keyCodes) {
1103                         mKeyboardActionListener.onKey(primaryCode, keyCodes);
1104                         dismissPopupKeyboard();
1105                     }
1106 
1107                     public void onText(CharSequence text) {
1108                         mKeyboardActionListener.onText(text);
1109                         dismissPopupKeyboard();
1110                     }
1111 
1112                     public void swipeLeft() { }
1113                     public void swipeRight() { }
1114                     public void swipeUp() { }
1115                     public void swipeDown() { }
1116                     public void onPress(int primaryCode) {
1117                         mKeyboardActionListener.onPress(primaryCode);
1118                     }
1119                     public void onRelease(int primaryCode) {
1120                         mKeyboardActionListener.onRelease(primaryCode);
1121                     }
1122                 });
1123                 //mInputView.setSuggest(mSuggest);
1124                 Keyboard keyboard;
1125                 if (popupKey.popupCharacters != null) {
1126                     keyboard = new Keyboard(getContext(), popupKeyboardId,
1127                             popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
1128                 } else {
1129                     keyboard = new Keyboard(getContext(), popupKeyboardId);
1130                 }
1131                 mMiniKeyboard.setKeyboard(keyboard);
1132                 mMiniKeyboard.setPopupParent(this);
1133                 mMiniKeyboardContainer.measure(
1134                         MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
1135                         MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
1136 
1137                 mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
1138             } else {
1139                 mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
1140                         com.android.internal.R.id.keyboardView);
1141             }
1142             getLocationInWindow(mCoordinates);
1143             mPopupX = popupKey.x + mPaddingLeft;
1144             mPopupY = popupKey.y + mPaddingTop;
1145             mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
1146             mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
1147             final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
1148             final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
1149             mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
1150             mMiniKeyboard.setShifted(isShifted());
1151             mPopupKeyboard.setContentView(mMiniKeyboardContainer);
1152             mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
1153             mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
1154             mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
1155             mMiniKeyboardOnScreen = true;
1156             //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
1157             invalidateAllKeys();
1158             return true;
1159         }
1160         return false;
1161     }
1162 
1163     @Override
1164     public boolean onHoverEvent(MotionEvent event) {
1165         if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
1166             final int action = event.getAction();
1167             switch (action) {
1168                 case MotionEvent.ACTION_HOVER_ENTER: {
1169                     event.setAction(MotionEvent.ACTION_DOWN);
1170                 } break;
1171                 case MotionEvent.ACTION_HOVER_MOVE: {
1172                     event.setAction(MotionEvent.ACTION_MOVE);
1173                 } break;
1174                 case MotionEvent.ACTION_HOVER_EXIT: {
1175                     event.setAction(MotionEvent.ACTION_UP);
1176                 } break;
1177             }
1178             return onTouchEvent(event);
1179         }
1180         return true;
1181     }
1182 
1183     @Override
1184     public boolean onTouchEvent(MotionEvent me) {
1185         // Convert multi-pointer up/down events to single up/down events to
1186         // deal with the typical multi-pointer behavior of two-thumb typing
1187         final int pointerCount = me.getPointerCount();
1188         final int action = me.getAction();
1189         boolean result = false;
1190         final long now = me.getEventTime();
1191 
1192         if (pointerCount != mOldPointerCount) {
1193             if (pointerCount == 1) {
1194                 // Send a down event for the latest pointer
1195                 MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
1196                         me.getX(), me.getY(), me.getMetaState());
1197                 result = onModifiedTouchEvent(down, false);
1198                 down.recycle();
1199                 // If it's an up action, then deliver the up as well.
1200                 if (action == MotionEvent.ACTION_UP) {
1201                     result = onModifiedTouchEvent(me, true);
1202                 }
1203             } else {
1204                 // Send an up event for the last pointer
1205                 MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
1206                         mOldPointerX, mOldPointerY, me.getMetaState());
1207                 result = onModifiedTouchEvent(up, true);
1208                 up.recycle();
1209             }
1210         } else {
1211             if (pointerCount == 1) {
1212                 result = onModifiedTouchEvent(me, false);
1213                 mOldPointerX = me.getX();
1214                 mOldPointerY = me.getY();
1215             } else {
1216                 // Don't do anything when 2 pointers are down and moving.
1217                 result = true;
1218             }
1219         }
1220         mOldPointerCount = pointerCount;
1221 
1222         return result;
1223     }
1224 
1225     private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
1226         int touchX = (int) me.getX() - mPaddingLeft;
1227         int touchY = (int) me.getY() - mPaddingTop;
1228         if (touchY >= -mVerticalCorrection)
1229             touchY += mVerticalCorrection;
1230         final int action = me.getAction();
1231         final long eventTime = me.getEventTime();
1232         int keyIndex = getKeyIndices(touchX, touchY, null);
1233         mPossiblePoly = possiblePoly;
1234 
1235         // Track the last few movements to look for spurious swipes.
1236         if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
1237         mSwipeTracker.addMovement(me);
1238 
1239         // Ignore all motion events until a DOWN.
1240         if (mAbortKey
1241                 && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
1242             return true;
1243         }
1244 
1245         if (mGestureDetector.onTouchEvent(me)) {
1246             showPreview(NOT_A_KEY);
1247             mHandler.removeMessages(MSG_REPEAT);
1248             mHandler.removeMessages(MSG_LONGPRESS);
1249             return true;
1250         }
1251 
1252         // Needs to be called after the gesture detector gets a turn, as it may have
1253         // displayed the mini keyboard
1254         if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
1255             return true;
1256         }
1257 
1258         switch (action) {
1259             case MotionEvent.ACTION_DOWN:
1260                 mAbortKey = false;
1261                 mStartX = touchX;
1262                 mStartY = touchY;
1263                 mLastCodeX = touchX;
1264                 mLastCodeY = touchY;
1265                 mLastKeyTime = 0;
1266                 mCurrentKeyTime = 0;
1267                 mLastKey = NOT_A_KEY;
1268                 mCurrentKey = keyIndex;
1269                 mDownKey = keyIndex;
1270                 mDownTime = me.getEventTime();
1271                 mLastMoveTime = mDownTime;
1272                 checkMultiTap(eventTime, keyIndex);
1273                 mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
1274                         mKeys[keyIndex].codes[0] : 0);
1275                 if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
1276                     mRepeatKeyIndex = mCurrentKey;
1277                     Message msg = mHandler.obtainMessage(MSG_REPEAT);
1278                     mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
1279                     repeatKey();
1280                     // Delivering the key could have caused an abort
1281                     if (mAbortKey) {
1282                         mRepeatKeyIndex = NOT_A_KEY;
1283                         break;
1284                     }
1285                 }
1286                 if (mCurrentKey != NOT_A_KEY) {
1287                     Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1288                     mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1289                 }
1290                 showPreview(keyIndex);
1291                 break;
1292 
1293             case MotionEvent.ACTION_MOVE:
1294                 boolean continueLongPress = false;
1295                 if (keyIndex != NOT_A_KEY) {
1296                     if (mCurrentKey == NOT_A_KEY) {
1297                         mCurrentKey = keyIndex;
1298                         mCurrentKeyTime = eventTime - mDownTime;
1299                     } else {
1300                         if (keyIndex == mCurrentKey) {
1301                             mCurrentKeyTime += eventTime - mLastMoveTime;
1302                             continueLongPress = true;
1303                         } else if (mRepeatKeyIndex == NOT_A_KEY) {
1304                             resetMultiTap();
1305                             mLastKey = mCurrentKey;
1306                             mLastCodeX = mLastX;
1307                             mLastCodeY = mLastY;
1308                             mLastKeyTime =
1309                                     mCurrentKeyTime + eventTime - mLastMoveTime;
1310                             mCurrentKey = keyIndex;
1311                             mCurrentKeyTime = 0;
1312                         }
1313                     }
1314                 }
1315                 if (!continueLongPress) {
1316                     // Cancel old longpress
1317                     mHandler.removeMessages(MSG_LONGPRESS);
1318                     // Start new longpress if key has changed
1319                     if (keyIndex != NOT_A_KEY) {
1320                         Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
1321                         mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
1322                     }
1323                 }
1324                 showPreview(mCurrentKey);
1325                 mLastMoveTime = eventTime;
1326                 break;
1327 
1328             case MotionEvent.ACTION_UP:
1329                 removeMessages();
1330                 if (keyIndex == mCurrentKey) {
1331                     mCurrentKeyTime += eventTime - mLastMoveTime;
1332                 } else {
1333                     resetMultiTap();
1334                     mLastKey = mCurrentKey;
1335                     mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
1336                     mCurrentKey = keyIndex;
1337                     mCurrentKeyTime = 0;
1338                 }
1339                 if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
1340                         && mLastKey != NOT_A_KEY) {
1341                     mCurrentKey = mLastKey;
1342                     touchX = mLastCodeX;
1343                     touchY = mLastCodeY;
1344                 }
1345                 showPreview(NOT_A_KEY);
1346                 Arrays.fill(mKeyIndices, NOT_A_KEY);
1347                 // If we're not on a repeating key (which sends on a DOWN event)
1348                 if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
1349                     detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
1350                 }
1351                 invalidateKey(keyIndex);
1352                 mRepeatKeyIndex = NOT_A_KEY;
1353                 break;
1354             case MotionEvent.ACTION_CANCEL:
1355                 removeMessages();
1356                 dismissPopupKeyboard();
1357                 mAbortKey = true;
1358                 showPreview(NOT_A_KEY);
1359                 invalidateKey(mCurrentKey);
1360                 break;
1361         }
1362         mLastX = touchX;
1363         mLastY = touchY;
1364         return true;
1365     }
1366 
repeatKey()1367     private boolean repeatKey() {
1368         Key key = mKeys[mRepeatKeyIndex];
1369         detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
1370         return true;
1371     }
1372 
swipeRight()1373     protected void swipeRight() {
1374         mKeyboardActionListener.swipeRight();
1375     }
1376 
swipeLeft()1377     protected void swipeLeft() {
1378         mKeyboardActionListener.swipeLeft();
1379     }
1380 
swipeUp()1381     protected void swipeUp() {
1382         mKeyboardActionListener.swipeUp();
1383     }
1384 
swipeDown()1385     protected void swipeDown() {
1386         mKeyboardActionListener.swipeDown();
1387     }
1388 
closing()1389     public void closing() {
1390         if (mPreviewPopup.isShowing()) {
1391             mPreviewPopup.dismiss();
1392         }
1393         removeMessages();
1394 
1395         dismissPopupKeyboard();
1396         mBuffer = null;
1397         mCanvas = null;
1398         mMiniKeyboardCache.clear();
1399     }
1400 
removeMessages()1401     private void removeMessages() {
1402         mHandler.removeMessages(MSG_REPEAT);
1403         mHandler.removeMessages(MSG_LONGPRESS);
1404         mHandler.removeMessages(MSG_SHOW_PREVIEW);
1405     }
1406 
1407     @Override
onDetachedFromWindow()1408     public void onDetachedFromWindow() {
1409         super.onDetachedFromWindow();
1410         closing();
1411     }
1412 
dismissPopupKeyboard()1413     private void dismissPopupKeyboard() {
1414         if (mPopupKeyboard.isShowing()) {
1415             mPopupKeyboard.dismiss();
1416             mMiniKeyboardOnScreen = false;
1417             invalidateAllKeys();
1418         }
1419     }
1420 
handleBack()1421     public boolean handleBack() {
1422         if (mPopupKeyboard.isShowing()) {
1423             dismissPopupKeyboard();
1424             return true;
1425         }
1426         return false;
1427     }
1428 
resetMultiTap()1429     private void resetMultiTap() {
1430         mLastSentIndex = NOT_A_KEY;
1431         mTapCount = 0;
1432         mLastTapTime = -1;
1433         mInMultiTap = false;
1434     }
1435 
checkMultiTap(long eventTime, int keyIndex)1436     private void checkMultiTap(long eventTime, int keyIndex) {
1437         if (keyIndex == NOT_A_KEY) return;
1438         Key key = mKeys[keyIndex];
1439         if (key.codes.length > 1) {
1440             mInMultiTap = true;
1441             if (eventTime < mLastTapTime + MULTITAP_INTERVAL
1442                     && keyIndex == mLastSentIndex) {
1443                 mTapCount = (mTapCount + 1) % key.codes.length;
1444                 return;
1445             } else {
1446                 mTapCount = -1;
1447                 return;
1448             }
1449         }
1450         if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
1451             resetMultiTap();
1452         }
1453     }
1454 
1455     private static class SwipeTracker {
1456 
1457         static final int NUM_PAST = 4;
1458         static final int LONGEST_PAST_TIME = 200;
1459 
1460         final float mPastX[] = new float[NUM_PAST];
1461         final float mPastY[] = new float[NUM_PAST];
1462         final long mPastTime[] = new long[NUM_PAST];
1463 
1464         float mYVelocity;
1465         float mXVelocity;
1466 
clear()1467         public void clear() {
1468             mPastTime[0] = 0;
1469         }
1470 
addMovement(MotionEvent ev)1471         public void addMovement(MotionEvent ev) {
1472             long time = ev.getEventTime();
1473             final int N = ev.getHistorySize();
1474             for (int i=0; i<N; i++) {
1475                 addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
1476                         ev.getHistoricalEventTime(i));
1477             }
1478             addPoint(ev.getX(), ev.getY(), time);
1479         }
1480 
addPoint(float x, float y, long time)1481         private void addPoint(float x, float y, long time) {
1482             int drop = -1;
1483             int i;
1484             final long[] pastTime = mPastTime;
1485             for (i=0; i<NUM_PAST; i++) {
1486                 if (pastTime[i] == 0) {
1487                     break;
1488                 } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
1489                     drop = i;
1490                 }
1491             }
1492             if (i == NUM_PAST && drop < 0) {
1493                 drop = 0;
1494             }
1495             if (drop == i) drop--;
1496             final float[] pastX = mPastX;
1497             final float[] pastY = mPastY;
1498             if (drop >= 0) {
1499                 final int start = drop+1;
1500                 final int count = NUM_PAST-drop-1;
1501                 System.arraycopy(pastX, start, pastX, 0, count);
1502                 System.arraycopy(pastY, start, pastY, 0, count);
1503                 System.arraycopy(pastTime, start, pastTime, 0, count);
1504                 i -= (drop+1);
1505             }
1506             pastX[i] = x;
1507             pastY[i] = y;
1508             pastTime[i] = time;
1509             i++;
1510             if (i < NUM_PAST) {
1511                 pastTime[i] = 0;
1512             }
1513         }
1514 
computeCurrentVelocity(int units)1515         public void computeCurrentVelocity(int units) {
1516             computeCurrentVelocity(units, Float.MAX_VALUE);
1517         }
1518 
computeCurrentVelocity(int units, float maxVelocity)1519         public void computeCurrentVelocity(int units, float maxVelocity) {
1520             final float[] pastX = mPastX;
1521             final float[] pastY = mPastY;
1522             final long[] pastTime = mPastTime;
1523 
1524             final float oldestX = pastX[0];
1525             final float oldestY = pastY[0];
1526             final long oldestTime = pastTime[0];
1527             float accumX = 0;
1528             float accumY = 0;
1529             int N=0;
1530             while (N < NUM_PAST) {
1531                 if (pastTime[N] == 0) {
1532                     break;
1533                 }
1534                 N++;
1535             }
1536 
1537             for (int i=1; i < N; i++) {
1538                 final int dur = (int)(pastTime[i] - oldestTime);
1539                 if (dur == 0) continue;
1540                 float dist = pastX[i] - oldestX;
1541                 float vel = (dist/dur) * units;   // pixels/frame.
1542                 if (accumX == 0) accumX = vel;
1543                 else accumX = (accumX + vel) * .5f;
1544 
1545                 dist = pastY[i] - oldestY;
1546                 vel = (dist/dur) * units;   // pixels/frame.
1547                 if (accumY == 0) accumY = vel;
1548                 else accumY = (accumY + vel) * .5f;
1549             }
1550             mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
1551                     : Math.min(accumX, maxVelocity);
1552             mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
1553                     : Math.min(accumY, maxVelocity);
1554         }
1555 
1556         public float getXVelocity() {
1557             return mXVelocity;
1558         }
1559 
1560         public float getYVelocity() {
1561             return mYVelocity;
1562         }
1563     }
1564 }
1565