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