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         if (isModifierCode(code) || code == Keyboard.CODE_DELETE
    357                 || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE)
    358             return false;
    359         return true;
    360     }
    361 
    362     public int getLastX() {
    363         return mLastX;
    364     }
    365 
    366     public int getLastY() {
    367         return mLastY;
    368     }
    369 
    370     public long getDownTime() {
    371         return mDownTime;
    372     }
    373 
    374     private int onDownKey(int x, int y, long eventTime) {
    375         mDownTime = eventTime;
    376         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
    377     }
    378 
    379     private int onMoveKeyInternal(int x, int y) {
    380         mLastX = x;
    381         mLastY = y;
    382         return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
    383     }
    384 
    385     private int onMoveKey(int x, int y) {
    386         return onMoveKeyInternal(x, y);
    387     }
    388 
    389     private int onMoveToNewKey(int keyIndex, int x, int y) {
    390         mKeyIndex = keyIndex;
    391         mKeyX = x;
    392         mKeyY = y;
    393         return keyIndex;
    394     }
    395 
    396     private int onUpKey(int x, int y, long eventTime) {
    397         mUpTime = eventTime;
    398         mKeyIndex = KeyDetector.NOT_A_KEY;
    399         return onMoveKeyInternal(x, y);
    400     }
    401 
    402     public void processMotionEvent(int action, int x, int y, long eventTime,
    403             KeyEventHandler handler) {
    404         switch (action) {
    405         case MotionEvent.ACTION_DOWN:
    406         case MotionEvent.ACTION_POINTER_DOWN:
    407             onDownEvent(x, y, eventTime, handler);
    408             break;
    409         case MotionEvent.ACTION_UP:
    410         case MotionEvent.ACTION_POINTER_UP:
    411             onUpEvent(x, y, eventTime);
    412             break;
    413         case MotionEvent.ACTION_MOVE:
    414             onMoveEvent(x, y, eventTime);
    415             break;
    416         case MotionEvent.ACTION_CANCEL:
    417             onCancelEvent(x, y, eventTime);
    418             break;
    419         }
    420     }
    421 
    422     public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
    423         if (DEBUG_EVENT)
    424             printTouchEvent("onDownEvent:", x, y, eventTime);
    425 
    426         mDrawingProxy = handler.getDrawingProxy();
    427         mTimerProxy = handler.getTimerProxy();
    428         setKeyboardActionListener(handler.getKeyboardActionListener());
    429         setKeyDetectorInner(handler.getKeyDetector());
    430         // Naive up-to-down noise filter.
    431         final long deltaT = eventTime - mUpTime;
    432         if (deltaT < sTouchNoiseThresholdMillis) {
    433             final int dx = x - mLastX;
    434             final int dy = y - mLastY;
    435             final int distanceSquared = (dx * dx + dy * dy);
    436             if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
    437                 if (DEBUG_MODE)
    438                     Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
    439                             + " distance=" + distanceSquared);
    440                 mKeyAlreadyProcessed = true;
    441                 return;
    442             }
    443         }
    444 
    445         final PointerTrackerQueue queue = sPointerTrackerQueue;
    446         if (queue != null) {
    447             if (isOnModifierKey(x, y)) {
    448                 // Before processing a down event of modifier key, all pointers already being
    449                 // tracked should be released.
    450                 queue.releaseAllPointers(eventTime);
    451             }
    452             queue.add(this);
    453         }
    454         onDownEventInternal(x, y, eventTime);
    455     }
    456 
    457     private void onDownEventInternal(int x, int y, long eventTime) {
    458         int keyIndex = onDownKey(x, y, eventTime);
    459         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
    460         // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
    461         mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
    462                 || mKeyDetector.alwaysAllowsSlidingInput();
    463         mKeyboardLayoutHasBeenChanged = false;
    464         mKeyAlreadyProcessed = false;
    465         mIsRepeatableKey = false;
    466         mIsInSlidingKeyInput = false;
    467         mIgnoreModifierKey = false;
    468         if (isValidKeyIndex(keyIndex)) {
    469             // This onPress call may have changed keyboard layout. Those cases are detected at
    470             // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
    471             // keyboard layout.
    472             if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
    473                 keyIndex = onDownKey(x, y, eventTime);
    474 
    475             startRepeatKey(keyIndex);
    476             startLongPressTimer(keyIndex);
    477             setPressedKeyGraphics(keyIndex);
    478         }
    479     }
    480 
    481     private void startSlidingKeyInput(Key key) {
    482         if (!mIsInSlidingKeyInput)
    483             mIgnoreModifierKey = isModifierCode(key.mCode);
    484         mIsInSlidingKeyInput = true;
    485     }
    486 
    487     public void onMoveEvent(int x, int y, long eventTime) {
    488         if (DEBUG_MOVE_EVENT)
    489             printTouchEvent("onMoveEvent:", x, y, eventTime);
    490         if (mKeyAlreadyProcessed)
    491             return;
    492 
    493         final int lastX = mLastX;
    494         final int lastY = mLastY;
    495         final int oldKeyIndex = mKeyIndex;
    496         final Key oldKey = getKey(oldKeyIndex);
    497         int keyIndex = onMoveKey(x, y);
    498         if (isValidKeyIndex(keyIndex)) {
    499             if (oldKey == null) {
    500                 // The pointer has been slid in to the new key, but the finger was not on any keys.
    501                 // In this case, we must call onPress() to notify that the new key is being pressed.
    502                 // This onPress call may have changed keyboard layout. Those cases are detected at
    503                 // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
    504                 // new keyboard layout.
    505                 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
    506                     keyIndex = onMoveKey(x, y);
    507                 onMoveToNewKey(keyIndex, x, y);
    508                 startLongPressTimer(keyIndex);
    509                 setPressedKeyGraphics(keyIndex);
    510             } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
    511                 // The pointer has been slid in to the new key from the previous key, we must call
    512                 // onRelease() first to notify that the previous key has been released, then call
    513                 // onPress() to notify that the new key is being pressed.
    514                 setReleasedKeyGraphics(oldKeyIndex);
    515                 callListenerOnRelease(oldKey, oldKey.mCode, true);
    516                 startSlidingKeyInput(oldKey);
    517                 mTimerProxy.cancelKeyTimers();
    518                 startRepeatKey(keyIndex);
    519                 if (mIsAllowedSlidingKeyInput) {
    520                     // This onPress call may have changed keyboard layout. Those cases are detected
    521                     // at {@link #setKeyboard}. In those cases, we should update keyIndex according
    522                     // to the new keyboard layout.
    523                     if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
    524                         keyIndex = onMoveKey(x, y);
    525                     onMoveToNewKey(keyIndex, x, y);
    526                     startLongPressTimer(keyIndex);
    527                     setPressedKeyGraphics(keyIndex);
    528                 } else {
    529                     // HACK: On some devices, quick successive touches may be translated to sudden
    530                     // move by touch panel firmware. This hack detects the case and translates the
    531                     // move event to successive up and down events.
    532                     final int dx = x - lastX;
    533                     final int dy = y - lastY;
    534                     final int lastMoveSquared = dx * dx + dy * dy;
    535                     if (lastMoveSquared >= mKeyQuarterWidthSquared) {
    536                         if (DEBUG_MODE)
    537                             Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
    538                                     + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
    539                         onUpEventInternal(lastX, lastY, eventTime);
    540                         onDownEventInternal(x, y, eventTime);
    541                     } else {
    542                         mKeyAlreadyProcessed = true;
    543                         setReleasedKeyGraphics(oldKeyIndex);
    544                     }
    545                 }
    546             }
    547         } else {
    548             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
    549                 // The pointer has been slid out from the previous key, we must call onRelease() to
    550                 // notify that the previous key has been released.
    551                 setReleasedKeyGraphics(oldKeyIndex);
    552                 callListenerOnRelease(oldKey, oldKey.mCode, true);
    553                 startSlidingKeyInput(oldKey);
    554                 mTimerProxy.cancelLongPressTimer();
    555                 if (mIsAllowedSlidingKeyInput) {
    556                     onMoveToNewKey(keyIndex, x, y);
    557                 } else {
    558                     mKeyAlreadyProcessed = true;
    559                 }
    560             }
    561         }
    562     }
    563 
    564     public void onUpEvent(int x, int y, long eventTime) {
    565         if (DEBUG_EVENT)
    566             printTouchEvent("onUpEvent  :", x, y, eventTime);
    567 
    568         final PointerTrackerQueue queue = sPointerTrackerQueue;
    569         if (queue != null) {
    570             if (isModifier()) {
    571                 // Before processing an up event of modifier key, all pointers already being
    572                 // tracked should be released.
    573                 queue.releaseAllPointersExcept(this, eventTime);
    574             } else {
    575                 queue.releaseAllPointersOlderThan(this, eventTime);
    576             }
    577             queue.remove(this);
    578         }
    579         onUpEventInternal(x, y, eventTime);
    580     }
    581 
    582     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
    583     // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
    584     // "virtual" up event.
    585     public void onPhantomUpEvent(int x, int y, long eventTime) {
    586         if (DEBUG_EVENT)
    587             printTouchEvent("onPhntEvent:", x, y, eventTime);
    588         onUpEventInternal(x, y, eventTime);
    589         mKeyAlreadyProcessed = true;
    590     }
    591 
    592     private void onUpEventInternal(int x, int y, long eventTime) {
    593         mTimerProxy.cancelKeyTimers();
    594         mDrawingProxy.cancelShowKeyPreview(this);
    595         mIsInSlidingKeyInput = false;
    596         final int keyX, keyY;
    597         if (isMajorEnoughMoveToBeOnNewKey(x, y, onMoveKey(x, y))) {
    598             keyX = x;
    599             keyY = y;
    600         } else {
    601             // Use previous fixed key coordinates.
    602             keyX = mKeyX;
    603             keyY = mKeyY;
    604         }
    605         final int keyIndex = onUpKey(keyX, keyY, eventTime);
    606         setReleasedKeyGraphics(keyIndex);
    607         if (mIsShowingMoreKeysPanel) {
    608             mDrawingProxy.dismissMoreKeysPanel();
    609             mIsShowingMoreKeysPanel = false;
    610         }
    611         if (mKeyAlreadyProcessed)
    612             return;
    613         if (!mIsRepeatableKey) {
    614             detectAndSendKey(keyIndex, keyX, keyY);
    615         }
    616     }
    617 
    618     public void onShowMoreKeysPanel(int x, int y, long eventTime, KeyEventHandler handler) {
    619         onLongPressed();
    620         onDownEvent(x, y, eventTime, handler);
    621         mIsShowingMoreKeysPanel = true;
    622     }
    623 
    624     public void onLongPressed() {
    625         mKeyAlreadyProcessed = true;
    626         setReleasedKeyGraphics(mKeyIndex);
    627         final PointerTrackerQueue queue = sPointerTrackerQueue;
    628         if (queue != null) {
    629             queue.remove(this);
    630         }
    631     }
    632 
    633     public void onCancelEvent(int x, int y, long eventTime) {
    634         if (DEBUG_EVENT)
    635             printTouchEvent("onCancelEvt:", x, y, eventTime);
    636 
    637         final PointerTrackerQueue queue = sPointerTrackerQueue;
    638         if (queue != null) {
    639             queue.releaseAllPointersExcept(this, eventTime);
    640             queue.remove(this);
    641         }
    642         onCancelEventInternal();
    643     }
    644 
    645     private void onCancelEventInternal() {
    646         mTimerProxy.cancelKeyTimers();
    647         mDrawingProxy.cancelShowKeyPreview(this);
    648         setReleasedKeyGraphics(mKeyIndex);
    649         mIsInSlidingKeyInput = false;
    650         if (mIsShowingMoreKeysPanel) {
    651             mDrawingProxy.dismissMoreKeysPanel();
    652             mIsShowingMoreKeysPanel = false;
    653         }
    654     }
    655 
    656     private void startRepeatKey(int keyIndex) {
    657         final Key key = getKey(keyIndex);
    658         if (key != null && key.mRepeatable) {
    659             onRepeatKey(keyIndex);
    660             mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this);
    661             mIsRepeatableKey = true;
    662         } else {
    663             mIsRepeatableKey = false;
    664         }
    665     }
    666 
    667     public void onRepeatKey(int keyIndex) {
    668         Key key = getKey(keyIndex);
    669         if (key != null) {
    670             detectAndSendKey(keyIndex, key.mX, key.mY);
    671         }
    672     }
    673 
    674     private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
    675         if (mKeys == null || mKeyDetector == null)
    676             throw new NullPointerException("keyboard and/or key detector not set");
    677         int curKey = mKeyIndex;
    678         if (newKey == curKey) {
    679             return false;
    680         } else if (isValidKeyIndex(curKey)) {
    681             return mKeys.get(curKey).squaredDistanceToEdge(x, y)
    682                     >= mKeyDetector.getKeyHysteresisDistanceSquared();
    683         } else {
    684             return true;
    685         }
    686     }
    687 
    688     private void startLongPressTimer(int keyIndex) {
    689         Key key = getKey(keyIndex);
    690         if (key == null) return;
    691         if (key.mCode == Keyboard.CODE_SHIFT) {
    692             if (sLongPressShiftKeyTimeout > 0) {
    693                 mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this);
    694             }
    695         } else if (key.mCode == Keyboard.CODE_SPACE) {
    696             if (sLongPressSpaceKeyTimeout > 0) {
    697                 mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this);
    698             }
    699         } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
    700             // We need not start long press timer on the key which has manual temporary upper case
    701             // code defined and the keyboard is in manual temporary upper case mode.
    702             return;
    703         } else if (sKeyboardSwitcher.isInMomentarySwitchState()) {
    704             // We use longer timeout for sliding finger input started from the symbols mode key.
    705             mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this);
    706         } else {
    707             mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this);
    708         }
    709     }
    710 
    711     private void detectAndSendKey(int index, int x, int y) {
    712         final Key key = getKey(index);
    713         if (key == null) {
    714             callListenerOnCancelInput();
    715             return;
    716         }
    717         if (key.mOutputText != null) {
    718             callListenerOnTextInput(key);
    719             callListenerOnRelease(key, key.mCode, false);
    720         } else {
    721             int code = key.mCode;
    722             final int[] codes = mKeyDetector.newCodeArray();
    723             mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
    724 
    725             // If keyboard is in manual temporary upper case state and key has manual temporary
    726             // uppercase letter as key hint letter, alternate character code should be sent.
    727             if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
    728                 code = key.mHintLabel.charAt(0);
    729                 codes[0] = code;
    730             }
    731 
    732             // Swap the first and second values in the codes array if the primary code is not the
    733             // first value but the second value in the array. This happens when key debouncing is
    734             // in effect.
    735             if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
    736                 codes[1] = codes[0];
    737                 codes[0] = code;
    738             }
    739             callListenerOnCodeInput(key, code, codes, x, y);
    740             callListenerOnRelease(key, code, false);
    741         }
    742     }
    743 
    744     private long mPreviousEventTime;
    745 
    746     private void printTouchEvent(String title, int x, int y, long eventTime) {
    747         final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
    748         final Key key = getKey(keyIndex);
    749         final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
    750         final long delta = eventTime - mPreviousEventTime;
    751         Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
    752                 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
    753         mPreviousEventTime = eventTime;
    754     }
    755 
    756     private static String keyCodePrintable(int primaryCode) {
    757         final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
    758         return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
    759     }
    760 }
    761