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.Resources; 20 import android.content.res.TypedArray; 21 import android.os.SystemClock; 22 import android.util.DisplayMetrics; 23 import android.util.Log; 24 import android.view.MotionEvent; 25 26 import com.android.inputmethod.accessibility.AccessibilityUtils; 27 import com.android.inputmethod.keyboard.internal.GestureStroke; 28 import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams; 29 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; 30 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams; 31 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 32 import com.android.inputmethod.latin.Constants; 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.latin.settings.Settings; 38 import com.android.inputmethod.latin.utils.CollectionUtils; 39 import com.android.inputmethod.latin.utils.CoordinateUtils; 40 import com.android.inputmethod.latin.utils.ResourceUtils; 41 import com.android.inputmethod.research.ResearchLogger; 42 43 import java.util.ArrayList; 44 45 public 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 PointerTracke 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 repeatCount, 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 repeatCount, 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 // if not a NOT_A_CODE, the key of this code is repeating 350 private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 351 352 // true if a sliding key input is allowed. 353 private boolean mIsAllowedSlidingKeyInput; 354 355 private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; 356 357 private static final int SMALL_TABLET_SMALLEST_WIDTH = 600; // dp 358 private static final int LARGE_TABLET_SMALLEST_WIDTH = 768; // dp 359 360 private static boolean needsProximateBogusDownMoveUpEventHack(final Resources res) { 361 // The proximate bogus down move up event hack is needed for a device such like, 362 // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi. 363 // Though it seems odd to use screen density as criteria of the quality of the touch 364 // screen, the small table that has a less density screen than hdpi most likely has been 365 // made with the touch screen that needs the hack. 366 final int sw = res.getConfiguration().smallestScreenWidthDp; 367 final boolean isLargeTablet = (sw >= LARGE_TABLET_SMALLEST_WIDTH); 368 final boolean isSmallTablet = 369 (sw >= SMALL_TABLET_SMALLEST_WIDTH && sw < LARGE_TABLET_SMALLEST_WIDTH); 370 final int densityDpi = res.getDisplayMetrics().densityDpi; 371 final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH); 372 final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen); 373 if (DEBUG_MODE) { 374 Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack 375 + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi); 376 } 377 return needsTheHack; 378 } 379 380 public static void init(final Resources res) { 381 sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean( 382 ResourceUtils.getDeviceOverrideValue( 383 res, R.array.phantom_sudden_move_event_device_list)); 384 sNeedsProximateBogusDownMoveUpEventHack = needsProximateBogusDownMoveUpEventHack(res); 385 sParams = PointerTrackerParams.DEFAULT; 386 sGestureStrokeParams = GestureStrokeParams.DEFAULT; 387 sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT; 388 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 389 } 390 391 public static void setParameters(final TypedArray mainKeyboardViewAttr) { 392 sParams = new PointerTrackerParams(mainKeyboardViewAttr); 393 sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); 394 sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr); 395 sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); 396 } 397 398 private static void updateGestureHandlingMode() { 399 sShouldHandleGesture = sMainDictionaryAvailable 400 && sGestureHandlingEnabledByInputField 401 && sGestureHandlingEnabledByUser 402 && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); 403 } 404 405 // Note that this method is called from a non-UI thread. 406 public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 407 sMainDictionaryAvailable = mainDictionaryAvailable; 408 updateGestureHandlingMode(); 409 } 410 411 public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 412 sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; 413 updateGestureHandlingMode(); 414 } 415 416 public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) { 417 final ArrayList<PointerTracker> trackers = sTrackers; 418 419 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 420 for (int i = trackers.size(); i <= id; i++) { 421 final PointerTracker tracker = new PointerTracker(i, handler); 422 trackers.add(tracker); 423 } 424 425 return trackers.get(id); 426 } 427 428 public static boolean isAnyInSlidingKeyInput() { 429 return sPointerTrackerQueue.isAnyInSlidingKeyInput(); 430 } 431 432 public static void cancelAllPointerTrackers() { 433 sPointerTrackerQueue.cancelAllPointerTrackers(); 434 } 435 436 public static void setKeyboardActionListener(final KeyboardActionListener listener) { 437 final int trackersSize = sTrackers.size(); 438 for (int i = 0; i < trackersSize; ++i) { 439 final PointerTracker tracker = sTrackers.get(i); 440 tracker.mListener = listener; 441 } 442 } 443 444 public static void setKeyDetector(final KeyDetector keyDetector) { 445 final int trackersSize = sTrackers.size(); 446 for (int i = 0; i < trackersSize; ++i) { 447 final PointerTracker tracker = sTrackers.get(i); 448 tracker.setKeyDetectorInner(keyDetector); 449 // Mark that keyboard layout has been changed. 450 tracker.mKeyboardLayoutHasBeenChanged = true; 451 } 452 final Keyboard keyboard = keyDetector.getKeyboard(); 453 sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput(); 454 updateGestureHandlingMode(); 455 } 456 457 public static void setReleasedKeyGraphicsToAllKeys() { 458 final int trackersSize = sTrackers.size(); 459 for (int i = 0; i < trackersSize; ++i) { 460 final PointerTracker tracker = sTrackers.get(i); 461 tracker.setReleasedKeyGraphics(tracker.mCurrentKey); 462 } 463 } 464 465 public static void dismissAllMoreKeysPanels() { 466 final int trackersSize = sTrackers.size(); 467 for (int i = 0; i < trackersSize; ++i) { 468 final PointerTracker tracker = sTrackers.get(i); 469 if (tracker.isShowingMoreKeysPanel()) { 470 tracker.mMoreKeysPanel.dismissMoreKeysPanel(); 471 tracker.mMoreKeysPanel = null; 472 } 473 } 474 } 475 476 private PointerTracker(final int id, final KeyEventHandler handler) { 477 if (handler == null) { 478 throw new NullPointerException(); 479 } 480 mPointerId = id; 481 mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( 482 id, sGestureStrokeParams, sGesturePreviewParams); 483 setKeyEventHandler(handler); 484 } 485 486 private void setKeyEventHandler(final KeyEventHandler handler) { 487 setKeyDetectorInner(handler.getKeyDetector()); 488 mListener = handler.getKeyboardActionListener(); 489 mDrawingProxy = handler.getDrawingProxy(); 490 mTimerProxy = handler.getTimerProxy(); 491 } 492 493 // Returns true if keyboard has been changed by this callback. 494 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, 495 final int repeatCount) { 496 // While gesture input is going on, this method should be a no-operation. But when gesture 497 // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code> 498 // are set to false. To keep this method is a no-operation, 499 // <code>mIsTrackingForActionDisabled</code> should also be taken account of. 500 if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { 501 return false; 502 } 503 final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier(); 504 if (DEBUG_LISTENER) { 505 Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId, 506 KeyDetector.printableCode(key), 507 ignoreModifierKey ? " ignoreModifier" : "", 508 key.isEnabled() ? "" : " disabled", 509 repeatCount > 0 ? " repeatCount=" + repeatCount : "")); 510 } 511 if (ignoreModifierKey) { 512 return false; 513 } 514 if (key.isEnabled()) { 515 mListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1); 516 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 517 mKeyboardLayoutHasBeenChanged = false; 518 mTimerProxy.startTypingStateTimer(key); 519 return keyboardLayoutHasBeenChanged; 520 } 521 return false; 522 } 523 524 // Note that we need primaryCode argument because the keyboard may in shifted state and the 525 // primaryCode is different from {@link Key#mCode}. 526 private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, 527 final int y, final long eventTime) { 528 final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier(); 529 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 530 final int code = altersCode ? key.getAltCode() : primaryCode; 531 if (DEBUG_LISTENER) { 532 final String output = code == Constants.CODE_OUTPUT_TEXT 533 ? key.getOutputText() : Constants.printableCode(code); 534 Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, 535 output, ignoreModifierKey ? " ignoreModifier" : "", 536 altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); 537 } 538 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 539 ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, 540 altersCode, code); 541 } 542 if (ignoreModifierKey) { 543 return; 544 } 545 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 546 if (key.isEnabled() || altersCode) { 547 sTimeRecorder.onCodeInput(code, eventTime); 548 if (code == Constants.CODE_OUTPUT_TEXT) { 549 mListener.onTextInput(key.getOutputText()); 550 } else if (code != Constants.CODE_UNSPECIFIED) { 551 mListener.onCodeInput(code, x, y); 552 } 553 } 554 } 555 556 // Note that we need primaryCode argument because the keyboard may be in shifted state and the 557 // primaryCode is different from {@link Key#mCode}. 558 private void callListenerOnRelease(final Key key, final int primaryCode, 559 final boolean withSliding) { 560 // See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}. 561 if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { 562 return; 563 } 564 final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier(); 565 if (DEBUG_LISTENER) { 566 Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, 567 Constants.printableCode(primaryCode), 568 withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", 569 key.isEnabled() ? "": " disabled")); 570 } 571 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 572 ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, 573 ignoreModifierKey); 574 } 575 if (ignoreModifierKey) { 576 return; 577 } 578 if (key.isEnabled()) { 579 mListener.onReleaseKey(primaryCode, withSliding); 580 } 581 } 582 583 private void callListenerOnFinishSlidingInput() { 584 if (DEBUG_LISTENER) { 585 Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId)); 586 } 587 mListener.onFinishSlidingInput(); 588 } 589 590 private void callListenerOnCancelInput() { 591 if (DEBUG_LISTENER) { 592 Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); 593 } 594 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 595 ResearchLogger.pointerTracker_callListenerOnCancelInput(); 596 } 597 mListener.onCancelInput(); 598 } 599 600 private void setKeyDetectorInner(final KeyDetector keyDetector) { 601 final Keyboard keyboard = keyDetector.getKeyboard(); 602 if (keyDetector == mKeyDetector && keyboard == mKeyboard) { 603 return; 604 } 605 mKeyDetector = keyDetector; 606 mKeyboard = keyDetector.getKeyboard(); 607 final int keyWidth = mKeyboard.mMostCommonKeyWidth; 608 final int keyHeight = mKeyboard.mMostCommonKeyHeight; 609 mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); 610 final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); 611 if (newKey != mCurrentKey) { 612 if (mDrawingProxy != null) { 613 setReleasedKeyGraphics(mCurrentKey); 614 } 615 // Keep {@link #mCurrentKey} that comes from previous keyboard. 616 } 617 mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD); 618 mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight); 619 } 620 621 @Override 622 public boolean isInSlidingKeyInput() { 623 return mIsInSlidingKeyInput; 624 } 625 626 public Key getKey() { 627 return mCurrentKey; 628 } 629 630 @Override 631 public boolean isModifier() { 632 return mCurrentKey != null && mCurrentKey.isModifier(); 633 } 634 635 public Key getKeyOn(final int x, final int y) { 636 return mKeyDetector.detectHitKey(x, y); 637 } 638 639 private void setReleasedKeyGraphics(final Key key) { 640 mDrawingProxy.dismissKeyPreview(this); 641 if (key == null) { 642 return; 643 } 644 645 // Even if the key is disabled, update the key release graphics just in case. 646 updateReleaseKeyGraphics(key); 647 648 if (key.isShift()) { 649 for (final Key shiftKey : mKeyboard.mShiftKeys) { 650 if (shiftKey != key) { 651 updateReleaseKeyGraphics(shiftKey); 652 } 653 } 654 } 655 656 if (key.altCodeWhileTyping()) { 657 final int altCode = key.getAltCode(); 658 final Key altKey = mKeyboard.getKey(altCode); 659 if (altKey != null) { 660 updateReleaseKeyGraphics(altKey); 661 } 662 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 663 if (k != key && k.getAltCode() == altCode) { 664 updateReleaseKeyGraphics(k); 665 } 666 } 667 } 668 } 669 670 private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 671 if (!sShouldHandleGesture) return false; 672 return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); 673 } 674 675 private void setPressedKeyGraphics(final Key key, final long eventTime) { 676 if (key == null) { 677 return; 678 } 679 680 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 681 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 682 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 683 if (!needsToUpdateGraphics) { 684 return; 685 } 686 687 if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) { 688 mDrawingProxy.showKeyPreview(this); 689 } 690 updatePressKeyGraphics(key); 691 692 if (key.isShift()) { 693 for (final Key shiftKey : mKeyboard.mShiftKeys) { 694 if (shiftKey != key) { 695 updatePressKeyGraphics(shiftKey); 696 } 697 } 698 } 699 700 if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { 701 final int altCode = key.getAltCode(); 702 final Key altKey = mKeyboard.getKey(altCode); 703 if (altKey != null) { 704 updatePressKeyGraphics(altKey); 705 } 706 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 707 if (k != key && k.getAltCode() == altCode) { 708 updatePressKeyGraphics(k); 709 } 710 } 711 } 712 } 713 714 private void updateReleaseKeyGraphics(final Key key) { 715 key.onReleased(); 716 mDrawingProxy.invalidateKey(key); 717 } 718 719 private void updatePressKeyGraphics(final Key key) { 720 key.onPressed(); 721 mDrawingProxy.invalidateKey(key); 722 } 723 724 public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { 725 return mGestureStrokeWithPreviewPoints; 726 } 727 728 public void getLastCoordinates(final int[] outCoords) { 729 CoordinateUtils.set(outCoords, mLastX, mLastY); 730 } 731 732 public long getDownTime() { 733 return mDownTime; 734 } 735 736 public void getDownCoordinates(final int[] outCoords) { 737 CoordinateUtils.copy(outCoords, mDownCoordinates); 738 } 739 740 private Key onDownKey(final int x, final int y, final long eventTime) { 741 mDownTime = eventTime; 742 CoordinateUtils.set(mDownCoordinates, x, y); 743 mBogusMoveEventDetector.onDownKey(); 744 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 745 } 746 747 static int getDistance(final int x1, final int y1, final int x2, final int y2) { 748 return (int)Math.hypot(x1 - x2, y1 - y2); 749 } 750 751 private Key onMoveKeyInternal(final int x, final int y) { 752 mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); 753 mLastX = x; 754 mLastY = y; 755 return mKeyDetector.detectHitKey(x, y); 756 } 757 758 private Key onMoveKey(final int x, final int y) { 759 return onMoveKeyInternal(x, y); 760 } 761 762 private Key onMoveToNewKey(final Key newKey, final int x, final int y) { 763 mCurrentKey = newKey; 764 mKeyX = x; 765 mKeyY = y; 766 return newKey; 767 } 768 769 private static int getActivePointerTrackerCount() { 770 return sPointerTrackerQueue.size(); 771 } 772 773 private boolean isOldestTrackerInQueue() { 774 return sPointerTrackerQueue.getOldestElement() == this; 775 } 776 777 private void mayStartBatchInput(final Key key) { 778 if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { 779 return; 780 } 781 if (key == null || !Character.isLetter(key.getCode())) { 782 return; 783 } 784 if (DEBUG_LISTENER) { 785 Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); 786 } 787 sInGesture = true; 788 synchronized (sAggregratedPointers) { 789 sAggregratedPointers.reset(); 790 sLastRecognitionPointSize = 0; 791 sLastRecognitionTime = 0; 792 mListener.onStartBatchInput(); 793 dismissAllMoreKeysPanels(); 794 } 795 mTimerProxy.cancelLongPressTimer(); 796 // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. 797 mDrawingProxy.showGestureTrail( 798 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); 799 } 800 801 public void updateBatchInputByTimer(final long eventTime) { 802 final int gestureTime = (int)(eventTime - sGestureFirstDownTime); 803 mGestureStrokeWithPreviewPoints.duplicateLastPointWith(gestureTime); 804 updateBatchInput(eventTime); 805 } 806 807 private void mayUpdateBatchInput(final long eventTime, final Key key) { 808 if (key != null) { 809 updateBatchInput(eventTime); 810 } 811 if (mIsTrackingForActionDisabled) { 812 return; 813 } 814 // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. 815 mDrawingProxy.showGestureTrail( 816 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); 817 } 818 819 private void updateBatchInput(final long eventTime) { 820 synchronized (sAggregratedPointers) { 821 final GestureStroke stroke = mGestureStrokeWithPreviewPoints; 822 stroke.appendIncrementalBatchPoints(sAggregratedPointers); 823 final int size = sAggregratedPointers.getPointerSize(); 824 if (size > sLastRecognitionPointSize 825 && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { 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 // The listener may change the size of the pointers (when auto-committing 833 // for example), so we need to get the size from the pointers again. 834 sLastRecognitionPointSize = sAggregratedPointers.getPointerSize(); 835 sLastRecognitionTime = eventTime; 836 } 837 } 838 } 839 840 private void mayEndBatchInput(final long eventTime) { 841 synchronized (sAggregratedPointers) { 842 mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); 843 if (getActivePointerTrackerCount() == 1) { 844 sInGesture = false; 845 sTimeRecorder.onEndBatchInput(eventTime); 846 mTimerProxy.cancelAllUpdateBatchInputTimers(); 847 if (!mIsTrackingForActionDisabled) { 848 if (DEBUG_LISTENER) { 849 Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", 850 mPointerId, sAggregratedPointers.getPointerSize())); 851 } 852 mListener.onEndBatchInput(sAggregratedPointers); 853 } 854 } 855 } 856 if (mIsTrackingForActionDisabled) { 857 return; 858 } 859 // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. 860 mDrawingProxy.showGestureTrail( 861 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); 862 } 863 864 private void cancelBatchInput() { 865 cancelAllPointerTrackers(); 866 mIsDetectingGesture = false; 867 if (!sInGesture) { 868 return; 869 } 870 sInGesture = false; 871 if (DEBUG_LISTENER) { 872 Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId)); 873 } 874 mListener.onCancelBatchInput(); 875 } 876 877 public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) { 878 final int action = me.getActionMasked(); 879 final long eventTime = me.getEventTime(); 880 if (action == MotionEvent.ACTION_MOVE) { 881 final int pointerCount = me.getPointerCount(); 882 for (int index = 0; index < pointerCount; index++) { 883 final int id = me.getPointerId(index); 884 final PointerTracker tracker = getPointerTracker(id, handler); 885 final int x = (int)me.getX(index); 886 final int y = (int)me.getY(index); 887 tracker.onMoveEvent(x, y, eventTime, me); 888 } 889 return; 890 } 891 final int index = me.getActionIndex(); 892 final int x = (int)me.getX(index); 893 final int y = (int)me.getY(index); 894 switch (action) { 895 case MotionEvent.ACTION_DOWN: 896 case MotionEvent.ACTION_POINTER_DOWN: 897 onDownEvent(x, y, eventTime, handler); 898 break; 899 case MotionEvent.ACTION_UP: 900 case MotionEvent.ACTION_POINTER_UP: 901 onUpEvent(x, y, eventTime); 902 break; 903 case MotionEvent.ACTION_CANCEL: 904 onCancelEvent(x, y, eventTime); 905 break; 906 } 907 } 908 909 private void onDownEvent(final int x, final int y, final long eventTime, 910 final KeyEventHandler handler) { 911 if (DEBUG_EVENT) { 912 printTouchEvent("onDownEvent:", x, y, eventTime); 913 } 914 setKeyEventHandler(handler); 915 // Naive up-to-down noise filter. 916 final long deltaT = eventTime - mUpTime; 917 if (deltaT < sParams.mTouchNoiseThresholdTime) { 918 final int distance = getDistance(x, y, mLastX, mLastY); 919 if (distance < sParams.mTouchNoiseThresholdDistance) { 920 if (DEBUG_MODE) 921 Log.w(TAG, String.format("[%d] onDownEvent:" 922 + " ignore potential noise: time=%d distance=%d", 923 mPointerId, deltaT, distance)); 924 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 925 ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); 926 } 927 cancelTrackingForAction(); 928 return; 929 } 930 } 931 932 final Key key = getKeyOn(x, y); 933 mBogusMoveEventDetector.onActualDownEvent(x, y); 934 if (key != null && key.isModifier()) { 935 // Before processing a down event of modifier key, all pointers already being 936 // tracked should be released. 937 sPointerTrackerQueue.releaseAllPointers(eventTime); 938 } 939 sPointerTrackerQueue.add(this); 940 onDownEventInternal(x, y, eventTime); 941 if (!sShouldHandleGesture) { 942 return; 943 } 944 // A gesture should start only from a non-modifier key. Note that the gesture detection is 945 // disabled when the key is repeating. 946 mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() 947 && key != null && !key.isModifier(); 948 if (mIsDetectingGesture) { 949 if (getActivePointerTrackerCount() == 1) { 950 sGestureFirstDownTime = eventTime; 951 } 952 mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, 953 sTimeRecorder.getLastLetterTypingTime()); 954 } 955 } 956 957 private boolean isShowingMoreKeysPanel() { 958 return (mMoreKeysPanel != null); 959 } 960 961 private void onDownEventInternal(final int x, final int y, final long eventTime) { 962 Key key = onDownKey(x, y, eventTime); 963 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 964 // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. 965 mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled 966 || (key != null && key.isModifier()) 967 || mKeyDetector.alwaysAllowsSlidingInput(); 968 mKeyboardLayoutHasBeenChanged = false; 969 mIsTrackingForActionDisabled = false; 970 resetSlidingKeyInput(); 971 if (key != null) { 972 // This onPress call may have changed keyboard layout. Those cases are detected at 973 // {@link #setKeyboard}. In those cases, we should update key according to the new 974 // keyboard layout. 975 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 976 key = onDownKey(x, y, eventTime); 977 } 978 979 startRepeatKey(key); 980 startLongPressTimer(key); 981 setPressedKeyGraphics(key, eventTime); 982 } 983 } 984 985 private void startSlidingKeyInput(final Key key) { 986 if (!mIsInSlidingKeyInput) { 987 mIsInSlidingKeyInputFromModifier = key.isModifier(); 988 } 989 mIsInSlidingKeyInput = true; 990 } 991 992 private void resetSlidingKeyInput() { 993 mIsInSlidingKeyInput = false; 994 mIsInSlidingKeyInputFromModifier = false; 995 mDrawingProxy.dismissSlidingKeyInputPreview(); 996 } 997 998 private void onGestureMoveEvent(final int x, final int y, final long eventTime, 999 final boolean isMajorEvent, final Key key) { 1000 final int gestureTime = (int)(eventTime - sGestureFirstDownTime); 1001 if (mIsDetectingGesture) { 1002 final int beforeLength = mGestureStrokeWithPreviewPoints.getLength(); 1003 final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard( 1004 x, y, gestureTime, isMajorEvent); 1005 if (mGestureStrokeWithPreviewPoints.getLength() > beforeLength) { 1006 mTimerProxy.startUpdateBatchInputTimer(this); 1007 } 1008 // If the move event goes out from valid batch input area, cancel batch input. 1009 if (!onValidArea) { 1010 cancelBatchInput(); 1011 return; 1012 } 1013 // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However, 1014 // the gestured touch points are still being recorded in case the panel is dismissed. 1015 if (isShowingMoreKeysPanel()) { 1016 return; 1017 } 1018 mayStartBatchInput(key); 1019 if (sInGesture) { 1020 mayUpdateBatchInput(eventTime, key); 1021 } 1022 } 1023 } 1024 1025 private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { 1026 if (DEBUG_MOVE_EVENT) { 1027 printTouchEvent("onMoveEvent:", x, y, eventTime); 1028 } 1029 if (mIsTrackingForActionDisabled) { 1030 return; 1031 } 1032 1033 if (sShouldHandleGesture && me != null) { 1034 // Add historical points to gesture path. 1035 final int pointerIndex = me.findPointerIndex(mPointerId); 1036 final int historicalSize = me.getHistorySize(); 1037 for (int h = 0; h < historicalSize; h++) { 1038 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 1039 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 1040 final long historicalTime = me.getHistoricalEventTime(h); 1041 onGestureMoveEvent(historicalX, historicalY, historicalTime, 1042 false /* isMajorEvent */, null); 1043 } 1044 } 1045 1046 if (isShowingMoreKeysPanel()) { 1047 final int translatedX = mMoreKeysPanel.translateX(x); 1048 final int translatedY = mMoreKeysPanel.translateY(y); 1049 mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); 1050 onMoveKey(x, y); 1051 if (mIsInSlidingKeyInputFromModifier) { 1052 mDrawingProxy.showSlidingKeyInputPreview(this); 1053 } 1054 return; 1055 } 1056 onMoveEventInternal(x, y, eventTime); 1057 } 1058 1059 private void processSlidingKeyInput(final Key newKey, final int x, final int y, 1060 final long eventTime) { 1061 // This onPress call may have changed keyboard layout. Those cases are detected 1062 // at {@link #setKeyboard}. In those cases, we should update key according 1063 // to the new keyboard layout. 1064 Key key = newKey; 1065 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 1066 key = onMoveKey(x, y); 1067 } 1068 onMoveToNewKey(key, x, y); 1069 if (mIsTrackingForActionDisabled) { 1070 return; 1071 } 1072 startLongPressTimer(key); 1073 setPressedKeyGraphics(key, eventTime); 1074 } 1075 1076 private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, 1077 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 1078 if (DEBUG_MODE) { 1079 Log.w(TAG, String.format("[%d] onMoveEvent:" 1080 + " phantom sudden move event (distance=%d) is translated to " 1081 + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, 1082 getDistance(x, y, lastX, lastY), 1083 lastX, lastY, Constants.printableCode(oldKey.getCode()), 1084 x, y, Constants.printableCode(key.getCode()))); 1085 } 1086 // TODO: This should be moved to outside of this nested if-clause? 1087 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 1088 ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); 1089 } 1090 onUpEventInternal(x, y, eventTime); 1091 onDownEventInternal(x, y, eventTime); 1092 } 1093 1094 private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, 1095 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 1096 if (DEBUG_MODE) { 1097 final float keyDiagonal = (float)Math.hypot( 1098 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1099 final float radiusRatio = 1100 mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) 1101 / keyDiagonal; 1102 Log.w(TAG, String.format("[%d] onMoveEvent:" 1103 + " bogus down-move-up event (raidus=%.2f key diagonal) is " 1104 + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", 1105 mPointerId, radiusRatio, 1106 lastX, lastY, Constants.printableCode(oldKey.getCode()), 1107 x, y, Constants.printableCode(key.getCode()))); 1108 } 1109 onUpEventInternal(x, y, eventTime); 1110 onDownEventInternal(x, y, eventTime); 1111 } 1112 1113 private void processSildeOutFromOldKey(final Key oldKey) { 1114 setReleasedKeyGraphics(oldKey); 1115 callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */); 1116 startSlidingKeyInput(oldKey); 1117 mTimerProxy.cancelKeyTimers(); 1118 } 1119 1120 private void slideFromOldKeyToNewKey(final Key key, final int x, final int y, 1121 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 1122 // The pointer has been slid in to the new key from the previous key, we must call 1123 // onRelease() first to notify that the previous key has been released, then call 1124 // onPress() to notify that the new key is being pressed. 1125 processSildeOutFromOldKey(oldKey); 1126 startRepeatKey(key); 1127 if (mIsAllowedSlidingKeyInput) { 1128 processSlidingKeyInput(key, x, y, eventTime); 1129 } 1130 // HACK: On some devices, quick successive touches may be reported as a sudden move by 1131 // touch panel firmware. This hack detects such cases and translates the move event to 1132 // successive up and down events. 1133 // TODO: Should find a way to balance gesture detection and this hack. 1134 else if (sNeedsPhantomSuddenMoveEventHack 1135 && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { 1136 processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); 1137 } 1138 // HACK: On some devices, quick successive proximate touches may be reported as a bogus 1139 // down-move-up event by touch panel firmware. This hack detects such cases and breaks 1140 // these events into separate up and down events. 1141 else if (sNeedsProximateBogusDownMoveUpEventHack && sTimeRecorder.isInFastTyping(eventTime) 1142 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { 1143 processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); 1144 } 1145 // HACK: If there are currently multiple touches, register the key even if the finger 1146 // slides off the key. This defends against noise from some touch panels when there are 1147 // close multiple touches. 1148 // Caveat: When in chording input mode with a modifier key, we don't use this hack. 1149 else if (getActivePointerTrackerCount() > 1 1150 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 1151 if (DEBUG_MODE) { 1152 Log.w(TAG, String.format("[%d] onMoveEvent:" 1153 + " detected sliding finger while multi touching", mPointerId)); 1154 } 1155 onUpEvent(x, y, eventTime); 1156 cancelTrackingForAction(); 1157 setReleasedKeyGraphics(oldKey); 1158 } else { 1159 if (!mIsDetectingGesture) { 1160 cancelTrackingForAction(); 1161 } 1162 setReleasedKeyGraphics(oldKey); 1163 } 1164 } 1165 1166 private void slideOutFromOldKey(final Key oldKey, final int x, final int y) { 1167 // The pointer has been slid out from the previous key, we must call onRelease() to 1168 // notify that the previous key has been released. 1169 processSildeOutFromOldKey(oldKey); 1170 if (mIsAllowedSlidingKeyInput) { 1171 onMoveToNewKey(null, x, y); 1172 } else { 1173 if (!mIsDetectingGesture) { 1174 cancelTrackingForAction(); 1175 } 1176 } 1177 } 1178 1179 private void onMoveEventInternal(final int x, final int y, final long eventTime) { 1180 final int lastX = mLastX; 1181 final int lastY = mLastY; 1182 final Key oldKey = mCurrentKey; 1183 final Key newKey = onMoveKey(x, y); 1184 1185 if (sShouldHandleGesture) { 1186 // Register move event on gesture tracker. 1187 onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); 1188 if (sInGesture) { 1189 mCurrentKey = null; 1190 setReleasedKeyGraphics(oldKey); 1191 return; 1192 } 1193 } 1194 1195 if (newKey != null) { 1196 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 1197 slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); 1198 } else if (oldKey == null) { 1199 // The pointer has been slid in to the new key, but the finger was not on any keys. 1200 // In this case, we must call onPress() to notify that the new key is being pressed. 1201 processSlidingKeyInput(newKey, x, y, eventTime); 1202 } 1203 } else { // newKey == null 1204 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 1205 slideOutFromOldKey(oldKey, x, y); 1206 } 1207 } 1208 if (mIsInSlidingKeyInputFromModifier) { 1209 mDrawingProxy.showSlidingKeyInputPreview(this); 1210 } 1211 } 1212 1213 private void onUpEvent(final int x, final int y, final long eventTime) { 1214 if (DEBUG_EVENT) { 1215 printTouchEvent("onUpEvent :", x, y, eventTime); 1216 } 1217 1218 mTimerProxy.cancelUpdateBatchInputTimer(this); 1219 if (!sInGesture) { 1220 if (mCurrentKey != null && mCurrentKey.isModifier()) { 1221 // Before processing an up event of modifier key, all pointers already being 1222 // tracked should be released. 1223 sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); 1224 } else { 1225 sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); 1226 } 1227 } 1228 onUpEventInternal(x, y, eventTime); 1229 sPointerTrackerQueue.remove(this); 1230 } 1231 1232 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 1233 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 1234 // "virtual" up event. 1235 @Override 1236 public void onPhantomUpEvent(final long eventTime) { 1237 if (DEBUG_EVENT) { 1238 printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime); 1239 } 1240 if (isShowingMoreKeysPanel()) { 1241 return; 1242 } 1243 onUpEventInternal(mLastX, mLastY, eventTime); 1244 cancelTrackingForAction(); 1245 } 1246 1247 private void onUpEventInternal(final int x, final int y, final long eventTime) { 1248 mTimerProxy.cancelKeyTimers(); 1249 final boolean isInSlidingKeyInput = mIsInSlidingKeyInput; 1250 final boolean isInSlidingKeyInputFromModifier = mIsInSlidingKeyInputFromModifier; 1251 resetSlidingKeyInput(); 1252 mIsDetectingGesture = false; 1253 final Key currentKey = mCurrentKey; 1254 mCurrentKey = null; 1255 final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode; 1256 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 1257 // Release the last pressed key. 1258 setReleasedKeyGraphics(currentKey); 1259 1260 if (isShowingMoreKeysPanel()) { 1261 if (!mIsTrackingForActionDisabled) { 1262 final int translatedX = mMoreKeysPanel.translateX(x); 1263 final int translatedY = mMoreKeysPanel.translateY(y); 1264 mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); 1265 } 1266 mMoreKeysPanel.dismissMoreKeysPanel(); 1267 mMoreKeysPanel = null; 1268 return; 1269 } 1270 1271 if (sInGesture) { 1272 if (currentKey != null) { 1273 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */); 1274 } 1275 mayEndBatchInput(eventTime); 1276 return; 1277 } 1278 1279 if (mIsTrackingForActionDisabled) { 1280 return; 1281 } 1282 if (currentKey != null && currentKey.isRepeatable() 1283 && (currentKey.getCode() == currentRepeatingKeyCode) && !isInSlidingKeyInput) { 1284 return; 1285 } 1286 detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); 1287 if (isInSlidingKeyInputFromModifier) { 1288 callListenerOnFinishSlidingInput(); 1289 } 1290 } 1291 1292 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 1293 setReleasedKeyGraphics(mCurrentKey); 1294 final int translatedX = panel.translateX(mLastX); 1295 final int translatedY = panel.translateY(mLastY); 1296 panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); 1297 mMoreKeysPanel = panel; 1298 } 1299 1300 @Override 1301 public void cancelTrackingForAction() { 1302 if (isShowingMoreKeysPanel()) { 1303 return; 1304 } 1305 mIsTrackingForActionDisabled = true; 1306 } 1307 1308 public void onLongPressed() { 1309 resetSlidingKeyInput(); 1310 cancelTrackingForAction(); 1311 setReleasedKeyGraphics(mCurrentKey); 1312 sPointerTrackerQueue.remove(this); 1313 } 1314 1315 private void onCancelEvent(final int x, final int y, final long eventTime) { 1316 if (DEBUG_EVENT) { 1317 printTouchEvent("onCancelEvt:", x, y, eventTime); 1318 } 1319 1320 cancelBatchInput(); 1321 cancelAllPointerTrackers(); 1322 sPointerTrackerQueue.releaseAllPointers(eventTime); 1323 onCancelEventInternal(); 1324 } 1325 1326 private void onCancelEventInternal() { 1327 mTimerProxy.cancelKeyTimers(); 1328 setReleasedKeyGraphics(mCurrentKey); 1329 resetSlidingKeyInput(); 1330 if (isShowingMoreKeysPanel()) { 1331 mMoreKeysPanel.dismissMoreKeysPanel(); 1332 mMoreKeysPanel = null; 1333 } 1334 } 1335 1336 private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, 1337 final Key newKey) { 1338 if (mKeyDetector == null) { 1339 throw new NullPointerException("keyboard and/or key detector not set"); 1340 } 1341 final Key curKey = mCurrentKey; 1342 if (newKey == curKey) { 1343 return false; 1344 } 1345 if (curKey == null /* && newKey != null */) { 1346 return true; 1347 } 1348 // Here curKey points to the different key from newKey. 1349 final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( 1350 mIsInSlidingKeyInputFromModifier); 1351 final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); 1352 if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { 1353 if (DEBUG_MODE) { 1354 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) 1355 / mKeyboard.mMostCommonKeyWidth; 1356 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1357 +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); 1358 } 1359 return true; 1360 } 1361 if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput 1362 && sTimeRecorder.isInFastTyping(eventTime) 1363 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { 1364 if (DEBUG_MODE) { 1365 final float keyDiagonal = (float)Math.hypot( 1366 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1367 final float lengthFromDownRatio = 1368 mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; 1369 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1370 + " %.2f key diagonal from virtual down point", 1371 mPointerId, lengthFromDownRatio)); 1372 } 1373 return true; 1374 } 1375 return false; 1376 } 1377 1378 private void startLongPressTimer(final Key key) { 1379 if (sInGesture) return; 1380 if (key == null) return; 1381 if (!key.isLongPressEnabled()) return; 1382 // Caveat: Please note that isLongPressEnabled() can be true even if the current key 1383 // doesn't have its more keys. (e.g. spacebar, globe key) 1384 // We always need to start the long press timer if the key has its more keys regardless of 1385 // whether or not we are in the sliding input mode. 1386 if (mIsInSlidingKeyInput && key.getMoreKeys() == null) return; 1387 final int delay; 1388 switch (key.getCode()) { 1389 case Constants.CODE_SHIFT: 1390 delay = sParams.mLongPressShiftLockTimeout; 1391 break; 1392 default: 1393 final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; 1394 if (mIsInSlidingKeyInputFromModifier) { 1395 // We use longer timeout for sliding finger input started from the modifier key. 1396 delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; 1397 } else { 1398 delay = longpressTimeout; 1399 } 1400 break; 1401 } 1402 mTimerProxy.startLongPressTimer(this, delay); 1403 } 1404 1405 private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { 1406 if (key == null) { 1407 callListenerOnCancelInput(); 1408 return; 1409 } 1410 1411 final int code = key.getCode(); 1412 callListenerOnCodeInput(key, code, x, y, eventTime); 1413 callListenerOnRelease(key, code, false /* withSliding */); 1414 } 1415 1416 private void startRepeatKey(final Key key) { 1417 if (sInGesture) return; 1418 if (key == null) return; 1419 if (!key.isRepeatable()) return; 1420 // Don't start key repeat when we are in sliding input mode. 1421 if (mIsInSlidingKeyInput) return; 1422 final int startRepeatCount = 1; 1423 mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout); 1424 } 1425 1426 public void onKeyRepeat(final int code, final int repeatCount) { 1427 final Key key = getKey(); 1428 if (key == null || key.getCode() != code) { 1429 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 1430 return; 1431 } 1432 mCurrentRepeatingKeyCode = code; 1433 mIsDetectingGesture = false; 1434 final int nextRepeatCount = repeatCount + 1; 1435 mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval); 1436 callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); 1437 callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis()); 1438 } 1439 1440 private void printTouchEvent(final String title, final int x, final int y, 1441 final long eventTime) { 1442 final Key key = mKeyDetector.detectHitKey(x, y); 1443 final String code = KeyDetector.printableCode(key); 1444 Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, 1445 (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code)); 1446 } 1447 } 1448