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