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