PointerTracker.java revision d9786ce2e389c8c02af7773b53b5c44fe4fa0b0c
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;
20faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaokaimport com.android.inputmethod.latin.LatinImeLogger;
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
27dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaokaimport java.util.Arrays;
28dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaokaimport java.util.List;
29dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
306a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaokapublic class PointerTracker {
31dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static final String TAG = PointerTracker.class.getSimpleName();
321d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    private static final boolean ENABLE_ASSERTION = false;
33dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static final boolean DEBUG_EVENT = false;
34dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static final boolean DEBUG_MOVE_EVENT = false;
35dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static final boolean DEBUG_LISTENER = false;
36faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
3740a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka
386a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public interface UIProxy {
396a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        public void invalidateKey(Key key);
40d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        public void showKeyPreview(int keyIndex, PointerTracker tracker);
41d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        public void dismissKeyPreview(PointerTracker tracker);
425e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        public boolean hasDistinctMultitouch();
43dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        public boolean isAccessibilityEnabled();
446a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
456a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
46c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka    public final int mPointerId;
47c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka
486a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    // Timing constants
495e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka    private final int mDelayBeforeKeyRepeatStart;
505e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka    private final int mLongPressKeyTimeout;
514189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    private final int mLongPressShiftKeyTimeout;
526a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
53d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    private final KeyboardView mKeyboardView;
546a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private final UIProxy mProxy;
556a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    private final UIHandler mHandler;
562085d43daf44752deae1b6b00a14cb0f517d69cbTadashi G. Takaoka    private final KeyDetector mKeyDetector;
57dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private KeyboardActionListener mListener = EMPTY_LISTENER;
589e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka    private final KeyboardSwitcher mKeyboardSwitcher;
59c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    private final boolean mHasDistinctMultitouch;
6067a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka    private final boolean mConfigSlidingKeyInputEnabled;
616a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
62baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka    private final int mTouchNoiseThresholdMillis;
63baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka    private final int mTouchNoiseThresholdDistanceSquared;
64baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka
655a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    private Keyboard mKeyboard;
66dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka    private List<Key> mKeys;
67eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka    private int mKeyHysteresisDistanceSquared = -1;
68faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka    private int mKeyQuarterWidthSquared;
696a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
70dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private final PointerTrackerKeyState mKeyState;
716a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
72dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette    // true if accessibility is enabled in the parent keyboard
73dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette    private boolean mIsAccessibilityEnabled;
74dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette
751a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka    // true if keyboard layout has been changed.
761a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka    private boolean mKeyboardLayoutHasBeenChanged;
771a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka
78c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    // true if event is already translated to a key action (long press or mini-keyboard)
79c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    private boolean mKeyAlreadyProcessed;
80c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka
816252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka    // true if this pointer is repeatable key
826252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka    private boolean mIsRepeatableKey;
836252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka
84cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka    // true if this pointer is in sliding key input
85cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka    private boolean mIsInSlidingKeyInput;
86cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka
8767a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka    // true if sliding key is allowed.
8867a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka    private boolean mIsAllowedSlidingKeyInput;
8967a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka
90996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka    // ignore modifier key if true
91996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka    private boolean mIgnoreModifierKey;
92996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka
93dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    // Empty {@link KeyboardActionListener}
94dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
95dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
96e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka        public void onPress(int primaryCode, boolean withSliding) {}
97dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
98e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka        public void onRelease(int primaryCode, boolean withSliding) {}
99dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
1008aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka        public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {}
101dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
1028aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka        public void onTextInput(CharSequence text) {}
103dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
1048aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka        public void onCancelInput() {}
105dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        @Override
1068aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka        public void onSwipeDown() {}
107dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    };
1086e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka
109d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler,
110d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka            KeyDetector keyDetector, UIProxy proxy) {
1116a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (proxy == null || handler == null || keyDetector == null)
1126a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new NullPointerException();
113c6cb2ec1f3264a7b626022bcfdc8da180b87920cTadashi G. Takaoka        mPointerId = id;
114d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        mKeyboardView = keyboardView;
1156a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mProxy = proxy;
1166a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler = handler;
1176a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mKeyDetector = keyDetector;
1189e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
119dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        mKeyState = new PointerTrackerKeyState(keyDetector);
120dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
1215e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
122d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        final Resources res = mKeyboardView.getResources();
12367a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka        mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
1245e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
1255e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka        mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
1264189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
127baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
128baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        final float touchNoiseThresholdDistance = res.getDimension(
129baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka                R.dimen.config_touch_noise_threshold_distance);
130baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        mTouchNoiseThresholdDistanceSquared = (int)(
131baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka                touchNoiseThresholdDistance * touchNoiseThresholdDistance);
1326a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
1336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
1345a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    public void setOnKeyboardActionListener(KeyboardActionListener listener) {
1356a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mListener = listener;
1366a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
1376a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
138dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
139dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        mIsAccessibilityEnabled = accessibilityEnabled;
140dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette    }
141dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette
1421a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka    // Returns true if keyboard has been changed by this callback.
143e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
144996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
145dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_LISTENER)
146996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
147996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                    + " ignoreModifier=" + ignoreModifierKey);
148996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        if (ignoreModifierKey)
149996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            return false;
150690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        if (key.mEnabled) {
151e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka            mListener.onPress(key.mCode, withSliding);
152690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
153690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            mKeyboardLayoutHasBeenChanged = false;
154690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            return keyboardLayoutHasBeenChanged;
155690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        }
156690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        return false;
157dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
158dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
159690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    // Note that we need primaryCode argument because the keyboard may in shifted state and the
160690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    // primaryCode is different from {@link Key#mCode}.
161690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
162996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
163dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_LISTENER)
1648aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka            Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
165996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
166996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                    + " ignoreModifier=" + ignoreModifierKey);
167996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        if (ignoreModifierKey)
168996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            return;
169690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        if (key.mEnabled)
170690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            mListener.onCodeInput(primaryCode, keyCodes, x, y);
171dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
172dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
173690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    private void callListenerOnTextInput(Key key) {
174dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_LISTENER)
175690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            Log.d(TAG, "onTextInput: text=" + key.mOutputText);
176690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        if (key.mEnabled)
177690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            mListener.onTextInput(key.mOutputText);
178dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
179dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
180690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    // Note that we need primaryCode argument because the keyboard may in shifted state and the
181690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka    // primaryCode is different from {@link Key#mCode}.
182e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka    private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
183996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
184dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_LISTENER)
185996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding="
186996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                    + withSliding + " ignoreModifier=" + ignoreModifierKey);
187996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        if (ignoreModifierKey)
188996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            return;
189690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka        if (key.mEnabled)
190e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka            mListener.onRelease(primaryCode, withSliding);
191dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
192dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
1938aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka    private void callListenerOnCancelInput() {
194dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_LISTENER)
1958aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka            Log.d(TAG, "onCancelInput");
1968aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka        mListener.onCancelInput();
197dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
198dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
199dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka    public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) {
200dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka        if (keyboard == null || keyHysteresisDistance < 0)
2016a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new IllegalArgumentException();
20266e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        mKeyboard = keyboard;
203dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka        mKeys = keyboard.getKeys();
204eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka        mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
205faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka        final int keyQuarterWidth = keyboard.getKeyWidth() / 4;
206faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka        mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
2071a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka        // Mark that keyboard layout has been changed.
2081a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka        mKeyboardLayoutHasBeenChanged = true;
2096a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2106a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
211cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka    public boolean isInSlidingKeyInput() {
212cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka        return mIsInSlidingKeyInput;
213cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka    }
214cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka
215c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka    private boolean isValidKeyIndex(int keyIndex) {
216dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka        return keyIndex >= 0 && keyIndex < mKeys.size();
217c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka    }
218c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka
2196a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public Key getKey(int keyIndex) {
220dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
2216a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2226a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
223dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static boolean isModifierCode(int primaryCode) {
224571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka        return primaryCode == Keyboard.CODE_SHIFT
225e18bd3e323e7d7448677bb66e8149eea0169c771Tadashi G. Takaoka                || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
22640a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka    }
22740a05f62edc6cdedb4365a722b48a72826ef2bf6Tadashi G. Takaoka
228dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private boolean isModifierInternal(int keyIndex) {
229dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final Key key = getKey(keyIndex);
230c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        return key == null ? false : isModifierCode(key.mCode);
231dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
232dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
2332aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    public boolean isModifier() {
2346e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return isModifierInternal(mKeyState.getKeyIndex());
2352aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    }
2362aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka
2371d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    private boolean isOnModifierKey(int x, int y) {
2382aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
2392aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka    }
2402aa8078df86029dab394d8dd616f4f6decb39035Tadashi G. Takaoka
241418d80d7de8d24150fc7e1710f7590a33301e546Tadashi G. Takaoka    public boolean isOnShiftKey(int x, int y) {
242418d80d7de8d24150fc7e1710f7590a33301e546Tadashi G. Takaoka        final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
243c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        return key != null && key.mCode == Keyboard.CODE_SHIFT;
244418d80d7de8d24150fc7e1710f7590a33301e546Tadashi G. Takaoka    }
245418d80d7de8d24150fc7e1710f7590a33301e546Tadashi G. Takaoka
2463a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka    public boolean isSpaceKey(int keyIndex) {
2473a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka        Key key = getKey(keyIndex);
248c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        return key != null && key.mCode == Keyboard.CODE_SPACE;
2493a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka    }
2503a2896c80475094f751ef447fc9c97028bfc2265Tadashi G. Takaoka
251d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    public void setReleasedKeyGraphics() {
252d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        setReleasedKeyGraphics(mKeyState.getKeyIndex());
2534189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka    }
2544189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka
255d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    private void setReleasedKeyGraphics(int keyIndex) {
256d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final Key key = getKey(keyIndex);
257d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        if (key != null) {
258d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            key.onReleased();
259d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            mProxy.invalidateKey(key);
2606a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
2616a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
2626a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
263d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    private void setPressedKeyGraphics(int keyIndex) {
264d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final Key key = getKey(keyIndex);
265d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        if (key != null && key.mEnabled) {
266d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            key.onPressed();
267d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            mProxy.invalidateKey(key);
268d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        }
269c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka    }
270c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka
2711d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    private void checkAssertion(PointerTrackerQueue queue) {
2721d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (mHasDistinctMultitouch && queue == null)
2731d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            throw new RuntimeException(
2741d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                    "PointerTrackerQueue must be passed on distinct multi touch device");
2751d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (!mHasDistinctMultitouch && queue != null)
2761d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            throw new RuntimeException(
2771d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                    "PointerTrackerQueue must be null on non-distinct multi touch device");
2781d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    }
2791d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
2801d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    public void onTouchEvent(int action, int x, int y, long eventTime, PointerTrackerQueue queue) {
281c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        switch (action) {
282c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_MOVE:
2831d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            onMoveEvent(x, y, eventTime, queue);
284c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
285c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_DOWN:
286c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_POINTER_DOWN:
2871d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            onDownEvent(x, y, eventTime, queue);
288c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
289c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_UP:
290c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_POINTER_UP:
2911d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            onUpEvent(x, y, eventTime, queue);
292c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
293c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        case MotionEvent.ACTION_CANCEL:
2941d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            onCancelEvent(x, y, eventTime, queue);
295c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka            break;
296c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        }
297c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka    }
298c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka
2991d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    public void onDownEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
3001d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (ENABLE_ASSERTION) checkAssertion(queue);
301dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_EVENT)
302dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            printTouchEvent("onDownEvent:", x, y, eventTime);
3031d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
304baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        // Naive up-to-down noise filter.
305baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        final long deltaT = eventTime - mKeyState.getUpTime();
306baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        if (deltaT < mTouchNoiseThresholdMillis) {
307baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka            final int dx = x - mKeyState.getLastX();
308baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka            final int dy = y - mKeyState.getLastY();
309baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka            final int distanceSquared = (dx * dx + dy * dy);
310baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka            if (distanceSquared < mTouchNoiseThresholdDistanceSquared) {
311faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                if (DEBUG_MODE)
312faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
313faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                            + " distance=" + distanceSquared);
314d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                mKeyAlreadyProcessed = true;
315baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka                return;
316baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka            }
317baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        }
318baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka
3191d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (queue != null) {
3201d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            if (isOnModifierKey(x, y)) {
3211d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                // Before processing a down event of modifier key, all pointers already being
3221d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                // tracked should be released.
3231d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                queue.releaseAllPointers(eventTime);
3241d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            }
3251d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            queue.add(this);
3261d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        }
3271d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        onDownEventInternal(x, y, eventTime);
3281d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    }
3291d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
330baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka    private void onDownEventInternal(int x, int y, long eventTime) {
3316e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int keyIndex = mKeyState.onDownKey(x, y, eventTime);
33267a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka        // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
333dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        // from modifier key, 3) this pointer is on mini-keyboard, or 4) accessibility is enabled.
33467a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka        mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
335dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette                || mKeyDetector instanceof MiniKeyboardKeyDetector
336dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette                || mIsAccessibilityEnabled;
3371a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka        mKeyboardLayoutHasBeenChanged = false;
338c5d33b16521de56ad01b0b6308217efb009078b7Tadashi G. Takaoka        mKeyAlreadyProcessed = false;
3396252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka        mIsRepeatableKey = false;
340cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka        mIsInSlidingKeyInput = false;
341996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        mIgnoreModifierKey = false;
342d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        if (isValidKeyIndex(keyIndex)) {
3431a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka            // This onPress call may have changed keyboard layout. Those cases are detected at
3441a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka            // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
3451a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka            // keyboard layout.
346d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
3471a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                keyIndex = mKeyState.onDownKey(x, y, eventTime);
348996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka
349dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette            // Accessibility disables key repeat because users may need to pause on a key to hear
350dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette            // its spoken description.
351d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            final Key key = getKey(keyIndex);
352d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            if (key != null && key.mRepeatable && !mIsAccessibilityEnabled) {
353c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka                repeatKey(keyIndex);
3545e02930a7f40b704f357f127d3d38fbdc193ffa1Tadashi G. Takaoka                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
3556252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka                mIsRepeatableKey = true;
356c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka            }
357adf24e2eb49acd32d2655a3964f68da1e54c05ecTadashi G. Takaoka            startLongPressTimer(keyIndex);
358d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            showKeyPreview(keyIndex);
359d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            setPressedKeyGraphics(keyIndex);
3606a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
3616a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
3626a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
363996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka    private void startSlidingKeyInput(Key key) {
364996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        if (!mIsInSlidingKeyInput)
365996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka            mIgnoreModifierKey = isModifierCode(key.mCode);
366996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka        mIsInSlidingKeyInput = true;
367996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka    }
368996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka
3691d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    public void onMoveEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
3701d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (ENABLE_ASSERTION) checkAssertion(queue);
371dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_MOVE_EVENT)
372dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            printTouchEvent("onMoveEvent:", x, y, eventTime);
373e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka        if (mKeyAlreadyProcessed)
374e8f45ab56f3e6f358953dede794a63fc5901961dTadashi G. Takaoka            return;
375dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final PointerTrackerKeyState keyState = mKeyState;
376baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka
377faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka        final int lastX = keyState.getLastX();
378faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka        final int lastY = keyState.getLastY();
379d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final int oldKeyIndex = keyState.getKeyIndex();
380d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final Key oldKey = getKey(oldKeyIndex);
3811a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka        int keyIndex = keyState.onMoveKey(x, y);
382c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        if (isValidKeyIndex(keyIndex)) {
383c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka            if (oldKey == null) {
3849e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // The pointer has been slid in to the new key, but the finger was not on any keys.
3859e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // In this case, we must call onPress() to notify that the new key is being pressed.
3861a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                // This onPress call may have changed keyboard layout. Those cases are detected at
3871a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
3881a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                // new keyboard layout.
389e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka                if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
3901a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                    keyIndex = keyState.onMoveKey(x, y);
3916e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka                keyState.onMoveToNewKey(keyIndex, x, y);
392adf24e2eb49acd32d2655a3964f68da1e54c05ecTadashi G. Takaoka                startLongPressTimer(keyIndex);
393d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                showKeyPreview(keyIndex);
394d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                setPressedKeyGraphics(keyIndex);
3956e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
3969e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // The pointer has been slid in to the new key from the previous key, we must call
3979e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // onRelease() first to notify that the previous key has been released, then call
3989e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // onPress() to notify that the new key is being pressed.
399d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                setReleasedKeyGraphics(oldKeyIndex);
400e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka                callListenerOnRelease(oldKey, oldKey.mCode, true);
401996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                startSlidingKeyInput(oldKey);
4025ef421b58afa7bc58be40ed9331ce04998efbf56Tadashi G. Takaoka                mHandler.cancelLongPressTimers();
40367a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                if (mIsAllowedSlidingKeyInput) {
4041a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                    // This onPress call may have changed keyboard layout. Those cases are detected
4051a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
4061a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                    // to the new keyboard layout.
407e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka                    if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
4081a6fba570260ca9f837e5a6874274f39a3c0a734Tadashi G. Takaoka                        keyIndex = keyState.onMoveKey(x, y);
40967a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                    keyState.onMoveToNewKey(keyIndex, x, y);
41067a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                    startLongPressTimer(keyIndex);
411d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                    setPressedKeyGraphics(keyIndex);
412d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                    showKeyPreview(keyIndex);
41367a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                } else {
414faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    // HACK: On some devices, quick successive touches may be translated to sudden
415faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    // move by touch panel firmware. This hack detects the case and translates the
416faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    // move event to successive up and down events.
417faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    final int dx = x - lastX;
418faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    final int dy = y - lastY;
419faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    final int lastMoveSquared = dx * dx + dy * dy;
420faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    if (lastMoveSquared >= mKeyQuarterWidthSquared) {
421faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                        if (DEBUG_MODE)
422faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                            Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
423faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                                    + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
424d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka                        onUpEventInternal(lastX, lastY, eventTime, true);
425faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                        onDownEventInternal(x, y, eventTime);
426faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    } else {
427d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                        mKeyAlreadyProcessed = true;
428d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka                        dismissKeyPreview();
429d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                        setReleasedKeyGraphics(oldKeyIndex);
430faf437b5078e882b630706cd315c335f204ab861Tadashi G. Takaoka                    }
43167a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                    return;
43267a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                }
4336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
4346a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        } else {
435ef71c4aa4c11c50ff3d369d7abfe245aceedde97Tadashi G. Takaoka            if (oldKey != null && !isMinorMoveBounce(x, y, keyIndex)) {
4369e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // The pointer has been slid out from the previous key, we must call onRelease() to
4379e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka                // notify that the previous key has been released.
438d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                setReleasedKeyGraphics(oldKeyIndex);
439e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka                callListenerOnRelease(oldKey, oldKey.mCode, true);
440996db15d3c018ed2a7b4eee96ea94b9f80d8e379Tadashi G. Takaoka                startSlidingKeyInput(oldKey);
4415ef421b58afa7bc58be40ed9331ce04998efbf56Tadashi G. Takaoka                mHandler.cancelLongPressTimers();
44267a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                if (mIsAllowedSlidingKeyInput) {
443d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                    keyState.onMoveToNewKey(keyIndex, x, y);
44467a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                } else {
445d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka                    mKeyAlreadyProcessed = true;
446d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka                    dismissKeyPreview();
44767a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                    return;
44867a4ecacc7525c9343cded13fc93e9a2381ea2d8Tadashi G. Takaoka                }
44907221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka            }
4506a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
4516a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
4526a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
4531d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
4541d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (ENABLE_ASSERTION) checkAssertion(queue);
455dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_EVENT)
456dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            printTouchEvent("onUpEvent  :", x, y, eventTime);
4571d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
4581d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (queue != null) {
4591d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            if (isModifier()) {
4601d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                // Before processing an up event of modifier key, all pointers already being
4611d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                // tracked should be released.
4621d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                queue.releaseAllPointersExcept(this, eventTime);
4631d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            } else {
4641d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka                queue.releaseAllPointersOlderThan(this, eventTime);
4651d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            }
4661d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            queue.remove(this);
4671d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        }
468d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        onUpEventInternal(x, y, eventTime, true);
4691d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    }
4701d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
471d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
472d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
473d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    // "virtual" up event.
474d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    public void onPhantomUpEvent(int x, int y, long eventTime) {
475d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        onUpEventInternal(x, y, eventTime, false);
476d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        mKeyAlreadyProcessed = true;
4771d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    }
4781d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
479d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    private void onUpEventInternal(int x, int y, long eventTime,
480d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka            boolean updateReleasedKeyGraphics) {
4816a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelKeyTimers();
482d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        mHandler.cancelShowKeyPreview(this);
483cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka        mIsInSlidingKeyInput = false;
484dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final PointerTrackerKeyState keyState = mKeyState;
485d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final int keyX, keyY;
486d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        if (!isMinorMoveBounce(x, y, keyState.onMoveKey(x, y))) {
487d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            keyX = x;
488d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            keyY = y;
489d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        } else {
490d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            // Use previous fixed key coordinates.
491d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            keyX = keyState.getKeyX();
492d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            keyY = keyState.getKeyY();
4936a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
494d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime);
495d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        dismissKeyPreview();
496d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        if (updateReleasedKeyGraphics)
497d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka            setReleasedKeyGraphics(keyIndex);
498d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        if (mKeyAlreadyProcessed)
499d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            return;
5006252f468bc1306f71c9933f65b116dbbb5530de8Tadashi G. Takaoka        if (!mIsRepeatableKey) {
501d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka            detectAndSendKey(keyIndex, keyX, keyY);
5026a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
5036a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5046a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
505d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    public void onLongPressed(PointerTrackerQueue queue) {
506d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        mKeyAlreadyProcessed = true;
507d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        queue.remove(this);
508d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    }
509d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka
5101d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
5111d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (ENABLE_ASSERTION) checkAssertion(queue);
512dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (DEBUG_EVENT)
513dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            printTouchEvent("onCancelEvt:", x, y, eventTime);
5141d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
5151d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka        if (queue != null)
5161d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka            queue.remove(this);
517baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka        onCancelEventInternal();
5181d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka    }
5191d7d9664a9850a7c8043651e4b7a055ec034f571Tadashi G. Takaoka
520baf83886be975d804eda3e1519b7255026e5163eTadashi G. Takaoka    private void onCancelEventInternal() {
5216a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        mHandler.cancelKeyTimers();
522d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        mHandler.cancelShowKeyPreview(this);
523d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        dismissKeyPreview();
524d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka        setReleasedKeyGraphics(mKeyState.getKeyIndex());
525cb2469ae17e0ca8a94767008fef3945cb2a3b406Tadashi G. Takaoka        mIsInSlidingKeyInput = false;
5266a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5276a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
5286a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public void repeatKey(int keyIndex) {
529c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        Key key = getKey(keyIndex);
530c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        if (key != null) {
531c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka            detectAndSendKey(keyIndex, key.mX, key.mY);
532c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        }
5336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5346a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
535400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public int getLastX() {
5366e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getLastX();
537400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    }
538400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka
539400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    public int getLastY() {
5406e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getLastY();
541400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka    }
542400046d62e22899e28efd2a62321c637c7831f81Tadashi G. Takaoka
54307221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka    public long getDownTime() {
5446e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        return mKeyState.getDownTime();
54507221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka    }
54607221a4ad11fa5ae6275c107f1f86260691bd505Tadashi G. Takaoka
5476e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka    private boolean isMinorMoveBounce(int x, int y, int newKey) {
548eb68036798f53763768e4ab37c7bfab9a2f36025Tadashi G. Takaoka        if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
5496a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            throw new IllegalStateException("keyboard and/or hysteresis not set");
5506e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka        int curKey = mKeyState.getKeyIndex();
5516a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        if (newKey == curKey) {
5526a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return true;
553c6df09182cac288c9a4de2cc05628dac6b6db41eTadashi G. Takaoka        } else if (isValidKeyIndex(curKey)) {
554dc90d0a15f662cdece97bc2c0ddbd95e703af730Tadashi G. Takaoka            return mKeys.get(curKey).squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
5556a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        } else {
5566a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            return false;
5576a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
5586a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5596a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
560d2c2b4d112ee17750c1a49ff223b9410aa9e4ec6Tadashi G. Takaoka    private void showKeyPreview(int keyIndex) {
561967d6073bfd5f3d0e21512754d78d2e87c958e27Tadashi G. Takaoka        final Key key = getKey(keyIndex);
562967d6073bfd5f3d0e21512754d78d2e87c958e27Tadashi G. Takaoka        if (key != null && !key.mEnabled)
563967d6073bfd5f3d0e21512754d78d2e87c958e27Tadashi G. Takaoka            return;
564c8b9afe0378e3f33c3f83271bd1df9678a70c2a2Tadashi G. Takaoka        // The modifier key, such as shift key, should not be shown as preview when multi-touch is
565c0b5c9c43e5eb7a6ed768d56f462ca9ed5c5f913Tadashi G. Takaoka        // supported. On the other hand, if multi-touch is not supported, the modifier key should
566dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        // be shown as preview. If accessibility is turned on, the modifier key should be shown as
567dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        // preview.
568d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled)
569d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka            return;
570d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        mProxy.showKeyPreview(keyIndex, this);
571d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    }
572d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka
573d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka    private void dismissKeyPreview() {
574d9786ce2e389c8c02af7773b53b5c44fe4fa0b0cTadashi G. Takaoka        mProxy.dismissKeyPreview(this);
5756a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
5766a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
57766e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka    private void startLongPressTimer(int keyIndex) {
578dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        // Accessibility disables long press because users are likely to need to pause on a key
579dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        // for an unspecified duration in order to hear the key's spoken description.
580dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        if (mIsAccessibilityEnabled) {
581dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette            return;
582dcade18113286a132a2f5fb508fdba836470c3fbAlan Viverette        }
58366e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka        Key key = getKey(keyIndex);
584967d6073bfd5f3d0e21512754d78d2e87c958e27Tadashi G. Takaoka        if (!key.mEnabled)
585967d6073bfd5f3d0e21512754d78d2e87c958e27Tadashi G. Takaoka            return;
586c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        if (key.mCode == Keyboard.CODE_SHIFT) {
5874189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka            mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
5882b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka        } else if (key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY
5892b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka                && mKeyboard.isManualTemporaryUpperCase()) {
5902b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka            // We need not start long press timer on the key which has manual temporary upper case
5912b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka            // code defined and the keyboard is in manual temporary upper case mode.
5922b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka            return;
5939e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka        } else if (mKeyboardSwitcher.isInMomentaryAutoModeSwitchState()) {
5949e91472285a1b903631f1e3c998f5aa1efd3e98eTadashi G. Takaoka            // We use longer timeout for sliding finger input started from the symbols mode key.
5955797cefca6a50cba36d873ca78b861486b6726d5Tadashi G. Takaoka            mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this);
5964189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        } else {
5974189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
5984189eb23082fcd4bf8cfb2085d18e226e0e7ce13Tadashi G. Takaoka        }
59966e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka    }
60066e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
601c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka    private void detectAndSendKey(int index, int x, int y) {
60283e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        final Key key = getKey(index);
60383e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        if (key == null) {
6048aa3f5a3ad6095a3355841ce30bce4877319d0a0Tadashi G. Takaoka            callListenerOnCancelInput();
605dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            return;
606dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        }
607dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        if (key.mOutputText != null) {
608690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            callListenerOnTextInput(key);
609e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka            callListenerOnRelease(key, key.mCode, false);
61083e63ace2a1bd5b3c27d26d914456c2b0def17c5Tadashi G. Takaoka        } else {
611c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka            int code = key.mCode;
612dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            final int[] codes = mKeyDetector.newCodeArray();
613dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
61466e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
615dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            // If keyboard is in manual temporary upper case state and key has manual temporary
616dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            // shift code, alternate character code should be sent.
6172b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka            if (mKeyboard.isManualTemporaryUpperCase()
6182b13b4f5e55b2bf5086b112f2d5d438810fdd70fTadashi G. Takaoka                    && key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY) {
619dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka                code = key.mManualTemporaryUpperCaseCode;
620dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka                codes[0] = code;
621dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            }
62266e306d01c6820d4f4d8b2209438ec086b48ac51Tadashi G. Takaoka
623dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            // Swap the first and second values in the codes array if the primary code is not the
624dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            // first value but the second value in the array. This happens when key debouncing is
625dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            // in effect.
626dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka            if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
627dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka                codes[1] = codes[0];
628dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka                codes[0] = code;
6296a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka            }
630690b1360bfda3cbaae896de65dcc3cd347dc8329Tadashi G. Takaoka            callListenerOnCodeInput(key, code, codes, x, y);
631e59491460b0411bed430a5ca6eca0c56c5bf18d9Tadashi G. Takaoka            callListenerOnRelease(key, code, false);
6326a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka        }
6336a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
6346a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka
6356a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    public CharSequence getPreviewText(Key key) {
636c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        return key.mLabel;
6376a1514a0deac7f3d8ec33430403b2caea05bc8b9Tadashi G. Takaoka    }
6383d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka
639dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private long mPreviousEventTime;
640dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
641dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private void printTouchEvent(String title, int x, int y, long eventTime) {
642dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
643dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final Key key = getKey(keyIndex);
644c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
645dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final long delta = eventTime - mPreviousEventTime;
646dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
647dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
648dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        mPreviousEventTime = eventTime;
649dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    }
650dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka
651dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka    private static String keyCodePrintable(int primaryCode) {
652dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
653dbc44989a5be68679c889ae45cde17002b748fdaTadashi G. Takaoka        return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
6543d4123fabb51a0c929401d98fca496759a5aa0d6Tadashi G. Takaoka    }
6556e5a3986854549a45c95770b5a88ae5577e93299Tadashi G. Takaoka}
656