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