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