From 5f6816fa8bf259f0340a3d12c551d1532f647d66 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 1 Jul 2011 13:07:59 +0900 Subject: [PATCH] Move keyboard event handling code to separate LatinKeyboardBaseView class To sort out the issue, main keyboard and mini keyboard have to share the pointer trackers. In order to prepare for that, KeyboardView needs to be refactored as two separate classes. KeyboardView is responsible for drawing keyboard visual. And LatinKeyboardBaseView is responsible for handling key event and mini keyboard as well. Bug: 4768084 Change-Id: I5643e333b9bdfde0c939a0693cea76bd22f38897 --- .../AccessibleKeyboardViewProxy.java | 6 +- .../inputmethod/keyboard/KeyboardView.java | 583 +-------------- .../keyboard/LatinKeyboardBaseView.java | 670 ++++++++++++++++++ .../keyboard/LatinKeyboardView.java | 2 +- .../inputmethod/keyboard/PointerTracker.java | 14 +- .../keyboard/PopupMiniKeyboardView.java | 2 +- .../internal/MiniKeyboardBuilder.java | 6 +- .../android/inputmethod/latin/LatinIME.java | 12 +- 8 files changed, 704 insertions(+), 591 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index a87ff9891..38d904a94 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -29,7 +29,7 @@ import com.android.inputmethod.compat.AccessibilityEventCompatUtils; import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboardBaseView; import com.android.inputmethod.keyboard.PointerTracker; public class AccessibleKeyboardViewProxy { @@ -40,7 +40,7 @@ public class AccessibleKeyboardViewProxy { private static final long DELAY_KEY_PRESS = 10; private int mScaledEdgeSlop; - private KeyboardView mView; + private LatinKeyboardBaseView mView; private AccessibleKeyboardActionListener mListener; private FlickGestureDetector mGestureDetector; @@ -57,7 +57,7 @@ public class AccessibleKeyboardViewProxy { return sInstance; } - public static void setView(KeyboardView view) { + public static void setView(LatinKeyboardBaseView view) { sInstance.mView = view; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 752f87d66..f3e023d3e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -17,7 +17,6 @@ package com.android.inputmethod.keyboard; import android.content.Context; -import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -32,39 +31,24 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Message; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; -import android.view.GestureDetector; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.widget.PopupWindow; import android.widget.TextView; -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.compat.FrameLayoutCompatUtils; -import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder; -import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; -import com.android.inputmethod.keyboard.internal.SwipeTracker; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; -import java.util.ArrayList; import java.util.HashMap; -import java.util.WeakHashMap; /** - * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key - * presses and touch movements. + * A view that renders a virtual {@link Keyboard}. * * @attr ref R.styleable#KeyboardView_backgroundDimAmount * @attr ref R.styleable#KeyboardView_keyBackground - * @attr ref R.styleable#KeyboardView_keyHysteresisDistance * @attr ref R.styleable#KeyboardView_keyLetterRatio * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio * @attr ref R.styleable#KeyboardView_keyLabelRatio @@ -85,29 +69,17 @@ import java.util.WeakHashMap; * @attr ref R.styleable#KeyboardView_keyHintLabelColor * @attr ref R.styleable#KeyboardView_keyUppercaseLetterInactivatedColor * @attr ref R.styleable#KeyboardView_keyUppercaseLetterActivatedColor - * @attr ref R.styleable#KeyboardView_verticalCorrection - * @attr ref R.styleable#KeyboardView_popupLayout * @attr ref R.styleable#KeyboardView_shadowColor * @attr ref R.styleable#KeyboardView_shadowRadius */ public class KeyboardView extends View implements PointerTracker.UIProxy { - private static final String TAG = KeyboardView.class.getSimpleName(); private static final boolean DEBUG_KEYBOARD_GRID = false; - private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true; - private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; - - // Timing constants - private final int mKeyRepeatInterval; - // Miscellaneous constants private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; // XML attribute private final float mBackgroundDimAmount; - private final float mKeyHysteresisDistance; - private final float mVerticalCorrection; - private final int mPopupLayout; // HORIZONTAL ELLIPSIS "...", character for popup hint. private static final String POPUP_HINT_CHAR = "\u2026"; @@ -124,32 +96,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { private int mDelayAfterPreview; private ViewGroup mPreviewPlacer; - // Mini keyboard - private PopupWindow mPopupWindow; - private PopupPanel mPopupMiniKeyboardPanel; - private final WeakHashMap mPopupPanelCache = - new WeakHashMap(); - - /** Listener for {@link KeyboardActionListener}. */ - private KeyboardActionListener mKeyboardActionListener; - - private final ArrayList mPointerTrackers = new ArrayList(); - - // TODO: Let the PointerTracker class manage this pointer queue - private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue(); - - private final boolean mHasDistinctMultitouch; - private int mOldPointerCount = 1; - private int mOldKeyIndex; - - protected KeyDetector mKeyDetector = new KeyDetector(); - - // Swipe gesture detector - protected GestureDetector mGestureDetector; - private final SwipeTracker mSwipeTracker = new SwipeTracker(); - private final int mSwipeThreshold; - private final boolean mDisambiguateSwipe; - // Drawing /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ private boolean mDrawPending; @@ -182,12 +128,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { public static class UIHandler extends StaticInnerHandlerWrapper { private static final int MSG_SHOW_KEY_PREVIEW = 1; private static final int MSG_DISMISS_KEY_PREVIEW = 2; - private static final int MSG_REPEAT_KEY = 3; - private static final int MSG_LONGPRESS_KEY = 4; - private static final int MSG_LONGPRESS_SHIFT_KEY = 5; - private static final int MSG_IGNORE_DOUBLE_TAP = 6; - - private boolean mInKeyRepeat; public UIHandler(KeyboardView outerInstance) { super(outerInstance); @@ -204,16 +144,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { case MSG_DISMISS_KEY_PREVIEW: keyboardView.mPreviewText.setVisibility(View.INVISIBLE); break; - case MSG_REPEAT_KEY: - tracker.onRepeatKey(msg.arg1); - startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker); - break; - case MSG_LONGPRESS_KEY: - keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker); - break; - case MSG_LONGPRESS_SHIFT_KEY: - keyboardView.onLongPressShiftKey(tracker); - break; } } @@ -249,55 +179,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { removeMessages(MSG_DISMISS_KEY_PREVIEW); } - public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { - mInKeyRepeat = true; - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); - } - - public void cancelKeyRepeatTimer() { - mInKeyRepeat = false; - removeMessages(MSG_REPEAT_KEY); - } - - public boolean isInKeyRepeat() { - return mInKeyRepeat; - } - - public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { - cancelLongPressTimers(); - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); - } - - public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { - cancelLongPressTimers(); - if (ENABLE_CAPSLOCK_BY_LONGPRESS) { - sendMessageDelayed( - obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); - } - } - - public void cancelLongPressTimers() { - removeMessages(MSG_LONGPRESS_KEY); - removeMessages(MSG_LONGPRESS_SHIFT_KEY); - } - - public void cancelKeyTimers() { - cancelKeyRepeatTimer(); - cancelLongPressTimers(); - removeMessages(MSG_IGNORE_DOUBLE_TAP); - } - - public void startIgnoringDoubleTap() { - sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP), - ViewConfiguration.getDoubleTapTimeout()); - } - - public boolean isIgnoringDoubleTap() { - return hasMessages(MSG_IGNORE_DOUBLE_TAP); - } - public void cancelAllMessages() { - cancelKeyTimers(); cancelAllShowKeyPreviews(); cancelAllDismissKeyPreviews(); } @@ -442,10 +324,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { mKeyDrawParams = new KeyDrawParams(a); mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams); - mKeyHysteresisDistance = a.getDimensionPixelOffset( - R.styleable.KeyboardView_keyHysteresisDistance, 0); - mVerticalCorrection = a.getDimensionPixelOffset( - R.styleable.KeyboardView_verticalCorrection, 0); final int previewLayout = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0); if (previewLayout != 0) { mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null); @@ -453,7 +331,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { mPreviewText = null; mShowKeyPreviewPopup = false; } - mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0); // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f); a.recycle(); @@ -466,82 +343,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { mPaint.setAntiAlias(true); mPaint.setTextAlign(Align.CENTER); mPaint.setAlpha(255); - - mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); - // TODO: Refer to frameworks/base/core/res/res/values/config.xml - mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); - - GestureDetector.SimpleOnGestureListener listener = - new GestureDetector.SimpleOnGestureListener() { - private boolean mProcessingShiftDoubleTapEvent = false; - - @Override - public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, - float velocityY) { - final float absX = Math.abs(velocityX); - final float absY = Math.abs(velocityY); - float deltaY = me2.getY() - me1.getY(); - int travelY = getHeight() / 2; // Half the keyboard height - mSwipeTracker.computeCurrentVelocity(1000); - final float endingVelocityY = mSwipeTracker.getYVelocity(); - if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { - if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { - onSwipeDown(); - return true; - } - } - return false; - } - - @Override - public boolean onDoubleTap(MotionEvent firstDown) { - if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard - && ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) { - final int pointerIndex = firstDown.getActionIndex(); - final int id = firstDown.getPointerId(pointerIndex); - final PointerTracker tracker = getPointerTracker(id); - // If the first down event is on shift key. - if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) { - mProcessingShiftDoubleTapEvent = true; - return true; - } - } - mProcessingShiftDoubleTapEvent = false; - return false; - } - - @Override - public boolean onDoubleTapEvent(MotionEvent secondTap) { - if (mProcessingShiftDoubleTapEvent - && secondTap.getAction() == MotionEvent.ACTION_DOWN) { - final MotionEvent secondDown = secondTap; - final int pointerIndex = secondDown.getActionIndex(); - final int id = secondDown.getPointerId(pointerIndex); - final PointerTracker tracker = getPointerTracker(id); - // If the second down event is also on shift key. - if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) { - // Detected a double tap on shift key. If we are in the ignoring double tap - // mode, it means we have already turned off caps lock in - // {@link KeyboardSwitcher#onReleaseShift} . - final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap(); - if (!ignoringDoubleTap) - onDoubleTapShiftKey(tracker); - return true; - } - // Otherwise these events should not be handled as double tap. - mProcessingShiftDoubleTapEvent = false; - } - return mProcessingShiftDoubleTapEvent; - } - }; - - final boolean ignoreMultitouch = true; - mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); - mGestureDetector.setIsLongpressEnabled(false); - - mHasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); } // Read fraction value in TypedArray as float. @@ -549,26 +350,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { return a.getFraction(index, 1000, 1000, 1) / 1000.0f; } - public void startIgnoringDoubleTap() { - if (ENABLE_CAPSLOCK_BY_DOUBLETAP) - mHandler.startIgnoringDoubleTap(); - } - - public void setOnKeyboardActionListener(KeyboardActionListener listener) { - mKeyboardActionListener = listener; - for (PointerTracker tracker : mPointerTrackers) { - tracker.setOnKeyboardActionListener(listener); - } - } - - /** - * Returns the {@link KeyboardActionListener} object. - * @return the listener attached to this keyboard - */ - protected KeyboardActionListener getOnKeyboardActionListener() { - return mKeyboardActionListener; - } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO: Should notify InputMethodService instead? @@ -583,24 +364,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { * @param keyboard the keyboard to display in this view */ public void setKeyboard(Keyboard keyboard) { - if (mKeyboard != null) { - dismissAllKeyPreviews(); - } // Remove any pending messages, except dismissing preview - mHandler.cancelKeyTimers(); mHandler.cancelAllShowKeyPreviews(); mKeyboard = keyboard; LatinImeLogger.onSetKeyboard(keyboard); - mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + mVerticalCorrection); - for (PointerTracker tracker : mPointerTrackers) { - tracker.setKeyboard(keyboard, mKeyHysteresisDistance); - } requestLayout(); mKeyboardChanged = true; invalidateAllKeys(); - mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth()); - mPopupPanelCache.clear(); final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap(); mKeyDrawParams.updateKeyHeight(keyHeight); mKeyPreviewDrawParams.updateKeyHeight(keyHeight); @@ -615,15 +385,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { return mKeyboard; } - /** - * Returns whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - @Override - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - /** * Enables or disables the key feedback popup. This is a popup that shows a magnified * version of the depressed key. By default the preview is enabled. @@ -645,23 +406,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { return mShowKeyPreviewPopup; } - /** - * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key - * codes for adjacent keys. When disabled, only the primary key code will be - * reported. - * @param enabled whether or not the proximity correction is enabled - */ - public void setProximityCorrectionEnabled(boolean enabled) { - mKeyDetector.setProximityCorrectionEnabled(enabled); - } - - /** - * Returns true if proximity correction is enabled. - */ - public boolean isProximityCorrectionEnabled() { - return mKeyDetector.isProximityCorrectionEnabled(); - } - @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Round up a little @@ -752,7 +496,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } // Overlay a dark rectangle to dim the keyboard - if (mPopupMiniKeyboardPanel != null) { + if (needsToDimKeyboard()) { mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); canvas.drawRect(0, 0, width, height, mPaint); } @@ -762,6 +506,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { mDirtyRect.setEmpty(); } + protected boolean needsToDimKeyboard() { + return false; + } + private static void onBufferDrawKey(final Key key, final Canvas canvas, Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) { final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG; @@ -1017,15 +765,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { canvas.translate(-x, -y); } - // TODO: clean up this method. - private void dismissAllKeyPreviews() { - for (PointerTracker tracker : mPointerTrackers) { - tracker.setReleasedKeyGraphics(); - dismissKeyPreview(tracker); - } - } - - public void cancelAllMessage() { + public void cancelAllMessages() { mHandler.cancelAllMessages(); } @@ -1039,6 +779,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } } + @Override + public void cancelShowKeyPreview(PointerTracker tracker) { + mHandler.cancelShowKeyPreview(tracker); + } + @Override public void dismissKeyPreview(PointerTracker tracker) { if (mShowKeyPreviewPopup) { @@ -1161,266 +906,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { invalidate(mInvalidatedKeyRect); } - private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) { - // Check if we have a popup layout specified first. - if (mPopupLayout == 0) { - return false; - } - - final Key parentKey = tracker.getKey(keyIndex); - if (parentKey == null) - return false; - boolean result = onLongPress(parentKey, tracker); - if (result) { - dismissAllKeyPreviews(); - tracker.onLongPressed(mPointerQueue); - } - return result; - } - - private void onLongPressShiftKey(PointerTracker tracker) { - tracker.onLongPressed(mPointerQueue); - mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); - } - - private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) { - // When shift key is double tapped, the first tap is correctly processed as usual tap. And - // the second tap is treated as this double tap event, so that we need not mark tracker - // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue. - mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); - } - - // This default implementation returns a popup mini keyboard panel. - // A derived class may return a language switcher popup panel, for instance. - protected PopupPanel onCreatePopupPanel(Key parentKey) { - if (parentKey.mPopupCharacters == null) - return null; - - final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null); - if (container == null) - throw new NullPointerException(); - - final PopupMiniKeyboardView miniKeyboardView = - (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); - miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() { - @Override - public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { - mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y); - dismissMiniKeyboard(); - } - - @Override - public void onTextInput(CharSequence text) { - mKeyboardActionListener.onTextInput(text); - dismissMiniKeyboard(); - } - - @Override - public void onCancelInput() { - mKeyboardActionListener.onCancelInput(); - dismissMiniKeyboard(); - } - - @Override - public void onSwipeDown() { - // Nothing to do. - } - @Override - public void onPress(int primaryCode, boolean withSliding) { - mKeyboardActionListener.onPress(primaryCode, withSliding); - } - @Override - public void onRelease(int primaryCode, boolean withSliding) { - mKeyboardActionListener.onRelease(primaryCode, withSliding); - } - }); - - final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(), - parentKey, mKeyboard).build(); - miniKeyboardView.setKeyboard(keyboard); - - container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), - MEASURESPEC_UNSPECIFIED); - - return miniKeyboardView; - } - - /** - * Called when a key is long pressed. By default this will open mini keyboard associated - * with this key. - * @param parentKey the key that was long pressed - * @param tracker the pointer tracker which pressed the parent key - * @return true if the long press is handled, false otherwise. Subclasses should call the - * method on the base class if the subclass doesn't wish to handle the call. - */ - protected boolean onLongPress(Key parentKey, PointerTracker tracker) { - PopupPanel popupPanel = mPopupPanelCache.get(parentKey); - if (popupPanel == null) { - popupPanel = onCreatePopupPanel(parentKey); - if (popupPanel == null) - return false; - mPopupPanelCache.put(parentKey, popupPanel); - } - if (mPopupWindow == null) { - mPopupWindow = new PopupWindow(getContext()); - mPopupWindow.setBackgroundDrawable(null); - mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation); - // Allow popup window to be drawn off the screen. - mPopupWindow.setClippingEnabled(false); - } - mPopupMiniKeyboardPanel = popupPanel; - popupPanel.showPanel(this, parentKey, tracker, mPopupWindow); - - invalidateAllKeys(); - return true; - } - - private PointerTracker getPointerTracker(final int id) { - final ArrayList pointers = mPointerTrackers; - final KeyboardActionListener listener = mKeyboardActionListener; - - // Create pointer trackers until we can get 'id+1'-th tracker, if needed. - for (int i = pointers.size(); i <= id; i++) { - final PointerTracker tracker = - new PointerTracker(i, this, mHandler, mKeyDetector, this); - if (mKeyboard != null) - tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance); - if (listener != null) - tracker.setOnKeyboardActionListener(listener); - pointers.add(tracker); - } - - return pointers.get(id); - } - - public boolean isInSlidingKeyInput() { - if (mPopupMiniKeyboardPanel != null) { - return mPopupMiniKeyboardPanel.isInSlidingKeyInput(); - } else { - return mPointerQueue.isInSlidingKeyInput(); - } - } - - public int getPointerCount() { - return mOldPointerCount; - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - final int action = me.getActionMasked(); - final int pointerCount = me.getPointerCount(); - final int oldPointerCount = mOldPointerCount; - mOldPointerCount = pointerCount; - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // If the device does not have distinct multi-touch support panel, ignore all multi-touch - // events except a transition from/to single-touch. - if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { - return true; - } - - // Track the last few movements to look for spurious swipes. - mSwipeTracker.addMovement(me); - - // Gesture detector must be enabled only when mini-keyboard is not on the screen. - if (mPopupMiniKeyboardPanel == null && mGestureDetector != null - && mGestureDetector.onTouchEvent(me)) { - dismissAllKeyPreviews(); - mHandler.cancelKeyTimers(); - return true; - } - - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - - // Needs to be called after the gesture detector gets a turn, as it may have displayed the - // mini keyboard - if (mPopupMiniKeyboardPanel != null) { - return mPopupMiniKeyboardPanel.onTouchEvent(me); - } - - if (mHandler.isInKeyRepeat()) { - final PointerTracker tracker = getPointerTracker(id); - // Key repeating timer will be canceled if 2 or more keys are in action, and current - // event (UP or DOWN) is non-modifier key. - if (pointerCount > 1 && !tracker.isModifier()) { - mHandler.cancelKeyRepeatTimer(); - } - // Up event will pass through. - } - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // Translate mutli-touch event to single-touch events on the device that has no distinct - // multi-touch panel. - if (!mHasDistinctMultitouch) { - // Use only main (id=0) pointer tracker. - PointerTracker tracker = getPointerTracker(0); - if (pointerCount == 1 && oldPointerCount == 2) { - // Multi-touch to single touch transition. - // Send a down event for the latest pointer if the key is different from the - // previous key. - final int newKeyIndex = tracker.getKeyIndexOn(x, y); - if (mOldKeyIndex != newKeyIndex) { - tracker.onDownEvent(x, y, eventTime, null); - if (action == MotionEvent.ACTION_UP) - tracker.onUpEvent(x, y, eventTime, null); - } - } else if (pointerCount == 2 && oldPointerCount == 1) { - // Single-touch to multi-touch transition. - // Send an up event for the last pointer. - final int lastX = tracker.getLastX(); - final int lastY = tracker.getLastY(); - mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY); - tracker.onUpEvent(lastX, lastY, eventTime, null); - } else if (pointerCount == 1 && oldPointerCount == 1) { - tracker.onTouchEvent(action, x, y, eventTime, null); - } else { - Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount - + " (old " + oldPointerCount + ")"); - } - return true; - } - - final PointerTrackerQueue queue = mPointerQueue; - if (action == MotionEvent.ACTION_MOVE) { - for (int i = 0; i < pointerCount; i++) { - final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); - tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue); - } - } else { - final PointerTracker tracker = getPointerTracker(id); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - tracker.onDownEvent(x, y, eventTime, queue); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - tracker.onUpEvent(x, y, eventTime, queue); - break; - case MotionEvent.ACTION_CANCEL: - tracker.onCancelEvent(x, y, eventTime, queue); - break; - } - } - - return true; - } - - protected void onSwipeDown() { - mKeyboardActionListener.onSwipeDown(); - } - public void closing() { mPreviewText.setVisibility(View.GONE); - mHandler.cancelAllMessages(); + cancelAllMessages(); - dismissMiniKeyboard(); mDirtyRect.union(0, 0, getWidth(), getHeight()); - mPopupPanelCache.clear(); requestLayout(); } @@ -1434,51 +924,4 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { super.onDetachedFromWindow(); closing(); } - - private boolean dismissMiniKeyboard() { - if (mPopupWindow != null && mPopupWindow.isShowing()) { - mPopupWindow.dismiss(); - mPopupMiniKeyboardPanel = null; - invalidateAllKeys(); - return true; - } - return false; - } - - public boolean handleBack() { - return dismissMiniKeyboard(); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event) - || super.dispatchTouchEvent(event); - } - - return super.dispatchTouchEvent(event); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - final PointerTracker tracker = getPointerTracker(0); - return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent( - event, tracker) || super.dispatchPopulateAccessibilityEvent(event); - } - - return super.dispatchPopulateAccessibilityEvent(event); - } - - public boolean onHoverEvent(MotionEvent event) { - // Since reflection doesn't support calling superclass methods, this - // method checks for the existence of onHoverEvent() in the View class - // before returning a value. - if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - final PointerTracker tracker = getPointerTracker(0); - return AccessibleKeyboardViewProxy.getInstance().onHoverEvent(event, tracker); - } - - return false; - } } diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java new file mode 100644 index 000000000..0a7ba05c8 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.keyboard; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.widget.PopupWindow; + +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder; +import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; +import com.android.inputmethod.keyboard.internal.SwipeTracker; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; + +import java.util.ArrayList; +import java.util.WeakHashMap; + +/** + * A view that is responsible for detecting key presses and touch movements. + * + * @attr ref R.styleable#KeyboardView_keyHysteresisDistance + * @attr ref R.styleable#KeyboardView_verticalCorrection + * @attr ref R.styleable#KeyboardView_popupLayout + */ +public class LatinKeyboardBaseView extends KeyboardView { + private static final String TAG = LatinKeyboardBaseView.class.getSimpleName(); + + private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true; + private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; + + // Timing constants + private final int mKeyRepeatInterval; + + // XML attribute + private final float mKeyHysteresisDistance; + private final float mVerticalCorrection; + private final int mPopupLayout; + + // Mini keyboard + private PopupWindow mPopupWindow; + private PopupPanel mPopupMiniKeyboardPanel; + private final WeakHashMap mPopupPanelCache = + new WeakHashMap(); + + /** Listener for {@link KeyboardActionListener}. */ + private KeyboardActionListener mKeyboardActionListener; + + private final ArrayList mPointerTrackers = new ArrayList(); + + // TODO: Let the PointerTracker class manage this pointer queue + private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue(); + + private final boolean mHasDistinctMultitouch; + private int mOldPointerCount = 1; + private int mOldKeyIndex; + + protected KeyDetector mKeyDetector = new KeyDetector(); + + // Swipe gesture detector + protected GestureDetector mGestureDetector; + private final SwipeTracker mSwipeTracker = new SwipeTracker(); + private final int mSwipeThreshold; + private final boolean mDisambiguateSwipe; + + private final UIHandler mHandler = new UIHandler(this); + + public static class UIHandler extends StaticInnerHandlerWrapper { + private static final int MSG_REPEAT_KEY = 3; + private static final int MSG_LONGPRESS_KEY = 4; + private static final int MSG_LONGPRESS_SHIFT_KEY = 5; + private static final int MSG_IGNORE_DOUBLE_TAP = 6; + + private boolean mInKeyRepeat; + + public UIHandler(LatinKeyboardBaseView outerInstance) { + super(outerInstance); + } + + @Override + public void handleMessage(Message msg) { + final LatinKeyboardBaseView keyboardView = getOuterInstance(); + final PointerTracker tracker = (PointerTracker) msg.obj; + switch (msg.what) { + case MSG_REPEAT_KEY: + tracker.onRepeatKey(msg.arg1); + startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker); + break; + case MSG_LONGPRESS_KEY: + keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker); + break; + case MSG_LONGPRESS_SHIFT_KEY: + keyboardView.onLongPressShiftKey(tracker); + break; + } + } + + public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { + mInKeyRepeat = true; + sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); + } + + public void cancelKeyRepeatTimer() { + mInKeyRepeat = false; + removeMessages(MSG_REPEAT_KEY); + } + + public boolean isInKeyRepeat() { + return mInKeyRepeat; + } + + public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { + cancelLongPressTimers(); + sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); + } + + public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { + cancelLongPressTimers(); + if (ENABLE_CAPSLOCK_BY_LONGPRESS) { + sendMessageDelayed( + obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); + } + } + + public void cancelLongPressTimers() { + removeMessages(MSG_LONGPRESS_KEY); + removeMessages(MSG_LONGPRESS_SHIFT_KEY); + } + + public void cancelKeyTimers() { + cancelKeyRepeatTimer(); + cancelLongPressTimers(); + removeMessages(MSG_IGNORE_DOUBLE_TAP); + } + + public void startIgnoringDoubleTap() { + sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP), + ViewConfiguration.getDoubleTapTimeout()); + } + + public boolean isIgnoringDoubleTap() { + return hasMessages(MSG_IGNORE_DOUBLE_TAP); + } + + public void cancelAllMessages() { + cancelKeyTimers(); + } + } + + public LatinKeyboardBaseView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.keyboardViewStyle); + } + + public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); + mKeyHysteresisDistance = a.getDimensionPixelOffset( + R.styleable.KeyboardView_keyHysteresisDistance, 0); + mVerticalCorrection = a.getDimensionPixelOffset( + R.styleable.KeyboardView_verticalCorrection, 0); + + mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0); + // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) + a.recycle(); + + final Resources res = getResources(); + + mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); + // TODO: Refer to frameworks/base/core/res/res/values/config.xml + mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); + + GestureDetector.SimpleOnGestureListener listener = + new GestureDetector.SimpleOnGestureListener() { + private boolean mProcessingShiftDoubleTapEvent = false; + + @Override + public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, + float velocityY) { + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + float deltaY = me2.getY() - me1.getY(); + int travelY = getHeight() / 2; // Half the keyboard height + mSwipeTracker.computeCurrentVelocity(1000); + final float endingVelocityY = mSwipeTracker.getYVelocity(); + if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { + if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { + onSwipeDown(); + return true; + } + } + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent firstDown) { + final Keyboard keyboard = getKeyboard(); + if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard + && ((LatinKeyboard) keyboard).isAlphaKeyboard()) { + final int pointerIndex = firstDown.getActionIndex(); + final int id = firstDown.getPointerId(pointerIndex); + final PointerTracker tracker = getPointerTracker(id); + // If the first down event is on shift key. + if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) { + mProcessingShiftDoubleTapEvent = true; + return true; + } + } + mProcessingShiftDoubleTapEvent = false; + return false; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent secondTap) { + if (mProcessingShiftDoubleTapEvent + && secondTap.getAction() == MotionEvent.ACTION_DOWN) { + final MotionEvent secondDown = secondTap; + final int pointerIndex = secondDown.getActionIndex(); + final int id = secondDown.getPointerId(pointerIndex); + final PointerTracker tracker = getPointerTracker(id); + // If the second down event is also on shift key. + if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) { + // Detected a double tap on shift key. If we are in the ignoring double tap + // mode, it means we have already turned off caps lock in + // {@link KeyboardSwitcher#onReleaseShift} . + final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap(); + if (!ignoringDoubleTap) + onDoubleTapShiftKey(tracker); + return true; + } + // Otherwise these events should not be handled as double tap. + mProcessingShiftDoubleTapEvent = false; + } + return mProcessingShiftDoubleTapEvent; + } + }; + + final boolean ignoreMultitouch = true; + mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); + mGestureDetector.setIsLongpressEnabled(false); + + mHasDistinctMultitouch = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); + } + + public void startIgnoringDoubleTap() { + if (ENABLE_CAPSLOCK_BY_DOUBLETAP) + mHandler.startIgnoringDoubleTap(); + } + + public void setOnKeyboardActionListener(KeyboardActionListener listener) { + mKeyboardActionListener = listener; + for (PointerTracker tracker : mPointerTrackers) { + tracker.setOnKeyboardActionListener(listener); + } + } + + /** + * Returns the {@link KeyboardActionListener} object. + * @return the listener attached to this keyboard + */ + protected KeyboardActionListener getOnKeyboardActionListener() { + return mKeyboardActionListener; + } + + /** + * Attaches a keyboard to this view. The keyboard can be switched at any time and the + * view will re-layout itself to accommodate the keyboard. + * @see Keyboard + * @see #getKeyboard() + * @param keyboard the keyboard to display in this view + */ + @Override + public void setKeyboard(Keyboard keyboard) { + if (getKeyboard() != null) { + dismissAllKeyPreviews(); + } + // Remove any pending messages, except dismissing preview + mHandler.cancelKeyTimers(); + super.setKeyboard(keyboard); + mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), + -getPaddingTop() + mVerticalCorrection); + for (PointerTracker tracker : mPointerTrackers) { + tracker.setKeyboard(keyboard, mKeyHysteresisDistance); + } + mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth()); + mPopupPanelCache.clear(); + } + + /** + * Returns whether the device has distinct multi-touch panel. + * @return true if the device has distinct multi-touch panel. + */ + public boolean hasDistinctMultitouch() { + return mHasDistinctMultitouch; + } + + /** + * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key + * codes for adjacent keys. When disabled, only the primary key code will be + * reported. + * @param enabled whether or not the proximity correction is enabled + */ + public void setProximityCorrectionEnabled(boolean enabled) { + mKeyDetector.setProximityCorrectionEnabled(enabled); + } + + /** + * Returns true if proximity correction is enabled. + */ + public boolean isProximityCorrectionEnabled() { + return mKeyDetector.isProximityCorrectionEnabled(); + } + + // TODO: clean up this method. + private void dismissAllKeyPreviews() { + for (PointerTracker tracker : mPointerTrackers) { + tracker.setReleasedKeyGraphics(); + dismissKeyPreview(tracker); + } + } + + @Override + public void cancelAllMessages() { + mHandler.cancelAllMessages(); + super.cancelAllMessages(); + } + + private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) { + // Check if we have a popup layout specified first. + if (mPopupLayout == 0) { + return false; + } + + final Key parentKey = tracker.getKey(keyIndex); + if (parentKey == null) + return false; + boolean result = onLongPress(parentKey, tracker); + if (result) { + dismissAllKeyPreviews(); + tracker.onLongPressed(mPointerQueue); + } + return result; + } + + private void onLongPressShiftKey(PointerTracker tracker) { + tracker.onLongPressed(mPointerQueue); + mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); + } + + private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) { + // When shift key is double tapped, the first tap is correctly processed as usual tap. And + // the second tap is treated as this double tap event, so that we need not mark tracker + // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue. + mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); + } + + // This default implementation returns a popup mini keyboard panel. + // A derived class may return a language switcher popup panel, for instance. + protected PopupPanel onCreatePopupPanel(Key parentKey) { + if (parentKey.mPopupCharacters == null) + return null; + + final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null); + if (container == null) + throw new NullPointerException(); + + final PopupMiniKeyboardView miniKeyboardView = + (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); + miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() { + @Override + public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { + mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y); + dismissMiniKeyboard(); + } + + @Override + public void onTextInput(CharSequence text) { + mKeyboardActionListener.onTextInput(text); + dismissMiniKeyboard(); + } + + @Override + public void onCancelInput() { + mKeyboardActionListener.onCancelInput(); + dismissMiniKeyboard(); + } + + @Override + public void onSwipeDown() { + // Nothing to do. + } + @Override + public void onPress(int primaryCode, boolean withSliding) { + mKeyboardActionListener.onPress(primaryCode, withSliding); + } + @Override + public void onRelease(int primaryCode, boolean withSliding) { + mKeyboardActionListener.onRelease(primaryCode, withSliding); + } + }); + + final Keyboard parentKeyboard = getKeyboard(); + final Keyboard miniKeyboard = new MiniKeyboardBuilder( + this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build(); + miniKeyboardView.setKeyboard(miniKeyboard); + + container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + + return miniKeyboardView; + } + + @Override + protected boolean needsToDimKeyboard() { + return mPopupMiniKeyboardPanel != null; + } + + /** + * Called when a key is long pressed. By default this will open mini keyboard associated + * with this key. + * @param parentKey the key that was long pressed + * @param tracker the pointer tracker which pressed the parent key + * @return true if the long press is handled, false otherwise. Subclasses should call the + * method on the base class if the subclass doesn't wish to handle the call. + */ + protected boolean onLongPress(Key parentKey, PointerTracker tracker) { + PopupPanel popupPanel = mPopupPanelCache.get(parentKey); + if (popupPanel == null) { + popupPanel = onCreatePopupPanel(parentKey); + if (popupPanel == null) + return false; + mPopupPanelCache.put(parentKey, popupPanel); + } + if (mPopupWindow == null) { + mPopupWindow = new PopupWindow(getContext()); + mPopupWindow.setBackgroundDrawable(null); + mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation); + // Allow popup window to be drawn off the screen. + mPopupWindow.setClippingEnabled(false); + } + mPopupMiniKeyboardPanel = popupPanel; + popupPanel.showPanel(this, parentKey, tracker, mPopupWindow); + + invalidateAllKeys(); + return true; + } + + private PointerTracker getPointerTracker(final int id) { + final ArrayList pointers = mPointerTrackers; + final KeyboardActionListener listener = mKeyboardActionListener; + final Keyboard keyboard = getKeyboard(); + + // Create pointer trackers until we can get 'id+1'-th tracker, if needed. + for (int i = pointers.size(); i <= id; i++) { + final PointerTracker tracker = + new PointerTracker(i, this, mHandler, mKeyDetector, this); + if (keyboard != null) + tracker.setKeyboard(keyboard, mKeyHysteresisDistance); + if (listener != null) + tracker.setOnKeyboardActionListener(listener); + pointers.add(tracker); + } + + return pointers.get(id); + } + + public boolean isInSlidingKeyInput() { + if (mPopupMiniKeyboardPanel != null) { + return mPopupMiniKeyboardPanel.isInSlidingKeyInput(); + } else { + return mPointerQueue.isInSlidingKeyInput(); + } + } + + public int getPointerCount() { + return mOldPointerCount; + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + final int action = me.getActionMasked(); + final int pointerCount = me.getPointerCount(); + final int oldPointerCount = mOldPointerCount; + mOldPointerCount = pointerCount; + + // TODO: cleanup this code into a multi-touch to single-touch event converter class? + // If the device does not have distinct multi-touch support panel, ignore all multi-touch + // events except a transition from/to single-touch. + if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { + return true; + } + + // Track the last few movements to look for spurious swipes. + mSwipeTracker.addMovement(me); + + // Gesture detector must be enabled only when mini-keyboard is not on the screen. + if (mPopupMiniKeyboardPanel == null && mGestureDetector != null + && mGestureDetector.onTouchEvent(me)) { + dismissAllKeyPreviews(); + mHandler.cancelKeyTimers(); + return true; + } + + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + + // Needs to be called after the gesture detector gets a turn, as it may have displayed the + // mini keyboard + if (mPopupMiniKeyboardPanel != null) { + return mPopupMiniKeyboardPanel.onTouchEvent(me); + } + + if (mHandler.isInKeyRepeat()) { + final PointerTracker tracker = getPointerTracker(id); + // Key repeating timer will be canceled if 2 or more keys are in action, and current + // event (UP or DOWN) is non-modifier key. + if (pointerCount > 1 && !tracker.isModifier()) { + mHandler.cancelKeyRepeatTimer(); + } + // Up event will pass through. + } + + // TODO: cleanup this code into a multi-touch to single-touch event converter class? + // Translate mutli-touch event to single-touch events on the device that has no distinct + // multi-touch panel. + if (!mHasDistinctMultitouch) { + // Use only main (id=0) pointer tracker. + PointerTracker tracker = getPointerTracker(0); + if (pointerCount == 1 && oldPointerCount == 2) { + // Multi-touch to single touch transition. + // Send a down event for the latest pointer if the key is different from the + // previous key. + final int newKeyIndex = tracker.getKeyIndexOn(x, y); + if (mOldKeyIndex != newKeyIndex) { + tracker.onDownEvent(x, y, eventTime, null); + if (action == MotionEvent.ACTION_UP) + tracker.onUpEvent(x, y, eventTime, null); + } + } else if (pointerCount == 2 && oldPointerCount == 1) { + // Single-touch to multi-touch transition. + // Send an up event for the last pointer. + final int lastX = tracker.getLastX(); + final int lastY = tracker.getLastY(); + mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY); + tracker.onUpEvent(lastX, lastY, eventTime, null); + } else if (pointerCount == 1 && oldPointerCount == 1) { + tracker.onTouchEvent(action, x, y, eventTime, null); + } else { + Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount + + " (old " + oldPointerCount + ")"); + } + return true; + } + + final PointerTrackerQueue queue = mPointerQueue; + if (action == MotionEvent.ACTION_MOVE) { + for (int i = 0; i < pointerCount; i++) { + final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); + tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue); + } + } else { + final PointerTracker tracker = getPointerTracker(id); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + tracker.onDownEvent(x, y, eventTime, queue); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + tracker.onUpEvent(x, y, eventTime, queue); + break; + case MotionEvent.ACTION_CANCEL: + tracker.onCancelEvent(x, y, eventTime, queue); + break; + } + } + + return true; + } + + protected void onSwipeDown() { + mKeyboardActionListener.onSwipeDown(); + } + + @Override + public void closing() { + super.closing(); + dismissMiniKeyboard(); + mPopupPanelCache.clear(); + } + + private boolean dismissMiniKeyboard() { + if (mPopupWindow != null && mPopupWindow.isShowing()) { + mPopupWindow.dismiss(); + mPopupMiniKeyboardPanel = null; + invalidateAllKeys(); + return true; + } + return false; + } + + public boolean handleBack() { + return dismissMiniKeyboard(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event) + || super.dispatchTouchEvent(event); + } + + return super.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + final PointerTracker tracker = getPointerTracker(0); + return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent( + event, tracker) || super.dispatchPopulateAccessibilityEvent(event); + } + + return super.dispatchPopulateAccessibilityEvent(event); + } + + public boolean onHoverEvent(MotionEvent event) { + // Since reflection doesn't support calling superclass methods, this + // method checks for the existence of onHoverEvent() in the View class + // before returning a value. + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + final PointerTracker tracker = getPointerTracker(0); + return AccessibleKeyboardViewProxy.getInstance().onHoverEvent(event, tracker); + } + + return false; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 75d022b52..510bc16b4 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -27,7 +27,7 @@ import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.Utils; // TODO: We should remove this class -public class LatinKeyboardView extends KeyboardView { +public class LatinKeyboardView extends LatinKeyboardBaseView { private static final String TAG = LatinKeyboardView.class.getSimpleName(); private static boolean DEBUG_MODE = LatinImeLogger.sDBG; diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 6228cc8b2..68284cd1b 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -21,7 +21,7 @@ import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; -import com.android.inputmethod.keyboard.KeyboardView.UIHandler; +import com.android.inputmethod.keyboard.LatinKeyboardBaseView.UIHandler; import com.android.inputmethod.keyboard.internal.PointerTrackerKeyState; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.LatinImeLogger; @@ -42,8 +42,8 @@ public class PointerTracker { public interface UIProxy { public void invalidateKey(Key key); public void showKeyPreview(int keyIndex, PointerTracker tracker); + public void cancelShowKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker); - public boolean hasDistinctMultitouch(); } public final int mPointerId; @@ -53,7 +53,7 @@ public class PointerTracker { private final int mLongPressKeyTimeout; private final int mLongPressShiftKeyTimeout; - private final KeyboardView mKeyboardView; + private final LatinKeyboardBaseView mKeyboardView; private final UIProxy mProxy; private final UIHandler mHandler; private final KeyDetector mKeyDetector; @@ -112,7 +112,7 @@ public class PointerTracker { public void onSwipeDown() {} }; - public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler, + public PointerTracker(int id, LatinKeyboardBaseView keyboardView, UIHandler handler, KeyDetector keyDetector, UIProxy proxy) { if (proxy == null || handler == null || keyDetector == null) throw new NullPointerException(); @@ -123,7 +123,7 @@ public class PointerTracker { mKeyDetector = keyDetector; mKeyboardSwitcher = KeyboardSwitcher.getInstance(); mKeyState = new PointerTrackerKeyState(keyDetector); - mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); + mHasDistinctMultitouch = keyboardView.hasDistinctMultitouch(); final Resources res = mKeyboardView.getResources(); mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); @@ -504,7 +504,7 @@ public class PointerTracker { private void onUpEventInternal(int x, int y, long eventTime, boolean updateReleasedKeyGraphics) { mHandler.cancelKeyTimers(); - mHandler.cancelShowKeyPreview(this); + mProxy.cancelShowKeyPreview(this); mIsInSlidingKeyInput = false; final PointerTrackerKeyState keyState = mKeyState; final int keyX, keyY; @@ -564,7 +564,7 @@ public class PointerTracker { private void onCancelEventInternal() { mHandler.cancelKeyTimers(); - mHandler.cancelShowKeyPreview(this); + mProxy.cancelShowKeyPreview(this); dismissKeyPreview(); setReleasedKeyGraphics(mKeyState.getKeyIndex()); mIsInSlidingKeyInput = false; diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java index 3b8c36487..959427aad 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java @@ -31,7 +31,7 @@ import com.android.inputmethod.latin.R; * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting * key presses and touch movements. */ -public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { +public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements PopupPanel { private final int[] mCoordinates = new int[2]; private final boolean mConfigShowMiniKeyboardAtTouchedPoint; diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java index cc89579bb..1885ea14e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java @@ -23,7 +23,7 @@ import android.graphics.Rect; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboardBaseView; import com.android.inputmethod.keyboard.MiniKeyboard; import com.android.inputmethod.latin.R; @@ -199,7 +199,7 @@ public class MiniKeyboardBuilder { } } - public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey, + public MiniKeyboardBuilder(LatinKeyboardBaseView view, int layoutTemplateResId, Key parentKey, Keyboard parentKeyboard) { final Context context = view.getContext(); mRes = context.getResources(); @@ -223,7 +223,7 @@ public class MiniKeyboardBuilder { keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2); } - private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters, + private static int getMaxKeyWidth(LatinKeyboardBaseView view, CharSequence[] popupCharacters, int minKeyWidth) { Paint paint = null; Rect bounds = null; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 929fdac31..4a813541d 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -66,7 +66,7 @@ import com.android.inputmethod.deprecated.recorrection.Recorrection; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboardBaseView; import com.android.inputmethod.keyboard.LatinKeyboard; import com.android.inputmethod.keyboard.LatinKeyboardView; @@ -655,7 +655,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onWindowHidden() { super.onWindowHidden(); - KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); } @@ -668,7 +668,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); - KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); @@ -677,8 +677,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onFinishInputView(boolean finishingInput) { super.onFinishInputView(finishingInput); - KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); - if (inputView != null) inputView.cancelAllMessage(); + LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) inputView.cancelAllMessages(); // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestions(); mHandler.cancelUpdateOldSuggestions(); @@ -866,7 +866,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onComputeInsets(InputMethodService.Insets outInsets) { super.onComputeInsets(outInsets); - final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + final LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView == null || mCandidateViewContainer == null) return; final int containerHeight = mCandidateViewContainer.getHeight();