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