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