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