aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod')
-rw-r--r--java/src/com/android/inputmethod/latin/KeyboardSwitcher.java17
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java27
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java446
-rw-r--r--java/src/com/android/inputmethod/latin/PointerTracker.java400
4 files changed, 485 insertions, 405 deletions
diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
index 45a54b1aa..a50c5aa41 100644
--- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -37,11 +37,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
public static final int MODE_EMAIL = 5;
public static final int MODE_IM = 6;
public static final int MODE_WEB = 7;
-
- public static final int MODE_TEXT_QWERTY = 0;
- public static final int MODE_TEXT_ALPHA = 1;
- public static final int MODE_TEXT_COUNT = 2;
-
+
public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal;
public static final int KEYBOARDMODE_URL = R.id.mode_url;
public static final int KEYBOARDMODE_EMAIL = R.id.mode_email;
@@ -59,7 +55,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private static final int CHAR_THEME_COLOR_BLACK = 1;
// Tables which contains resource ids for each character theme color
- private static final int[] KBD_ALPHA = new int[] {R.xml.kbd_alpha, R.xml.kbd_alpha_black};
private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black};
private static final int[] KBD_PHONE_SYMBOLS = new int[] {
R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black};
@@ -92,7 +87,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private int mMode = MODE_NONE; /** One of the MODE_XXX values */
private int mImeOptions;
- private int mTextMode = MODE_TEXT_QWERTY;
private boolean mIsSymbols;
private boolean mHasVoice;
private boolean mVoiceOnPrimary;
@@ -291,11 +285,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
"getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols);
/* fall through */
case MODE_TEXT:
- if (mTextMode == MODE_TEXT_ALPHA) {
- return new KeyboardId(
- KBD_ALPHA[charColorId], KEYBOARDMODE_NORMAL, true, hasVoice);
- }
- // Normally mTextMode should be MODE_TEXT_QWERTY.
return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_NORMAL, true, hasVoice);
case MODE_SYMBOLS:
return new KeyboardId(KBD_SYMBOLS[charColorId], hasVoice);
@@ -320,10 +309,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
boolean isTextMode() {
return mMode == MODE_TEXT;
}
-
- int getTextModeCount() {
- return MODE_TEXT_COUNT;
- }
boolean isAlphabetMode() {
if (mCurrentId == null) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f26cbc059..74ed90ff6 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -647,16 +647,14 @@ public class LatinIME extends InputMethodService
(attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
mInputTypeNoAutoCorrect = true;
}
- if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
mPredictionOn = false;
- mCompletionOn = true && isFullscreenMode();
+ mCompletionOn = isFullscreenMode();
}
- updateShiftKeyState(attribute);
break;
default:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
attribute.imeOptions, enableVoiceButton);
- updateShiftKeyState(attribute);
}
inputView.closing();
mComposing.setLength(0);
@@ -666,8 +664,9 @@ public class LatinIME extends InputMethodService
loadSettings();
updateShiftKeyState(attribute);
- setCandidatesViewShown(false);
- setSuggestions(null, false, false, false);
+ setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn,
+ false /* needsInputViewShown */ );
+ updateSuggestions();
// If the dictionary is not big enough, don't auto correct
mHasDictionary = mSuggest.hasMainDictionary();
@@ -831,21 +830,24 @@ public class LatinIME extends InputMethodService
// When in fullscreen mode, show completions generated by the application
setSuggestions(stringList, true, true, true);
mBestWord = null;
- setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+ setCandidatesViewShown(true);
}
}
- @Override
- public void setCandidatesViewShown(boolean shown) {
+ private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
// TODO: Remove this if we support candidates with hard keyboard
if (onEvaluateInputViewShown()) {
- // Show the candidates view only if input view is showing
super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
- && mKeyboardSwitcher.getInputView().isShown());
+ && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
}
}
@Override
+ public void setCandidatesViewShown(boolean shown) {
+ setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ );
+ }
+
+ @Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
if (!isFullscreenMode()) {
@@ -1430,8 +1432,7 @@ public class LatinIME extends InputMethodService
}
private boolean isPredictionOn() {
- boolean predictionOn = mPredictionOn;
- return predictionOn;
+ return mPredictionOn;
}
private boolean isCandidateStripVisible() {
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index d9fa9f2a2..0833a4043 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -61,7 +61,8 @@ import java.util.Map;
* @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection
* @attr ref R.styleable#LatinKeyboardBaseView_popupLayout
*/
-public class LatinKeyboardBaseView extends View implements View.OnClickListener {
+public class LatinKeyboardBaseView extends View implements View.OnClickListener,
+ PointerTracker.UIProxy {
private static final boolean DEBUG = false;
public static final int NOT_A_TOUCH_COORDINATE = -1;
@@ -146,15 +147,10 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
// Timing constants
private static final int DELAY_BEFORE_PREVIEW = 0;
private static final int DELAY_AFTER_PREVIEW = 70;
- private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
- private static final int REPEAT_START_DELAY = 400;
- private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
- private static final int MULTITAP_INTERVAL = 800; // milliseconds
- private static final int KEY_DEBOUNCE_TIME = 70;
+ private static final int REPEAT_INTERVAL = PointerTracker.REPEAT_INTERVAL;
// Miscellaneous constants
- static final int NOT_A_KEY = -1;
- private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+ /* package */ static final int NOT_A_KEY = -1;
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
// XML attribute
@@ -188,7 +184,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
private int mPopupPreviewX;
private int mPopupPreviewY;
private int mWindowY;
- private final StringBuilder mPreviewLabel = new StringBuilder(1);
// Popup mini keyboard
private PopupWindow mPopupKeyboard;
@@ -204,20 +199,11 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
/** Listener for {@link OnKeyboardActionListener}. */
private OnKeyboardActionListener mKeyboardActionListener;
- private final KeyDebouncer mDebouncer = new KeyDebouncer();
+ private final PointerTracker mPointerTracker;
private final float mDebounceHysteresis;
- private int mCurrentKey = NOT_A_KEY;
- private int mStartX;
- private int mStartY;
private final ProximityKeyDetector mProximityKeyDetector = new ProximityKeyDetector();
- // For multi-tap
- private int mLastSentIndex;
- private int mTapCount;
- private long mLastTapTime;
- private boolean mInMultiTap;
-
// Variables for dealing with multiple pointers
private int mOldPointerCount = 1;
private int mOldPointerX;
@@ -251,7 +237,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
private static final int MSG_POPUP_PREVIEW = 1;
private static final int MSG_DISMISS_PREVIEW = 2;
private static final int MSG_REPEAT_KEY = 3;
- private static final int MSG_LOGPRESS_KEY = 4;
+ private static final int MSG_LONGPRESS_KEY = 4;
private boolean mInKeyRepeat;
@@ -259,24 +245,32 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_POPUP_PREVIEW:
- showKey(msg.arg1);
+ showKey(msg.arg1, (PointerTracker)msg.obj);
break;
case MSG_DISMISS_PREVIEW:
mPreviewText.setVisibility(INVISIBLE);
break;
- case MSG_REPEAT_KEY:
- repeatKey(msg.arg1);
- startKeyRepeatTimer(REPEAT_INTERVAL, msg.arg1);
+ case MSG_REPEAT_KEY: {
+ final PointerTracker tracker = (PointerTracker)msg.obj;
+ tracker.repeatKey(msg.arg1);
+ startKeyRepeatTimer(REPEAT_INTERVAL, msg.arg1, tracker);
break;
- case MSG_LOGPRESS_KEY:
+ }
+ case MSG_LONGPRESS_KEY:
openPopupIfRequired(msg.arg1);
break;
}
}
- public void popupPreview(int keyIndex, long delay) {
+ public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
removeMessages(MSG_POPUP_PREVIEW);
- sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0), delay);
+ if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+ // Show right away, if it's already visible and finger is moving around
+ showKey(keyIndex, tracker);
+ } else {
+ sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker),
+ delay);
+ }
}
public void cancelPopupPreview() {
@@ -284,16 +278,18 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
public void dismissPreview(long delay) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+ if (mPreviewPopup.isShowing()) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+ }
}
public void cancelDismissPreview() {
removeMessages(MSG_DISMISS_PREVIEW);
}
- public void startKeyRepeatTimer(long delay, int keyIndex) {
+ public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
mInKeyRepeat = true;
- sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0), delay);
+ sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
}
public void cancelKeyRepeatTimer() {
@@ -306,12 +302,12 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
public void startLongPressTimer(int keyIndex, long delay) {
- removeMessages(MSG_LOGPRESS_KEY);
- sendMessageDelayed(obtainMessage(MSG_LOGPRESS_KEY, keyIndex, 0), delay);
+ removeMessages(MSG_LONGPRESS_KEY);
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0), delay);
}
public void cancelLongPressTimer() {
- removeMessages(MSG_LOGPRESS_KEY);
+ removeMessages(MSG_LONGPRESS_KEY);
}
public void cancelKeyTimers() {
@@ -326,114 +322,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
};
- static class KeyDebouncer {
- private Key[] mKeys;
- private int mKeyDebounceThresholdSquared = -1;
-
- // for move de-bouncing
- private int mLastCodeX;
- private int mLastCodeY;
- private int mLastX;
- private int mLastY;
-
- // for time de-bouncing
- private int mLastKey;
- private long mLastKeyTime;
- private long mLastMoveTime;
- private long mCurrentKeyTime;
-
- public void setKeyboard(Key[] keys, float hysteresisPixel) {
- if (keys == null || hysteresisPixel < 1.0f)
- throw new IllegalArgumentException();
- mKeys = keys;
- mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
- }
-
- public int getLastCodeX() {
- return mLastCodeX;
- }
-
- public int getLastCodeY() {
- return mLastCodeY;
- }
-
- public int getLastX() {
- return mLastX;
- }
-
- public int getLastY() {
- return mLastY;
- }
-
- public int getLastKey() {
- return mLastKey;
- }
-
- public void startMoveDebouncing(int x, int y) {
- mLastCodeX = x;
- mLastCodeY = y;
- }
-
- public void updateMoveDebouncing(int x, int y) {
- mLastX = x;
- mLastY = y;
- }
-
- public void resetMoveDebouncing() {
- mLastCodeX = mLastX;
- mLastCodeY = mLastY;
- }
-
- public boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
- if (mKeys == null || mKeyDebounceThresholdSquared < 0)
- throw new IllegalStateException("keyboard and/or hysteresis not set");
- if (newKey == curKey) {
- return true;
- } else if (curKey >= 0 && curKey < mKeys.length) {
- return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
- < mKeyDebounceThresholdSquared;
- } else {
- return false;
- }
- }
-
- private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
- final int left = key.x;
- final int right = key.x + key.width;
- final int top = key.y;
- final int bottom = key.y + key.height;
- final int edgeX = x < left ? left : (x > right ? right : x);
- final int edgeY = y < top ? top : (y > bottom ? bottom : y);
- final int dx = x - edgeX;
- final int dy = y - edgeY;
- return dx * dx + dy * dy;
- }
-
- public void startTimeDebouncing(long eventTime) {
- mLastKey = NOT_A_KEY;
- mLastKeyTime = 0;
- mCurrentKeyTime = 0;
- mLastMoveTime = eventTime;
- }
-
- public void updateTimeDebouncing(long eventTime) {
- mCurrentKeyTime += eventTime - mLastMoveTime;
- mLastMoveTime = eventTime;
- }
-
- public void resetTimeDebouncing(long eventTime, int currentKey) {
- mLastKey = currentKey;
- mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
- mCurrentKeyTime = 0;
- mLastMoveTime = eventTime;
- }
-
- public boolean isMinorTimeBounce() {
- return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME
- && mLastKey != NOT_A_KEY;
- }
- }
-
public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
@@ -542,7 +430,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
// TODO: Refer frameworks/base/core/res/res/values/config.xml
mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
mDebounceHysteresis = res.getDimension(R.dimen.key_debounce_hysteresis_distance);
- resetMultiTap();
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@@ -586,10 +473,13 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
final boolean ignoreMultitouch = true;
mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch);
mGestureDetector.setIsLongpressEnabled(false);
+
+ mPointerTracker = new PointerTracker(mHandler, mProximityKeyDetector, this);
}
public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
mKeyboardActionListener = listener;
+ mPointerTracker.setOnKeyboardActionListener(listener);
}
/**
@@ -609,7 +499,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
*/
public void setKeyboard(Keyboard keyboard) {
if (mKeyboard != null) {
- showPreview(NOT_A_KEY);
+ dismissKeyPreview();
}
// Remove any pending messages, except dismissing preview
mHandler.cancelKeyTimers();
@@ -619,7 +509,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
List<Key> keys = mKeyboard.getKeys();
mKeys = keys.toArray(new Key[keys.size()]);
mProximityKeyDetector.setKeyboard(keyboard, mKeys);
- mDebouncer.setKeyboard(mKeys, mDebounceHysteresis);
+ mPointerTracker.setKeyboard(mKeys, mDebounceHysteresis);
requestLayout();
// Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
@@ -889,16 +779,19 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
if (DEBUG) {
if (mShowTouchPoints) {
- int lastX = mDebouncer.getLastX();
- int lastY = mDebouncer.getLastY();
+ PointerTracker tracker = mPointerTracker;
+ int startX = tracker.getStartX();
+ int startY = tracker.getStartY();
+ int lastX = tracker.getLastX();
+ int lastY = tracker.getLastY();
paint.setAlpha(128);
paint.setColor(0xFFFF0000);
- canvas.drawCircle(mStartX, mStartY, 3, paint);
- canvas.drawLine(mStartX, mStartY, lastX, lastY, paint);
+ canvas.drawCircle(startX, startY, 3, paint);
+ canvas.drawLine(startX, startY, lastX, lastY, paint);
paint.setColor(0xFF0000FF);
canvas.drawCircle(lastX, lastY, 3, paint);
paint.setColor(0xFF00FF00);
- canvas.drawCircle((mStartX + lastX) / 2, (mStartY + lastY) / 2, 2, paint);
+ canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint);
}
}
@@ -906,107 +799,38 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
mDirtyRect.setEmpty();
}
-
- private void detectAndSendKey(int index, int x, int y, long eventTime) {
- if (index != NOT_A_KEY && index < mKeys.length) {
- final Key key = mKeys[index];
- if (key.text != null) {
- mKeyboardActionListener.onText(key.text);
- mKeyboardActionListener.onRelease(NOT_A_KEY);
- } else {
- int code = key.codes[0];
- //TextEntryState.keyPressedAt(key, x, y);
- int[] codes = mProximityKeyDetector.newCodeArray();
- mProximityKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
- // Multi-tap
- if (mInMultiTap) {
- if (mTapCount != -1) {
- mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
- } else {
- mTapCount = 0;
- }
- code = key.codes[mTapCount];
- }
- /*
- * Swap the first and second values in the codes array if the primary code is not
- * the first value but the second value in the array. This happens when key
- * debouncing is in effect.
- */
- if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
- codes[1] = codes[0];
- codes[0] = code;
- }
- mKeyboardActionListener.onKey(code, codes, x, y);
- mKeyboardActionListener.onRelease(code);
- }
- mLastSentIndex = index;
- mLastTapTime = eventTime;
- }
+ // TODO: clean up this method.
+ private void dismissKeyPreview() {
+ mPointerTracker.updateKey(NOT_A_KEY);
+ showPreview(NOT_A_KEY, mPointerTracker);
}
- /**
- * Handle multi-tap keys by producing the key label for the current multi-tap state.
- */
- private CharSequence getPreviewText(Key key) {
- if (mInMultiTap) {
- // Multi-tap
- mPreviewLabel.setLength(0);
- mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
- return adjustCase(mPreviewLabel);
- } else {
- return adjustCase(key.label);
- }
- }
-
- private void showPreview(int keyIndex) {
+ public void showPreview(int keyIndex, PointerTracker tracker) {
int oldKeyIndex = mOldPreviewKeyIndex;
mOldPreviewKeyIndex = keyIndex;
-
- // Release the old key and press the new key
- final Key[] keys = mKeys;
- if (oldKeyIndex != keyIndex) {
- if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
- // if new key index is not a key, old key was just released inside of the key.
- final boolean inside = (keyIndex == NOT_A_KEY);
- keys[oldKeyIndex].onReleased(inside);
- invalidateKey(oldKeyIndex);
- }
- if (keyIndex != NOT_A_KEY && keys.length > keyIndex) {
- keys[keyIndex].onPressed();
- invalidateKey(keyIndex);
- }
- }
// If key changed and preview is on ...
if (oldKeyIndex != keyIndex && mShowPreview) {
- final PopupWindow previewPopup = mPreviewPopup;
if (keyIndex == NOT_A_KEY) {
mHandler.cancelPopupPreview();
- if (previewPopup.isShowing()) {
- mHandler.dismissPreview(DELAY_AFTER_PREVIEW);
- }
+ mHandler.dismissPreview(DELAY_AFTER_PREVIEW);
} else {
- if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
- // Show right away, if it's already visible and finger is moving around
- showKey(keyIndex);
- } else {
- mHandler.popupPreview(keyIndex, DELAY_BEFORE_PREVIEW);
- }
+ mHandler.popupPreview(DELAY_BEFORE_PREVIEW, keyIndex, tracker);
}
}
}
- private void showKey(final int keyIndex) {
+ private void showKey(final int keyIndex, PointerTracker tracker) {
+ Key key = tracker.getKey(keyIndex);
+ if (key == null)
+ return;
final PopupWindow previewPopup = mPreviewPopup;
- final Key[] keys = mKeys;
- if (keyIndex < 0 || keyIndex >= mKeys.length) return;
- Key key = keys[keyIndex];
if (key.icon != null) {
mPreviewText.setCompoundDrawables(null, null, null,
key.iconPreview != null ? key.iconPreview : key.icon);
mPreviewText.setText(null);
} else {
mPreviewText.setCompoundDrawables(null, null, null, null);
- mPreviewText.setText(getPreviewText(key));
+ mPreviewText.setText(adjustCase(tracker.getPreviewText(key)));
if (key.label.length() > 1 && key.codes.length < 2) {
mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
@@ -1077,7 +901,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
* because the keyboard renders the keys to an off-screen buffer and an invalidate() only
* draws the cached buffer.
- * @see #invalidateKey(int)
+ * @see #invalidateKey(Key)
*/
public void invalidateAllKeys() {
mDirtyRect.union(0, 0, getWidth(), getHeight());
@@ -1089,15 +913,12 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
* one key is changing it's content. Any changes that affect the position or size of the key
* may not be honored.
- * @param keyIndex the index of the key in the attached {@link Keyboard}.
+ * @param key key in the attached {@link Keyboard}.
* @see #invalidateAllKeys
*/
- public void invalidateKey(int keyIndex) {
- if (mKeys == null) return;
- if (keyIndex < 0 || keyIndex >= mKeys.length) {
+ public void invalidateKey(Key key) {
+ if (key == null)
return;
- }
- final Key key = mKeys[keyIndex];
mInvalidatedKey = key;
mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
@@ -1118,7 +939,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
Key popupKey = mKeys[keyIndex];
boolean result = onLongPress(popupKey);
if (result) {
- showPreview(NOT_A_KEY);
+ dismissKeyPreview();
}
return result;
}
@@ -1208,6 +1029,11 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
return false;
}
+ // TODO: Should cleanup after refactoring mini-keyboard.
+ public boolean isMiniKeyboardOnScreen() {
+ return mMiniKeyboardOnScreen;
+ }
+
private int getTouchX(float x) {
return (int)x - getPaddingLeft();
}
@@ -1234,7 +1060,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
// We must disable gesture detector while mini-keyboard is on the screen.
if (!mMiniKeyboardOnScreen && mGestureDetector.onTouchEvent(me)) {
- showPreview(NOT_A_KEY);
+ dismissKeyPreview();
mHandler.cancelKeyTimers();
return true;
}
@@ -1261,20 +1087,20 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
if (pointerCount != mOldPointerCount) {
if (pointerCount == 1) {
// Send a down event for the latest pointer
- onDownEvent(touchX, touchY, eventTime);
+ mPointerTracker.onDownEvent(touchX, touchY, eventTime);
// If it's an up action, then deliver the up as well.
if (action == MotionEvent.ACTION_UP) {
- onUpEvent(touchX, touchY, eventTime);
+ mPointerTracker.onUpEvent(touchX, touchY, eventTime);
}
} else {
// Send an up event for the last pointer
- onUpEvent(mOldPointerX, mOldPointerY, eventTime);
+ mPointerTracker.onUpEvent(mOldPointerX, mOldPointerY, eventTime);
}
mOldPointerCount = pointerCount;
return true;
} else {
if (pointerCount == 1) {
- onModifiedTouchEvent(action, touchX, touchY, eventTime);
+ mPointerTracker.onModifiedTouchEvent(action, touchX, touchY, eventTime);
mOldPointerX = touchX;
mOldPointerY = touchY;
return true;
@@ -1284,112 +1110,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
return false;
}
- private void onModifiedTouchEvent(int action, int touchX, int touchY, long eventTime) {
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- onDownEvent(touchX, touchY, eventTime);
- break;
- case MotionEvent.ACTION_MOVE:
- onMoveEvent(touchX, touchY, eventTime);
- break;
- case MotionEvent.ACTION_UP:
- onUpEvent(touchX, touchY, eventTime);
- break;
- case MotionEvent.ACTION_CANCEL:
- onCancelEvent(touchX, touchY, eventTime);
- break;
- }
- }
-
- private void onDownEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- mCurrentKey = keyIndex;
- mStartX = touchX;
- mStartY = touchY;
- mDebouncer.startMoveDebouncing(touchX, touchY);
- mDebouncer.startTimeDebouncing(eventTime);
- checkMultiTap(eventTime, keyIndex);
- mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? mKeys[keyIndex].codes[0] : 0);
- if (keyIndex >= 0 && mKeys[keyIndex].repeatable) {
- repeatKey(keyIndex);
- mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex);
- }
- if (keyIndex != NOT_A_KEY) {
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- }
- showPreview(keyIndex);
- mDebouncer.updateMoveDebouncing(touchX, touchY);
- }
-
- private void onMoveEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- if (keyIndex != NOT_A_KEY) {
- if (mCurrentKey == NOT_A_KEY) {
- mDebouncer.updateTimeDebouncing(eventTime);
- mCurrentKey = keyIndex;
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- } else if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
- mDebouncer.updateTimeDebouncing(eventTime);
- } else {
- resetMultiTap();
- mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
- mDebouncer.resetMoveDebouncing();
- mCurrentKey = keyIndex;
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- }
- } else {
- mHandler.cancelLongPressTimer();
- }
- /*
- * While time debouncing is in effect, mCurrentKey holds the new key and mDebouncer
- * holds the last key. At ACTION_UP event if time debouncing will be in effect
- * eventually, the last key should be sent as the result. In such case mCurrentKey
- * should not be showed as popup preview.
- */
- showPreview(mDebouncer.isMinorTimeBounce() ? mDebouncer.getLastKey() : mCurrentKey);
- mDebouncer.updateMoveDebouncing(touchX, touchY);
- }
-
- private void onUpEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- boolean wasInKeyRepeat = mHandler.isInKeyRepeat();
- mHandler.cancelKeyTimers();
- mHandler.cancelPopupPreview();
- if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
- mDebouncer.updateTimeDebouncing(eventTime);
- } else {
- resetMultiTap();
- mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
- mCurrentKey = keyIndex;
- }
- if (mDebouncer.isMinorTimeBounce()) {
- mCurrentKey = mDebouncer.getLastKey();
- touchX = mDebouncer.getLastCodeX();
- touchY = mDebouncer.getLastCodeY();
- }
- showPreview(NOT_A_KEY);
- // If we're not on a repeating key (which sends on a DOWN event)
- if (!wasInKeyRepeat && !mMiniKeyboardOnScreen) {
- detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
- }
- invalidateKey(keyIndex);
- }
-
- private void onCancelEvent(int touchX, int touchY, long eventTime) {
- mHandler.cancelKeyTimers();
- mHandler.cancelPopupPreview();
- dismissPopupKeyboard();
- showPreview(NOT_A_KEY);
- invalidateKey(mCurrentKey);
- }
-
- private void repeatKey(int keyIndex) {
- Key key = mKeys[keyIndex];
- // While key is repeating, because there is no need to handle multi-tap key, we can pass
- // -1 as eventTime argument.
- detectAndSendKey(keyIndex, key.x, key.y, -1);
- }
-
protected void swipeRight() {
mKeyboardActionListener.swipeRight();
}
@@ -1424,7 +1144,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
closing();
}
- private void dismissPopupKeyboard() {
+ public void dismissPopupKeyboard() {
if (mPopupKeyboard.isShowing()) {
mPopupKeyboard.dismiss();
mMiniKeyboardOnScreen = false;
@@ -1439,30 +1159,4 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
return false;
}
-
- private void resetMultiTap() {
- mLastSentIndex = NOT_A_KEY;
- mTapCount = 0;
- mLastTapTime = -1;
- mInMultiTap = false;
- }
-
- private void checkMultiTap(long eventTime, int keyIndex) {
- if (keyIndex == NOT_A_KEY) return;
- Key key = mKeys[keyIndex];
- if (key.codes.length > 1) {
- mInMultiTap = true;
- if (eventTime < mLastTapTime + MULTITAP_INTERVAL
- && keyIndex == mLastSentIndex) {
- mTapCount = (mTapCount + 1) % key.codes.length;
- return;
- } else {
- mTapCount = -1;
- return;
- }
- }
- if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
- resetMultiTap();
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
new file mode 100644
index 000000000..0c35ea966
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener;
+import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+public class PointerTracker {
+ public interface UIProxy {
+ public void invalidateKey(Key key);
+ public void showPreview(int keyIndex, PointerTracker tracker);
+ // TODO: These methods might be temporary.
+ public void dismissPopupKeyboard();
+ public boolean isMiniKeyboardOnScreen();
+ }
+
+ // Timing constants
+ private static final int REPEAT_START_DELAY = 400;
+ /* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+ private static final int MULTITAP_INTERVAL = 800; // milliseconds
+ private static final int KEY_DEBOUNCE_TIME = 70;
+
+ // Miscellaneous constants
+ private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY;
+ private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+
+ private final UIProxy mProxy;
+ private final UIHandler mHandler;
+ private final ProximityKeyDetector mKeyDetector;
+ private OnKeyboardActionListener mListener;
+
+ private Key[] mKeys;
+ private int mKeyDebounceThresholdSquared = -1;
+
+ private int mCurrentKey = NOT_A_KEY;
+ private int mStartX;
+ private int mStartY;
+
+ // for move de-bouncing
+ private int mLastCodeX;
+ private int mLastCodeY;
+ private int mLastX;
+ private int mLastY;
+
+ // for time de-bouncing
+ private int mLastKey;
+ private long mLastKeyTime;
+ private long mLastMoveTime;
+ private long mCurrentKeyTime;
+
+ // For multi-tap
+ private int mLastSentIndex;
+ private int mTapCount;
+ private long mLastTapTime;
+ private boolean mInMultiTap;
+ private final StringBuilder mPreviewLabel = new StringBuilder(1);
+
+ // pressed key
+ private int mPreviousKey;
+
+ public PointerTracker(UIHandler handler, ProximityKeyDetector keyDetector, UIProxy proxy) {
+ if (proxy == null || handler == null || keyDetector == null)
+ throw new NullPointerException();
+ mProxy = proxy;
+ mHandler = handler;
+ mKeyDetector = keyDetector;
+ resetMultiTap();
+ }
+
+ public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+ mListener = listener;
+ }
+
+ public void setKeyboard(Key[] keys, float hysteresisPixel) {
+ if (keys == null || hysteresisPixel < 1.0f)
+ throw new IllegalArgumentException();
+ mKeys = keys;
+ mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
+ }
+
+ public Key getKey(int keyIndex) {
+ return (keyIndex >= 0 && keyIndex < mKeys.length) ? mKeys[keyIndex] : null;
+ }
+
+ public void updateKey(int keyIndex) {
+ int oldKeyIndex = mPreviousKey;
+ mPreviousKey = keyIndex;
+ if (keyIndex != oldKeyIndex) {
+ if (oldKeyIndex != NOT_A_KEY && oldKeyIndex < mKeys.length) {
+ // if new key index is not a key, old key was just released inside of the key.
+ final boolean inside = (keyIndex == NOT_A_KEY);
+ mKeys[oldKeyIndex].onReleased(inside);
+ mProxy.invalidateKey(mKeys[oldKeyIndex]);
+ }
+ if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length) {
+ mKeys[keyIndex].onPressed();
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+ }
+ }
+
+ public void onModifiedTouchEvent(int action, int touchX, int touchY, long eventTime) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ onDownEvent(touchX, touchY, eventTime);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onMoveEvent(touchX, touchY, eventTime);
+ break;
+ case MotionEvent.ACTION_UP:
+ onUpEvent(touchX, touchY, eventTime);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ onCancelEvent(touchX, touchY, eventTime);
+ break;
+ }
+ }
+
+ public void onDownEvent(int touchX, int touchY, long eventTime) {
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
+ mCurrentKey = keyIndex;
+ mStartX = touchX;
+ mStartY = touchY;
+ startMoveDebouncing(touchX, touchY);
+ startTimeDebouncing(eventTime);
+ checkMultiTap(eventTime, keyIndex);
+ if (mListener != null) {
+ int primaryCode = (keyIndex != NOT_A_KEY) ? mKeys[keyIndex].codes[0] : 0;
+ mListener.onPress(primaryCode);
+ }
+ if (keyIndex >= 0 && mKeys[keyIndex].repeatable) {
+ repeatKey(keyIndex);
+ mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this);
+ }
+ if (keyIndex != NOT_A_KEY) {
+ mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
+ }
+ showKeyPreviewAndUpdateKey(keyIndex);
+ updateMoveDebouncing(touchX, touchY);
+ }
+
+ public void onMoveEvent(int touchX, int touchY, long eventTime) {
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
+ if (keyIndex != NOT_A_KEY) {
+ if (mCurrentKey == NOT_A_KEY) {
+ updateTimeDebouncing(eventTime);
+ mCurrentKey = keyIndex;
+ mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
+ } else if (isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
+ updateTimeDebouncing(eventTime);
+ } else {
+ resetMultiTap();
+ resetTimeDebouncing(eventTime, mCurrentKey);
+ resetMoveDebouncing();
+ mCurrentKey = keyIndex;
+ mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
+ }
+ } else {
+ mHandler.cancelLongPressTimer();
+ }
+ /*
+ * While time debouncing is in effect, mCurrentKey holds the new key and this tracker
+ * holds the last key. At ACTION_UP event if time debouncing will be in effect
+ * eventually, the last key should be sent as the result. In such case mCurrentKey
+ * should not be showed as popup preview.
+ */
+ showKeyPreviewAndUpdateKey(isMinorTimeBounce() ? mLastKey : mCurrentKey);
+ updateMoveDebouncing(touchX, touchY);
+ }
+
+ public void onUpEvent(int touchX, int touchY, long eventTime) {
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
+ boolean wasInKeyRepeat = mHandler.isInKeyRepeat();
+ mHandler.cancelKeyTimers();
+ mHandler.cancelPopupPreview();
+ if (isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
+ updateTimeDebouncing(eventTime);
+ } else {
+ resetMultiTap();
+ resetTimeDebouncing(eventTime, mCurrentKey);
+ mCurrentKey = keyIndex;
+ }
+ if (isMinorTimeBounce()) {
+ mCurrentKey = mLastKey;
+ touchX = mLastCodeX;
+ touchY = mLastCodeY;
+ }
+ showKeyPreviewAndUpdateKey(NOT_A_KEY);
+ // If we're not on a repeating key (which sends on a DOWN event)
+ if (!wasInKeyRepeat && !mProxy.isMiniKeyboardOnScreen()) {
+ detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
+ }
+ if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length)
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+
+ public void onCancelEvent(int touchX, int touchY, long eventTime) {
+ mHandler.cancelKeyTimers();
+ mHandler.cancelPopupPreview();
+ mProxy.dismissPopupKeyboard();
+ showKeyPreviewAndUpdateKey(NOT_A_KEY);
+ int keyIndex = mCurrentKey;
+ if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length)
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+
+ public void repeatKey(int keyIndex) {
+ Key key = mKeys[keyIndex];
+ // While key is repeating, because there is no need to handle multi-tap key, we can pass
+ // -1 as eventTime argument.
+ detectAndSendKey(keyIndex, key.x, key.y, -1);
+ }
+
+ // These package scope methods are only for debugging purpose.
+ /* package */ int getStartX() {
+ return mStartX;
+ }
+
+ /* package */ int getStartY() {
+ return mStartY;
+ }
+
+ /* package */ int getLastX() {
+ return mLastX;
+ }
+
+ /* package */ int getLastY() {
+ return mLastY;
+ }
+
+ private void startMoveDebouncing(int x, int y) {
+ mLastCodeX = x;
+ mLastCodeY = y;
+ }
+
+ private void updateMoveDebouncing(int x, int y) {
+ mLastX = x;
+ mLastY = y;
+ }
+
+ private void resetMoveDebouncing() {
+ mLastCodeX = mLastX;
+ mLastCodeY = mLastY;
+ }
+
+ private boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
+ if (mKeys == null || mKeyDebounceThresholdSquared < 0)
+ throw new IllegalStateException("keyboard and/or hysteresis not set");
+ if (newKey == curKey) {
+ return true;
+ } else if (curKey >= 0 && curKey < mKeys.length) {
+ return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
+ < mKeyDebounceThresholdSquared;
+ } else {
+ return false;
+ }
+ }
+
+ private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
+ final int left = key.x;
+ final int right = key.x + key.width;
+ final int top = key.y;
+ final int bottom = key.y + key.height;
+ final int edgeX = x < left ? left : (x > right ? right : x);
+ final int edgeY = y < top ? top : (y > bottom ? bottom : y);
+ final int dx = x - edgeX;
+ final int dy = y - edgeY;
+ return dx * dx + dy * dy;
+ }
+
+ private void startTimeDebouncing(long eventTime) {
+ mLastKey = NOT_A_KEY;
+ mLastKeyTime = 0;
+ mCurrentKeyTime = 0;
+ mLastMoveTime = eventTime;
+ }
+
+ private void updateTimeDebouncing(long eventTime) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ mLastMoveTime = eventTime;
+ }
+
+ private void resetTimeDebouncing(long eventTime, int currentKey) {
+ mLastKey = currentKey;
+ mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKeyTime = 0;
+ mLastMoveTime = eventTime;
+ }
+
+ private boolean isMinorTimeBounce() {
+ return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME
+ && mLastKey != NOT_A_KEY;
+ }
+
+ private void showKeyPreviewAndUpdateKey(int keyIndex) {
+ updateKey(keyIndex);
+ mProxy.showPreview(keyIndex, this);
+ }
+
+ private void detectAndSendKey(int index, int x, int y, long eventTime) {
+ if (index != NOT_A_KEY && index < mKeys.length) {
+ final Key key = mKeys[index];
+ OnKeyboardActionListener listener = mListener;
+ if (key.text != null) {
+ if (listener != null) {
+ listener.onText(key.text);
+ listener.onRelease(NOT_A_KEY);
+ }
+ } else {
+ int code = key.codes[0];
+ //TextEntryState.keyPressedAt(key, x, y);
+ int[] codes = mKeyDetector.newCodeArray();
+ mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+ // Multi-tap
+ if (mInMultiTap) {
+ if (mTapCount != -1) {
+ mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
+ } else {
+ mTapCount = 0;
+ }
+ code = key.codes[mTapCount];
+ }
+ /*
+ * Swap the first and second values in the codes array if the primary code is not
+ * the first value but the second value in the array. This happens when key
+ * debouncing is in effect.
+ */
+ if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+ codes[1] = codes[0];
+ codes[0] = code;
+ }
+ if (listener != null) {
+ listener.onKey(code, codes, x, y);
+ listener.onRelease(code);
+ }
+ }
+ mLastSentIndex = index;
+ mLastTapTime = eventTime;
+ }
+ }
+
+ /**
+ * Handle multi-tap keys by producing the key label for the current multi-tap state.
+ */
+ public CharSequence getPreviewText(Key key) {
+ if (mInMultiTap) {
+ // Multi-tap
+ mPreviewLabel.setLength(0);
+ mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+ return mPreviewLabel;
+ } else {
+ return key.label;
+ }
+ }
+
+ private void resetMultiTap() {
+ mLastSentIndex = NOT_A_KEY;
+ mTapCount = 0;
+ mLastTapTime = -1;
+ mInMultiTap = false;
+ }
+
+ private void checkMultiTap(long eventTime, int keyIndex) {
+ if (keyIndex == NOT_A_KEY) return;
+ Key key = mKeys[keyIndex];
+ if (key.codes.length > 1) {
+ mInMultiTap = true;
+ if (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex) {
+ mTapCount = (mTapCount + 1) % key.codes.length;
+ return;
+ } else {
+ mTapCount = -1;
+ return;
+ }
+ }
+ if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+ resetMultiTap();
+ }
+ }
+} \ No newline at end of file