1 /* 2 * Copyright (C) 2010 The Android Open Source Project 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 com.android.inputmethod.keyboard; 18 19 import android.content.res.TypedArray; 20 import android.os.SystemClock; 21 import android.util.Log; 22 import android.view.MotionEvent; 23 24 import com.android.inputmethod.accessibility.AccessibilityUtils; 25 import com.android.inputmethod.keyboard.internal.GestureStroke; 26 import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams; 27 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; 28 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 29 import com.android.inputmethod.latin.CollectionUtils; 30 import com.android.inputmethod.latin.InputPointers; 31 import com.android.inputmethod.latin.LatinImeLogger; 32 import com.android.inputmethod.latin.R; 33 import com.android.inputmethod.latin.define.ProductionFlag; 34 import com.android.inputmethod.research.ResearchLogger; 35 36 import java.util.ArrayList; 37 38 public final class PointerTracker implements PointerTrackerQueue.Element { 39 private static final String TAG = PointerTracker.class.getSimpleName(); 40 private static final boolean DEBUG_EVENT = false; 41 private static final boolean DEBUG_MOVE_EVENT = false; 42 private static final boolean DEBUG_LISTENER = false; 43 private static boolean DEBUG_MODE = LatinImeLogger.sDBG || DEBUG_EVENT; 44 45 /** True if {@link PointerTracker}s should handle gesture events. */ 46 private static boolean sShouldHandleGesture = false; 47 private static boolean sMainDictionaryAvailable = false; 48 private static boolean sGestureHandlingEnabledByInputField = false; 49 private static boolean sGestureHandlingEnabledByUser = false; 50 51 public interface KeyEventHandler { 52 /** 53 * Get KeyDetector object that is used for this PointerTracker. 54 * @return the KeyDetector object that is used for this PointerTracker 55 */ 56 public KeyDetector getKeyDetector(); 57 58 /** 59 * Get KeyboardActionListener object that is used to register key code and so on. 60 * @return the KeyboardActionListner for this PointerTracker 61 */ 62 public KeyboardActionListener getKeyboardActionListener(); 63 64 /** 65 * Get DrawingProxy object that is used for this PointerTracker. 66 * @return the DrawingProxy object that is used for this PointerTracker 67 */ 68 public DrawingProxy getDrawingProxy(); 69 70 /** 71 * Get TimerProxy object that handles key repeat and long press timer event for this 72 * PointerTracker. 73 * @return the TimerProxy object that handles key repeat and long press timer event. 74 */ 75 public TimerProxy getTimerProxy(); 76 } 77 78 public interface DrawingProxy extends MoreKeysPanel.Controller { 79 public void invalidateKey(Key key); 80 public void showKeyPreview(PointerTracker tracker); 81 public void dismissKeyPreview(PointerTracker tracker); 82 public void showGesturePreviewTrail(PointerTracker tracker, boolean isOldestTracker); 83 } 84 85 public interface TimerProxy { 86 public void startTypingStateTimer(Key typedKey); 87 public boolean isTypingState(); 88 public void startKeyRepeatTimer(PointerTracker tracker); 89 public void startLongPressTimer(PointerTracker tracker); 90 public void startLongPressTimer(int code); 91 public void cancelLongPressTimer(); 92 public void startDoubleTapTimer(); 93 public void cancelDoubleTapTimer(); 94 public boolean isInDoubleTapTimeout(); 95 public void cancelKeyTimers(); 96 97 public static class Adapter implements TimerProxy { 98 @Override 99 public void startTypingStateTimer(Key typedKey) {} 100 @Override 101 public boolean isTypingState() { return false; } 102 @Override 103 public void startKeyRepeatTimer(PointerTracker tracker) {} 104 @Override 105 public void startLongPressTimer(PointerTracker tracker) {} 106 @Override 107 public void startLongPressTimer(int code) {} 108 @Override 109 public void cancelLongPressTimer() {} 110 @Override 111 public void startDoubleTapTimer() {} 112 @Override 113 public void cancelDoubleTapTimer() {} 114 @Override 115 public boolean isInDoubleTapTimeout() { return false; } 116 @Override 117 public void cancelKeyTimers() {} 118 } 119 } 120 121 static final class PointerTrackerParams { 122 public final boolean mSlidingKeyInputEnabled; 123 public final int mTouchNoiseThresholdTime; 124 public final int mTouchNoiseThresholdDistance; 125 public final int mSuppressKeyPreviewAfterBatchInputDuration; 126 127 public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); 128 129 private PointerTrackerParams() { 130 mSlidingKeyInputEnabled = false; 131 mTouchNoiseThresholdTime = 0; 132 mTouchNoiseThresholdDistance = 0; 133 mSuppressKeyPreviewAfterBatchInputDuration = 0; 134 } 135 136 public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { 137 mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean( 138 R.styleable.MainKeyboardView_slidingKeyInputEnable, false); 139 mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( 140 R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); 141 mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize( 142 R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); 143 mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt( 144 R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0); 145 } 146 } 147 148 // Parameters for pointer handling. 149 private static PointerTrackerParams sParams; 150 private static GestureStrokeParams sGestureStrokeParams; 151 private static boolean sNeedsPhantomSuddenMoveEventHack; 152 // Move this threshold to resource. 153 // TODO: Device specific parameter would be better for device specific hack? 154 private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth 155 // This hack might be device specific. 156 private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true; 157 158 private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); 159 private static PointerTrackerQueue sPointerTrackerQueue; 160 161 public final int mPointerId; 162 163 private DrawingProxy mDrawingProxy; 164 private TimerProxy mTimerProxy; 165 private KeyDetector mKeyDetector; 166 private KeyboardActionListener mListener = EMPTY_LISTENER; 167 168 private Keyboard mKeyboard; 169 private int mPhantonSuddenMoveThreshold; 170 private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector(); 171 172 private boolean mIsDetectingGesture = false; // per PointerTracker. 173 private static boolean sInGesture = false; 174 private static long sGestureFirstDownTime; 175 private static TimeRecorder sTimeRecorder; 176 private static final InputPointers sAggregratedPointers = new InputPointers( 177 GestureStroke.DEFAULT_CAPACITY); 178 private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers 179 private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers 180 181 static final class BogusMoveEventDetector { 182 // Move these thresholds to resource. 183 // These thresholds' unit is a diagonal length of a key. 184 private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f; 185 private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f; 186 187 private int mAccumulatedDistanceThreshold; 188 private int mRadiusThreshold; 189 190 // Accumulated distance from actual and artificial down keys. 191 /* package */ int mAccumulatedDistanceFromDownKey; 192 private int mActualDownX; 193 private int mActualDownY; 194 195 public void setKeyboardGeometry(final int keyWidth, final int keyHeight) { 196 final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight); 197 mAccumulatedDistanceThreshold = (int)( 198 keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD); 199 mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD); 200 } 201 202 public void onActualDownEvent(final int x, final int y) { 203 mActualDownX = x; 204 mActualDownY = y; 205 } 206 207 public void onDownKey() { 208 mAccumulatedDistanceFromDownKey = 0; 209 } 210 211 public void onMoveKey(final int distance) { 212 mAccumulatedDistanceFromDownKey += distance; 213 } 214 215 public boolean hasTraveledLongDistance(final int x, final int y) { 216 final int dx = Math.abs(x - mActualDownX); 217 final int dy = Math.abs(y - mActualDownY); 218 // A bogus move event should be a horizontal movement. A vertical movement might be 219 // a sloppy typing and should be ignored. 220 return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold; 221 } 222 223 /* package */ int getDistanceFromDownEvent(final int x, final int y) { 224 return getDistance(x, y, mActualDownX, mActualDownY); 225 } 226 227 public boolean isCloseToActualDownEvent(final int x, final int y) { 228 return getDistanceFromDownEvent(x, y) < mRadiusThreshold; 229 } 230 } 231 232 static final class TimeRecorder { 233 private final int mSuppressKeyPreviewAfterBatchInputDuration; 234 private final int mStaticTimeThresholdAfterFastTyping; // msec 235 private long mLastTypingTime; 236 private long mLastLetterTypingTime; 237 private long mLastBatchInputTime; 238 239 public TimeRecorder(final PointerTrackerParams pointerTrackerParams, 240 final GestureStrokeParams gestureStrokeParams) { 241 mSuppressKeyPreviewAfterBatchInputDuration = 242 pointerTrackerParams.mSuppressKeyPreviewAfterBatchInputDuration; 243 mStaticTimeThresholdAfterFastTyping = 244 gestureStrokeParams.mStaticTimeThresholdAfterFastTyping; 245 } 246 247 public boolean isInFastTyping(final long eventTime) { 248 final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime; 249 return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping; 250 } 251 252 private boolean wasLastInputTyping() { 253 return mLastTypingTime >= mLastBatchInputTime; 254 } 255 256 public void onCodeInput(final int code, final long eventTime) { 257 // Record the letter typing time when 258 // 1. Letter keys are typed successively without any batch input in between. 259 // 2. A letter key is typed within the threshold time since the last any key typing. 260 // 3. A non-letter key is typed within the threshold time since the last letter key 261 // typing. 262 if (Character.isLetter(code)) { 263 if (wasLastInputTyping() 264 || eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) { 265 mLastLetterTypingTime = eventTime; 266 } 267 } else { 268 if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) { 269 // This non-letter typing should be treated as a part of fast typing. 270 mLastLetterTypingTime = eventTime; 271 } 272 } 273 mLastTypingTime = eventTime; 274 } 275 276 public void onEndBatchInput(final long eventTime) { 277 mLastBatchInputTime = eventTime; 278 } 279 280 public long getLastLetterTypingTime() { 281 return mLastLetterTypingTime; 282 } 283 284 public boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 285 return !wasLastInputTyping() 286 && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration; 287 } 288 } 289 290 // The position and time at which first down event occurred. 291 private long mDownTime; 292 private long mUpTime; 293 294 // The current key where this pointer is. 295 private Key mCurrentKey = null; 296 // The position where the current key was recognized for the first time. 297 private int mKeyX; 298 private int mKeyY; 299 300 // Last pointer position. 301 private int mLastX; 302 private int mLastY; 303 304 // true if keyboard layout has been changed. 305 private boolean mKeyboardLayoutHasBeenChanged; 306 307 // true if event is already translated to a key action. 308 private boolean mKeyAlreadyProcessed; 309 310 // true if this pointer has been long-pressed and is showing a more keys panel. 311 private boolean mIsShowingMoreKeysPanel; 312 313 // true if this pointer is in a sliding key input. 314 boolean mIsInSlidingKeyInput; 315 // true if this pointer is in a sliding key input from a modifier key, 316 // so that further modifier keys should be ignored. 317 boolean mIsInSlidingKeyInputFromModifier; 318 319 // true if a sliding key input is allowed. 320 private boolean mIsAllowedSlidingKeyInput; 321 322 // Empty {@link KeyboardActionListener} 323 private static final KeyboardActionListener EMPTY_LISTENER = 324 new KeyboardActionListener.Adapter(); 325 326 private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; 327 328 public static void init(boolean hasDistinctMultitouch, 329 boolean needsPhantomSuddenMoveEventHack) { 330 if (hasDistinctMultitouch) { 331 sPointerTrackerQueue = new PointerTrackerQueue(); 332 } else { 333 sPointerTrackerQueue = null; 334 } 335 sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; 336 sParams = PointerTrackerParams.DEFAULT; 337 sGestureStrokeParams = GestureStrokeParams.DEFAULT; 338 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 339 } 340 341 public static void setParameters(final TypedArray mainKeyboardViewAttr) { 342 sParams = new PointerTrackerParams(mainKeyboardViewAttr); 343 sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); 344 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 345 } 346 347 private static void updateGestureHandlingMode() { 348 sShouldHandleGesture = sMainDictionaryAvailable 349 && sGestureHandlingEnabledByInputField 350 && sGestureHandlingEnabledByUser 351 && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); 352 } 353 354 // Note that this method is called from a non-UI thread. 355 public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 356 sMainDictionaryAvailable = mainDictionaryAvailable; 357 updateGestureHandlingMode(); 358 } 359 360 public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 361 sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; 362 updateGestureHandlingMode(); 363 } 364 365 public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) { 366 final ArrayList<PointerTracker> trackers = sTrackers; 367 368 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 369 for (int i = trackers.size(); i <= id; i++) { 370 final PointerTracker tracker = new PointerTracker(i, handler); 371 trackers.add(tracker); 372 } 373 374 return trackers.get(id); 375 } 376 377 public static boolean isAnyInSlidingKeyInput() { 378 return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; 379 } 380 381 public static void setKeyboardActionListener(final KeyboardActionListener listener) { 382 final int trackersSize = sTrackers.size(); 383 for (int i = 0; i < trackersSize; ++i) { 384 final PointerTracker tracker = sTrackers.get(i); 385 tracker.mListener = listener; 386 } 387 } 388 389 public static void setKeyDetector(final KeyDetector keyDetector) { 390 final int trackersSize = sTrackers.size(); 391 for (int i = 0; i < trackersSize; ++i) { 392 final PointerTracker tracker = sTrackers.get(i); 393 tracker.setKeyDetectorInner(keyDetector); 394 // Mark that keyboard layout has been changed. 395 tracker.mKeyboardLayoutHasBeenChanged = true; 396 } 397 final Keyboard keyboard = keyDetector.getKeyboard(); 398 sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput(); 399 updateGestureHandlingMode(); 400 } 401 402 public static void setReleasedKeyGraphicsToAllKeys() { 403 final int trackersSize = sTrackers.size(); 404 for (int i = 0; i < trackersSize; ++i) { 405 final PointerTracker tracker = sTrackers.get(i); 406 tracker.setReleasedKeyGraphics(tracker.mCurrentKey); 407 } 408 } 409 410 private PointerTracker(final int id, final KeyEventHandler handler) { 411 if (handler == null) { 412 throw new NullPointerException(); 413 } 414 mPointerId = id; 415 mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( 416 id, sGestureStrokeParams); 417 setKeyDetectorInner(handler.getKeyDetector()); 418 mListener = handler.getKeyboardActionListener(); 419 mDrawingProxy = handler.getDrawingProxy(); 420 mTimerProxy = handler.getTimerProxy(); 421 } 422 423 // Returns true if keyboard has been changed by this callback. 424 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) { 425 if (sInGesture) { 426 return false; 427 } 428 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 429 if (DEBUG_LISTENER) { 430 Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId, 431 KeyDetector.printableCode(key), 432 ignoreModifierKey ? " ignoreModifier" : "", 433 key.isEnabled() ? "" : " disabled")); 434 } 435 if (ignoreModifierKey) { 436 return false; 437 } 438 if (key.isEnabled()) { 439 mListener.onPressKey(key.mCode); 440 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 441 mKeyboardLayoutHasBeenChanged = false; 442 mTimerProxy.startTypingStateTimer(key); 443 return keyboardLayoutHasBeenChanged; 444 } 445 return false; 446 } 447 448 // Note that we need primaryCode argument because the keyboard may in shifted state and the 449 // primaryCode is different from {@link Key#mCode}. 450 private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, 451 final int y, final long eventTime) { 452 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 453 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 454 final int code = altersCode ? key.getAltCode() : primaryCode; 455 if (DEBUG_LISTENER) { 456 final String output = code == Keyboard.CODE_OUTPUT_TEXT 457 ? key.getOutputText() : Keyboard.printableCode(code); 458 Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, 459 output, ignoreModifierKey ? " ignoreModifier" : "", 460 altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); 461 } 462 if (ProductionFlag.IS_EXPERIMENTAL) { 463 ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, 464 altersCode, code); 465 } 466 if (ignoreModifierKey) { 467 return; 468 } 469 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 470 if (key.isEnabled() || altersCode) { 471 sTimeRecorder.onCodeInput(code, eventTime); 472 if (code == Keyboard.CODE_OUTPUT_TEXT) { 473 mListener.onTextInput(key.getOutputText()); 474 } else if (code != Keyboard.CODE_UNSPECIFIED) { 475 mListener.onCodeInput(code, x, y); 476 } 477 } 478 } 479 480 // Note that we need primaryCode argument because the keyboard may be in shifted state and the 481 // primaryCode is different from {@link Key#mCode}. 482 private void callListenerOnRelease(final Key key, final int primaryCode, 483 final boolean withSliding) { 484 if (sInGesture) { 485 return; 486 } 487 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 488 if (DEBUG_LISTENER) { 489 Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, 490 Keyboard.printableCode(primaryCode), 491 withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", 492 key.isEnabled() ? "": " disabled")); 493 } 494 if (ProductionFlag.IS_EXPERIMENTAL) { 495 ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, 496 ignoreModifierKey); 497 } 498 if (ignoreModifierKey) { 499 return; 500 } 501 if (key.isEnabled()) { 502 mListener.onReleaseKey(primaryCode, withSliding); 503 } 504 } 505 506 private void callListenerOnCancelInput() { 507 if (DEBUG_LISTENER) { 508 Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); 509 } 510 if (ProductionFlag.IS_EXPERIMENTAL) { 511 ResearchLogger.pointerTracker_callListenerOnCancelInput(); 512 } 513 mListener.onCancelInput(); 514 } 515 516 private void setKeyDetectorInner(final KeyDetector keyDetector) { 517 final Keyboard keyboard = keyDetector.getKeyboard(); 518 if (keyDetector == mKeyDetector && keyboard == mKeyboard) { 519 return; 520 } 521 mKeyDetector = keyDetector; 522 mKeyboard = keyDetector.getKeyboard(); 523 final int keyWidth = mKeyboard.mMostCommonKeyWidth; 524 final int keyHeight = mKeyboard.mMostCommonKeyHeight; 525 mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth); 526 final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); 527 if (newKey != mCurrentKey) { 528 if (mDrawingProxy != null) { 529 setReleasedKeyGraphics(mCurrentKey); 530 } 531 // Keep {@link #mCurrentKey} that comes from previous keyboard. 532 } 533 mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD); 534 mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight); 535 } 536 537 @Override 538 public boolean isInSlidingKeyInput() { 539 return mIsInSlidingKeyInput; 540 } 541 542 public Key getKey() { 543 return mCurrentKey; 544 } 545 546 @Override 547 public boolean isModifier() { 548 return mCurrentKey != null && mCurrentKey.isModifier(); 549 } 550 551 public Key getKeyOn(final int x, final int y) { 552 return mKeyDetector.detectHitKey(x, y); 553 } 554 555 private void setReleasedKeyGraphics(final Key key) { 556 mDrawingProxy.dismissKeyPreview(this); 557 if (key == null) { 558 return; 559 } 560 561 // Even if the key is disabled, update the key release graphics just in case. 562 updateReleaseKeyGraphics(key); 563 564 if (key.isShift()) { 565 for (final Key shiftKey : mKeyboard.mShiftKeys) { 566 if (shiftKey != key) { 567 updateReleaseKeyGraphics(shiftKey); 568 } 569 } 570 } 571 572 if (key.altCodeWhileTyping()) { 573 final int altCode = key.getAltCode(); 574 final Key altKey = mKeyboard.getKey(altCode); 575 if (altKey != null) { 576 updateReleaseKeyGraphics(altKey); 577 } 578 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 579 if (k != key && k.getAltCode() == altCode) { 580 updateReleaseKeyGraphics(k); 581 } 582 } 583 } 584 } 585 586 private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 587 if (!sShouldHandleGesture) return false; 588 return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); 589 } 590 591 private void setPressedKeyGraphics(final Key key, final long eventTime) { 592 if (key == null) { 593 return; 594 } 595 596 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 597 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 598 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 599 if (!needsToUpdateGraphics) { 600 return; 601 } 602 603 if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) { 604 mDrawingProxy.showKeyPreview(this); 605 } 606 updatePressKeyGraphics(key); 607 608 if (key.isShift()) { 609 for (final Key shiftKey : mKeyboard.mShiftKeys) { 610 if (shiftKey != key) { 611 updatePressKeyGraphics(shiftKey); 612 } 613 } 614 } 615 616 if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { 617 final int altCode = key.getAltCode(); 618 final Key altKey = mKeyboard.getKey(altCode); 619 if (altKey != null) { 620 updatePressKeyGraphics(altKey); 621 } 622 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 623 if (k != key && k.getAltCode() == altCode) { 624 updatePressKeyGraphics(k); 625 } 626 } 627 } 628 } 629 630 private void updateReleaseKeyGraphics(final Key key) { 631 key.onReleased(); 632 mDrawingProxy.invalidateKey(key); 633 } 634 635 private void updatePressKeyGraphics(final Key key) { 636 key.onPressed(); 637 mDrawingProxy.invalidateKey(key); 638 } 639 640 public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { 641 return mGestureStrokeWithPreviewPoints; 642 } 643 644 public int getLastX() { 645 return mLastX; 646 } 647 648 public int getLastY() { 649 return mLastY; 650 } 651 652 public long getDownTime() { 653 return mDownTime; 654 } 655 656 private Key onDownKey(final int x, final int y, final long eventTime) { 657 mDownTime = eventTime; 658 mBogusMoveEventDetector.onDownKey(); 659 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 660 } 661 662 static int getDistance(final int x1, final int y1, final int x2, final int y2) { 663 return (int)Math.hypot(x1 - x2, y1 - y2); 664 } 665 666 private Key onMoveKeyInternal(final int x, final int y) { 667 mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); 668 mLastX = x; 669 mLastY = y; 670 return mKeyDetector.detectHitKey(x, y); 671 } 672 673 private Key onMoveKey(final int x, final int y) { 674 return onMoveKeyInternal(x, y); 675 } 676 677 private Key onMoveToNewKey(final Key newKey, final int x, final int y) { 678 mCurrentKey = newKey; 679 mKeyX = x; 680 mKeyY = y; 681 return newKey; 682 } 683 684 private static int getActivePointerTrackerCount() { 685 return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); 686 } 687 688 private void mayStartBatchInput(final Key key) { 689 if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { 690 return; 691 } 692 if (key == null || !Character.isLetter(key.mCode)) { 693 return; 694 } 695 if (DEBUG_LISTENER) { 696 Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); 697 } 698 sInGesture = true; 699 synchronized (sAggregratedPointers) { 700 sAggregratedPointers.reset(); 701 sLastRecognitionPointSize = 0; 702 sLastRecognitionTime = 0; 703 mListener.onStartBatchInput(); 704 } 705 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 706 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 707 } 708 709 private void mayUpdateBatchInput(final long eventTime, final Key key) { 710 if (key != null) { 711 synchronized (sAggregratedPointers) { 712 final GestureStroke stroke = mGestureStrokeWithPreviewPoints; 713 stroke.appendIncrementalBatchPoints(sAggregratedPointers); 714 final int size = sAggregratedPointers.getPointerSize(); 715 if (size > sLastRecognitionPointSize 716 && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { 717 sLastRecognitionPointSize = size; 718 sLastRecognitionTime = eventTime; 719 if (DEBUG_LISTENER) { 720 Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", 721 mPointerId, size)); 722 } 723 mListener.onUpdateBatchInput(sAggregratedPointers); 724 } 725 } 726 } 727 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 728 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 729 } 730 731 private void mayEndBatchInput(final long eventTime) { 732 synchronized (sAggregratedPointers) { 733 mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); 734 if (getActivePointerTrackerCount() == 1) { 735 if (DEBUG_LISTENER) { 736 Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", 737 mPointerId, sAggregratedPointers.getPointerSize())); 738 } 739 sInGesture = false; 740 sTimeRecorder.onEndBatchInput(eventTime); 741 mListener.onEndBatchInput(sAggregratedPointers); 742 } 743 } 744 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 745 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 746 } 747 748 public void processMotionEvent(final int action, final int x, final int y, final long eventTime, 749 final KeyEventHandler handler) { 750 switch (action) { 751 case MotionEvent.ACTION_DOWN: 752 case MotionEvent.ACTION_POINTER_DOWN: 753 onDownEvent(x, y, eventTime, handler); 754 break; 755 case MotionEvent.ACTION_UP: 756 case MotionEvent.ACTION_POINTER_UP: 757 onUpEvent(x, y, eventTime); 758 break; 759 case MotionEvent.ACTION_MOVE: 760 onMoveEvent(x, y, eventTime, null); 761 break; 762 case MotionEvent.ACTION_CANCEL: 763 onCancelEvent(x, y, eventTime); 764 break; 765 } 766 } 767 768 public void onDownEvent(final int x, final int y, final long eventTime, 769 final KeyEventHandler handler) { 770 if (DEBUG_EVENT) { 771 printTouchEvent("onDownEvent:", x, y, eventTime); 772 } 773 774 mDrawingProxy = handler.getDrawingProxy(); 775 mTimerProxy = handler.getTimerProxy(); 776 setKeyboardActionListener(handler.getKeyboardActionListener()); 777 setKeyDetectorInner(handler.getKeyDetector()); 778 // Naive up-to-down noise filter. 779 final long deltaT = eventTime - mUpTime; 780 if (deltaT < sParams.mTouchNoiseThresholdTime) { 781 final int distance = getDistance(x, y, mLastX, mLastY); 782 if (distance < sParams.mTouchNoiseThresholdDistance) { 783 if (DEBUG_MODE) 784 Log.w(TAG, String.format("[%d] onDownEvent:" 785 + " ignore potential noise: time=%d distance=%d", 786 mPointerId, deltaT, distance)); 787 if (ProductionFlag.IS_EXPERIMENTAL) { 788 ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); 789 } 790 mKeyAlreadyProcessed = true; 791 return; 792 } 793 } 794 795 final Key key = getKeyOn(x, y); 796 mBogusMoveEventDetector.onActualDownEvent(x, y); 797 final PointerTrackerQueue queue = sPointerTrackerQueue; 798 if (queue != null) { 799 if (key != null && key.isModifier()) { 800 // Before processing a down event of modifier key, all pointers already being 801 // tracked should be released. 802 queue.releaseAllPointers(eventTime); 803 } 804 queue.add(this); 805 } 806 onDownEventInternal(x, y, eventTime); 807 if (!sShouldHandleGesture) { 808 return; 809 } 810 // A gesture should start only from the letter key. 811 mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() 812 && !mIsShowingMoreKeysPanel && key != null && Keyboard.isLetterCode(key.mCode); 813 if (mIsDetectingGesture) { 814 if (getActivePointerTrackerCount() == 1) { 815 sGestureFirstDownTime = eventTime; 816 } 817 mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, 818 sTimeRecorder.getLastLetterTypingTime()); 819 } 820 } 821 822 private void onDownEventInternal(final int x, final int y, final long eventTime) { 823 Key key = onDownKey(x, y, eventTime); 824 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 825 // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. 826 mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled 827 || (key != null && key.isModifier()) 828 || mKeyDetector.alwaysAllowsSlidingInput(); 829 mKeyboardLayoutHasBeenChanged = false; 830 mKeyAlreadyProcessed = false; 831 resetSlidingKeyInput(); 832 if (key != null) { 833 // This onPress call may have changed keyboard layout. Those cases are detected at 834 // {@link #setKeyboard}. In those cases, we should update key according to the new 835 // keyboard layout. 836 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 837 key = onDownKey(x, y, eventTime); 838 } 839 840 startRepeatKey(key); 841 startLongPressTimer(key); 842 setPressedKeyGraphics(key, eventTime); 843 } 844 } 845 846 private void startSlidingKeyInput(final Key key) { 847 if (!mIsInSlidingKeyInput) { 848 mIsInSlidingKeyInputFromModifier = key.isModifier(); 849 } 850 mIsInSlidingKeyInput = true; 851 } 852 853 private void resetSlidingKeyInput() { 854 mIsInSlidingKeyInput = false; 855 mIsInSlidingKeyInputFromModifier = false; 856 } 857 858 private void onGestureMoveEvent(final int x, final int y, final long eventTime, 859 final boolean isMajorEvent, final Key key) { 860 final int gestureTime = (int)(eventTime - sGestureFirstDownTime); 861 if (mIsDetectingGesture) { 862 mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); 863 mayStartBatchInput(key); 864 if (sInGesture) { 865 mayUpdateBatchInput(eventTime, key); 866 } 867 } 868 } 869 870 public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { 871 if (DEBUG_MOVE_EVENT) { 872 printTouchEvent("onMoveEvent:", x, y, eventTime); 873 } 874 if (mKeyAlreadyProcessed) { 875 return; 876 } 877 878 if (sShouldHandleGesture && me != null) { 879 // Add historical points to gesture path. 880 final int pointerIndex = me.findPointerIndex(mPointerId); 881 final int historicalSize = me.getHistorySize(); 882 for (int h = 0; h < historicalSize; h++) { 883 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 884 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 885 final long historicalTime = me.getHistoricalEventTime(h); 886 onGestureMoveEvent(historicalX, historicalY, historicalTime, 887 false /* isMajorEvent */, null); 888 } 889 } 890 891 onMoveEventInternal(x, y, eventTime); 892 } 893 894 private void onMoveEventInternal(final int x, final int y, final long eventTime) { 895 final int lastX = mLastX; 896 final int lastY = mLastY; 897 final Key oldKey = mCurrentKey; 898 Key key = onMoveKey(x, y); 899 900 if (sShouldHandleGesture) { 901 // Register move event on gesture tracker. 902 onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key); 903 if (sInGesture) { 904 mTimerProxy.cancelLongPressTimer(); 905 mCurrentKey = null; 906 setReleasedKeyGraphics(oldKey); 907 return; 908 } 909 } 910 911 if (key != null) { 912 if (oldKey == null) { 913 // The pointer has been slid in to the new key, but the finger was not on any keys. 914 // In this case, we must call onPress() to notify that the new key is being pressed. 915 // This onPress call may have changed keyboard layout. Those cases are detected at 916 // {@link #setKeyboard}. In those cases, we should update key according to the 917 // new keyboard layout. 918 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 919 key = onMoveKey(x, y); 920 } 921 onMoveToNewKey(key, x, y); 922 startLongPressTimer(key); 923 setPressedKeyGraphics(key, eventTime); 924 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { 925 // The pointer has been slid in to the new key from the previous key, we must call 926 // onRelease() first to notify that the previous key has been released, then call 927 // onPress() to notify that the new key is being pressed. 928 setReleasedKeyGraphics(oldKey); 929 callListenerOnRelease(oldKey, oldKey.mCode, true); 930 startSlidingKeyInput(oldKey); 931 mTimerProxy.cancelKeyTimers(); 932 startRepeatKey(key); 933 if (mIsAllowedSlidingKeyInput) { 934 // This onPress call may have changed keyboard layout. Those cases are detected 935 // at {@link #setKeyboard}. In those cases, we should update key according 936 // to the new keyboard layout. 937 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 938 key = onMoveKey(x, y); 939 } 940 onMoveToNewKey(key, x, y); 941 startLongPressTimer(key); 942 setPressedKeyGraphics(key, eventTime); 943 } else { 944 // HACK: On some devices, quick successive touches may be reported as a sudden 945 // move by touch panel firmware. This hack detects such cases and translates the 946 // move event to successive up and down events. 947 // TODO: Should find a way to balance gesture detection and this hack. 948 if (sNeedsPhantomSuddenMoveEventHack 949 && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { 950 if (DEBUG_MODE) { 951 Log.w(TAG, String.format("[%d] onMoveEvent:" 952 + " phantom sudden move event (distance=%d) is translated to " 953 + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, 954 getDistance(x, y, lastX, lastY), 955 lastX, lastY, Keyboard.printableCode(oldKey.mCode), 956 x, y, Keyboard.printableCode(key.mCode))); 957 } 958 // TODO: This should be moved to outside of this nested if-clause? 959 if (ProductionFlag.IS_EXPERIMENTAL) { 960 ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); 961 } 962 onUpEventInternal(eventTime); 963 onDownEventInternal(x, y, eventTime); 964 } 965 // HACK: On some devices, quick successive proximate touches may be reported as 966 // a bogus down-move-up event by touch panel firmware. This hack detects such 967 // cases and breaks these events into separate up and down events. 968 else if (sNeedsProximateBogusDownMoveUpEventHack 969 && sTimeRecorder.isInFastTyping(eventTime) 970 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { 971 if (DEBUG_MODE) { 972 final float keyDiagonal = (float)Math.hypot( 973 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 974 final float radiusRatio = 975 mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) 976 / keyDiagonal; 977 Log.w(TAG, String.format("[%d] onMoveEvent:" 978 + " bogus down-move-up event (raidus=%.2f key diagonal) is " 979 + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", 980 mPointerId, radiusRatio, 981 lastX, lastY, Keyboard.printableCode(oldKey.mCode), 982 x, y, Keyboard.printableCode(key.mCode))); 983 } 984 onUpEventInternal(eventTime); 985 onDownEventInternal(x, y, eventTime); 986 } else { 987 // HACK: If there are currently multiple touches, register the key even if 988 // the finger slides off the key. This defends against noise from some 989 // touch panels when there are close multiple touches. 990 // Caveat: When in chording input mode with a modifier key, we don't use 991 // this hack. 992 if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null 993 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 994 if (DEBUG_MODE) { 995 Log.w(TAG, String.format("[%d] onMoveEvent:" 996 + " detected sliding finger while multi touching", 997 mPointerId)); 998 } 999 onUpEvent(x, y, eventTime); 1000 mKeyAlreadyProcessed = true; 1001 } 1002 if (!mIsDetectingGesture) { 1003 mKeyAlreadyProcessed = true; 1004 } 1005 setReleasedKeyGraphics(oldKey); 1006 } 1007 } 1008 } 1009 } else { 1010 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { 1011 // The pointer has been slid out from the previous key, we must call onRelease() to 1012 // notify that the previous key has been released. 1013 setReleasedKeyGraphics(oldKey); 1014 callListenerOnRelease(oldKey, oldKey.mCode, true); 1015 startSlidingKeyInput(oldKey); 1016 mTimerProxy.cancelLongPressTimer(); 1017 if (mIsAllowedSlidingKeyInput) { 1018 onMoveToNewKey(key, x, y); 1019 } else { 1020 if (!mIsDetectingGesture) { 1021 mKeyAlreadyProcessed = true; 1022 } 1023 } 1024 } 1025 } 1026 } 1027 1028 public void onUpEvent(final int x, final int y, final long eventTime) { 1029 if (DEBUG_EVENT) { 1030 printTouchEvent("onUpEvent :", x, y, eventTime); 1031 } 1032 1033 final PointerTrackerQueue queue = sPointerTrackerQueue; 1034 if (queue != null) { 1035 if (!sInGesture) { 1036 if (mCurrentKey != null && mCurrentKey.isModifier()) { 1037 // Before processing an up event of modifier key, all pointers already being 1038 // tracked should be released. 1039 queue.releaseAllPointersExcept(this, eventTime); 1040 } else { 1041 queue.releaseAllPointersOlderThan(this, eventTime); 1042 } 1043 } 1044 } 1045 onUpEventInternal(eventTime); 1046 if (queue != null) { 1047 queue.remove(this); 1048 } 1049 } 1050 1051 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 1052 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 1053 // "virtual" up event. 1054 @Override 1055 public void onPhantomUpEvent(final long eventTime) { 1056 if (DEBUG_EVENT) { 1057 printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); 1058 } 1059 onUpEventInternal(eventTime); 1060 mKeyAlreadyProcessed = true; 1061 } 1062 1063 private void onUpEventInternal(final long eventTime) { 1064 mTimerProxy.cancelKeyTimers(); 1065 resetSlidingKeyInput(); 1066 mIsDetectingGesture = false; 1067 final Key currentKey = mCurrentKey; 1068 mCurrentKey = null; 1069 // Release the last pressed key. 1070 setReleasedKeyGraphics(currentKey); 1071 if (mIsShowingMoreKeysPanel) { 1072 mDrawingProxy.dismissMoreKeysPanel(); 1073 mIsShowingMoreKeysPanel = false; 1074 } 1075 1076 if (sInGesture) { 1077 if (currentKey != null) { 1078 callListenerOnRelease(currentKey, currentKey.mCode, true); 1079 } 1080 mayEndBatchInput(eventTime); 1081 return; 1082 } 1083 1084 if (mKeyAlreadyProcessed) { 1085 return; 1086 } 1087 if (currentKey != null && !currentKey.isRepeatable()) { 1088 detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); 1089 } 1090 } 1091 1092 public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { 1093 onLongPressed(); 1094 mIsShowingMoreKeysPanel = true; 1095 onDownEvent(x, y, SystemClock.uptimeMillis(), handler); 1096 } 1097 1098 public void onLongPressed() { 1099 mKeyAlreadyProcessed = true; 1100 setReleasedKeyGraphics(mCurrentKey); 1101 final PointerTrackerQueue queue = sPointerTrackerQueue; 1102 if (queue != null) { 1103 queue.remove(this); 1104 } 1105 } 1106 1107 public void onCancelEvent(final int x, final int y, final long eventTime) { 1108 if (DEBUG_EVENT) { 1109 printTouchEvent("onCancelEvt:", x, y, eventTime); 1110 } 1111 1112 final PointerTrackerQueue queue = sPointerTrackerQueue; 1113 if (queue != null) { 1114 queue.releaseAllPointersExcept(this, eventTime); 1115 queue.remove(this); 1116 } 1117 onCancelEventInternal(); 1118 } 1119 1120 private void onCancelEventInternal() { 1121 mTimerProxy.cancelKeyTimers(); 1122 setReleasedKeyGraphics(mCurrentKey); 1123 resetSlidingKeyInput(); 1124 if (mIsShowingMoreKeysPanel) { 1125 mDrawingProxy.dismissMoreKeysPanel(); 1126 mIsShowingMoreKeysPanel = false; 1127 } 1128 } 1129 1130 private void startRepeatKey(final Key key) { 1131 if (key != null && key.isRepeatable() && !sInGesture) { 1132 onRegisterKey(key); 1133 mTimerProxy.startKeyRepeatTimer(this); 1134 } 1135 } 1136 1137 public void onRegisterKey(final Key key) { 1138 if (key != null) { 1139 detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); 1140 mTimerProxy.startTypingStateTimer(key); 1141 } 1142 } 1143 1144 private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, 1145 final Key newKey) { 1146 if (mKeyDetector == null) { 1147 throw new NullPointerException("keyboard and/or key detector not set"); 1148 } 1149 final Key curKey = mCurrentKey; 1150 if (newKey == curKey) { 1151 return false; 1152 } else if (curKey != null) { 1153 final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( 1154 mIsInSlidingKeyInputFromModifier); 1155 final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); 1156 if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { 1157 if (DEBUG_MODE) { 1158 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) 1159 / mKeyboard.mMostCommonKeyWidth; 1160 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1161 +" %.2f key width from key edge", 1162 mPointerId, distanceToEdgeRatio)); 1163 } 1164 return true; 1165 } 1166 if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput 1167 && sTimeRecorder.isInFastTyping(eventTime) 1168 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { 1169 if (DEBUG_MODE) { 1170 final float keyDiagonal = (float)Math.hypot( 1171 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1172 final float lengthFromDownRatio = 1173 mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; 1174 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1175 + " %.2f key diagonal from virtual down point", 1176 mPointerId, lengthFromDownRatio)); 1177 } 1178 return true; 1179 } 1180 return false; 1181 } else { // curKey == null && newKey != null 1182 return true; 1183 } 1184 } 1185 1186 private void startLongPressTimer(final Key key) { 1187 if (key != null && key.isLongPressEnabled() && !sInGesture) { 1188 mTimerProxy.startLongPressTimer(this); 1189 } 1190 } 1191 1192 private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { 1193 if (key == null) { 1194 callListenerOnCancelInput(); 1195 return; 1196 } 1197 1198 final int code = key.mCode; 1199 callListenerOnCodeInput(key, code, x, y, eventTime); 1200 callListenerOnRelease(key, code, false); 1201 } 1202 1203 private void printTouchEvent(final String title, final int x, final int y, 1204 final long eventTime) { 1205 final Key key = mKeyDetector.detectHitKey(x, y); 1206 final String code = KeyDetector.printableCode(key); 1207 Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, 1208 (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code)); 1209 } 1210 } 1211