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