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.DrawingProxy; 29 import com.android.inputmethod.keyboard.internal.GestureEnabler; 30 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams; 31 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints; 32 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams; 33 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 34 import com.android.inputmethod.keyboard.internal.TimerProxy; 35 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder; 36 import com.android.inputmethod.latin.R; 37 import com.android.inputmethod.latin.common.Constants; 38 import com.android.inputmethod.latin.common.CoordinateUtils; 39 import com.android.inputmethod.latin.common.InputPointers; 40 import com.android.inputmethod.latin.define.DebugFlags; 41 import com.android.inputmethod.latin.settings.Settings; 42 import com.android.inputmethod.latin.utils.ResourceUtils; 43 44 import java.util.ArrayList; 45 46 import javax.annotation.Nonnull; 47 import javax.annotation.Nullable; 48 49 public 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(), true /* withAnimation */); 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, final boolean withAnimation) { 386 if (key == null) { 387 return; 388 } 389 390 sDrawingProxy.onKeyReleased(key, withAnimation); 391 392 if (key.isShift()) { 393 for (final Key shiftKey : mKeyboard.mShiftKeys) { 394 if (shiftKey != key) { 395 sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */); 396 } 397 } 398 } 399 400 if (key.altCodeWhileTyping()) { 401 final int altCode = key.getAltCode(); 402 final Key altKey = mKeyboard.getKey(altCode); 403 if (altKey != null) { 404 sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */); 405 } 406 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 407 if (k != key && k.getAltCode() == altCode) { 408 sDrawingProxy.onKeyReleased(k, false /* withAnimation */); 409 } 410 } 411 } 412 } 413 414 private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 415 if (!sGestureEnabler.shouldHandleGesture()) return false; 416 return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); 417 } 418 419 private void setPressedKeyGraphics(@Nullable final Key key, final long eventTime) { 420 if (key == null) { 421 return; 422 } 423 424 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 425 final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); 426 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 427 if (!needsToUpdateGraphics) { 428 return; 429 } 430 431 final boolean noKeyPreview = sInGesture || needsToSuppressKeyPreviewPopup(eventTime); 432 sDrawingProxy.onKeyPressed(key, !noKeyPreview); 433 434 if (key.isShift()) { 435 for (final Key shiftKey : mKeyboard.mShiftKeys) { 436 if (shiftKey != key) { 437 sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */); 438 } 439 } 440 } 441 442 if (altersCode) { 443 final int altCode = key.getAltCode(); 444 final Key altKey = mKeyboard.getKey(altCode); 445 if (altKey != null) { 446 sDrawingProxy.onKeyPressed(altKey, false /* withPreview */); 447 } 448 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 449 if (k != key && k.getAltCode() == altCode) { 450 sDrawingProxy.onKeyPressed(k, false /* withPreview */); 451 } 452 } 453 } 454 } 455 456 public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() { 457 return mGestureStrokeDrawingPoints; 458 } 459 460 public void getLastCoordinates(@Nonnull final int[] outCoords) { 461 CoordinateUtils.set(outCoords, mLastX, mLastY); 462 } 463 464 public long getDownTime() { 465 return mDownTime; 466 } 467 468 public void getDownCoordinates(@Nonnull final int[] outCoords) { 469 CoordinateUtils.copy(outCoords, mDownCoordinates); 470 } 471 472 private Key onDownKey(final int x, final int y, final long eventTime) { 473 mDownTime = eventTime; 474 CoordinateUtils.set(mDownCoordinates, x, y); 475 mBogusMoveEventDetector.onDownKey(); 476 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 477 } 478 479 private static int getDistance(final int x1, final int y1, final int x2, final int y2) { 480 return (int)Math.hypot(x1 - x2, y1 - y2); 481 } 482 483 private Key onMoveKeyInternal(final int x, final int y) { 484 mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); 485 mLastX = x; 486 mLastY = y; 487 return mKeyDetector.detectHitKey(x, y); 488 } 489 490 private Key onMoveKey(final int x, final int y) { 491 return onMoveKeyInternal(x, y); 492 } 493 494 private Key onMoveToNewKey(final Key newKey, final int x, final int y) { 495 mCurrentKey = newKey; 496 mKeyX = x; 497 mKeyY = y; 498 return newKey; 499 } 500 501 /* package */ static int getActivePointerTrackerCount() { 502 return sPointerTrackerQueue.size(); 503 } 504 505 private boolean isOldestTrackerInQueue() { 506 return sPointerTrackerQueue.getOldestElement() == this; 507 } 508 509 // Implements {@link BatchInputArbiterListener}. 510 @Override 511 public void onStartBatchInput() { 512 if (DEBUG_LISTENER) { 513 Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); 514 } 515 sListener.onStartBatchInput(); 516 dismissAllMoreKeysPanels(); 517 sTimerProxy.cancelLongPressTimersOf(this); 518 } 519 520 private void showGestureTrail() { 521 if (mIsTrackingForActionDisabled) { 522 return; 523 } 524 // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. 525 sDrawingProxy.showGestureTrail( 526 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); 527 } 528 529 public void updateBatchInputByTimer(final long syntheticMoveEventTime) { 530 mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this); 531 } 532 533 // Implements {@link BatchInputArbiterListener}. 534 @Override 535 public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) { 536 if (DEBUG_LISTENER) { 537 Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId, 538 aggregatedPointers.getPointerSize())); 539 } 540 sListener.onUpdateBatchInput(aggregatedPointers); 541 } 542 543 // Implements {@link BatchInputArbiterListener}. 544 @Override 545 public void onStartUpdateBatchInputTimer() { 546 sTimerProxy.startUpdateBatchInputTimer(this); 547 } 548 549 // Implements {@link BatchInputArbiterListener}. 550 @Override 551 public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) { 552 sTypingTimeRecorder.onEndBatchInput(eventTime); 553 sTimerProxy.cancelAllUpdateBatchInputTimers(); 554 if (mIsTrackingForActionDisabled) { 555 return; 556 } 557 if (DEBUG_LISTENER) { 558 Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", 559 mPointerId, aggregatedPointers.getPointerSize())); 560 } 561 sListener.onEndBatchInput(aggregatedPointers); 562 } 563 564 private void cancelBatchInput() { 565 cancelAllPointerTrackers(); 566 mIsDetectingGesture = false; 567 if (!sInGesture) { 568 return; 569 } 570 sInGesture = false; 571 if (DEBUG_LISTENER) { 572 Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId)); 573 } 574 sListener.onCancelBatchInput(); 575 } 576 577 public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) { 578 final int action = me.getActionMasked(); 579 final long eventTime = me.getEventTime(); 580 if (action == MotionEvent.ACTION_MOVE) { 581 // When this pointer is the only active pointer and is showing a more keys panel, 582 // we should ignore other pointers' motion event. 583 final boolean shouldIgnoreOtherPointers = 584 isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1; 585 final int pointerCount = me.getPointerCount(); 586 for (int index = 0; index < pointerCount; index++) { 587 final int id = me.getPointerId(index); 588 if (shouldIgnoreOtherPointers && id != mPointerId) { 589 continue; 590 } 591 final int x = (int)me.getX(index); 592 final int y = (int)me.getY(index); 593 final PointerTracker tracker = getPointerTracker(id); 594 tracker.onMoveEvent(x, y, eventTime, me); 595 } 596 return; 597 } 598 final int index = me.getActionIndex(); 599 final int x = (int)me.getX(index); 600 final int y = (int)me.getY(index); 601 switch (action) { 602 case MotionEvent.ACTION_DOWN: 603 case MotionEvent.ACTION_POINTER_DOWN: 604 onDownEvent(x, y, eventTime, keyDetector); 605 break; 606 case MotionEvent.ACTION_UP: 607 case MotionEvent.ACTION_POINTER_UP: 608 onUpEvent(x, y, eventTime); 609 break; 610 case MotionEvent.ACTION_CANCEL: 611 onCancelEvent(x, y, eventTime); 612 break; 613 } 614 } 615 616 private void onDownEvent(final int x, final int y, final long eventTime, 617 final KeyDetector keyDetector) { 618 setKeyDetectorInner(keyDetector); 619 if (DEBUG_EVENT) { 620 printTouchEvent("onDownEvent:", x, y, eventTime); 621 } 622 // Naive up-to-down noise filter. 623 final long deltaT = eventTime - mUpTime; 624 if (deltaT < sParams.mTouchNoiseThresholdTime) { 625 final int distance = getDistance(x, y, mLastX, mLastY); 626 if (distance < sParams.mTouchNoiseThresholdDistance) { 627 if (DEBUG_MODE) 628 Log.w(TAG, String.format("[%d] onDownEvent:" 629 + " ignore potential noise: time=%d distance=%d", 630 mPointerId, deltaT, distance)); 631 cancelTrackingForAction(); 632 return; 633 } 634 } 635 636 final Key key = getKeyOn(x, y); 637 mBogusMoveEventDetector.onActualDownEvent(x, y); 638 if (key != null && key.isModifier()) { 639 // Before processing a down event of modifier key, all pointers already being 640 // tracked should be released. 641 sPointerTrackerQueue.releaseAllPointers(eventTime); 642 } 643 sPointerTrackerQueue.add(this); 644 onDownEventInternal(x, y, eventTime); 645 if (!sGestureEnabler.shouldHandleGesture()) { 646 return; 647 } 648 // A gesture should start only from a non-modifier key. Note that the gesture detection is 649 // disabled when the key is repeating. 650 mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() 651 && key != null && !key.isModifier(); 652 if (mIsDetectingGesture) { 653 mBatchInputArbiter.addDownEventPoint(x, y, eventTime, 654 sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount()); 655 mGestureStrokeDrawingPoints.onDownEvent( 656 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); 657 } 658 } 659 660 /* package */ boolean isShowingMoreKeysPanel() { 661 return (mMoreKeysPanel != null); 662 } 663 664 private void dismissMoreKeysPanel() { 665 if (isShowingMoreKeysPanel()) { 666 mMoreKeysPanel.dismissMoreKeysPanel(); 667 mMoreKeysPanel = null; 668 } 669 } 670 671 private void onDownEventInternal(final int x, final int y, final long eventTime) { 672 Key key = onDownKey(x, y, eventTime); 673 // Key selection by dragging finger is allowed when 1) key selection by dragging finger is 674 // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this 675 // pointer's KeyDetector always allows key selection by dragging finger, such as 676 // {@link MoreKeysKeyboard}. 677 mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger 678 || (key != null && key.isModifier()) 679 || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger(); 680 mKeyboardLayoutHasBeenChanged = false; 681 mIsTrackingForActionDisabled = false; 682 resetKeySelectionByDraggingFinger(); 683 if (key != null) { 684 // This onPress call may have changed keyboard layout. Those cases are detected at 685 // {@link #setKeyboard}. In those cases, we should update key according to the new 686 // keyboard layout. 687 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 688 key = onDownKey(x, y, eventTime); 689 } 690 691 startRepeatKey(key); 692 startLongPressTimer(key); 693 setPressedKeyGraphics(key, eventTime); 694 } 695 } 696 697 private void startKeySelectionByDraggingFinger(final Key key) { 698 if (!mIsInDraggingFinger) { 699 mIsInSlidingKeyInput = key.isModifier(); 700 } 701 mIsInDraggingFinger = true; 702 } 703 704 private void resetKeySelectionByDraggingFinger() { 705 mIsInDraggingFinger = false; 706 mIsInSlidingKeyInput = false; 707 sDrawingProxy.showSlidingKeyInputPreview(null /* tracker */); 708 } 709 710 private void onGestureMoveEvent(final int x, final int y, final long eventTime, 711 final boolean isMajorEvent, final Key key) { 712 if (!mIsDetectingGesture) { 713 return; 714 } 715 final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint( 716 x, y, eventTime, isMajorEvent, this); 717 // If the move event goes out from valid batch input area, cancel batch input. 718 if (!onValidArea) { 719 cancelBatchInput(); 720 return; 721 } 722 mGestureStrokeDrawingPoints.onMoveEvent( 723 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); 724 // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However, 725 // the gestured touch points are still being recorded in case the panel is dismissed. 726 if (isShowingMoreKeysPanel()) { 727 return; 728 } 729 if (!sInGesture && key != null && Character.isLetter(key.getCode()) 730 && mBatchInputArbiter.mayStartBatchInput(this)) { 731 sInGesture = true; 732 } 733 if (sInGesture) { 734 if (key != null) { 735 mBatchInputArbiter.updateBatchInput(eventTime, this); 736 } 737 showGestureTrail(); 738 } 739 } 740 741 private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { 742 if (DEBUG_MOVE_EVENT) { 743 printTouchEvent("onMoveEvent:", x, y, eventTime); 744 } 745 if (mIsTrackingForActionDisabled) { 746 return; 747 } 748 749 if (sGestureEnabler.shouldHandleGesture() && me != null) { 750 // Add historical points to gesture path. 751 final int pointerIndex = me.findPointerIndex(mPointerId); 752 final int historicalSize = me.getHistorySize(); 753 for (int h = 0; h < historicalSize; h++) { 754 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 755 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 756 final long historicalTime = me.getHistoricalEventTime(h); 757 onGestureMoveEvent(historicalX, historicalY, historicalTime, 758 false /* isMajorEvent */, null); 759 } 760 } 761 762 if (isShowingMoreKeysPanel()) { 763 final int translatedX = mMoreKeysPanel.translateX(x); 764 final int translatedY = mMoreKeysPanel.translateY(y); 765 mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); 766 onMoveKey(x, y); 767 if (mIsInSlidingKeyInput) { 768 sDrawingProxy.showSlidingKeyInputPreview(this); 769 } 770 return; 771 } 772 onMoveEventInternal(x, y, eventTime); 773 } 774 775 private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y, 776 final long eventTime) { 777 // This onPress call may have changed keyboard layout. Those cases are detected 778 // at {@link #setKeyboard}. In those cases, we should update key according 779 // to the new keyboard layout. 780 Key key = newKey; 781 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 782 key = onMoveKey(x, y); 783 } 784 onMoveToNewKey(key, x, y); 785 if (mIsTrackingForActionDisabled) { 786 return; 787 } 788 startLongPressTimer(key); 789 setPressedKeyGraphics(key, eventTime); 790 } 791 792 private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, 793 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 794 if (DEBUG_MODE) { 795 Log.w(TAG, String.format("[%d] onMoveEvent:" 796 + " phantom sudden move event (distance=%d) is translated to " 797 + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, 798 getDistance(x, y, lastX, lastY), 799 lastX, lastY, Constants.printableCode(oldKey.getCode()), 800 x, y, Constants.printableCode(key.getCode()))); 801 } 802 onUpEventInternal(x, y, eventTime); 803 onDownEventInternal(x, y, eventTime); 804 } 805 806 private void processProximateBogusDownMoveUpEventHack(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 final float keyDiagonal = (float)Math.hypot( 810 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 811 final float radiusRatio = 812 mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) 813 / keyDiagonal; 814 Log.w(TAG, String.format("[%d] onMoveEvent:" 815 + " bogus down-move-up event (raidus=%.2f key diagonal) is " 816 + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", 817 mPointerId, radiusRatio, 818 lastX, lastY, Constants.printableCode(oldKey.getCode()), 819 x, y, Constants.printableCode(key.getCode()))); 820 } 821 onUpEventInternal(x, y, eventTime); 822 onDownEventInternal(x, y, eventTime); 823 } 824 825 private void processDraggingFingerOutFromOldKey(final Key oldKey) { 826 setReleasedKeyGraphics(oldKey, true /* withAnimation */); 827 callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */); 828 startKeySelectionByDraggingFinger(oldKey); 829 sTimerProxy.cancelKeyTimersOf(this); 830 } 831 832 private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y, 833 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 834 // The pointer has been slid in to the new key from the previous key, we must call 835 // onRelease() first to notify that the previous key has been released, then call 836 // onPress() to notify that the new key is being pressed. 837 processDraggingFingerOutFromOldKey(oldKey); 838 startRepeatKey(key); 839 if (mIsAllowedDraggingFinger) { 840 processDraggingFingerInToNewKey(key, x, y, eventTime); 841 } 842 // HACK: On some devices, quick successive touches may be reported as a sudden move by 843 // touch panel firmware. This hack detects such cases and translates the move event to 844 // successive up and down events. 845 // TODO: Should find a way to balance gesture detection and this hack. 846 else if (sNeedsPhantomSuddenMoveEventHack 847 && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) { 848 processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); 849 } 850 // HACK: On some devices, quick successive proximate touches may be reported as a bogus 851 // down-move-up event by touch panel firmware. This hack detects such cases and breaks 852 // these events into separate up and down events. 853 else if (sTypingTimeRecorder.isInFastTyping(eventTime) 854 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { 855 processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); 856 } 857 // HACK: If there are currently multiple touches, register the key even if the finger 858 // slides off the key. This defends against noise from some touch panels when there are 859 // close multiple touches. 860 // Caveat: When in chording input mode with a modifier key, we don't use this hack. 861 else if (getActivePointerTrackerCount() > 1 862 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 863 if (DEBUG_MODE) { 864 Log.w(TAG, String.format("[%d] onMoveEvent:" 865 + " detected sliding finger while multi touching", mPointerId)); 866 } 867 onUpEvent(x, y, eventTime); 868 cancelTrackingForAction(); 869 setReleasedKeyGraphics(oldKey, true /* withAnimation */); 870 } else { 871 if (!mIsDetectingGesture) { 872 cancelTrackingForAction(); 873 } 874 setReleasedKeyGraphics(oldKey, true /* withAnimation */); 875 } 876 } 877 878 private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) { 879 // The pointer has been slid out from the previous key, we must call onRelease() to 880 // notify that the previous key has been released. 881 processDraggingFingerOutFromOldKey(oldKey); 882 if (mIsAllowedDraggingFinger) { 883 onMoveToNewKey(null, x, y); 884 } else { 885 if (!mIsDetectingGesture) { 886 cancelTrackingForAction(); 887 } 888 } 889 } 890 891 private void onMoveEventInternal(final int x, final int y, final long eventTime) { 892 final int lastX = mLastX; 893 final int lastY = mLastY; 894 final Key oldKey = mCurrentKey; 895 final Key newKey = onMoveKey(x, y); 896 897 if (sGestureEnabler.shouldHandleGesture()) { 898 // Register move event on gesture tracker. 899 onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); 900 if (sInGesture) { 901 mCurrentKey = null; 902 setReleasedKeyGraphics(oldKey, true /* withAnimation */); 903 return; 904 } 905 } 906 907 if (newKey != null) { 908 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 909 dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); 910 } else if (oldKey == null) { 911 // The pointer has been slid in to the new key, but the finger was not on any keys. 912 // In this case, we must call onPress() to notify that the new key is being pressed. 913 processDraggingFingerInToNewKey(newKey, x, y, eventTime); 914 } 915 } else { // newKey == null 916 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 917 dragFingerOutFromOldKey(oldKey, x, y); 918 } 919 } 920 if (mIsInSlidingKeyInput) { 921 sDrawingProxy.showSlidingKeyInputPreview(this); 922 } 923 } 924 925 private void onUpEvent(final int x, final int y, final long eventTime) { 926 if (DEBUG_EVENT) { 927 printTouchEvent("onUpEvent :", x, y, eventTime); 928 } 929 930 sTimerProxy.cancelUpdateBatchInputTimer(this); 931 if (!sInGesture) { 932 if (mCurrentKey != null && mCurrentKey.isModifier()) { 933 // Before processing an up event of modifier key, all pointers already being 934 // tracked should be released. 935 sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); 936 } else { 937 sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); 938 } 939 } 940 onUpEventInternal(x, y, eventTime); 941 sPointerTrackerQueue.remove(this); 942 } 943 944 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 945 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 946 // "virtual" up event. 947 @Override 948 public void onPhantomUpEvent(final long eventTime) { 949 if (DEBUG_EVENT) { 950 printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime); 951 } 952 onUpEventInternal(mLastX, mLastY, eventTime); 953 cancelTrackingForAction(); 954 } 955 956 private void onUpEventInternal(final int x, final int y, final long eventTime) { 957 sTimerProxy.cancelKeyTimersOf(this); 958 final boolean isInDraggingFinger = mIsInDraggingFinger; 959 final boolean isInSlidingKeyInput = mIsInSlidingKeyInput; 960 resetKeySelectionByDraggingFinger(); 961 mIsDetectingGesture = false; 962 final Key currentKey = mCurrentKey; 963 mCurrentKey = null; 964 final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode; 965 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 966 // Release the last pressed key. 967 setReleasedKeyGraphics(currentKey, true /* withAnimation */); 968 969 if (isShowingMoreKeysPanel()) { 970 if (!mIsTrackingForActionDisabled) { 971 final int translatedX = mMoreKeysPanel.translateX(x); 972 final int translatedY = mMoreKeysPanel.translateY(y); 973 mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); 974 } 975 dismissMoreKeysPanel(); 976 return; 977 } 978 979 if (sInGesture) { 980 if (currentKey != null) { 981 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */); 982 } 983 if (mBatchInputArbiter.mayEndBatchInput( 984 eventTime, getActivePointerTrackerCount(), this)) { 985 sInGesture = false; 986 } 987 showGestureTrail(); 988 return; 989 } 990 991 if (mIsTrackingForActionDisabled) { 992 return; 993 } 994 if (currentKey != null && currentKey.isRepeatable() 995 && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) { 996 return; 997 } 998 detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); 999 if (isInSlidingKeyInput) { 1000 callListenerOnFinishSlidingInput(); 1001 } 1002 } 1003 1004 @Override 1005 public void cancelTrackingForAction() { 1006 if (isShowingMoreKeysPanel()) { 1007 return; 1008 } 1009 mIsTrackingForActionDisabled = true; 1010 } 1011 1012 public boolean isInOperation() { 1013 return !mIsTrackingForActionDisabled; 1014 } 1015 1016 public void onLongPressed() { 1017 sTimerProxy.cancelLongPressTimersOf(this); 1018 if (isShowingMoreKeysPanel()) { 1019 return; 1020 } 1021 final Key key = getKey(); 1022 if (key == null) { 1023 return; 1024 } 1025 if (key.hasNoPanelAutoMoreKey()) { 1026 cancelKeyTracking(); 1027 final int moreKeyCode = key.getMoreKeys()[0].mCode; 1028 sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */); 1029 sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, 1030 Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); 1031 sListener.onReleaseKey(moreKeyCode, false /* withSliding */); 1032 return; 1033 } 1034 final int code = key.getCode(); 1035 if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { 1036 // Long pressing the space key invokes IME switcher dialog. 1037 if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { 1038 cancelKeyTracking(); 1039 sListener.onReleaseKey(code, false /* withSliding */); 1040 return; 1041 } 1042 } 1043 1044 setReleasedKeyGraphics(key, false /* withAnimation */); 1045 final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this); 1046 if (moreKeysPanel == null) { 1047 return; 1048 } 1049 final int translatedX = moreKeysPanel.translateX(mLastX); 1050 final int translatedY = moreKeysPanel.translateY(mLastY); 1051 moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); 1052 mMoreKeysPanel = moreKeysPanel; 1053 } 1054 1055 private void cancelKeyTracking() { 1056 resetKeySelectionByDraggingFinger(); 1057 cancelTrackingForAction(); 1058 setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); 1059 sPointerTrackerQueue.remove(this); 1060 } 1061 1062 private void onCancelEvent(final int x, final int y, final long eventTime) { 1063 if (DEBUG_EVENT) { 1064 printTouchEvent("onCancelEvt:", x, y, eventTime); 1065 } 1066 1067 cancelBatchInput(); 1068 cancelAllPointerTrackers(); 1069 sPointerTrackerQueue.releaseAllPointers(eventTime); 1070 onCancelEventInternal(); 1071 } 1072 1073 private void onCancelEventInternal() { 1074 sTimerProxy.cancelKeyTimersOf(this); 1075 setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); 1076 resetKeySelectionByDraggingFinger(); 1077 dismissMoreKeysPanel(); 1078 } 1079 1080 private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, 1081 final Key newKey) { 1082 final Key curKey = mCurrentKey; 1083 if (newKey == curKey) { 1084 return false; 1085 } 1086 if (curKey == null /* && newKey != null */) { 1087 return true; 1088 } 1089 // Here curKey points to the different key from newKey. 1090 final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( 1091 mIsInSlidingKeyInput); 1092 final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); 1093 if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { 1094 if (DEBUG_MODE) { 1095 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) 1096 / mKeyboard.mMostCommonKeyWidth; 1097 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1098 +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); 1099 } 1100 return true; 1101 } 1102 if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime) 1103 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { 1104 if (DEBUG_MODE) { 1105 final float keyDiagonal = (float)Math.hypot( 1106 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1107 final float lengthFromDownRatio = 1108 mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal; 1109 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1110 + " %.2f key diagonal from virtual down point", 1111 mPointerId, lengthFromDownRatio)); 1112 } 1113 return true; 1114 } 1115 return false; 1116 } 1117 1118 private void startLongPressTimer(final Key key) { 1119 // Note that we need to cancel all active long press shift key timers if any whenever we 1120 // start a new long press timer for both non-shift and shift keys. 1121 sTimerProxy.cancelLongPressShiftKeyTimer(); 1122 if (sInGesture) return; 1123 if (key == null) return; 1124 if (!key.isLongPressEnabled()) return; 1125 // Caveat: Please note that isLongPressEnabled() can be true even if the current key 1126 // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger 1127 // mode, we will disable long press timer of such key. 1128 // We always need to start the long press timer if the key has its more keys regardless of 1129 // whether or not we are in the dragging finger mode. 1130 if (mIsInDraggingFinger && key.getMoreKeys() == null) return; 1131 1132 final int delay = getLongPressTimeout(key.getCode()); 1133 if (delay <= 0) return; 1134 sTimerProxy.startLongPressTimerOf(this, delay); 1135 } 1136 1137 private int getLongPressTimeout(final int code) { 1138 if (code == Constants.CODE_SHIFT) { 1139 return sParams.mLongPressShiftLockTimeout; 1140 } 1141 final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; 1142 if (mIsInSlidingKeyInput) { 1143 // We use longer timeout for sliding finger input started from the modifier key. 1144 return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; 1145 } 1146 return longpressTimeout; 1147 } 1148 1149 private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { 1150 if (key == null) { 1151 callListenerOnCancelInput(); 1152 return; 1153 } 1154 1155 final int code = key.getCode(); 1156 callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */); 1157 callListenerOnRelease(key, code, false /* withSliding */); 1158 } 1159 1160 private void startRepeatKey(final Key key) { 1161 if (sInGesture) return; 1162 if (key == null) return; 1163 if (!key.isRepeatable()) return; 1164 // Don't start key repeat when we are in the dragging finger mode. 1165 if (mIsInDraggingFinger) return; 1166 final int startRepeatCount = 1; 1167 startKeyRepeatTimer(startRepeatCount); 1168 } 1169 1170 public void onKeyRepeat(final int code, final int repeatCount) { 1171 final Key key = getKey(); 1172 if (key == null || key.getCode() != code) { 1173 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 1174 return; 1175 } 1176 mCurrentRepeatingKeyCode = code; 1177 mIsDetectingGesture = false; 1178 final int nextRepeatCount = repeatCount + 1; 1179 startKeyRepeatTimer(nextRepeatCount); 1180 callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); 1181 callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(), 1182 true /* isKeyRepeat */); 1183 } 1184 1185 private void startKeyRepeatTimer(final int repeatCount) { 1186 final int delay = 1187 (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval; 1188 sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay); 1189 } 1190 1191 private void printTouchEvent(final String title, final int x, final int y, 1192 final long eventTime) { 1193 final Key key = mKeyDetector.detectHitKey(x, y); 1194 final String code = (key == null ? "none" : Constants.printableCode(key.getCode())); 1195 Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, 1196 (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code)); 1197 } 1198 } 1199