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