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