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