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