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