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