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