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