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