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