diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index a87ff989171fb78e33e47f4a3289f02a18a49a3e..38d904a94455536c004a172c83dd8bfbb1aad828 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 752f87d6646d5813032b7c527025d51fc92ac57f..f3e023d3ebb3ab8c2257c59bd7a53e6e7f484a74 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<Key, PopupPanel> mPopupPanelCache = - new WeakHashMap<Key, PopupPanel>(); - - /** Listener for {@link KeyboardActionListener}. */ - private KeyboardActionListener mKeyboardActionListener; - - private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); - - // 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<KeyboardView> { 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<PointerTracker> 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 0000000000000000000000000000000000000000..0a7ba05c867a6ea1472b3ca367baa91939c0e0ec --- /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<Key, PopupPanel> mPopupPanelCache = + new WeakHashMap<Key, PopupPanel>(); + + /** Listener for {@link KeyboardActionListener}. */ + private KeyboardActionListener mKeyboardActionListener; + + private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); + + // 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<LatinKeyboardBaseView> { + 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<PointerTracker> 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 75d022b520fbc1c264f885f69f716c124b7fe00a..510bc16b48f997157e5888ccd93a91b6bfad06b7 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 6228cc8b2cb73abb2e0d1bf76a78e1ea566471a6..68284cd1b3555bb972cfc6c69c8c321343b6ded2 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 3b8c364877a5b597ae980f322b682a488339916e..959427aad2e2eef5c58da1547e7f38ed98327299 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 cc89579bb9b977b099eccbe7e48024d8bbe8eb33..1885ea14ee47c11a7195178998c4e0366758cdec 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 929fdac313acfec3cef0301616d5000b8aaa85ed..4a813541d4c7556627a4ca57c89dae368943b439 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();