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