1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.keyboard; 18 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.os.SystemClock; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 25 import com.android.inputmethod.keyboard.internal.BatchInputArbiter; 26 import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener; 27 import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector; 28 import com.android.inputmethod.keyboard.internal.GestureEnabler; 29 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams; 30 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints; 31 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams; 32 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 33 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder; 34 import com.android.inputmethod.latin.Constants; 35 import com.android.inputmethod.latin.InputPointers; 36 import com.android.inputmethod.latin.R; 37 import com.android.inputmethod.latin.define.DebugFlags; 38 import com.android.inputmethod.latin.settings.Settings; 39 import com.android.inputmethod.latin.utils.CoordinateUtils; 40 import com.android.inputmethod.latin.utils.ResourceUtils; 41 42 import java.util.ArrayList; 43 44 public final class PointerTracker implements PointerTrackerQueue.Element, 45 BatchInputArbiterListener { 46 private static final String TAG = PointerTracker.class.getSimpleName(); 47 private static final boolean DEBUG_EVENT = false; 48 private static final boolean DEBUG_MOVE_EVENT = false; 49 private static final boolean DEBUG_LISTENER = false; 50 private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT; 51 52 public interface DrawingProxy { invalidateKey(Key key)53 public void invalidateKey(Key key); showKeyPreview(Key key)54 public void showKeyPreview(Key key); dismissKeyPreview(Key key)55 public void dismissKeyPreview(Key key); showSlidingKeyInputPreview(PointerTracker tracker)56 public void showSlidingKeyInputPreview(PointerTracker tracker); dismissSlidingKeyInputPreview()57 public void dismissSlidingKeyInputPreview(); showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText)58 public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText); 59 } 60 61 public interface TimerProxy { startTypingStateTimer(Key typedKey)62 public void startTypingStateTimer(Key typedKey); isTypingState()63 public boolean isTypingState(); startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay)64 public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay); startLongPressTimerOf(PointerTracker tracker, int delay)65 public void startLongPressTimerOf(PointerTracker tracker, int delay); cancelLongPressTimerOf(PointerTracker tracker)66 public void cancelLongPressTimerOf(PointerTracker tracker); cancelLongPressShiftKeyTimers()67 public void cancelLongPressShiftKeyTimers(); cancelKeyTimersOf(PointerTracker tracker)68 public void cancelKeyTimersOf(PointerTracker tracker); startDoubleTapShiftKeyTimer()69 public void startDoubleTapShiftKeyTimer(); cancelDoubleTapShiftKeyTimer()70 public void cancelDoubleTapShiftKeyTimer(); isInDoubleTapShiftKeyTimeout()71 public boolean isInDoubleTapShiftKeyTimeout(); startUpdateBatchInputTimer(PointerTracker tracker)72 public void startUpdateBatchInputTimer(PointerTracker tracker); cancelUpdateBatchInputTimer(PointerTracker tracker)73 public void cancelUpdateBatchInputTimer(PointerTracker tracker); cancelAllUpdateBatchInputTimers()74 public void cancelAllUpdateBatchInputTimers(); 75 76 public static class Adapter implements TimerProxy { 77 @Override startTypingStateTimer(Key typedKey)78 public void startTypingStateTimer(Key typedKey) {} 79 @Override isTypingState()80 public boolean isTypingState() { return false; } 81 @Override startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay)82 public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {} 83 @Override startLongPressTimerOf(PointerTracker tracker, int delay)84 public void startLongPressTimerOf(PointerTracker tracker, int delay) {} 85 @Override cancelLongPressTimerOf(PointerTracker tracker)86 public void cancelLongPressTimerOf(PointerTracker tracker) {} 87 @Override cancelLongPressShiftKeyTimers()88 public void cancelLongPressShiftKeyTimers() {} 89 @Override cancelKeyTimersOf(PointerTracker tracker)90 public void cancelKeyTimersOf(PointerTracker tracker) {} 91 @Override startDoubleTapShiftKeyTimer()92 public void startDoubleTapShiftKeyTimer() {} 93 @Override cancelDoubleTapShiftKeyTimer()94 public void cancelDoubleTapShiftKeyTimer() {} 95 @Override isInDoubleTapShiftKeyTimeout()96 public boolean isInDoubleTapShiftKeyTimeout() { return false; } 97 @Override startUpdateBatchInputTimer(PointerTracker tracker)98 public void startUpdateBatchInputTimer(PointerTracker tracker) {} 99 @Override cancelUpdateBatchInputTimer(PointerTracker tracker)100 public void cancelUpdateBatchInputTimer(PointerTracker tracker) {} 101 @Override cancelAllUpdateBatchInputTimers()102 public void cancelAllUpdateBatchInputTimers() {} 103 } 104 } 105 106 static final class PointerTrackerParams { 107 public final boolean mKeySelectionByDraggingFinger; 108 public final int mTouchNoiseThresholdTime; 109 public final int mTouchNoiseThresholdDistance; 110 public final int mSuppressKeyPreviewAfterBatchInputDuration; 111 public final int mKeyRepeatStartTimeout; 112 public final int mKeyRepeatInterval; 113 public final int mLongPressShiftLockTimeout; 114 PointerTrackerParams(final TypedArray mainKeyboardViewAttr)115 public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { 116 mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean( 117 R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false); 118 mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( 119 R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); 120 mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize( 121 R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); 122 mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt( 123 R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0); 124 mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( 125 R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); 126 mKeyRepeatInterval = mainKeyboardViewAttr.getInt( 127 R.styleable.MainKeyboardView_keyRepeatInterval, 0); 128 mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( 129 R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); 130 } 131 } 132 133 private static GestureEnabler sGestureEnabler = new GestureEnabler(); 134 135 // Parameters for pointer handling. 136 private static PointerTrackerParams sParams; 137 private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams; 138 private static GestureStrokeDrawingParams sGestureStrokeDrawingParams; 139 private static boolean sNeedsPhantomSuddenMoveEventHack; 140 // Move this threshold to resource. 141 // TODO: Device specific parameter would be better for device specific hack? 142 private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth 143 144 private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>(); 145 private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue(); 146 147 public final int mPointerId; 148 149 private static DrawingProxy sDrawingProxy; 150 private static TimerProxy sTimerProxy; 151 private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER; 152 153 // The {@link KeyDetector} is set whenever the down event is processed. Also this is updated 154 // when new {@link Keyboard} is set by {@link #setKeyDetector(KeyDetector)}. 155 private KeyDetector mKeyDetector = new KeyDetector(); 156 private Keyboard mKeyboard; 157 private int mPhantomSuddenMoveThreshold; 158 private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector(); 159 160 private boolean mIsDetectingGesture = false; // per PointerTracker. 161 private static boolean sInGesture = false; 162 private static TypingTimeRecorder sTypingTimeRecorder; 163 164 // The position and time at which first down event occurred. 165 private long mDownTime; 166 private int[] mDownCoordinates = CoordinateUtils.newInstance(); 167 private long mUpTime; 168 169 // The current key where this pointer is. 170 private Key mCurrentKey = null; 171 // The position where the current key was recognized for the first time. 172 private int mKeyX; 173 private int mKeyY; 174 175 // Last pointer position. 176 private int mLastX; 177 private int mLastY; 178 179 // true if keyboard layout has been changed. 180 private boolean mKeyboardLayoutHasBeenChanged; 181 182 // true if this pointer is no longer triggering any action because it has been canceled. 183 private boolean mIsTrackingForActionDisabled; 184 185 // the more keys panel currently being shown. equals null if no panel is active. 186 private MoreKeysPanel mMoreKeysPanel; 187 188 private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3; 189 // true if this pointer is in the dragging finger mode. 190 boolean mIsInDraggingFinger; 191 // true if this pointer is sliding from a modifier key and in the sliding key input mode, 192 // so that further modifier keys should be ignored. 193 boolean mIsInSlidingKeyInput; 194 // if not a NOT_A_CODE, the key of this code is repeating 195 private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 196 197 // true if dragging finger is allowed. 198 private boolean mIsAllowedDraggingFinger; 199 200 private final BatchInputArbiter mBatchInputArbiter; 201 private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints; 202 203 // TODO: Add PointerTrackerFactory singleton and move some class static methods into it. init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy, final DrawingProxy drawingProxy)204 public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy, 205 final DrawingProxy drawingProxy) { 206 sParams = new PointerTrackerParams(mainKeyboardViewAttr); 207 sGestureStrokeRecognitionParams = new GestureStrokeRecognitionParams(mainKeyboardViewAttr); 208 sGestureStrokeDrawingParams = new GestureStrokeDrawingParams(mainKeyboardViewAttr); 209 sTypingTimeRecorder = new TypingTimeRecorder( 210 sGestureStrokeRecognitionParams.mStaticTimeThresholdAfterFastTyping, 211 sParams.mSuppressKeyPreviewAfterBatchInputDuration); 212 213 final Resources res = mainKeyboardViewAttr.getResources(); 214 sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean( 215 ResourceUtils.getDeviceOverrideValue(res, 216 R.array.phantom_sudden_move_event_device_list, Boolean.FALSE.toString())); 217 BogusMoveEventDetector.init(res); 218 219 sTimerProxy = timerProxy; 220 sDrawingProxy = drawingProxy; 221 } 222 223 // Note that this method is called from a non-UI thread. setMainDictionaryAvailability(final boolean mainDictionaryAvailable)224 public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 225 sGestureEnabler.setMainDictionaryAvailability(mainDictionaryAvailable); 226 } 227 setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser)228 public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 229 sGestureEnabler.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); 230 } 231 getPointerTracker(final int id)232 public static PointerTracker getPointerTracker(final int id) { 233 final ArrayList<PointerTracker> trackers = sTrackers; 234 235 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 236 for (int i = trackers.size(); i <= id; i++) { 237 final PointerTracker tracker = new PointerTracker(i); 238 trackers.add(tracker); 239 } 240 241 return trackers.get(id); 242 } 243 isAnyInDraggingFinger()244 public static boolean isAnyInDraggingFinger() { 245 return sPointerTrackerQueue.isAnyInDraggingFinger(); 246 } 247 cancelAllPointerTrackers()248 public static void cancelAllPointerTrackers() { 249 sPointerTrackerQueue.cancelAllPointerTrackers(); 250 } 251 setKeyboardActionListener(final KeyboardActionListener listener)252 public static void setKeyboardActionListener(final KeyboardActionListener listener) { 253 sListener = listener; 254 } 255 setKeyDetector(final KeyDetector keyDetector)256 public static void setKeyDetector(final KeyDetector keyDetector) { 257 final Keyboard keyboard = keyDetector.getKeyboard(); 258 if (keyboard == null) { 259 return; 260 } 261 final int trackersSize = sTrackers.size(); 262 for (int i = 0; i < trackersSize; ++i) { 263 final PointerTracker tracker = sTrackers.get(i); 264 tracker.setKeyDetectorInner(keyDetector); 265 } 266 sGestureEnabler.setPasswordMode(keyboard.mId.passwordInput()); 267 } 268 setReleasedKeyGraphicsToAllKeys()269 public static void setReleasedKeyGraphicsToAllKeys() { 270 final int trackersSize = sTrackers.size(); 271 for (int i = 0; i < trackersSize; ++i) { 272 final PointerTracker tracker = sTrackers.get(i); 273 tracker.setReleasedKeyGraphics(tracker.getKey()); 274 } 275 } 276 dismissAllMoreKeysPanels()277 public static void dismissAllMoreKeysPanels() { 278 final int trackersSize = sTrackers.size(); 279 for (int i = 0; i < trackersSize; ++i) { 280 final PointerTracker tracker = sTrackers.get(i); 281 tracker.dismissMoreKeysPanel(); 282 } 283 } 284 PointerTracker(final int id)285 private PointerTracker(final int id) { 286 mPointerId = id; 287 mBatchInputArbiter = new BatchInputArbiter(id, sGestureStrokeRecognitionParams); 288 mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints(sGestureStrokeDrawingParams); 289 } 290 291 // Returns true if keyboard has been changed by this callback. callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, final int repeatCount)292 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, 293 final int repeatCount) { 294 // While gesture input is going on, this method should be a no-operation. But when gesture 295 // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code> 296 // are set to false. To keep this method is a no-operation, 297 // <code>mIsTrackingForActionDisabled</code> should also be taken account of. 298 if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { 299 return false; 300 } 301 final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); 302 if (DEBUG_LISTENER) { 303 Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId, 304 (key == null ? "none" : Constants.printableCode(key.getCode())), 305 ignoreModifierKey ? " ignoreModifier" : "", 306 key.isEnabled() ? "" : " disabled", 307 repeatCount > 0 ? " repeatCount=" + repeatCount : "")); 308 } 309 if (ignoreModifierKey) { 310 return false; 311 } 312 if (key.isEnabled()) { 313 sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1); 314 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 315 mKeyboardLayoutHasBeenChanged = false; 316 sTimerProxy.startTypingStateTimer(key); 317 return keyboardLayoutHasBeenChanged; 318 } 319 return false; 320 } 321 322 // Note that we need primaryCode argument because the keyboard may in shifted state and the 323 // primaryCode is different from {@link Key#mKeyCode}. callListenerOnCodeInput(final Key key, final int primaryCode, final int x, final int y, final long eventTime, final boolean isKeyRepeat)324 private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, 325 final int y, final long eventTime, final boolean isKeyRepeat) { 326 final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); 327 final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); 328 final int code = altersCode ? key.getAltCode() : primaryCode; 329 if (DEBUG_LISTENER) { 330 final String output = code == Constants.CODE_OUTPUT_TEXT 331 ? key.getOutputText() : Constants.printableCode(code); 332 Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, 333 output, ignoreModifierKey ? " ignoreModifier" : "", 334 altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); 335 } 336 if (ignoreModifierKey) { 337 return; 338 } 339 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 340 if (key.isEnabled() || altersCode) { 341 sTypingTimeRecorder.onCodeInput(code, eventTime); 342 if (code == Constants.CODE_OUTPUT_TEXT) { 343 sListener.onTextInput(key.getOutputText()); 344 } else if (code != Constants.CODE_UNSPECIFIED) { 345 if (mKeyboard.hasProximityCharsCorrection(code)) { 346 sListener.onCodeInput(code, x, y, isKeyRepeat); 347 } else { 348 sListener.onCodeInput(code, 349 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat); 350 } 351 } 352 } 353 } 354 355 // Note that we need primaryCode argument because the keyboard may be in shifted state and the 356 // primaryCode is different from {@link Key#mKeyCode}. callListenerOnRelease(final Key key, final int primaryCode, final boolean withSliding)357 private void callListenerOnRelease(final Key key, final int primaryCode, 358 final boolean withSliding) { 359 // See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}. 360 if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) { 361 return; 362 } 363 final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); 364 if (DEBUG_LISTENER) { 365 Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, 366 Constants.printableCode(primaryCode), 367 withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", 368 key.isEnabled() ? "": " disabled")); 369 } 370 if (ignoreModifierKey) { 371 return; 372 } 373 if (key.isEnabled()) { 374 sListener.onReleaseKey(primaryCode, withSliding); 375 } 376 } 377 callListenerOnFinishSlidingInput()378 private void callListenerOnFinishSlidingInput() { 379 if (DEBUG_LISTENER) { 380 Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId)); 381 } 382 sListener.onFinishSlidingInput(); 383 } 384 callListenerOnCancelInput()385 private void callListenerOnCancelInput() { 386 if (DEBUG_LISTENER) { 387 Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); 388 } 389 sListener.onCancelInput(); 390 } 391 setKeyDetectorInner(final KeyDetector keyDetector)392 private void setKeyDetectorInner(final KeyDetector keyDetector) { 393 final Keyboard keyboard = keyDetector.getKeyboard(); 394 if (keyboard == null) { 395 return; 396 } 397 if (keyDetector == mKeyDetector && keyboard == mKeyboard) { 398 return; 399 } 400 mKeyDetector = keyDetector; 401 mKeyboard = keyboard; 402 // Mark that keyboard layout has been changed. 403 mKeyboardLayoutHasBeenChanged = true; 404 final int keyWidth = mKeyboard.mMostCommonKeyWidth; 405 final int keyHeight = mKeyboard.mMostCommonKeyHeight; 406 mBatchInputArbiter.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); 407 // Keep {@link #mCurrentKey} that comes from previous keyboard. The key preview of 408 // {@link #mCurrentKey} will be dismissed by {@setReleasedKeyGraphics(Key)} via 409 // {@link onMoveEventInternal(int,int,long)} or {@link #onUpEventInternal(int,int,long)}. 410 mPhantomSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD); 411 mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight); 412 } 413 414 @Override isInDraggingFinger()415 public boolean isInDraggingFinger() { 416 return mIsInDraggingFinger; 417 } 418 getKey()419 public Key getKey() { 420 return mCurrentKey; 421 } 422 423 @Override isModifier()424 public boolean isModifier() { 425 return mCurrentKey != null && mCurrentKey.isModifier(); 426 } 427 getKeyOn(final int x, final int y)428 public Key getKeyOn(final int x, final int y) { 429 return mKeyDetector.detectHitKey(x, y); 430 } 431 setReleasedKeyGraphics(final Key key)432 private void setReleasedKeyGraphics(final Key key) { 433 sDrawingProxy.dismissKeyPreview(key); 434 if (key == null) { 435 return; 436 } 437 438 // Even if the key is disabled, update the key release graphics just in case. 439 updateReleaseKeyGraphics(key); 440 441 if (key.isShift()) { 442 for (final Key shiftKey : mKeyboard.mShiftKeys) { 443 if (shiftKey != key) { 444 updateReleaseKeyGraphics(shiftKey); 445 } 446 } 447 } 448 449 if (key.altCodeWhileTyping()) { 450 final int altCode = key.getAltCode(); 451 final Key altKey = mKeyboard.getKey(altCode); 452 if (altKey != null) { 453 updateReleaseKeyGraphics(altKey); 454 } 455 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 456 if (k != key && k.getAltCode() == altCode) { 457 updateReleaseKeyGraphics(k); 458 } 459 } 460 } 461 } 462 needsToSuppressKeyPreviewPopup(final long eventTime)463 private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) { 464 if (!sGestureEnabler.shouldHandleGesture()) return false; 465 return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); 466 } 467 setPressedKeyGraphics(final Key key, final long eventTime)468 private void setPressedKeyGraphics(final Key key, final long eventTime) { 469 if (key == null) { 470 return; 471 } 472 473 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 474 final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); 475 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 476 if (!needsToUpdateGraphics) { 477 return; 478 } 479 480 if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) { 481 sDrawingProxy.showKeyPreview(key); 482 } 483 updatePressKeyGraphics(key); 484 485 if (key.isShift()) { 486 for (final Key shiftKey : mKeyboard.mShiftKeys) { 487 if (shiftKey != key) { 488 updatePressKeyGraphics(shiftKey); 489 } 490 } 491 } 492 493 if (altersCode) { 494 final int altCode = key.getAltCode(); 495 final Key altKey = mKeyboard.getKey(altCode); 496 if (altKey != null) { 497 updatePressKeyGraphics(altKey); 498 } 499 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 500 if (k != key && k.getAltCode() == altCode) { 501 updatePressKeyGraphics(k); 502 } 503 } 504 } 505 } 506 updateReleaseKeyGraphics(final Key key)507 private static void updateReleaseKeyGraphics(final Key key) { 508 key.onReleased(); 509 sDrawingProxy.invalidateKey(key); 510 } 511 updatePressKeyGraphics(final Key key)512 private static void updatePressKeyGraphics(final Key key) { 513 key.onPressed(); 514 sDrawingProxy.invalidateKey(key); 515 } 516 getGestureStrokeDrawingPoints()517 public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() { 518 return mGestureStrokeDrawingPoints; 519 } 520 getLastCoordinates(final int[] outCoords)521 public void getLastCoordinates(final int[] outCoords) { 522 CoordinateUtils.set(outCoords, mLastX, mLastY); 523 } 524 getDownTime()525 public long getDownTime() { 526 return mDownTime; 527 } 528 getDownCoordinates(final int[] outCoords)529 public void getDownCoordinates(final int[] outCoords) { 530 CoordinateUtils.copy(outCoords, mDownCoordinates); 531 } 532 onDownKey(final int x, final int y, final long eventTime)533 private Key onDownKey(final int x, final int y, final long eventTime) { 534 mDownTime = eventTime; 535 CoordinateUtils.set(mDownCoordinates, x, y); 536 mBogusMoveEventDetector.onDownKey(); 537 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 538 } 539 getDistance(final int x1, final int y1, final int x2, final int y2)540 private static int getDistance(final int x1, final int y1, final int x2, final int y2) { 541 return (int)Math.hypot(x1 - x2, y1 - y2); 542 } 543 onMoveKeyInternal(final int x, final int y)544 private Key onMoveKeyInternal(final int x, final int y) { 545 mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY)); 546 mLastX = x; 547 mLastY = y; 548 return mKeyDetector.detectHitKey(x, y); 549 } 550 onMoveKey(final int x, final int y)551 private Key onMoveKey(final int x, final int y) { 552 return onMoveKeyInternal(x, y); 553 } 554 onMoveToNewKey(final Key newKey, final int x, final int y)555 private Key onMoveToNewKey(final Key newKey, final int x, final int y) { 556 mCurrentKey = newKey; 557 mKeyX = x; 558 mKeyY = y; 559 return newKey; 560 } 561 getActivePointerTrackerCount()562 /* package */ static int getActivePointerTrackerCount() { 563 return sPointerTrackerQueue.size(); 564 } 565 isOldestTrackerInQueue()566 private boolean isOldestTrackerInQueue() { 567 return sPointerTrackerQueue.getOldestElement() == this; 568 } 569 570 // Implements {@link BatchInputArbiterListener}. 571 @Override onStartBatchInput()572 public void onStartBatchInput() { 573 if (DEBUG_LISTENER) { 574 Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); 575 } 576 sListener.onStartBatchInput(); 577 dismissAllMoreKeysPanels(); 578 sTimerProxy.cancelLongPressTimerOf(this); 579 } 580 showGestureTrail()581 private void showGestureTrail() { 582 if (mIsTrackingForActionDisabled) { 583 return; 584 } 585 // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. 586 sDrawingProxy.showGestureTrail( 587 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); 588 } 589 updateBatchInputByTimer(final long syntheticMoveEventTime)590 public void updateBatchInputByTimer(final long syntheticMoveEventTime) { 591 mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this); 592 } 593 594 // Implements {@link BatchInputArbiterListener}. 595 @Override onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime)596 public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) { 597 if (DEBUG_LISTENER) { 598 Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId, 599 aggregatedPointers.getPointerSize())); 600 } 601 sListener.onUpdateBatchInput(aggregatedPointers); 602 } 603 604 // Implements {@link BatchInputArbiterListener}. 605 @Override onStartUpdateBatchInputTimer()606 public void onStartUpdateBatchInputTimer() { 607 sTimerProxy.startUpdateBatchInputTimer(this); 608 } 609 610 // Implements {@link BatchInputArbiterListener}. 611 @Override onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime)612 public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) { 613 sTypingTimeRecorder.onEndBatchInput(eventTime); 614 sTimerProxy.cancelAllUpdateBatchInputTimers(); 615 if (mIsTrackingForActionDisabled) { 616 return; 617 } 618 if (DEBUG_LISTENER) { 619 Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", 620 mPointerId, aggregatedPointers.getPointerSize())); 621 } 622 sListener.onEndBatchInput(aggregatedPointers); 623 } 624 cancelBatchInput()625 private void cancelBatchInput() { 626 cancelAllPointerTrackers(); 627 mIsDetectingGesture = false; 628 if (!sInGesture) { 629 return; 630 } 631 sInGesture = false; 632 if (DEBUG_LISTENER) { 633 Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId)); 634 } 635 sListener.onCancelBatchInput(); 636 } 637 processMotionEvent(final MotionEvent me, final KeyDetector keyDetector)638 public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) { 639 final int action = me.getActionMasked(); 640 final long eventTime = me.getEventTime(); 641 if (action == MotionEvent.ACTION_MOVE) { 642 // When this pointer is the only active pointer and is showing a more keys panel, 643 // we should ignore other pointers' motion event. 644 final boolean shouldIgnoreOtherPointers = 645 isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1; 646 final int pointerCount = me.getPointerCount(); 647 for (int index = 0; index < pointerCount; index++) { 648 final int id = me.getPointerId(index); 649 if (shouldIgnoreOtherPointers && id != mPointerId) { 650 continue; 651 } 652 final int x = (int)me.getX(index); 653 final int y = (int)me.getY(index); 654 final PointerTracker tracker = getPointerTracker(id); 655 tracker.onMoveEvent(x, y, eventTime, me); 656 } 657 return; 658 } 659 final int index = me.getActionIndex(); 660 final int x = (int)me.getX(index); 661 final int y = (int)me.getY(index); 662 switch (action) { 663 case MotionEvent.ACTION_DOWN: 664 case MotionEvent.ACTION_POINTER_DOWN: 665 onDownEvent(x, y, eventTime, keyDetector); 666 break; 667 case MotionEvent.ACTION_UP: 668 case MotionEvent.ACTION_POINTER_UP: 669 onUpEvent(x, y, eventTime); 670 break; 671 case MotionEvent.ACTION_CANCEL: 672 onCancelEvent(x, y, eventTime); 673 break; 674 } 675 } 676 onDownEvent(final int x, final int y, final long eventTime, final KeyDetector keyDetector)677 private void onDownEvent(final int x, final int y, final long eventTime, 678 final KeyDetector keyDetector) { 679 setKeyDetectorInner(keyDetector); 680 if (DEBUG_EVENT) { 681 printTouchEvent("onDownEvent:", x, y, eventTime); 682 } 683 // Naive up-to-down noise filter. 684 final long deltaT = eventTime - mUpTime; 685 if (deltaT < sParams.mTouchNoiseThresholdTime) { 686 final int distance = getDistance(x, y, mLastX, mLastY); 687 if (distance < sParams.mTouchNoiseThresholdDistance) { 688 if (DEBUG_MODE) 689 Log.w(TAG, String.format("[%d] onDownEvent:" 690 + " ignore potential noise: time=%d distance=%d", 691 mPointerId, deltaT, distance)); 692 cancelTrackingForAction(); 693 return; 694 } 695 } 696 697 final Key key = getKeyOn(x, y); 698 mBogusMoveEventDetector.onActualDownEvent(x, y); 699 if (key != null && key.isModifier()) { 700 // Before processing a down event of modifier key, all pointers already being 701 // tracked should be released. 702 sPointerTrackerQueue.releaseAllPointers(eventTime); 703 } 704 sPointerTrackerQueue.add(this); 705 onDownEventInternal(x, y, eventTime); 706 if (!sGestureEnabler.shouldHandleGesture()) { 707 return; 708 } 709 // A gesture should start only from a non-modifier key. Note that the gesture detection is 710 // disabled when the key is repeating. 711 mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() 712 && key != null && !key.isModifier(); 713 if (mIsDetectingGesture) { 714 mBatchInputArbiter.addDownEventPoint(x, y, eventTime, 715 sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount()); 716 mGestureStrokeDrawingPoints.onDownEvent( 717 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); 718 } 719 } 720 isShowingMoreKeysPanel()721 /* package */ boolean isShowingMoreKeysPanel() { 722 return (mMoreKeysPanel != null); 723 } 724 dismissMoreKeysPanel()725 private void dismissMoreKeysPanel() { 726 if (isShowingMoreKeysPanel()) { 727 mMoreKeysPanel.dismissMoreKeysPanel(); 728 mMoreKeysPanel = null; 729 } 730 } 731 onDownEventInternal(final int x, final int y, final long eventTime)732 private void onDownEventInternal(final int x, final int y, final long eventTime) { 733 Key key = onDownKey(x, y, eventTime); 734 // Key selection by dragging finger is allowed when 1) key selection by dragging finger is 735 // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this 736 // pointer's KeyDetector always allows key selection by dragging finger, such as 737 // {@link MoreKeysKeyboard}. 738 mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger 739 || (key != null && key.isModifier()) 740 || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger(); 741 mKeyboardLayoutHasBeenChanged = false; 742 mIsTrackingForActionDisabled = false; 743 resetKeySelectionByDraggingFinger(); 744 if (key != null) { 745 // This onPress call may have changed keyboard layout. Those cases are detected at 746 // {@link #setKeyboard}. In those cases, we should update key according to the new 747 // keyboard layout. 748 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 749 key = onDownKey(x, y, eventTime); 750 } 751 752 startRepeatKey(key); 753 startLongPressTimer(key); 754 setPressedKeyGraphics(key, eventTime); 755 } 756 } 757 startKeySelectionByDraggingFinger(final Key key)758 private void startKeySelectionByDraggingFinger(final Key key) { 759 if (!mIsInDraggingFinger) { 760 mIsInSlidingKeyInput = key.isModifier(); 761 } 762 mIsInDraggingFinger = true; 763 } 764 resetKeySelectionByDraggingFinger()765 private void resetKeySelectionByDraggingFinger() { 766 mIsInDraggingFinger = false; 767 mIsInSlidingKeyInput = false; 768 sDrawingProxy.dismissSlidingKeyInputPreview(); 769 } 770 onGestureMoveEvent(final int x, final int y, final long eventTime, final boolean isMajorEvent, final Key key)771 private void onGestureMoveEvent(final int x, final int y, final long eventTime, 772 final boolean isMajorEvent, final Key key) { 773 if (!mIsDetectingGesture) { 774 return; 775 } 776 final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint( 777 x, y, eventTime, isMajorEvent, this); 778 // If the move event goes out from valid batch input area, cancel batch input. 779 if (!onValidArea) { 780 cancelBatchInput(); 781 return; 782 } 783 mGestureStrokeDrawingPoints.onMoveEvent( 784 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime)); 785 // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However, 786 // the gestured touch points are still being recorded in case the panel is dismissed. 787 if (isShowingMoreKeysPanel()) { 788 return; 789 } 790 if (!sInGesture && key != null && Character.isLetter(key.getCode()) 791 && mBatchInputArbiter.mayStartBatchInput(this)) { 792 sInGesture = true; 793 } 794 if (sInGesture) { 795 if (key != null) { 796 mBatchInputArbiter.updateBatchInput(eventTime, this); 797 } 798 showGestureTrail(); 799 } 800 } 801 onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me)802 private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { 803 if (DEBUG_MOVE_EVENT) { 804 printTouchEvent("onMoveEvent:", x, y, eventTime); 805 } 806 if (mIsTrackingForActionDisabled) { 807 return; 808 } 809 810 if (sGestureEnabler.shouldHandleGesture() && me != null) { 811 // Add historical points to gesture path. 812 final int pointerIndex = me.findPointerIndex(mPointerId); 813 final int historicalSize = me.getHistorySize(); 814 for (int h = 0; h < historicalSize; h++) { 815 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 816 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 817 final long historicalTime = me.getHistoricalEventTime(h); 818 onGestureMoveEvent(historicalX, historicalY, historicalTime, 819 false /* isMajorEvent */, null); 820 } 821 } 822 823 if (isShowingMoreKeysPanel()) { 824 final int translatedX = mMoreKeysPanel.translateX(x); 825 final int translatedY = mMoreKeysPanel.translateY(y); 826 mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); 827 onMoveKey(x, y); 828 if (mIsInSlidingKeyInput) { 829 sDrawingProxy.showSlidingKeyInputPreview(this); 830 } 831 return; 832 } 833 onMoveEventInternal(x, y, eventTime); 834 } 835 processDraggingFingerInToNewKey(final Key newKey, final int x, final int y, final long eventTime)836 private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y, 837 final long eventTime) { 838 // This onPress call may have changed keyboard layout. Those cases are detected 839 // at {@link #setKeyboard}. In those cases, we should update key according 840 // to the new keyboard layout. 841 Key key = newKey; 842 if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { 843 key = onMoveKey(x, y); 844 } 845 onMoveToNewKey(key, x, y); 846 if (mIsTrackingForActionDisabled) { 847 return; 848 } 849 startLongPressTimer(key); 850 setPressedKeyGraphics(key, eventTime); 851 } 852 processPhantomSuddenMoveHack(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)853 private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, 854 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 855 if (DEBUG_MODE) { 856 Log.w(TAG, String.format("[%d] onMoveEvent:" 857 + " phantom sudden move event (distance=%d) is translated to " 858 + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, 859 getDistance(x, y, lastX, lastY), 860 lastX, lastY, Constants.printableCode(oldKey.getCode()), 861 x, y, Constants.printableCode(key.getCode()))); 862 } 863 onUpEventInternal(x, y, eventTime); 864 onDownEventInternal(x, y, eventTime); 865 } 866 processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)867 private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, 868 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 869 if (DEBUG_MODE) { 870 final float keyDiagonal = (float)Math.hypot( 871 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 872 final float radiusRatio = 873 mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) 874 / keyDiagonal; 875 Log.w(TAG, String.format("[%d] onMoveEvent:" 876 + " bogus down-move-up event (raidus=%.2f key diagonal) is " 877 + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", 878 mPointerId, radiusRatio, 879 lastX, lastY, Constants.printableCode(oldKey.getCode()), 880 x, y, Constants.printableCode(key.getCode()))); 881 } 882 onUpEventInternal(x, y, eventTime); 883 onDownEventInternal(x, y, eventTime); 884 } 885 processDraggingFingerOutFromOldKey(final Key oldKey)886 private void processDraggingFingerOutFromOldKey(final Key oldKey) { 887 setReleasedKeyGraphics(oldKey); 888 callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */); 889 startKeySelectionByDraggingFinger(oldKey); 890 sTimerProxy.cancelKeyTimersOf(this); 891 } 892 dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)893 private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y, 894 final long eventTime, final Key oldKey, final int lastX, final int lastY) { 895 // The pointer has been slid in to the new key from the previous key, we must call 896 // onRelease() first to notify that the previous key has been released, then call 897 // onPress() to notify that the new key is being pressed. 898 processDraggingFingerOutFromOldKey(oldKey); 899 startRepeatKey(key); 900 if (mIsAllowedDraggingFinger) { 901 processDraggingFingerInToNewKey(key, x, y, eventTime); 902 } 903 // HACK: On some devices, quick successive touches may be reported as a sudden move by 904 // touch panel firmware. This hack detects such cases and translates the move event to 905 // successive up and down events. 906 // TODO: Should find a way to balance gesture detection and this hack. 907 else if (sNeedsPhantomSuddenMoveEventHack 908 && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) { 909 processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); 910 } 911 // HACK: On some devices, quick successive proximate touches may be reported as a bogus 912 // down-move-up event by touch panel firmware. This hack detects such cases and breaks 913 // these events into separate up and down events. 914 else if (sTypingTimeRecorder.isInFastTyping(eventTime) 915 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { 916 processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); 917 } 918 // HACK: If there are currently multiple touches, register the key even if the finger 919 // slides off the key. This defends against noise from some touch panels when there are 920 // close multiple touches. 921 // Caveat: When in chording input mode with a modifier key, we don't use this hack. 922 else if (getActivePointerTrackerCount() > 1 923 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 924 if (DEBUG_MODE) { 925 Log.w(TAG, String.format("[%d] onMoveEvent:" 926 + " detected sliding finger while multi touching", mPointerId)); 927 } 928 onUpEvent(x, y, eventTime); 929 cancelTrackingForAction(); 930 setReleasedKeyGraphics(oldKey); 931 } else { 932 if (!mIsDetectingGesture) { 933 cancelTrackingForAction(); 934 } 935 setReleasedKeyGraphics(oldKey); 936 } 937 } 938 dragFingerOutFromOldKey(final Key oldKey, final int x, final int y)939 private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) { 940 // The pointer has been slid out from the previous key, we must call onRelease() to 941 // notify that the previous key has been released. 942 processDraggingFingerOutFromOldKey(oldKey); 943 if (mIsAllowedDraggingFinger) { 944 onMoveToNewKey(null, x, y); 945 } else { 946 if (!mIsDetectingGesture) { 947 cancelTrackingForAction(); 948 } 949 } 950 } 951 onMoveEventInternal(final int x, final int y, final long eventTime)952 private void onMoveEventInternal(final int x, final int y, final long eventTime) { 953 final int lastX = mLastX; 954 final int lastY = mLastY; 955 final Key oldKey = mCurrentKey; 956 final Key newKey = onMoveKey(x, y); 957 958 if (sGestureEnabler.shouldHandleGesture()) { 959 // Register move event on gesture tracker. 960 onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); 961 if (sInGesture) { 962 mCurrentKey = null; 963 setReleasedKeyGraphics(oldKey); 964 return; 965 } 966 } 967 968 if (newKey != null) { 969 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 970 dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); 971 } else if (oldKey == null) { 972 // The pointer has been slid in to the new key, but the finger was not on any keys. 973 // In this case, we must call onPress() to notify that the new key is being pressed. 974 processDraggingFingerInToNewKey(newKey, x, y, eventTime); 975 } 976 } else { // newKey == null 977 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { 978 dragFingerOutFromOldKey(oldKey, x, y); 979 } 980 } 981 if (mIsInSlidingKeyInput) { 982 sDrawingProxy.showSlidingKeyInputPreview(this); 983 } 984 } 985 onUpEvent(final int x, final int y, final long eventTime)986 private void onUpEvent(final int x, final int y, final long eventTime) { 987 if (DEBUG_EVENT) { 988 printTouchEvent("onUpEvent :", x, y, eventTime); 989 } 990 991 sTimerProxy.cancelUpdateBatchInputTimer(this); 992 if (!sInGesture) { 993 if (mCurrentKey != null && mCurrentKey.isModifier()) { 994 // Before processing an up event of modifier key, all pointers already being 995 // tracked should be released. 996 sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); 997 } else { 998 sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); 999 } 1000 } 1001 onUpEventInternal(x, y, eventTime); 1002 sPointerTrackerQueue.remove(this); 1003 } 1004 1005 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 1006 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 1007 // "virtual" up event. 1008 @Override onPhantomUpEvent(final long eventTime)1009 public void onPhantomUpEvent(final long eventTime) { 1010 if (DEBUG_EVENT) { 1011 printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime); 1012 } 1013 onUpEventInternal(mLastX, mLastY, eventTime); 1014 cancelTrackingForAction(); 1015 } 1016 onUpEventInternal(final int x, final int y, final long eventTime)1017 private void onUpEventInternal(final int x, final int y, final long eventTime) { 1018 sTimerProxy.cancelKeyTimersOf(this); 1019 final boolean isInDraggingFinger = mIsInDraggingFinger; 1020 final boolean isInSlidingKeyInput = mIsInSlidingKeyInput; 1021 resetKeySelectionByDraggingFinger(); 1022 mIsDetectingGesture = false; 1023 final Key currentKey = mCurrentKey; 1024 mCurrentKey = null; 1025 final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode; 1026 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 1027 // Release the last pressed key. 1028 setReleasedKeyGraphics(currentKey); 1029 1030 if (isShowingMoreKeysPanel()) { 1031 if (!mIsTrackingForActionDisabled) { 1032 final int translatedX = mMoreKeysPanel.translateX(x); 1033 final int translatedY = mMoreKeysPanel.translateY(y); 1034 mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); 1035 } 1036 dismissMoreKeysPanel(); 1037 return; 1038 } 1039 1040 if (sInGesture) { 1041 if (currentKey != null) { 1042 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */); 1043 } 1044 if (mBatchInputArbiter.mayEndBatchInput( 1045 eventTime, getActivePointerTrackerCount(), this)) { 1046 sInGesture = false; 1047 } 1048 showGestureTrail(); 1049 return; 1050 } 1051 1052 if (mIsTrackingForActionDisabled) { 1053 return; 1054 } 1055 if (currentKey != null && currentKey.isRepeatable() 1056 && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) { 1057 return; 1058 } 1059 detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); 1060 if (isInSlidingKeyInput) { 1061 callListenerOnFinishSlidingInput(); 1062 } 1063 } 1064 onShowMoreKeysPanel(final MoreKeysPanel panel)1065 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 1066 setReleasedKeyGraphics(mCurrentKey); 1067 final int translatedX = panel.translateX(mLastX); 1068 final int translatedY = panel.translateY(mLastY); 1069 panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); 1070 mMoreKeysPanel = panel; 1071 } 1072 1073 @Override cancelTrackingForAction()1074 public void cancelTrackingForAction() { 1075 if (isShowingMoreKeysPanel()) { 1076 return; 1077 } 1078 mIsTrackingForActionDisabled = true; 1079 } 1080 isInOperation()1081 public boolean isInOperation() { 1082 return !mIsTrackingForActionDisabled; 1083 } 1084 cancelLongPressTimer()1085 public void cancelLongPressTimer() { 1086 sTimerProxy.cancelLongPressTimerOf(this); 1087 } 1088 onLongPressed()1089 public void onLongPressed() { 1090 resetKeySelectionByDraggingFinger(); 1091 cancelTrackingForAction(); 1092 setReleasedKeyGraphics(mCurrentKey); 1093 sPointerTrackerQueue.remove(this); 1094 } 1095 onCancelEvent(final int x, final int y, final long eventTime)1096 private void onCancelEvent(final int x, final int y, final long eventTime) { 1097 if (DEBUG_EVENT) { 1098 printTouchEvent("onCancelEvt:", x, y, eventTime); 1099 } 1100 1101 cancelBatchInput(); 1102 cancelAllPointerTrackers(); 1103 sPointerTrackerQueue.releaseAllPointers(eventTime); 1104 onCancelEventInternal(); 1105 } 1106 onCancelEventInternal()1107 private void onCancelEventInternal() { 1108 sTimerProxy.cancelKeyTimersOf(this); 1109 setReleasedKeyGraphics(mCurrentKey); 1110 resetKeySelectionByDraggingFinger(); 1111 dismissMoreKeysPanel(); 1112 } 1113 isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, final Key newKey)1114 private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, 1115 final Key newKey) { 1116 final Key curKey = mCurrentKey; 1117 if (newKey == curKey) { 1118 return false; 1119 } 1120 if (curKey == null /* && newKey != null */) { 1121 return true; 1122 } 1123 // Here curKey points to the different key from newKey. 1124 final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( 1125 mIsInSlidingKeyInput); 1126 final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); 1127 if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { 1128 if (DEBUG_MODE) { 1129 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) 1130 / mKeyboard.mMostCommonKeyWidth; 1131 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1132 +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); 1133 } 1134 return true; 1135 } 1136 if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime) 1137 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { 1138 if (DEBUG_MODE) { 1139 final float keyDiagonal = (float)Math.hypot( 1140 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 1141 final float lengthFromDownRatio = 1142 mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal; 1143 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" 1144 + " %.2f key diagonal from virtual down point", 1145 mPointerId, lengthFromDownRatio)); 1146 } 1147 return true; 1148 } 1149 return false; 1150 } 1151 startLongPressTimer(final Key key)1152 private void startLongPressTimer(final Key key) { 1153 // Note that we need to cancel all active long press shift key timers if any whenever we 1154 // start a new long press timer for both non-shift and shift keys. 1155 sTimerProxy.cancelLongPressShiftKeyTimers(); 1156 if (sInGesture) return; 1157 if (key == null) return; 1158 if (!key.isLongPressEnabled()) return; 1159 // Caveat: Please note that isLongPressEnabled() can be true even if the current key 1160 // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger 1161 // mode, we will disable long press timer of such key. 1162 // We always need to start the long press timer if the key has its more keys regardless of 1163 // whether or not we are in the dragging finger mode. 1164 if (mIsInDraggingFinger && key.getMoreKeys() == null) return; 1165 1166 final int delay = getLongPressTimeout(key.getCode()); 1167 if (delay <= 0) return; 1168 sTimerProxy.startLongPressTimerOf(this, delay); 1169 } 1170 getLongPressTimeout(final int code)1171 private int getLongPressTimeout(final int code) { 1172 if (code == Constants.CODE_SHIFT) { 1173 return sParams.mLongPressShiftLockTimeout; 1174 } 1175 final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; 1176 if (mIsInSlidingKeyInput) { 1177 // We use longer timeout for sliding finger input started from the modifier key. 1178 return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; 1179 } 1180 return longpressTimeout; 1181 } 1182 detectAndSendKey(final Key key, final int x, final int y, final long eventTime)1183 private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { 1184 if (key == null) { 1185 callListenerOnCancelInput(); 1186 return; 1187 } 1188 1189 final int code = key.getCode(); 1190 callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */); 1191 callListenerOnRelease(key, code, false /* withSliding */); 1192 } 1193 startRepeatKey(final Key key)1194 private void startRepeatKey(final Key key) { 1195 if (sInGesture) return; 1196 if (key == null) return; 1197 if (!key.isRepeatable()) return; 1198 // Don't start key repeat when we are in the dragging finger mode. 1199 if (mIsInDraggingFinger) return; 1200 final int startRepeatCount = 1; 1201 startKeyRepeatTimer(startRepeatCount); 1202 } 1203 onKeyRepeat(final int code, final int repeatCount)1204 public void onKeyRepeat(final int code, final int repeatCount) { 1205 final Key key = getKey(); 1206 if (key == null || key.getCode() != code) { 1207 mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; 1208 return; 1209 } 1210 mCurrentRepeatingKeyCode = code; 1211 mIsDetectingGesture = false; 1212 final int nextRepeatCount = repeatCount + 1; 1213 startKeyRepeatTimer(nextRepeatCount); 1214 callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); 1215 callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(), 1216 true /* isKeyRepeat */); 1217 } 1218 startKeyRepeatTimer(final int repeatCount)1219 private void startKeyRepeatTimer(final int repeatCount) { 1220 final int delay = 1221 (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval; 1222 sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay); 1223 } 1224 printTouchEvent(final String title, final int x, final int y, final long eventTime)1225 private void printTouchEvent(final String title, final int x, final int y, 1226 final long eventTime) { 1227 final Key key = mKeyDetector.detectHitKey(x, y); 1228 final String code = (key == null ? "none" : Constants.printableCode(key.getCode())); 1229 Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, 1230 (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code)); 1231 } 1232 } 1233