PointerTracker.java revision 9ec80d9d89eb599329c354451acdc482cc3de836
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.Context; 20import android.content.res.Resources; 21import android.util.Log; 22 23import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 24import com.android.inputmethod.latin.LatinImeLogger; 25import com.android.inputmethod.latin.R; 26 27import java.util.ArrayList; 28import java.util.Arrays; 29import java.util.List; 30 31public class PointerTracker { 32 private static final String TAG = PointerTracker.class.getSimpleName(); 33 private static final boolean DEBUG_EVENT = false; 34 private static final boolean DEBUG_MOVE_EVENT = false; 35 private static final boolean DEBUG_LISTENER = false; 36 private static boolean DEBUG_MODE = LatinImeLogger.sDBG; 37 38 public interface KeyEventHandler { 39 /** 40 * Get KeyDetector object that is used for this PointerTracker. 41 * @return the KeyDetector object that is used for this PointerTracker 42 */ 43 public KeyDetector getKeyDetector(); 44 45 /** 46 * Get KeyboardActionListener object that is used to register key code and so on. 47 * @return the KeyboardActionListner for this PointerTracker 48 */ 49 public KeyboardActionListener getKeyboardActionListener(); 50 51 /** 52 * Get DrawingProxy object that is used for this PointerTracker. 53 * @return the DrawingProxy object that is used for this PointerTracker 54 */ 55 public DrawingProxy getDrawingProxy(); 56 57 /** 58 * Get TimerProxy object that handles key repeat and long press timer event for this 59 * PointerTracker. 60 * @return the TimerProxy object that handles key repeat and long press timer event. 61 */ 62 public TimerProxy getTimerProxy(); 63 } 64 65 public interface DrawingProxy { 66 public void invalidateKey(Key key); 67 public void showKeyPreview(int keyIndex, PointerTracker tracker); 68 public void cancelShowKeyPreview(PointerTracker tracker); 69 public void dismissKeyPreview(PointerTracker tracker); 70 public boolean dismissPopupPanel(); 71 } 72 73 public interface TimerProxy { 74 public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker); 75 public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker); 76 public void cancelLongPressTimer(); 77 public void cancelKeyTimers(); 78 } 79 80 private static KeyboardSwitcher sKeyboardSwitcher; 81 private static boolean sConfigSlidingKeyInputEnabled; 82 // Timing constants 83 private static int sDelayBeforeKeyRepeatStart; 84 private static int sLongPressKeyTimeout; 85 private static int sLongPressShiftKeyTimeout; 86 private static int sLongPressSpaceKeyTimeout; 87 private static int sTouchNoiseThresholdMillis; 88 private static int sTouchNoiseThresholdDistanceSquared; 89 90 private static final List<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); 91 private static PointerTrackerQueue sPointerTrackerQueue; 92 93 public final int mPointerId; 94 95 private DrawingProxy mDrawingProxy; 96 private TimerProxy mTimerProxy; 97 private KeyDetector mKeyDetector; 98 private KeyboardActionListener mListener = EMPTY_LISTENER; 99 100 private Keyboard mKeyboard; 101 private List<Key> mKeys; 102 private int mKeyQuarterWidthSquared; 103 104 // The position and time at which first down event occurred. 105 private long mDownTime; 106 private long mUpTime; 107 108 // The current key index where this pointer is. 109 private int mKeyIndex = KeyDetector.NOT_A_KEY; 110 // The position where mKeyIndex was recognized for the first time. 111 private int mKeyX; 112 private int mKeyY; 113 114 // Last pointer position. 115 private int mLastX; 116 private int mLastY; 117 118 // true if keyboard layout has been changed. 119 private boolean mKeyboardLayoutHasBeenChanged; 120 121 // true if event is already translated to a key action. 122 private boolean mKeyAlreadyProcessed; 123 124 // true if this pointer has been long-pressed and is showing a popup panel. 125 private boolean mIsShowingPopupPanel; 126 127 // true if this pointer is repeatable key 128 private boolean mIsRepeatableKey; 129 130 // true if this pointer is in sliding key input 131 boolean mIsInSlidingKeyInput; 132 133 // true if sliding key is allowed. 134 private boolean mIsAllowedSlidingKeyInput; 135 136 // ignore modifier key if true 137 private boolean mIgnoreModifierKey; 138 139 // Empty {@link KeyboardActionListener} 140 private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() { 141 @Override 142 public void onPress(int primaryCode, boolean withSliding) {} 143 @Override 144 public void onRelease(int primaryCode, boolean withSliding) {} 145 @Override 146 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {} 147 @Override 148 public void onTextInput(CharSequence text) {} 149 @Override 150 public void onCancelInput() {} 151 }; 152 153 public static void init(boolean hasDistinctMultitouch, Context context) { 154 if (hasDistinctMultitouch) { 155 sPointerTrackerQueue = new PointerTrackerQueue(); 156 } else { 157 sPointerTrackerQueue = null; 158 } 159 160 final Resources res = context.getResources(); 161 sConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); 162 sDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); 163 sLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); 164 sLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); 165 sLongPressSpaceKeyTimeout = res.getInteger(R.integer.config_long_press_space_key_timeout); 166 sTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis); 167 final float touchNoiseThresholdDistance = res.getDimension( 168 R.dimen.config_touch_noise_threshold_distance); 169 sTouchNoiseThresholdDistanceSquared = (int)( 170 touchNoiseThresholdDistance * touchNoiseThresholdDistance); 171 sKeyboardSwitcher = KeyboardSwitcher.getInstance(); 172 } 173 174 public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { 175 final List<PointerTracker> trackers = sTrackers; 176 177 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 178 for (int i = trackers.size(); i <= id; i++) { 179 final PointerTracker tracker = new PointerTracker(i, handler); 180 trackers.add(tracker); 181 } 182 183 return trackers.get(id); 184 } 185 186 public static boolean isAnyInSlidingKeyInput() { 187 return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; 188 } 189 190 public static void setKeyboardActionListener(KeyboardActionListener listener) { 191 for (final PointerTracker tracker : sTrackers) { 192 tracker.mListener = listener; 193 } 194 } 195 196 public static void setKeyDetector(KeyDetector keyDetector) { 197 for (final PointerTracker tracker : sTrackers) { 198 tracker.setKeyDetectorInner(keyDetector); 199 // Mark that keyboard layout has been changed. 200 tracker.mKeyboardLayoutHasBeenChanged = true; 201 } 202 } 203 204 public static void dismissAllKeyPreviews() { 205 for (final PointerTracker tracker : sTrackers) { 206 tracker.setReleasedKeyGraphics(tracker.mKeyIndex); 207 } 208 } 209 210 public PointerTracker(int id, KeyEventHandler handler) { 211 if (handler == null) 212 throw new NullPointerException(); 213 mPointerId = id; 214 setKeyDetectorInner(handler.getKeyDetector()); 215 mListener = handler.getKeyboardActionListener(); 216 mDrawingProxy = handler.getDrawingProxy(); 217 mTimerProxy = handler.getTimerProxy(); 218 } 219 220 // Returns true if keyboard has been changed by this callback. 221 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) { 222 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 223 if (DEBUG_LISTENER) 224 Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding 225 + " ignoreModifier=" + ignoreModifierKey); 226 if (ignoreModifierKey) 227 return false; 228 if (key.isEnabled()) { 229 mListener.onPress(key.mCode, withSliding); 230 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 231 mKeyboardLayoutHasBeenChanged = false; 232 return keyboardLayoutHasBeenChanged; 233 } 234 return false; 235 } 236 237 // Note that we need primaryCode argument because the keyboard may in shifted state and the 238 // primaryCode is different from {@link Key#mCode}. 239 private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) { 240 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 241 if (DEBUG_LISTENER) 242 Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode) 243 + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y 244 + " ignoreModifier=" + ignoreModifierKey); 245 if (ignoreModifierKey) 246 return; 247 if (key.isEnabled()) 248 mListener.onCodeInput(primaryCode, keyCodes, x, y); 249 } 250 251 private void callListenerOnTextInput(Key key) { 252 if (DEBUG_LISTENER) 253 Log.d(TAG, "onTextInput: text=" + key.mOutputText); 254 if (key.isEnabled()) 255 mListener.onTextInput(key.mOutputText); 256 } 257 258 // Note that we need primaryCode argument because the keyboard may in shifted state and the 259 // primaryCode is different from {@link Key#mCode}. 260 private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { 261 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 262 if (DEBUG_LISTENER) 263 Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding=" 264 + withSliding + " ignoreModifier=" + ignoreModifierKey); 265 if (ignoreModifierKey) 266 return; 267 if (key.isEnabled()) 268 mListener.onRelease(primaryCode, withSliding); 269 } 270 271 private void callListenerOnCancelInput() { 272 if (DEBUG_LISTENER) 273 Log.d(TAG, "onCancelInput"); 274 mListener.onCancelInput(); 275 } 276 277 public void setKeyDetectorInner(KeyDetector keyDetector) { 278 mKeyDetector = keyDetector; 279 mKeyboard = keyDetector.getKeyboard(); 280 mKeys = mKeyboard.getKeys(); 281 final int keyQuarterWidth = mKeyboard.getKeyWidth() / 4; 282 mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; 283 } 284 285 public boolean isInSlidingKeyInput() { 286 return mIsInSlidingKeyInput; 287 } 288 289 private boolean isValidKeyIndex(int keyIndex) { 290 return keyIndex >= 0 && keyIndex < mKeys.size(); 291 } 292 293 public Key getKey(int keyIndex) { 294 return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null; 295 } 296 297 private static boolean isModifierCode(int primaryCode) { 298 return primaryCode == Keyboard.CODE_SHIFT 299 || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL; 300 } 301 302 private boolean isModifierInternal(int keyIndex) { 303 final Key key = getKey(keyIndex); 304 return key == null ? false : isModifierCode(key.mCode); 305 } 306 307 public boolean isModifier() { 308 return isModifierInternal(mKeyIndex); 309 } 310 311 private boolean isOnModifierKey(int x, int y) { 312 return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); 313 } 314 315 public boolean isOnShiftKey(int x, int y) { 316 final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); 317 return key != null && key.mCode == Keyboard.CODE_SHIFT; 318 } 319 320 public int getKeyIndexOn(int x, int y) { 321 return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); 322 } 323 324 public boolean isSpaceKey(int keyIndex) { 325 Key key = getKey(keyIndex); 326 return key != null && key.mCode == Keyboard.CODE_SPACE; 327 } 328 329 private void setReleasedKeyGraphics(int keyIndex) { 330 mDrawingProxy.dismissKeyPreview(this); 331 final Key key = getKey(keyIndex); 332 if (key != null) { 333 key.onReleased(); 334 mDrawingProxy.invalidateKey(key); 335 } 336 } 337 338 private void setPressedKeyGraphics(int keyIndex) { 339 if (isKeyPreviewRequired(keyIndex)) { 340 mDrawingProxy.showKeyPreview(keyIndex, this); 341 } 342 final Key key = getKey(keyIndex); 343 if (key != null && key.isEnabled()) { 344 key.onPressed(); 345 mDrawingProxy.invalidateKey(key); 346 } 347 } 348 349 // The modifier key, such as shift key, should not show its key preview. 350 private boolean isKeyPreviewRequired(int keyIndex) { 351 final Key key = getKey(keyIndex); 352 if (key == null || !key.isEnabled()) 353 return false; 354 final int code = key.mCode; 355 if (isModifierCode(code) || code == Keyboard.CODE_DELETE 356 || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE) 357 return false; 358 return true; 359 } 360 361 public int getLastX() { 362 return mLastX; 363 } 364 365 public int getLastY() { 366 return mLastY; 367 } 368 369 public long getDownTime() { 370 return mDownTime; 371 } 372 373 private int onDownKey(int x, int y, long eventTime) { 374 mDownTime = eventTime; 375 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 376 } 377 378 private int onMoveKeyInternal(int x, int y) { 379 mLastX = x; 380 mLastY = y; 381 return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); 382 } 383 384 private int onMoveKey(int x, int y) { 385 return onMoveKeyInternal(x, y); 386 } 387 388 private int onMoveToNewKey(int keyIndex, int x, int y) { 389 mKeyIndex = keyIndex; 390 mKeyX = x; 391 mKeyY = y; 392 return keyIndex; 393 } 394 395 private int onUpKey(int x, int y, long eventTime) { 396 mUpTime = eventTime; 397 mKeyIndex = KeyDetector.NOT_A_KEY; 398 return onMoveKeyInternal(x, y); 399 } 400 401 public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) { 402 if (DEBUG_EVENT) 403 printTouchEvent("onDownEvent:", x, y, eventTime); 404 405 mDrawingProxy = handler.getDrawingProxy(); 406 mTimerProxy = handler.getTimerProxy(); 407 setKeyboardActionListener(handler.getKeyboardActionListener()); 408 setKeyDetectorInner(handler.getKeyDetector()); 409 // Naive up-to-down noise filter. 410 final long deltaT = eventTime - mUpTime; 411 if (deltaT < sTouchNoiseThresholdMillis) { 412 final int dx = x - mLastX; 413 final int dy = y - mLastY; 414 final int distanceSquared = (dx * dx + dy * dy); 415 if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { 416 if (DEBUG_MODE) 417 Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT 418 + " distance=" + distanceSquared); 419 mKeyAlreadyProcessed = true; 420 return; 421 } 422 } 423 424 final PointerTrackerQueue queue = sPointerTrackerQueue; 425 if (queue != null) { 426 if (isOnModifierKey(x, y)) { 427 // Before processing a down event of modifier key, all pointers already being 428 // tracked should be released. 429 queue.releaseAllPointers(eventTime); 430 } 431 queue.add(this); 432 } 433 onDownEventInternal(x, y, eventTime); 434 } 435 436 private void onDownEventInternal(int x, int y, long eventTime) { 437 int keyIndex = onDownKey(x, y, eventTime); 438 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 439 // from modifier key, or 3) this pointer is on mini-keyboard. 440 mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) 441 || mKeyDetector instanceof MiniKeyboardKeyDetector; 442 mKeyboardLayoutHasBeenChanged = false; 443 mKeyAlreadyProcessed = false; 444 mIsRepeatableKey = false; 445 mIsInSlidingKeyInput = false; 446 mIgnoreModifierKey = false; 447 if (isValidKeyIndex(keyIndex)) { 448 // This onPress call may have changed keyboard layout. Those cases are detected at 449 // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new 450 // keyboard layout. 451 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false)) 452 keyIndex = onDownKey(x, y, eventTime); 453 454 startRepeatKey(keyIndex); 455 startLongPressTimer(keyIndex); 456 setPressedKeyGraphics(keyIndex); 457 } 458 } 459 460 private void startSlidingKeyInput(Key key) { 461 if (!mIsInSlidingKeyInput) 462 mIgnoreModifierKey = isModifierCode(key.mCode); 463 mIsInSlidingKeyInput = true; 464 } 465 466 public void onMoveEvent(int x, int y, long eventTime) { 467 if (DEBUG_MOVE_EVENT) 468 printTouchEvent("onMoveEvent:", x, y, eventTime); 469 if (mKeyAlreadyProcessed) 470 return; 471 472 final int lastX = mLastX; 473 final int lastY = mLastY; 474 final int oldKeyIndex = mKeyIndex; 475 final Key oldKey = getKey(oldKeyIndex); 476 int keyIndex = onMoveKey(x, y); 477 if (isValidKeyIndex(keyIndex)) { 478 if (oldKey == null) { 479 // The pointer has been slid in to the new key, but the finger was not on any keys. 480 // In this case, we must call onPress() to notify that the new key is being pressed. 481 // This onPress call may have changed keyboard layout. Those cases are detected at 482 // {@link #setKeyboard}. In those cases, we should update keyIndex according to the 483 // new keyboard layout. 484 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) 485 keyIndex = onMoveKey(x, y); 486 onMoveToNewKey(keyIndex, x, y); 487 startLongPressTimer(keyIndex); 488 setPressedKeyGraphics(keyIndex); 489 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { 490 // The pointer has been slid in to the new key from the previous key, we must call 491 // onRelease() first to notify that the previous key has been released, then call 492 // onPress() to notify that the new key is being pressed. 493 setReleasedKeyGraphics(oldKeyIndex); 494 callListenerOnRelease(oldKey, oldKey.mCode, true); 495 startSlidingKeyInput(oldKey); 496 mTimerProxy.cancelKeyTimers(); 497 startRepeatKey(keyIndex); 498 if (mIsAllowedSlidingKeyInput) { 499 // This onPress call may have changed keyboard layout. Those cases are detected 500 // at {@link #setKeyboard}. In those cases, we should update keyIndex according 501 // to the new keyboard layout. 502 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) 503 keyIndex = onMoveKey(x, y); 504 onMoveToNewKey(keyIndex, x, y); 505 startLongPressTimer(keyIndex); 506 setPressedKeyGraphics(keyIndex); 507 } else { 508 // HACK: On some devices, quick successive touches may be translated to sudden 509 // move by touch panel firmware. This hack detects the case and translates the 510 // move event to successive up and down events. 511 final int dx = x - lastX; 512 final int dy = y - lastY; 513 final int lastMoveSquared = dx * dx + dy * dy; 514 if (lastMoveSquared >= mKeyQuarterWidthSquared) { 515 if (DEBUG_MODE) 516 Log.w(TAG, String.format("onMoveEvent: sudden move is translated to " 517 + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); 518 onUpEventInternal(lastX, lastY, eventTime); 519 onDownEventInternal(x, y, eventTime); 520 } else { 521 mKeyAlreadyProcessed = true; 522 setReleasedKeyGraphics(oldKeyIndex); 523 } 524 } 525 } 526 } else { 527 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { 528 // The pointer has been slid out from the previous key, we must call onRelease() to 529 // notify that the previous key has been released. 530 setReleasedKeyGraphics(oldKeyIndex); 531 callListenerOnRelease(oldKey, oldKey.mCode, true); 532 startSlidingKeyInput(oldKey); 533 mTimerProxy.cancelLongPressTimer(); 534 if (mIsAllowedSlidingKeyInput) { 535 onMoveToNewKey(keyIndex, x, y); 536 } else { 537 mKeyAlreadyProcessed = true; 538 } 539 } 540 } 541 } 542 543 public void onUpEvent(int x, int y, long eventTime) { 544 if (DEBUG_EVENT) 545 printTouchEvent("onUpEvent :", x, y, eventTime); 546 547 final PointerTrackerQueue queue = sPointerTrackerQueue; 548 if (queue != null) { 549 if (isModifier()) { 550 // Before processing an up event of modifier key, all pointers already being 551 // tracked should be released. 552 queue.releaseAllPointersExcept(this, eventTime); 553 } else { 554 queue.releaseAllPointersOlderThan(this, eventTime); 555 } 556 queue.remove(this); 557 } 558 onUpEventInternal(x, y, eventTime); 559 } 560 561 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 562 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 563 // "virtual" up event. 564 public void onPhantomUpEvent(int x, int y, long eventTime) { 565 if (DEBUG_EVENT) 566 printTouchEvent("onPhntEvent:", x, y, eventTime); 567 onUpEventInternal(x, y, eventTime); 568 mKeyAlreadyProcessed = true; 569 } 570 571 private void onUpEventInternal(int x, int y, long eventTime) { 572 mTimerProxy.cancelKeyTimers(); 573 mDrawingProxy.cancelShowKeyPreview(this); 574 mIsInSlidingKeyInput = false; 575 final int keyX, keyY; 576 if (isMajorEnoughMoveToBeOnNewKey(x, y, onMoveKey(x, y))) { 577 keyX = x; 578 keyY = y; 579 } else { 580 // Use previous fixed key coordinates. 581 keyX = mKeyX; 582 keyY = mKeyY; 583 } 584 final int keyIndex = onUpKey(keyX, keyY, eventTime); 585 setReleasedKeyGraphics(keyIndex); 586 if (mIsShowingPopupPanel) { 587 mDrawingProxy.dismissPopupPanel(); 588 mIsShowingPopupPanel = false; 589 } 590 if (mKeyAlreadyProcessed) 591 return; 592 if (!mIsRepeatableKey) { 593 detectAndSendKey(keyIndex, keyX, keyY); 594 } 595 } 596 597 public void onShowPopupPanel(int x, int y, long eventTime, KeyEventHandler handler) { 598 onLongPressed(); 599 onDownEvent(x, y, eventTime, handler); 600 mIsShowingPopupPanel = true; 601 } 602 603 public void onLongPressed() { 604 mKeyAlreadyProcessed = true; 605 setReleasedKeyGraphics(mKeyIndex); 606 final PointerTrackerQueue queue = sPointerTrackerQueue; 607 if (queue != null) { 608 queue.remove(this); 609 } 610 } 611 612 public void onCancelEvent(int x, int y, long eventTime) { 613 if (DEBUG_EVENT) 614 printTouchEvent("onCancelEvt:", x, y, eventTime); 615 616 final PointerTrackerQueue queue = sPointerTrackerQueue; 617 if (queue != null) { 618 queue.releaseAllPointersExcept(this, eventTime); 619 queue.remove(this); 620 } 621 onCancelEventInternal(); 622 } 623 624 private void onCancelEventInternal() { 625 mTimerProxy.cancelKeyTimers(); 626 mDrawingProxy.cancelShowKeyPreview(this); 627 setReleasedKeyGraphics(mKeyIndex); 628 mIsInSlidingKeyInput = false; 629 if (mIsShowingPopupPanel) { 630 mDrawingProxy.dismissPopupPanel(); 631 mIsShowingPopupPanel = false; 632 } 633 } 634 635 private void startRepeatKey(int keyIndex) { 636 final Key key = getKey(keyIndex); 637 if (key != null && key.mRepeatable) { 638 onRepeatKey(keyIndex); 639 mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this); 640 mIsRepeatableKey = true; 641 } else { 642 mIsRepeatableKey = false; 643 } 644 } 645 646 public void onRepeatKey(int keyIndex) { 647 Key key = getKey(keyIndex); 648 if (key != null) { 649 detectAndSendKey(keyIndex, key.mX, key.mY); 650 } 651 } 652 653 private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) { 654 if (mKeys == null || mKeyDetector == null) 655 throw new NullPointerException("keyboard and/or key detector not set"); 656 int curKey = mKeyIndex; 657 if (newKey == curKey) { 658 return false; 659 } else if (isValidKeyIndex(curKey)) { 660 return mKeys.get(curKey).squaredDistanceToEdge(x, y) 661 >= mKeyDetector.getKeyHysteresisDistanceSquared(); 662 } else { 663 return true; 664 } 665 } 666 667 private void startLongPressTimer(int keyIndex) { 668 Key key = getKey(keyIndex); 669 if (key.mCode == Keyboard.CODE_SHIFT) { 670 if (sLongPressShiftKeyTimeout > 0) { 671 mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this); 672 } 673 } else if (key.mCode == Keyboard.CODE_SPACE) { 674 if (sLongPressSpaceKeyTimeout > 0) { 675 mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this); 676 } 677 } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) { 678 // We need not start long press timer on the key which has manual temporary upper case 679 // code defined and the keyboard is in manual temporary upper case mode. 680 return; 681 } else if (sKeyboardSwitcher.isInMomentarySwitchState()) { 682 // We use longer timeout for sliding finger input started from the symbols mode key. 683 mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this); 684 } else { 685 mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this); 686 } 687 } 688 689 private void detectAndSendKey(int index, int x, int y) { 690 final Key key = getKey(index); 691 if (key == null) { 692 callListenerOnCancelInput(); 693 return; 694 } 695 if (key.mOutputText != null) { 696 callListenerOnTextInput(key); 697 callListenerOnRelease(key, key.mCode, false); 698 } else { 699 int code = key.mCode; 700 final int[] codes = mKeyDetector.newCodeArray(); 701 mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); 702 703 // If keyboard is in manual temporary upper case state and key has manual temporary 704 // uppercase letter as key hint letter, alternate character code should be sent. 705 if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) { 706 code = key.mHintLabel.charAt(0); 707 codes[0] = code; 708 } 709 710 // Swap the first and second values in the codes array if the primary code is not the 711 // first value but the second value in the array. This happens when key debouncing is 712 // in effect. 713 if (codes.length >= 2 && codes[0] != code && codes[1] == code) { 714 codes[1] = codes[0]; 715 codes[0] = code; 716 } 717 callListenerOnCodeInput(key, code, codes, x, y); 718 callListenerOnRelease(key, code, false); 719 } 720 } 721 722 private long mPreviousEventTime; 723 724 private void printTouchEvent(String title, int x, int y, long eventTime) { 725 final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); 726 final Key key = getKey(keyIndex); 727 final String code = (key == null) ? "----" : keyCodePrintable(key.mCode); 728 final long delta = eventTime - mPreviousEventTime; 729 Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title, 730 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code)); 731 mPreviousEventTime = eventTime; 732 } 733 734 private static String keyCodePrintable(int primaryCode) { 735 final String modifier = isModifierCode(primaryCode) ? " modifier" : ""; 736 return String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier; 737 } 738} 739