PointerTracker.java revision 8b449c6dda88174ec19bfc366baf048a72857215
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 PointerTrackerQueue sPointerTrackerQueue; 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(boolean hasDistinctMultitouch, 330 boolean needsPhantomSuddenMoveEventHack) { 331 if (hasDistinctMultitouch) { 332 sPointerTrackerQueue = new PointerTrackerQueue(); 333 } else { 334 sPointerTrackerQueue = null; 335 } 336 sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; 337 sParams = PointerTrackerParams.DEFAULT; 338 sGestureStrokeParams = GestureStrokeParams.DEFAULT; 339 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 340 } 341 342 public static void setParameters(final TypedArray mainKeyboardViewAttr) { 343 sParams = new PointerTrackerParams(mainKeyboardViewAttr); 344 sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); 345 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 346 } 347 348 private static void updateGestureHandlingMode() { 349 sShouldHandleGesture = sMainDictionaryAvailable 350 && sGestureHandlingEnabledByInputField 351 && sGestureHandlingEnabledByUser 352 && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); 353 } 354 355 // Note that this method is called from a non-UI thread. 356 public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 357 sMainDictionaryAvailable = mainDictionaryAvailable; 358 updateGestureHandlingMode(); 359 } 360 361 public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 362 sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; 363 updateGestureHandlingMode(); 364 } 365 366 public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) { 367 final ArrayList<PointerTracker> trackers = sTrackers; 368 369 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 370 for (int i = trackers.size(); i <= id; i++) { 371 final PointerTracker tracker = new PointerTracker(i, handler); 372 trackers.add(tracker); 373 } 374 375 return trackers.get(id); 376 } 377 378 public static boolean isAnyInSlidingKeyInput() { 379 return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; 380 } 381 382 public static void setKeyboardActionListener(final KeyboardActionListener listener) { 383 final int trackersSize = sTrackers.size(); 384 for (int i = 0; i < trackersSize; ++i) { 385 final PointerTracker tracker = sTrackers.get(i); 386 tracker.mListener = listener; 387 } 388 } 389 390 public static void setKeyDetector(final KeyDetector keyDetector) { 391 final int trackersSize = sTrackers.size(); 392 for (int i = 0; i < trackersSize; ++i) { 393 final PointerTracker tracker = sTrackers.get(i); 394 tracker.setKeyDetectorInner(keyDetector); 395 // Mark that keyboard layout has been changed. 396 tracker.mKeyboardLayoutHasBeenChanged = true; 397 } 398 final Keyboard keyboard = keyDetector.getKeyboard(); 399 sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput(); 400 updateGestureHandlingMode(); 401 } 402 403 public static void setReleasedKeyGraphicsToAllKeys() { 404 final int trackersSize = sTrackers.size(); 405 for (int i = 0; i < trackersSize; ++i) { 406 final PointerTracker tracker = sTrackers.get(i); 407 tracker.setReleasedKeyGraphics(tracker.mCurrentKey); 408 } 409 } 410 411 private PointerTracker(final int id, final KeyEventHandler handler) { 412 if (handler == null) { 413 throw new NullPointerException(); 414 } 415 mPointerId = id; 416 mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( 417 id, sGestureStrokeParams); 418 setKeyDetectorInner(handler.getKeyDetector()); 419 mListener = handler.getKeyboardActionListener(); 420 mDrawingProxy = handler.getDrawingProxy(); 421 mTimerProxy = handler.getTimerProxy(); 422 } 423 424 // Returns true if keyboard has been changed by this callback. 425 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) { 426 if (sInGesture) { 427 return false; 428 } 429 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 430 if (DEBUG_LISTENER) { 431 Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId, 432 KeyDetector.printableCode(key), 433 ignoreModifierKey ? " ignoreModifier" : "", 434 key.isEnabled() ? "" : " disabled")); 435 } 436 if (ignoreModifierKey) { 437 return false; 438 } 439 if (key.isEnabled()) { 440 mListener.onPressKey(key.mCode); 441 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 442 mKeyboardLayoutHasBeenChanged = false; 443 mTimerProxy.startTypingStateTimer(key); 444 return keyboardLayoutHasBeenChanged; 445 } 446 return false; 447 } 448 449 // Note that we need primaryCode argument because the keyboard may in shifted state and the 450 // primaryCode is different from {@link Key#mCode}. 451 private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, 452 final int y, final long eventTime) { 453 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 454 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 455 final int code = altersCode ? key.getAltCode() : primaryCode; 456 if (DEBUG_LISTENER) { 457 final String output = code == Constants.CODE_OUTPUT_TEXT 458 ? key.getOutputText() : Constants.printableCode(code); 459 Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, 460 output, ignoreModifierKey ? " ignoreModifier" : "", 461 altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); 462 } 463 if (ProductionFlag.IS_EXPERIMENTAL) { 464 ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, 465 altersCode, code); 466 } 467 if (ignoreModifierKey) { 468 return; 469 } 470 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 471 if (key.isEnabled() || altersCode) { 472 sTimeRecorder.onCodeInput(code, eventTime); 473 if (code == Constants.CODE_OUTPUT_TEXT) { 474 mListener.onTextInput(key.getOutputText()); 475 } else if (code != Constants.CODE_UNSPECIFIED) { 476 mListener.onCodeInput(code, x, y); 477 } 478 } 479 } 480 481 // Note that we need primaryCode argument because the keyboard may be in shifted state and the 482 // primaryCode is different from {@link Key#mCode}. 483 private void callListenerOnRelease(final Key key, final int primaryCode, 484 final boolean withSliding) { 485 if (sInGesture) { 486 return; 487 } 488 final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); 489 if (DEBUG_LISTENER) { 490 Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, 491 Constants.printableCode(primaryCode), 492 withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", 493 key.isEnabled() ? "": " disabled")); 494 } 495 if (ProductionFlag.IS_EXPERIMENTAL) { 496 ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, 497 ignoreModifierKey); 498 } 499 if (ignoreModifierKey) { 500 return; 501 } 502 if (key.isEnabled()) { 503 mListener.onReleaseKey(primaryCode, withSliding); 504 } 505 } 506 507 private void callListenerOnCancelInput() { 508 if (DEBUG_LISTENER) { 509 Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); 510 } 511 if (ProductionFlag.IS_EXPERIMENTAL) { 512 ResearchLogger.pointerTracker_callListenerOnCancelInput(); 513 } 514 mListener.onCancelInput(); 515 } 516 517 private void setKeyDetectorInner(final KeyDetector keyDetector) { 518 final Keyboard keyboard = keyDetector.getKeyboard(); 519 if (keyDetector == mKeyDetector && keyboard == mKeyboard) { 520 return; 521 } 522 mKeyDetector = keyDetector; 523 mKeyboard = keyDetector.getKeyboard(); 524 final int keyWidth = mKeyboard.mMostCommonKeyWidth; 525 final int keyHeight = mKeyboard.mMostCommonKeyHeight; 526 mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth); 527 final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); 528 if (newKey != mCurrentKey) { 529 if (mDrawingProxy != null) { 530 setReleasedKeyGraphics(mCurrentKey); 531 } 532 // Keep {@link #mCurrentKey} that comes from previous keyboard. 533 } 534 mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD); 535 mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight); 536 } 537 538 @Override 539 public boolean isInSlidingKeyInput() { 540 return mIsInSlidingKeyInput; 541 } 542 543 public Key getKey() { 544 return mCurrentKey; 545 } 546 547 @Override 548 public boolean isModifier() { 549 return mCurrentKey != null && mCurrentKey.isModifier(); 550 } 551 552 public Key getKeyOn(final int x, final int y) { 553 return mKeyDetector.detectHitKey(x, y); 554 } 555 556 private void setReleasedKeyGraphics(final Key key) { 557 mDrawingProxy.dismissKeyPreview(this); 558 if (key == null) { 559 return; 560 } 561 562 // Even if the key is disabled, update the key release graphics just in case. 563 updateReleaseKeyGraphics(key); 564 565 if (key.isShift()) { 566 for (final Key shiftKey : mKeyboard.mShiftKeys) { 567 if (shiftKey != key) { 568 updateReleaseKeyGraphics(shiftKey); 569 } 570 } 571 } 572 573 if (key.altCodeWhileTyping()) { 574 final int altCode = key.getAltCode(); 575 final Key altKey = mKeyboard.getKey(altCode); 576 if (altKey != null) { 577 updateReleaseKeyGraphics(altKey); 578 } 579 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 580 if (k != key && k.getAltCode() == altCode) { 581 updateReleaseKeyGraphics(k); 582 } 583 } 584 } 585 } 586 587 private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 588 if (!sShouldHandleGesture) return false; 589 return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); 590 } 591 592 private void setPressedKeyGraphics(final Key key, final long eventTime) { 593 if (key == null) { 594 return; 595 } 596 597 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 598 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 599 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 600 if (!needsToUpdateGraphics) { 601 return; 602 } 603 604 if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) { 605 mDrawingProxy.showKeyPreview(this); 606 } 607 updatePressKeyGraphics(key); 608 609 if (key.isShift()) { 610 for (final Key shiftKey : mKeyboard.mShiftKeys) { 611 if (shiftKey != key) { 612 updatePressKeyGraphics(shiftKey); 613 } 614 } 615 } 616 617 if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { 618 final int altCode = key.getAltCode(); 619 final Key altKey = mKeyboard.getKey(altCode); 620 if (altKey != null) { 621 updatePressKeyGraphics(altKey); 622 } 623 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 624 if (k != key && k.getAltCode() == altCode) { 625 updatePressKeyGraphics(k); 626 } 627 } 628 } 629 } 630 631 private void updateReleaseKeyGraphics(final Key key) { 632 key.onReleased(); 633 mDrawingProxy.invalidateKey(key); 634 } 635 636 private void updatePressKeyGraphics(final Key key) { 637 key.onPressed(); 638 mDrawingProxy.invalidateKey(key); 639 } 640 641 public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { 642 return mGestureStrokeWithPreviewPoints; 643 } 644 645 public int getLastX() { 646 return mLastX; 647 } 648 649 public int getLastY() { 650 return mLastY; 651 } 652 653 public long getDownTime() { 654 return mDownTime; 655 } 656 657 private Key onDownKey(final int x, final int y, final long eventTime) { 658 mDownTime = eventTime; 659 mBogusMoveEventDetector.onDownKey(); 660 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 661 } 662 663 static int getDistance(final int x1, final int y1, final int x2, final int y2) { 664 return (int)Math.hypot(x1 - x2, y1 - y2); 665 } 666 667 private Key onMoveKeyInternal(final int x, final int y) { 668 mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); 669 mLastX = x; 670 mLastY = y; 671 return mKeyDetector.detectHitKey(x, y); 672 } 673 674 private Key onMoveKey(final int x, final int y) { 675 return onMoveKeyInternal(x, y); 676 } 677 678 private Key onMoveToNewKey(final Key newKey, final int x, final int y) { 679 mCurrentKey = newKey; 680 mKeyX = x; 681 mKeyY = y; 682 return newKey; 683 } 684 685 private static int getActivePointerTrackerCount() { 686 return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); 687 } 688 689 private void mayStartBatchInput(final Key key) { 690 if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { 691 return; 692 } 693 if (key == null || !Character.isLetter(key.mCode)) { 694 return; 695 } 696 if (DEBUG_LISTENER) { 697 Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); 698 } 699 sInGesture = true; 700 synchronized (sAggregratedPointers) { 701 sAggregratedPointers.reset(); 702 sLastRecognitionPointSize = 0; 703 sLastRecognitionTime = 0; 704 mListener.onStartBatchInput(); 705 } 706 mTimerProxy.cancelLongPressTimer(); 707 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 708 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 709 } 710 711 private void mayUpdateBatchInput(final long eventTime, final Key key) { 712 if (key != null) { 713 synchronized (sAggregratedPointers) { 714 final GestureStroke stroke = mGestureStrokeWithPreviewPoints; 715 stroke.appendIncrementalBatchPoints(sAggregratedPointers); 716 final int size = sAggregratedPointers.getPointerSize(); 717 if (size > sLastRecognitionPointSize 718 && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { 719 sLastRecognitionPointSize = size; 720 sLastRecognitionTime = eventTime; 721 if (DEBUG_LISTENER) { 722 Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", 723 mPointerId, size)); 724 } 725 mListener.onUpdateBatchInput(sAggregratedPointers); 726 } 727 } 728 } 729 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 730 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 731 } 732 733 private void mayEndBatchInput(final long eventTime) { 734 synchronized (sAggregratedPointers) { 735 mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); 736 if (getActivePointerTrackerCount() == 1) { 737 if (DEBUG_LISTENER) { 738 Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", 739 mPointerId, sAggregratedPointers.getPointerSize())); 740 } 741 sInGesture = false; 742 sTimeRecorder.onEndBatchInput(eventTime); 743 mListener.onEndBatchInput(sAggregratedPointers); 744 } 745 } 746 final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; 747 mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 748 } 749 750 public void processMotionEvent(final int action, final int x, final int y, final long eventTime, 751 final KeyEventHandler handler) { 752 switch (action) { 753 case MotionEvent.ACTION_DOWN: 754 case MotionEvent.ACTION_POINTER_DOWN: 755 onDownEvent(x, y, eventTime, handler); 756 break; 757 case MotionEvent.ACTION_UP: 758 case MotionEvent.ACTION_POINTER_UP: 759 onUpEvent(x, y, eventTime); 760 break; 761 case MotionEvent.ACTION_MOVE: 762 onMoveEvent(x, y, eventTime, null); 763 break; 764 case MotionEvent.ACTION_CANCEL: 765 onCancelEvent(x, y, eventTime); 766 break; 767 } 768 } 769 770 public void onDownEvent(final int x, final int y, final long eventTime, 771 final KeyEventHandler handler) { 772 if (DEBUG_EVENT) { 773 printTouchEvent("onDownEvent:", x, y, eventTime); 774 } 775 776 mDrawingProxy = handler.getDrawingProxy(); 777 mTimerProxy = handler.getTimerProxy(); 778 setKeyboardActionListener(handler.getKeyboardActionListener()); 779 setKeyDetectorInner(handler.getKeyDetector()); 780 // Naive up-to-down noise filter. 781 final long deltaT = eventTime - mUpTime; 782 if (deltaT < sParams.mTouchNoiseThresholdTime) { 783 final int distance = getDistance(x, y, mLastX, mLastY); 784 if (distance < sParams.mTouchNoiseThresholdDistance) { 785 if (DEBUG_MODE) 786 Log.w(TAG, String.format("[%d] onDownEvent:" 787 + " ignore potential noise: time=%d distance=%d", 788 mPointerId, deltaT, distance)); 789 if (ProductionFlag.IS_EXPERIMENTAL) { 790 ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); 791 } 792 mKeyAlreadyProcessed = true; 793 return; 794 } 795 } 796 797 final Key key = getKeyOn(x, y); 798 mBogusMoveEventDetector.onActualDownEvent(x, y); 799 final PointerTrackerQueue queue = sPointerTrackerQueue; 800 if (queue != null) { 801 if (key != null && key.isModifier()) { 802 // Before processing a down event of modifier key, all pointers already being 803 // tracked should be released. 804 queue.releaseAllPointers(eventTime); 805 } 806 queue.add(this); 807 } 808 onDownEventInternal(x, y, eventTime); 809 if (!sShouldHandleGesture) { 810 return; 811 } 812 // A gesture should start only from a non-modifier key. 813 mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() 814 && !mIsShowingMoreKeysPanel && key != null && !key.isModifier(); 815 if (mIsDetectingGesture) { 816 if (getActivePointerTrackerCount() == 1) { 817 sGestureFirstDownTime = eventTime; 818 } 819 mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, 820 sTimeRecorder.getLastLetterTypingTime()); 821 } 822 } 823 824 private void onDownEventInternal(final int x, final int y, final long eventTime) { 825 Key key = onDownKey(x, y, eventTime); 826 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 827 // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. 828 mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled 829 || (key != null && key.isModifier()) 830 || mKeyDetector.alwaysAllowsSlidingInput(); 831 mKeyboardLayoutHasBeenChanged = false; 832 mKeyAlreadyProcessed = false; 833 resetSlidingKeyInput(); 834 if (key != null) { 835 // This onPress call may have changed keyboard layout. Those cases are detected at 836 // {@link #setKeyboard}. In those cases, we should update key according to the new 837 // keyboard layout. 838 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 839 key = onDownKey(x, y, eventTime); 840 } 841 842 startRepeatKey(key); 843 startLongPressTimer(key); 844 setPressedKeyGraphics(key, eventTime); 845 } 846 } 847 848 private void startSlidingKeyInput(final Key key) { 849 if (!mIsInSlidingKeyInput) { 850 mIsInSlidingKeyInputFromModifier = key.isModifier(); 851 } 852 mIsInSlidingKeyInput = true; 853 } 854 855 private void resetSlidingKeyInput() { 856 mIsInSlidingKeyInput = false; 857 mIsInSlidingKeyInputFromModifier = false; 858 } 859 860 private void onGestureMoveEvent(final int x, final int y, final long eventTime, 861 final boolean isMajorEvent, final Key key) { 862 final int gestureTime = (int)(eventTime - sGestureFirstDownTime); 863 if (mIsDetectingGesture) { 864 mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); 865 mayStartBatchInput(key); 866 if (sInGesture) { 867 mayUpdateBatchInput(eventTime, key); 868 } 869 } 870 } 871 872 public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { 873 if (DEBUG_MOVE_EVENT) { 874 printTouchEvent("onMoveEvent:", x, y, eventTime); 875 } 876 if (mKeyAlreadyProcessed) { 877 return; 878 } 879 880 if (sShouldHandleGesture && me != null) { 881 // Add historical points to gesture path. 882 final int pointerIndex = me.findPointerIndex(mPointerId); 883 final int historicalSize = me.getHistorySize(); 884 for (int h = 0; h < historicalSize; h++) { 885 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 886 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 887 final long historicalTime = me.getHistoricalEventTime(h); 888 onGestureMoveEvent(historicalX, historicalY, historicalTime, 889 false /* isMajorEvent */, null); 890 } 891 } 892 893 onMoveEventInternal(x, y, eventTime); 894 } 895 896 private void slideInToNewKey(final Key newKey, final int x, final int y, final long eventTime) { 897 // The pointer has been slid in to the new key, but the finger was not on any keys. 898 // In this case, we must call onPress() to notify that the new key is being pressed. 899 // This onPress call may have changed keyboard layout. Those cases are detected at 900 // {@link #setKeyboard}. In those cases, we should update key according to the 901 // new keyboard layout. 902 Key key = newKey; 903 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 904 key = onMoveKey(x, y); 905 } 906 onMoveToNewKey(key, x, y); 907 startLongPressTimer(key); 908 setPressedKeyGraphics(key, eventTime); 909 } 910 911 private void slideFromOldKeyToNewKey(final Key newKey, final int x, final int y, 912 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 913 // The pointer has been slid in to the new key from the previous key, we must call 914 // onRelease() first to notify that the previous key has been released, then call 915 // onPress() to notify that the new key is being pressed. 916 Key key = newKey; 917 setReleasedKeyGraphics(oldKey); 918 callListenerOnRelease(oldKey, oldKey.mCode, true); 919 startSlidingKeyInput(oldKey); 920 mTimerProxy.cancelKeyTimers(); 921 startRepeatKey(key); 922 if (mIsAllowedSlidingKeyInput) { 923 // This onPress call may have changed keyboard layout. Those cases are detected 924 // at {@link #setKeyboard}. In those cases, we should update key according 925 // to the new keyboard layout. 926 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 927 key = onMoveKey(x, y); 928 } 929 onMoveToNewKey(key, x, y); 930 startLongPressTimer(key); 931 setPressedKeyGraphics(key, eventTime); 932 } else { 933 // HACK: On some devices, quick successive touches may be reported as a sudden 934 // move by touch panel firmware. This hack detects such cases and translates the 935 // move event to successive up and down events. 936 // TODO: Should find a way to balance gesture detection and this hack. 937 if (sNeedsPhantomSuddenMoveEventHack 938 && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { 939 if (DEBUG_MODE) { 940 Log.w(TAG, String.format("[%d] onMoveEvent:" 941 + " phantom sudden move event (distance=%d) is translated to " 942 + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, 943 getDistance(x, y, lastX, lastY), 944 lastX, lastY, Constants.printableCode(oldKey.mCode), 945 x, y, Constants.printableCode(key.mCode))); 946 } 947 // TODO: This should be moved to outside of this nested if-clause? 948 if (ProductionFlag.IS_EXPERIMENTAL) { 949 ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); 950 } 951 onUpEventInternal(eventTime); 952 onDownEventInternal(x, y, eventTime); 953 } 954 // HACK: On some devices, quick successive proximate touches may be reported as 955 // a bogus down-move-up event by touch panel firmware. This hack detects such 956 // cases and breaks these events into separate up and down events. 957 else if (sNeedsProximateBogusDownMoveUpEventHack 958 && sTimeRecorder.isInFastTyping(eventTime) 959 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { 960 if (DEBUG_MODE) { 961 final float keyDiagonal = (float)Math.hypot( 962 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 963 final float radiusRatio = 964 mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) 965 / keyDiagonal; 966 Log.w(TAG, String.format("[%d] onMoveEvent:" 967 + " bogus down-move-up event (raidus=%.2f key diagonal) is " 968 + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", 969 mPointerId, radiusRatio, 970 lastX, lastY, Constants.printableCode(oldKey.mCode), 971 x, y, Constants.printableCode(key.mCode))); 972 } 973 onUpEventInternal(eventTime); 974 onDownEventInternal(x, y, eventTime); 975 } else { 976 // HACK: If there are currently multiple touches, register the key even if 977 // the finger slides off the key. This defends against noise from some 978 // touch panels when there are close multiple touches. 979 // Caveat: When in chording input mode with a modifier key, we don't use 980 // this hack. 981 if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null 982 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 983 if (DEBUG_MODE) { 984 Log.w(TAG, String.format("[%d] onMoveEvent:" 985 + " detected sliding finger while multi touching", 986 mPointerId)); 987 } 988 onUpEvent(x, y, eventTime); 989 mKeyAlreadyProcessed = true; 990 } 991 if (!mIsDetectingGesture) { 992 mKeyAlreadyProcessed = true; 993 } 994 setReleasedKeyGraphics(oldKey); 995 } 996 } 997 } 998 999 private void slideOutFromOldKey(final Key oldKey, final int x, final int y) { 1000 // The pointer has been slid out from the previous key, we must call onRelease() to 1001 // notify that the previous key has been released. 1002 setReleasedKeyGraphics(oldKey); 1003 callListenerOnRelease(oldKey, oldKey.mCode, true); 1004 startSlidingKeyInput(oldKey); 1005 mTimerProxy.cancelLongPressTimer(); 1006 if (mIsAllowedSlidingKeyInput) { 1007 onMoveToNewKey(null, x, y); 1008 } else { 1009 if (!mIsDetectingGesture) { 1010 mKeyAlreadyProcessed = true; 1011 } 1012 } 1013 } 1014 1015 private void onMoveEventInternal(final int x, final int y, final long eventTime) { 1016 final int lastX = mLastX; 1017 final int lastY = mLastY; 1018 final Key oldKey = mCurrentKey; 1019 final Key newKey = onMoveKey(x, y); 1020 1021 if (sShouldHandleGesture) { 1022 // Register move event on gesture tracker. 1023 onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); 1024 if (sInGesture) { 1025 mCurrentKey = null; 1026 setReleasedKeyGraphics(oldKey); 1027 return; 1028 } 1029 } 1030 1031 if (newKey != null) { 1032 if (oldKey == null) { 1033 slideInToNewKey(newKey, x, y, eventTime); 1034 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 1035 slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); 1036 } 1037 } else { 1038 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 1039 slideOutFromOldKey(oldKey, x, y); 1040 } 1041 } 1042 } 1043 1044 public void onUpEvent(final int x, final int y, final long eventTime) { 1045 if (DEBUG_EVENT) { 1046 printTouchEvent("onUpEvent :", x, y, eventTime); 1047 } 1048 1049 final PointerTrackerQueue queue = sPointerTrackerQueue; 1050 if (queue != null) { 1051 if (!sInGesture) { 1052 if (mCurrentKey != null && mCurrentKey.isModifier()) { 1053 // Before processing an up event of modifier key, all pointers already being 1054 // tracked should be released. 1055 queue.releaseAllPointersExcept(this, eventTime); 1056 } else { 1057 queue.releaseAllPointersOlderThan(this, eventTime); 1058 } 1059 } 1060 } 1061 onUpEventInternal(eventTime); 1062 if (queue != null) { 1063 queue.remove(this); 1064 } 1065 } 1066 1067 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 1068 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 1069 // "virtual" up event. 1070 @Override 1071 public void onPhantomUpEvent(final long eventTime) { 1072 if (DEBUG_EVENT) { 1073 printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); 1074 } 1075 onUpEventInternal(eventTime); 1076 mKeyAlreadyProcessed = true; 1077 } 1078 1079 private void onUpEventInternal(final long eventTime) { 1080 mTimerProxy.cancelKeyTimers(); 1081 resetSlidingKeyInput(); 1082 mIsDetectingGesture = false; 1083 final Key currentKey = mCurrentKey; 1084 mCurrentKey = null; 1085 // Release the last pressed key. 1086 setReleasedKeyGraphics(currentKey); 1087 if (mIsShowingMoreKeysPanel) { 1088 mDrawingProxy.dismissMoreKeysPanel(); 1089 mIsShowingMoreKeysPanel = false; 1090 } 1091 1092 if (sInGesture) { 1093 if (currentKey != null) { 1094 callListenerOnRelease(currentKey, currentKey.mCode, true); 1095 } 1096 mayEndBatchInput(eventTime); 1097 return; 1098 } 1099 1100 if (mKeyAlreadyProcessed) { 1101 return; 1102 } 1103 if (currentKey != null && !currentKey.isRepeatable()) { 1104 detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); 1105 } 1106 } 1107 1108 public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { 1109 onLongPressed(); 1110 mIsShowingMoreKeysPanel = true; 1111 onDownEvent(x, y, SystemClock.uptimeMillis(), handler); 1112 } 1113 1114 public void onLongPressed() { 1115 mKeyAlreadyProcessed = true; 1116 setReleasedKeyGraphics(mCurrentKey); 1117 final PointerTrackerQueue queue = sPointerTrackerQueue; 1118 if (queue != null) { 1119 queue.remove(this); 1120 } 1121 } 1122 1123 public void onCancelEvent(final int x, final int y, final long eventTime) { 1124 if (DEBUG_EVENT) { 1125 printTouchEvent("onCancelEvt:", x, y, eventTime); 1126 } 1127 1128 final PointerTrackerQueue queue = sPointerTrackerQueue; 1129 if (queue != null) { 1130 queue.releaseAllPointersExcept(this, eventTime); 1131 queue.remove(this); 1132 } 1133 onCancelEventInternal(); 1134 } 1135 1136 private void onCancelEventInternal() { 1137 mTimerProxy.cancelKeyTimers(); 1138 setReleasedKeyGraphics(mCurrentKey); 1139 resetSlidingKeyInput(); 1140 if (mIsShowingMoreKeysPanel) { 1141 mDrawingProxy.dismissMoreKeysPanel(); 1142 mIsShowingMoreKeysPanel = false; 1143 } 1144 } 1145 1146 private void startRepeatKey(final Key key) { 1147 if (key != null && key.isRepeatable() && !sInGesture) { 1148 onRegisterKey(key); 1149 mTimerProxy.startKeyRepeatTimer(this); 1150 } 1151 } 1152 1153 public void onRegisterKey(final Key key) { 1154 if (key != null) { 1155 detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); 1156 mTimerProxy.startTypingStateTimer(key); 1157 } 1158 } 1159 1160 private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, 1161 final Key newKey) { 1162 if (mKeyDetector == null) { 1163 throw new NullPointerException("keyboard and/or key detector not set"); 1164 } 1165 final Key curKey = mCurrentKey; 1166 if (newKey == curKey) { 1167 return false; 1168 } else if (curKey != null) { 1169 final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( 1170 mIsInSlidingKeyInputFromModifier); 1171 final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); 1172 if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { 1173 if (DEBUG_MODE) { 1174 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) 1175 / mKeyboard.mMostCommonKeyWidth; 1176 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1177 +" %.2f key width from key edge", 1178 mPointerId, distanceToEdgeRatio)); 1179 } 1180 return true; 1181 } 1182 if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput 1183 && sTimeRecorder.isInFastTyping(eventTime) 1184 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { 1185 if (DEBUG_MODE) { 1186 final float keyDiagonal = (float)Math.hypot( 1187 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1188 final float lengthFromDownRatio = 1189 mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; 1190 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1191 + " %.2f key diagonal from virtual down point", 1192 mPointerId, lengthFromDownRatio)); 1193 } 1194 return true; 1195 } 1196 return false; 1197 } else { // curKey == null && newKey != null 1198 return true; 1199 } 1200 } 1201 1202 private void startLongPressTimer(final Key key) { 1203 if (key != null && key.isLongPressEnabled() && !sInGesture) { 1204 mTimerProxy.startLongPressTimer(this); 1205 } 1206 } 1207 1208 private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { 1209 if (key == null) { 1210 callListenerOnCancelInput(); 1211 return; 1212 } 1213 1214 final int code = key.mCode; 1215 callListenerOnCodeInput(key, code, x, y, eventTime); 1216 callListenerOnRelease(key, code, false); 1217 } 1218 1219 private void printTouchEvent(final String title, final int x, final int y, 1220 final long eventTime) { 1221 final Key key = mKeyDetector.detectHitKey(x, y); 1222 final String code = KeyDetector.printableCode(key); 1223 Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, 1224 (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code)); 1225 } 1226} 1227