PointerTracker.java revision 5a309f57155fb95667c2ccdda730eaf175de8876
16a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka/*
26a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * Copyright (C) 2010 Google Inc.
36a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka *
46a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not
56a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * use this file except in compliance with the License. You may obtain a copy of
66a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * the License at
76a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka *
86a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0
96a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka *
106a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
116a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
126a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
136a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * License for the specific language governing permissions and limitations under
146a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka * the License.
156a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka */
166a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
175a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokapackage com.android.inputmethod.keyboard;
186a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
195a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyboardView.UIHandler;
205a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokaimport com.android.inputmethod.latin.LatinIME;
215a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokaimport com.android.inputmethod.latin.R;
226a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
235e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaokaimport android.content.res.Resources;
2440a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaokaimport android.util.Log;
25c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaokaimport android.view.MotionEvent;
266a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
276a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaokapublic class PointerTracker {
2840a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka    private static final String TAG = "PointerTracker";
2940a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka    private static final boolean DEBUG = false;
30c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    private static final boolean DEBUG_MOVE = false;
3140a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka
326a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public interface UIProxy {
336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        public void invalidateKey(Key key);
346a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        public void showPreview(int keyIndex, PointerTracker tracker);
355e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        public boolean hasDistinctMultitouch();
366a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
376a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
38c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka    public final int mPointerId;
39c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka
406a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // Timing constants
415e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka    private final int mDelayBeforeKeyRepeatStart;
425e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka    private final int mLongPressKeyTimeout;
434189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    private final int mLongPressShiftKeyTimeout;
445e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka    private final int mMultiTapKeyTimeout;
456a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
466a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // Miscellaneous constants
475a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    private static final int NOT_A_KEY = KeyDetector.NOT_A_KEY;
485a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
496a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
506a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private final UIProxy mProxy;
516a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private final UIHandler mHandler;
522085d43daf44752deae1b6b00a14cb0f517d69cbTadashi G. Takaoka    private final KeyDetector mKeyDetector;
535a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    private KeyboardActionListener mListener;
54c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    private final boolean mHasDistinctMultitouch;
556a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
565a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    private Keyboard mKeyboard;
576a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private Key[] mKeys;
58eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka    private int mKeyHysteresisDistanceSquared = -1;
596a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
606e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    private final KeyState mKeyState;
616a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
62c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    // true if event is already translated to a key action (long press or mini-keyboard)
63c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    private boolean mKeyAlreadyProcessed;
64c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka
656252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka    // true if this pointer is repeatable key
666252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka    private boolean mIsRepeatableKey;
676252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka
686a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // For multi-tap
696a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private int mLastSentIndex;
706a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private int mTapCount;
716a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private long mLastTapTime;
726a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private boolean mInMultiTap;
736a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private final StringBuilder mPreviewLabel = new StringBuilder(1);
746a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
756a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // pressed key
76a521b31d96ff51672a3bc399a27a8f02dca840faTadashi G. Takaoka    private int mPreviousKey = NOT_A_KEY;
776a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
786e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    // This class keeps track of a key index and a position where this pointer is.
796e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    private static class KeyState {
806e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private final KeyDetector mKeyDetector;
816e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
826e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        // The position and time at which first down event occurred.
836e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mStartX;
846e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mStartY;
856e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private long mDownTime;
866e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
876e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        // The current key index where this pointer is.
886e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mKeyIndex = NOT_A_KEY;
896e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        // The position where mKeyIndex was recognized for the first time.
906e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mKeyX;
916e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mKeyY;
926e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
936e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        // Last pointer position.
946e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mLastX;
956e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int mLastY;
966e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
976e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public KeyState(KeyDetector keyDetecor) {
986e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mKeyDetector = keyDetecor;
996e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1006e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1016e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getKeyIndex() {
1026e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mKeyIndex;
1036e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1046e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1056e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getKeyX() {
1066e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mKeyX;
1076e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1086e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1096e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getKeyY() {
1106e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mKeyY;
1116e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1126e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1136e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getStartX() {
1146e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mStartX;
1156e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1166e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1176e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getStartY() {
1186e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mStartY;
1196e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1206e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1216e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public long getDownTime() {
1226e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mDownTime;
1236e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1246e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1256e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getLastX() {
1266e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mLastX;
1276e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1286e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1296e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int getLastY() {
1306e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mLastY;
1316e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1326e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1336e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int onDownKey(int x, int y, long eventTime) {
1346e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mStartX = x;
1356e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mStartY = y;
1366e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mDownTime = eventTime;
1376e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1386e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
1396e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1406e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1416e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        private int onMoveKeyInternal(int x, int y) {
1426e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mLastX = x;
1436e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mLastY = y;
1446e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
1456e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1466e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1476e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int onMoveKey(int x, int y) {
1486e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return onMoveKeyInternal(x, y);
1496e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1506e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1516e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int onMoveToNewKey(int keyIndex, int x, int y) {
1526e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mKeyIndex = keyIndex;
1536e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mKeyX = x;
1546e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mKeyY = y;
1556e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return keyIndex;
1566e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1576e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1586e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public int onUpKey(int x, int y) {
1596e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            return onMoveKeyInternal(x, y);
1606e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1616e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
1626e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        public void onSetKeyboard() {
1636e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
1646e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        }
1656e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    }
1666e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
167c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
1685e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka            Resources res) {
1696a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (proxy == null || handler == null || keyDetector == null)
1706a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new NullPointerException();
171c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka        mPointerId = id;
1726a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mProxy = proxy;
1736a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler = handler;
1746a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mKeyDetector = keyDetector;
1756e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        mKeyState = new KeyState(keyDetector);
1765e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
1775e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
1785e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
1794189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
1805e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mMultiTapKeyTimeout = res.getInteger(R.integer.config_multi_tap_key_timeout);
1816a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        resetMultiTap();
1826a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
1836a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
1845a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    public void setOnKeyboardActionListener(KeyboardActionListener listener) {
1856a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mListener = listener;
1866a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
1876a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
1885a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    public void setKeyboard(Keyboard keyboard, Key[] keys, float keyHysteresisDistance) {
18966e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        if (keyboard == null || keys == null || keyHysteresisDistance < 0)
1906a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new IllegalArgumentException();
19166e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        mKeyboard = keyboard;
1926a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mKeys = keys;
193eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka        mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
194efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        // Update current key index because keyboard layout has been changed.
1956e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        mKeyState.onSetKeyboard();
1966a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
1976a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
198c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka    private boolean isValidKeyIndex(int keyIndex) {
199c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        return keyIndex >= 0 && keyIndex < mKeys.length;
200c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka    }
201c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka
2026a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public Key getKey(int keyIndex) {
203c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
2046a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2056a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
2062aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    private boolean isModifierInternal(int keyIndex) {
2072aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka        Key key = getKey(keyIndex);
20840a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka        if (key == null)
20940a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka            return false;
21040a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka        int primaryCode = key.codes[0];
2115a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka        return primaryCode == Keyboard.KEYCODE_SHIFT
2125a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka                || primaryCode == Keyboard.KEYCODE_MODE_CHANGE;
21340a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka    }
21440a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka
2152aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    public boolean isModifier() {
2166e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return isModifierInternal(mKeyState.getKeyIndex());
2172aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    }
2182aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka
2192aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    public boolean isOnModifierKey(int x, int y) {
2202aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
2212aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    }
2222aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka
2233a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka    public boolean isSpaceKey(int keyIndex) {
2243a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka        Key key = getKey(keyIndex);
2253a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka        return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE;
2263a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka    }
2273a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka
2284189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    public void releaseKey() {
2294189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        updateKeyGraphics(NOT_A_KEY);
2304189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    }
2314189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka
2324189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    private void updateKeyGraphics(int keyIndex) {
2336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        int oldKeyIndex = mPreviousKey;
2346a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mPreviousKey = keyIndex;
2356a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (keyIndex != oldKeyIndex) {
236c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            if (isValidKeyIndex(oldKeyIndex)) {
2376a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                // if new key index is not a key, old key was just released inside of the key.
2386a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                final boolean inside = (keyIndex == NOT_A_KEY);
2396a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mKeys[oldKeyIndex].onReleased(inside);
2406a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mProxy.invalidateKey(mKeys[oldKeyIndex]);
2416a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
242c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            if (isValidKeyIndex(keyIndex)) {
2436a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mKeys[keyIndex].onPressed();
2446a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mProxy.invalidateKey(mKeys[keyIndex]);
2456a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
2466a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
2476a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2486a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
249c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    public void setAlreadyProcessed() {
250c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka        mKeyAlreadyProcessed = true;
251c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    }
252c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka
253c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    public void onTouchEvent(int action, int x, int y, long eventTime) {
254c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        switch (action) {
255c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_MOVE:
256c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            onMoveEvent(x, y, eventTime);
257c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
258c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_DOWN:
259c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_POINTER_DOWN:
260c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            onDownEvent(x, y, eventTime);
261c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
262c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_UP:
263c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_POINTER_UP:
264c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            onUpEvent(x, y, eventTime);
265c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
266c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_CANCEL:
267c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            onCancelEvent(x, y, eventTime);
268c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
269c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        }
270c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    }
271c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka
272400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public void onDownEvent(int x, int y, long eventTime) {
273efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        if (DEBUG)
274efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka            debugLog("onDownEvent:", x, y);
2756e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int keyIndex = mKeyState.onDownKey(x, y, eventTime);
276c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka        mKeyAlreadyProcessed = false;
2776252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka        mIsRepeatableKey = false;
2786a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        checkMultiTap(eventTime, keyIndex);
2796a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (mListener != null) {
2809dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa            if (isValidKeyIndex(keyIndex)) {
2819dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa                mListener.onPress(mKeys[keyIndex].codes[0]);
2829dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa                // This onPress call may have changed keyboard layout and have updated mKeyIndex.
2839dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa                // If that's the case, mKeyIndex has been updated in setKeyboard().
2849dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa                keyIndex = mKeyState.getKeyIndex();
2859dce586eaa8a99fac05da398694d6e26b2a6dfa8Ken Wakasa            }
2866a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
287c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (isValidKeyIndex(keyIndex)) {
288c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            if (mKeys[keyIndex].repeatable) {
289c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka                repeatKey(keyIndex);
2905e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
2916252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka                mIsRepeatableKey = true;
292c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            }
293adf24e2eb49acd32d2655a3964f68da1e54c05ecTadashi G. Takaoka            startLongPressTimer(keyIndex);
2946a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
2954189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        showKeyPreviewAndUpdateKeyGraphics(keyIndex);
2966a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2976a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
298400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public void onMoveEvent(int x, int y, long eventTime) {
299efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        if (DEBUG_MOVE)
300efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka            debugLog("onMoveEvent:", x, y);
301e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka        if (mKeyAlreadyProcessed)
302e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka            return;
3036e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        KeyState keyState = mKeyState;
304c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        final int keyIndex = keyState.onMoveKey(x, y);
305c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        final Key oldKey = getKey(keyState.getKeyIndex());
306c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        if (isValidKeyIndex(keyIndex)) {
307c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka            if (oldKey == null) {
3086e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka                keyState.onMoveToNewKey(keyIndex, x, y);
309adf24e2eb49acd32d2655a3964f68da1e54c05ecTadashi G. Takaoka                startLongPressTimer(keyIndex);
3106e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
311c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka                if (mListener != null)
312c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka                    mListener.onRelease(oldKey.codes[0]);
3136a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                resetMultiTap();
3146e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka                keyState.onMoveToNewKey(keyIndex, x, y);
315adf24e2eb49acd32d2655a3964f68da1e54c05ecTadashi G. Takaoka                startLongPressTimer(keyIndex);
3166a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
3176a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        } else {
318c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka            if (oldKey != null) {
319c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka                if (mListener != null)
320c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka                    mListener.onRelease(oldKey.codes[0]);
3216e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka                keyState.onMoveToNewKey(keyIndex, x ,y);
3228aaab7c2867414f53228aa4faa2b8751105ac6ddTadashi G. Takaoka                mHandler.cancelLongPressTimers();
3236e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
32407221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka                resetMultiTap();
3256e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka                keyState.onMoveToNewKey(keyIndex, x ,y);
3268aaab7c2867414f53228aa4faa2b8751105ac6ddTadashi G. Takaoka                mHandler.cancelLongPressTimers();
32707221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka            }
3286a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
3294189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex());
3306a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3316a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
332400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public void onUpEvent(int x, int y, long eventTime) {
33340a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka        if (DEBUG)
334400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka            debugLog("onUpEvent  :", x, y);
3354189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
336efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        if (mKeyAlreadyProcessed)
337efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka            return;
3386a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelKeyTimers();
3396a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelPopupPreview();
3406e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int keyIndex = mKeyState.onUpKey(x, y);
3416e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        if (isMinorMoveBounce(x, y, keyIndex)) {
3426e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            // Use previous fixed key index and coordinates.
3436e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            keyIndex = mKeyState.getKeyIndex();
3446e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            x = mKeyState.getKeyX();
3456e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            y = mKeyState.getKeyY();
3466a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
3476252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka        if (!mIsRepeatableKey) {
3486e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            detectAndSendKey(keyIndex, x, y, eventTime);
3496a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
3506e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
351c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (isValidKeyIndex(keyIndex))
3526a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mProxy.invalidateKey(mKeys[keyIndex]);
3536a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3546a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
355400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public void onCancelEvent(int x, int y, long eventTime) {
35640a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka        if (DEBUG)
357400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka            debugLog("onCancelEvt:", x, y);
3586a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelKeyTimers();
3596a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelPopupPreview();
3604189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
3616e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int keyIndex = mKeyState.getKeyIndex();
362c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (isValidKeyIndex(keyIndex))
3636a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka           mProxy.invalidateKey(mKeys[keyIndex]);
3646a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3656a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
3666a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public void repeatKey(int keyIndex) {
367c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        Key key = getKey(keyIndex);
368c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (key != null) {
369c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            // While key is repeating, because there is no need to handle multi-tap key, we can
370c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            // pass -1 as eventTime argument.
371c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            detectAndSendKey(keyIndex, key.x, key.y, -1);
372c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        }
3736a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3746a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
375400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public int getLastX() {
3766e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getLastX();
377400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    }
378400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka
379400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public int getLastY() {
3806e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getLastY();
381400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    }
382400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka
38307221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka    public long getDownTime() {
3846e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getDownTime();
38507221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka    }
38607221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka
3876a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // These package scope methods are only for debugging purpose.
3886a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    /* package */ int getStartX() {
3896e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getStartX();
3906a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3916a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
3926a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    /* package */ int getStartY() {
3936e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getStartY();
3946a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3956a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
3966e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    private boolean isMinorMoveBounce(int x, int y, int newKey) {
397eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka        if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
3986a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new IllegalStateException("keyboard and/or hysteresis not set");
3996e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int curKey = mKeyState.getKeyIndex();
4006a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (newKey == curKey) {
4016a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return true;
402c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        } else if (isValidKeyIndex(curKey)) {
40359b7bd07301196ac333dabafb5dd80750fcd2987Tadashi G. Takaoka            return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
4046a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        } else {
4056a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return false;
4066a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
4076a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
4086a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
4094189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) {
4104189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        updateKeyGraphics(keyIndex);
411c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        // The modifier key, such as shift key, should not be shown as preview when multi-touch is
412c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        // supported. On the other hand, if multi-touch is not supported, the modifier key should
413c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        // be shown as preview.
414efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        if (mHasDistinctMultitouch && isModifier()) {
415efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka            mProxy.showPreview(NOT_A_KEY, this);
416efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        } else {
41740a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka            mProxy.showPreview(keyIndex, this);
418efc4a437942f0bccd8815059c5f9d823023cfac1Tadashi G. Takaoka        }
4196a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
4206a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
42166e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka    private void startLongPressTimer(int keyIndex) {
42266e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        Key key = getKey(keyIndex);
4235a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka        if (key.codes[0] == Keyboard.KEYCODE_SHIFT) {
4244189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka            mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
4254189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        } else {
4264189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
4274189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        }
42866e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka    }
42966e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
430f27364600c742509b48857e6b8f17312033e0dc7Tadashi G. Takaoka    private boolean isManualTemporaryUpperCase() {
43166e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        return mKeyboard instanceof LatinKeyboard
432f27364600c742509b48857e6b8f17312033e0dc7Tadashi G. Takaoka                && ((LatinKeyboard)mKeyboard).isManualTemporaryUpperCase();
43366e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka    }
43466e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
4356a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private void detectAndSendKey(int index, int x, int y, long eventTime) {
4365a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka        final KeyboardActionListener listener = mListener;
43783e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        final Key key = getKey(index);
43883e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka
43983e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        if (key == null) {
44083e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka            if (listener != null)
44183e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka                listener.onCancel();
44283e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        } else {
4436a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            if (key.text != null) {
4446a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                if (listener != null) {
4456a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    listener.onText(key.text);
4466a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    listener.onRelease(NOT_A_KEY);
4476a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                }
4486a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            } else {
4496a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                int code = key.codes[0];
4506a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                //TextEntryState.keyPressedAt(key, x, y);
4516a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                int[] codes = mKeyDetector.newCodeArray();
4526a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
4536a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                // Multi-tap
4546a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                if (mInMultiTap) {
4556a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    if (mTapCount != -1) {
4565a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka                        mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
4576a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    } else {
4586a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                        mTapCount = 0;
4596a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    }
4606a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    code = key.codes[mTapCount];
4616a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                }
46266e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
46310227a71a047706a2290ff0b57f3437d5add7b35Tadashi G. Takaoka                // If keyboard is in manual temporary upper case state and key has manual temporary
46410227a71a047706a2290ff0b57f3437d5add7b35Tadashi G. Takaoka                // shift code, alternate character code should be sent.
465f27364600c742509b48857e6b8f17312033e0dc7Tadashi G. Takaoka                if (isManualTemporaryUpperCase() && key.manualTemporaryUpperCaseCode != 0) {
46610227a71a047706a2290ff0b57f3437d5add7b35Tadashi G. Takaoka                    code = key.manualTemporaryUpperCaseCode;
46766e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka                    codes[0] = code;
46866e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka                }
46966e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
4706a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                /*
4716a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                 * Swap the first and second values in the codes array if the primary code is not
4726a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                 * the first value but the second value in the array. This happens when key
4736a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                 * debouncing is in effect.
4746a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                 */
4756a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
4766a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    codes[1] = codes[0];
4776a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    codes[0] = code;
4786a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                }
4796a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                if (listener != null) {
4806a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    listener.onKey(code, codes, x, y);
4816a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                    listener.onRelease(code);
4826a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                }
4836a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
4846a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mLastSentIndex = index;
4856a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mLastTapTime = eventTime;
4866a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
4876a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
4886a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
4896a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    /**
4906a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka     * Handle multi-tap keys by producing the key label for the current multi-tap state.
4916a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka     */
4926a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public CharSequence getPreviewText(Key key) {
4936a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (mInMultiTap) {
4946a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            // Multi-tap
4956a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mPreviewLabel.setLength(0);
4966a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
4976a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return mPreviewLabel;
4986a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        } else {
4996a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return key.label;
5006a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
5016a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5026a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
5036a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private void resetMultiTap() {
5046a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mLastSentIndex = NOT_A_KEY;
5056a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mTapCount = 0;
5066a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mLastTapTime = -1;
5076a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mInMultiTap = false;
5086a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5096a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
5106a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private void checkMultiTap(long eventTime, int keyIndex) {
511c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        Key key = getKey(keyIndex);
512c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (key == null)
513c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            return;
514c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka
515c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        final boolean isMultiTap =
5165e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka                (eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex);
5176a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (key.codes.length > 1) {
5186a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            mInMultiTap = true;
519c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            if (isMultiTap) {
5206a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mTapCount = (mTapCount + 1) % key.codes.length;
5216a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                return;
5226a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            } else {
5236a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                mTapCount = -1;
5246a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka                return;
5256a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
5266a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
527c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (!isMultiTap) {
5286a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            resetMultiTap();
5296a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
5306a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5313d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka
5323d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka    private void debugLog(String title, int x, int y) {
5336e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
5346e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        Key key = getKey(keyIndex);
5353d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka        final String code;
5363d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka        if (key == null) {
5373d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka            code = "----";
5383d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka        } else {
5393d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka            int primaryCode = key.codes[0];
5403d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka            code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
5413d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka        }
542afb353277928b9c13a97f2aefde7412f432e060aTadashi G. Takaoka        Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title,
543afb353277928b9c13a97f2aefde7412f432e060aTadashi G. Takaoka                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code,
544afb353277928b9c13a97f2aefde7412f432e060aTadashi G. Takaoka                (isModifier() ? "modifier" : "")));
5453d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka    }
5466e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka}
547