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
main
Tadashi G. Takaoka 2011-07-01 13:07:59 +09:00
parent 386899a8b0
commit 5f6816fa8b
8 changed files with 704 additions and 591 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();