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