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