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