PageRenderTime 43ms CodeModel.GetById 12ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 1ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/PointerTracker.java

http://eyes-free.googlecode.com/
Java | 546 lines | 425 code | 70 blank | 51 comment | 105 complexity | 4821467f9a885eb8575ad1477ca9d99c MD5 | raw file
  1/*
  2 * Copyright (C) 2010 Google Inc.
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 *
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16
 17package com.googlecode.eyesfree.inputmethod.latin;
 18
 19import android.content.res.Resources;
 20import android.inputmethodservice.Keyboard;
 21import android.inputmethodservice.Keyboard.Key;
 22import android.util.Log;
 23import android.view.MotionEvent;
 24
 25import com.googlecode.eyesfree.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener;
 26import com.googlecode.eyesfree.inputmethod.latin.LatinKeyboardBaseView.UIHandler;
 27
 28public class PointerTracker {
 29    private static final String TAG = "PointerTracker";
 30    private static final boolean DEBUG = false;
 31    private static final boolean DEBUG_MOVE = false;
 32
 33    public interface UIProxy {
 34        public void invalidateKey(Key key);
 35        public void showPreview(int keyIndex, PointerTracker tracker);
 36        public boolean hasDistinctMultitouch();
 37        public boolean isAccessibilityEnabled();
 38    }
 39
 40    public final int mPointerId;
 41
 42    // Timing constants
 43    private final int mDelayBeforeKeyRepeatStart;
 44    private final int mLongPressKeyTimeout;
 45    private final int mMultiTapKeyTimeout;
 46
 47    // Miscellaneous constants
 48    private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY;
 49    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
 50
 51    private final UIProxy mProxy;
 52    private final UIHandler mHandler;
 53    private final KeyDetector mKeyDetector;
 54    private OnKeyboardActionListener mListener;
 55    private final boolean mHasDistinctMultitouch;
 56
 57    private boolean mIsAccessibilityEnabled;
 58
 59    private Key[] mKeys;
 60    private int mKeyHysteresisDistanceSquared = -1;
 61
 62    private final KeyState mKeyState;
 63
 64    // true if event is already translated to a key action (long press or mini-keyboard)
 65    private boolean mKeyAlreadyProcessed;
 66
 67    // true if this pointer is repeatable key
 68    private boolean mIsRepeatableKey;
 69
 70    // For multi-tap
 71    private int mLastSentIndex;
 72    private int mTapCount;
 73    private long mLastTapTime;
 74    private boolean mInMultiTap;
 75    private final StringBuilder mPreviewLabel = new StringBuilder(1);
 76
 77    // pressed key
 78    private int mPreviousKey = NOT_A_KEY;
 79
 80    // This class keeps track of a key index and a position where this pointer is.
 81    private static class KeyState {
 82        private final KeyDetector mKeyDetector;
 83
 84        // The position and time at which first down event occurred.
 85        private int mStartX;
 86        private int mStartY;
 87        private long mDownTime;
 88
 89        // The current key index where this pointer is.
 90        private int mKeyIndex = NOT_A_KEY;
 91        // The position where mKeyIndex was recognized for the first time.
 92        private int mKeyX;
 93        private int mKeyY;
 94
 95        // Last pointer position.
 96        private int mLastX;
 97        private int mLastY;
 98
 99        public KeyState(KeyDetector keyDetecor) {
100            mKeyDetector = keyDetecor;
101        }
102
103        public int getKeyIndex() {
104            return mKeyIndex;
105        }
106
107        public int getKeyX() {
108            return mKeyX;
109        }
110
111        public int getKeyY() {
112            return mKeyY;
113        }
114
115        public int getStartX() {
116            return mStartX;
117        }
118
119        public int getStartY() {
120            return mStartY;
121        }
122
123        public long getDownTime() {
124            return mDownTime;
125        }
126
127        public int getLastX() {
128            return mLastX;
129        }
130
131        public int getLastY() {
132            return mLastY;
133        }
134
135        public int onDownKey(int x, int y, long eventTime) {
136            mStartX = x;
137            mStartY = y;
138            mDownTime = eventTime;
139
140            return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
141        }
142
143        private int onMoveKeyInternal(int x, int y) {
144            mLastX = x;
145            mLastY = y;
146            return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
147        }
148
149        public int onMoveKey(int x, int y) {
150            return onMoveKeyInternal(x, y);
151        }
152
153        public int onMoveToNewKey(int keyIndex, int x, int y) {
154            mKeyIndex = keyIndex;
155            mKeyX = x;
156            mKeyY = y;
157            return keyIndex;
158        }
159
160        public int onUpKey(int x, int y) {
161            return onMoveKeyInternal(x, y);
162        }
163
164        public void onSetKeyboard() {
165            mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
166        }
167    }
168
169    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
170            Resources res) {
171        if (proxy == null || handler == null || keyDetector == null)
172            throw new NullPointerException();
173        mPointerId = id;
174        mProxy = proxy;
175        mHandler = handler;
176        mKeyDetector = keyDetector;
177        mKeyState = new KeyState(keyDetector);
178        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
179        mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
180        mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
181        mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
182        mMultiTapKeyTimeout = res.getInteger(R.integer.config_multi_tap_key_timeout);
183        resetMultiTap();
184    }
185
186    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
187        mListener = listener;
188    }
189
190    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
191        mIsAccessibilityEnabled = accessibilityEnabled;
192    }
193
194    public void setKeyboard(Key[] keys, float keyHysteresisDistance) {
195        if (keys == null || keyHysteresisDistance < 0)
196            throw new IllegalArgumentException();
197        mKeys = keys;
198        mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
199        // Update current key index because keyboard layout has been changed.
200        mKeyState.onSetKeyboard();
201    }
202
203    private boolean isValidKeyIndex(int keyIndex) {
204        return keyIndex >= 0 && keyIndex < mKeys.length;
205    }
206
207    public Key getKey(int keyIndex) {
208        return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
209    }
210
211    private boolean isModifierInternal(int keyIndex) {
212        Key key = getKey(keyIndex);
213        if (key == null)
214            return false;
215        int primaryCode = key.codes[0];
216        return primaryCode == Keyboard.KEYCODE_SHIFT
217                || primaryCode == Keyboard.KEYCODE_MODE_CHANGE;
218    }
219
220    public boolean isModifier() {
221        return isModifierInternal(mKeyState.getKeyIndex());
222    }
223
224    public boolean isOnModifierKey(int x, int y) {
225        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
226    }
227
228    public boolean isSpaceKey(int keyIndex) {
229        Key key = getKey(keyIndex);
230        return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE;
231    }
232
233    public void updateKey(int keyIndex) {
234        if (mKeyAlreadyProcessed)
235            return;
236        int oldKeyIndex = mPreviousKey;
237        mPreviousKey = keyIndex;
238        if (keyIndex != oldKeyIndex) {
239            if (isValidKeyIndex(oldKeyIndex)) {
240                // if new key index is not a key, old key was just released inside of the key.
241                final boolean inside = (keyIndex == NOT_A_KEY);
242                mKeys[oldKeyIndex].onReleased(inside);
243                mProxy.invalidateKey(mKeys[oldKeyIndex]);
244            }
245            if (isValidKeyIndex(keyIndex)) {
246                mKeys[keyIndex].onPressed();
247                mProxy.invalidateKey(mKeys[keyIndex]);
248            }
249        }
250    }
251
252    public void setAlreadyProcessed() {
253        mKeyAlreadyProcessed = true;
254    }
255
256    public void onTouchEvent(int action, int x, int y, long eventTime) {
257        switch (action) {
258        case MotionEvent.ACTION_MOVE:
259            onMoveEvent(x, y, eventTime);
260            break;
261        case MotionEvent.ACTION_DOWN:
262        case MotionEvent.ACTION_POINTER_DOWN:
263            onDownEvent(x, y, eventTime);
264            break;
265        case MotionEvent.ACTION_UP:
266        case MotionEvent.ACTION_POINTER_UP:
267            onUpEvent(x, y, eventTime);
268            break;
269        case MotionEvent.ACTION_CANCEL:
270            onCancelEvent(x, y, eventTime);
271            break;
272        }
273    }
274
275    public void onDownEvent(int x, int y, long eventTime) {
276        if (DEBUG)
277            debugLog("onDownEvent:", x, y);
278        int keyIndex = mKeyState.onDownKey(x, y, eventTime);
279        mKeyAlreadyProcessed = false;
280        mIsRepeatableKey = false;
281        checkMultiTap(eventTime, keyIndex);
282        if (mListener != null) {
283            if (isValidKeyIndex(keyIndex)) {
284                mListener.onPress(mKeys[keyIndex]);
285                // This onPress call may have changed keyboard layout and have updated mKeyIndex.
286                // If that's the case, mKeyIndex has been updated in setKeyboard().
287                keyIndex = mKeyState.getKeyIndex();
288            }
289        }
290        if (isValidKeyIndex(keyIndex)) {
291            // Accessibility disables key repeat because users may need to pause on a key to hear
292            // its spoken description.
293            if (mKeys[keyIndex].repeatable && !mIsAccessibilityEnabled) {
294                repeatKey(keyIndex);
295                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
296                mIsRepeatableKey = true;
297            }
298            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
299        }
300        showKeyPreviewAndUpdateKey(keyIndex);
301    }
302
303    public void onMoveEvent(int x, int y, long eventTime) {
304        if (DEBUG_MOVE)
305            debugLog("onMoveEvent:", x, y);
306        if (mKeyAlreadyProcessed)
307            return;
308        KeyState keyState = mKeyState;
309        final int keyIndex = keyState.onMoveKey(x, y);
310        final Key oldKey = getKey(keyState.getKeyIndex());
311        if (isValidKeyIndex(keyIndex)) {
312            if (oldKey == null) {
313                if (mListener != null)
314                    mListener.onPress(mKeys[keyIndex]);
315                keyState.onMoveToNewKey(keyIndex, x, y);
316                mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
317            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
318                if (mListener != null) {
319                    mListener.onLeaving(oldKey);
320                    mListener.onPress(mKeys[keyIndex]);
321                }
322                resetMultiTap();
323                keyState.onMoveToNewKey(keyIndex, x, y);
324                mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
325            }
326        } else {
327            if (oldKey != null) {
328                 if (mListener != null)
329                     mListener.onLeaving(oldKey);
330                keyState.onMoveToNewKey(keyIndex, x ,y);
331                mHandler.cancelLongPressTimer();
332            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
333                resetMultiTap();
334                keyState.onMoveToNewKey(keyIndex, x ,y);
335                mHandler.cancelLongPressTimer();
336            }
337        }
338        showKeyPreviewAndUpdateKey(mKeyState.getKeyIndex());
339    }
340
341    public void onUpEvent(int x, int y, long eventTime) {
342        if (DEBUG)
343            debugLog("onUpEvent  :", x, y);
344        if (mKeyAlreadyProcessed)
345            return;
346        mHandler.cancelKeyTimers();
347        mHandler.cancelPopupPreview();
348        int keyIndex = mKeyState.onUpKey(x, y);
349        if (isMinorMoveBounce(x, y, keyIndex)) {
350            // Use previous fixed key index and coordinates.
351            keyIndex = mKeyState.getKeyIndex();
352            x = mKeyState.getKeyX();
353            y = mKeyState.getKeyY();
354        }
355        showKeyPreviewAndUpdateKey(NOT_A_KEY);
356        if (!mIsRepeatableKey) {
357            detectAndSendKey(keyIndex, x, y, eventTime);
358        }
359
360        if (isValidKeyIndex(keyIndex))
361            mProxy.invalidateKey(mKeys[keyIndex]);
362    }
363
364    public void onCancelEvent(int x, int y, long eventTime) {
365        if (DEBUG)
366            debugLog("onCancelEvt:", x, y);
367        mHandler.cancelKeyTimers();
368        mHandler.cancelPopupPreview();
369        showKeyPreviewAndUpdateKey(NOT_A_KEY);
370        int keyIndex = mKeyState.getKeyIndex();
371        if (isValidKeyIndex(keyIndex))
372           mProxy.invalidateKey(mKeys[keyIndex]);
373    }
374
375    public void repeatKey(int keyIndex) {
376        Key key = getKey(keyIndex);
377        if (key != null) {
378            // While key is repeating, because there is no need to handle multi-tap key, we can
379            // pass -1 as eventTime argument.
380            detectAndSendKey(keyIndex, key.x, key.y, -1);
381        }
382    }
383
384    public int getLastX() {
385        return mKeyState.getLastX();
386    }
387
388    public int getLastY() {
389        return mKeyState.getLastY();
390    }
391
392    public long getDownTime() {
393        return mKeyState.getDownTime();
394    }
395
396    // These package scope methods are only for debugging purpose.
397    /* package */ int getStartX() {
398        return mKeyState.getStartX();
399    }
400
401    /* package */ int getStartY() {
402        return mKeyState.getStartY();
403    }
404
405    private boolean isMinorMoveBounce(int x, int y, int newKey) {
406        if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
407            throw new IllegalStateException("keyboard and/or hysteresis not set");
408        int curKey = mKeyState.getKeyIndex();
409        if (newKey == curKey) {
410            return true;
411        } else if (isValidKeyIndex(curKey)) {
412            return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) < mKeyHysteresisDistanceSquared;
413        } else {
414            return false;
415        }
416    }
417
418    private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
419        final int left = key.x;
420        final int right = key.x + key.width;
421        final int top = key.y;
422        final int bottom = key.y + key.height;
423        final int edgeX = x < left ? left : (x > right ? right : x);
424        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
425        final int dx = x - edgeX;
426        final int dy = y - edgeY;
427        return dx * dx + dy * dy;
428    }
429
430    private void showKeyPreviewAndUpdateKey(int keyIndex) {
431        updateKey(keyIndex);
432        // The modifier key, such as shift key, should not be shown as preview when multi-touch is
433        // supported. On the other hand, if multi-touch is not supported, the modifier key should
434        // be shown as preview. If accessibility is turned on, the modifier key should be shown as
435        // preview.
436        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled) {
437            mProxy.showPreview(NOT_A_KEY, this);
438        } else {
439            mProxy.showPreview(keyIndex, this);
440        }
441    }
442
443    private void detectAndSendKey(int index, int x, int y, long eventTime) {
444        final OnKeyboardActionListener listener = mListener;
445        final Key key = getKey(index);
446
447        if (key == null) {
448            if (listener != null)
449                listener.onCancel();
450        } else {
451            if (key.text != null) {
452                if (listener != null) {
453                    listener.onText(key.text);
454                    listener.onRelease(null);
455                }
456            } else {
457                int code = key.codes[0];
458                //TextEntryState.keyPressedAt(key, x, y);
459                int[] codes = mKeyDetector.newCodeArray();
460                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
461                // Multi-tap
462                if (mInMultiTap) {
463                    if (mTapCount != -1) {
464                        mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
465                    } else {
466                        mTapCount = 0;
467                    }
468                    code = key.codes[mTapCount];
469                }
470                /*
471                 * Swap the first and second values in the codes array if the primary code is not
472                 * the first value but the second value in the array. This happens when key
473                 * debouncing is in effect.
474                 */
475                if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
476                    codes[1] = codes[0];
477                    codes[0] = code;
478                }
479                if (listener != null) {
480                    listener.onKey(code, codes, x, y);
481                    listener.onRelease(key);
482                }
483            }
484            mLastSentIndex = index;
485            mLastTapTime = eventTime;
486        }
487    }
488
489    /**
490     * Handle multi-tap keys by producing the key label for the current multi-tap state.
491     */
492    public CharSequence getPreviewText(Key key) {
493        if (mInMultiTap) {
494            // Multi-tap
495            mPreviewLabel.setLength(0);
496            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
497            return mPreviewLabel;
498        } else {
499            return key.label;
500        }
501    }
502
503    private void resetMultiTap() {
504        mLastSentIndex = NOT_A_KEY;
505        mTapCount = 0;
506        mLastTapTime = -1;
507        mInMultiTap = false;
508    }
509
510    private void checkMultiTap(long eventTime, int keyIndex) {
511        Key key = getKey(keyIndex);
512        if (key == null)
513            return;
514
515        final boolean isMultiTap =
516                (eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex);
517        if (key.codes.length > 1) {
518            mInMultiTap = true;
519            if (isMultiTap) {
520                mTapCount = (mTapCount + 1) % key.codes.length;
521                return;
522            } else {
523                mTapCount = -1;
524                return;
525            }
526        }
527        if (!isMultiTap) {
528            resetMultiTap();
529        }
530    }
531
532    private void debugLog(String title, int x, int y) {
533        int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
534        Key key = getKey(keyIndex);
535        final String code;
536        if (key == null) {
537            code = "----";
538        } else {
539            int primaryCode = key.codes[0];
540            code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
541        }
542        Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title,
543                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code,
544                (isModifier() ? "modifier" : "")));
545    }
546}