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