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