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