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