summaryrefslogtreecommitdiff
path: root/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java')
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java691
1 files changed, 691 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
new file mode 100644
index 000000000..be04b5a52
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -0,0 +1,691 @@
+/*
+ * 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.graphics.Canvas;
+import android.os.Message;
+import android.os.SystemClock;
+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.deprecated.VoiceProxy;
+import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.Utils;
+
+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 LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+ SuddenJumpingTouchEventHandler.ProcessMotionEvent {
+ private static final String TAG = LatinKeyboardView.class.getSimpleName();
+
+ private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
+
+ private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
+
+ // Timing constants
+ private final int mKeyRepeatInterval;
+
+ // XML attribute
+ private final float mVerticalCorrection;
+ private final int mPopupLayout;
+
+ // Mini keyboard
+ private PopupWindow mPopupWindow;
+ private PopupPanel mPopupPanel;
+ private int mPopupPanelPointerTrackerId;
+ private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
+ new WeakHashMap<Key, PopupPanel>();
+
+ /** Listener for {@link KeyboardActionListener}. */
+ private KeyboardActionListener mKeyboardActionListener;
+
+ private final boolean mHasDistinctMultitouch;
+ private int mOldPointerCount = 1;
+ private int mOldKeyIndex;
+
+ protected KeyDetector mKeyDetector;
+
+ // To detect double tap.
+ protected GestureDetector mGestureDetector;
+
+ private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
+
+ private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
+ implements TimerProxy {
+ private static final int MSG_REPEAT_KEY = 1;
+ private static final int MSG_LONGPRESS_KEY = 2;
+ private static final int MSG_IGNORE_DOUBLE_TAP = 3;
+
+ private boolean mInKeyRepeat;
+
+ public KeyTimerHandler(LatinKeyboardView outerInstance) {
+ super(outerInstance);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final LatinKeyboardView 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;
+ }
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+ cancelLongPressTimer();
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+ }
+
+ @Override
+ public void cancelLongPressTimer() {
+ removeMessages(MSG_LONGPRESS_KEY);
+ }
+
+ @Override
+ public void cancelKeyTimers() {
+ cancelKeyRepeatTimer();
+ cancelLongPressTimer();
+ 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();
+ }
+ }
+
+ private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+ private boolean mProcessingShiftDoubleTapEvent = 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 = mKeyTimerHandler.isIgnoringDoubleTap();
+ if (!ignoringDoubleTap)
+ onDoubleTapShiftKey(tracker);
+ return true;
+ }
+ // Otherwise these events should not be handled as double tap.
+ mProcessingShiftDoubleTapEvent = false;
+ }
+ return mProcessingShiftDoubleTapEvent;
+ }
+ }
+
+ public LatinKeyboardView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.keyboardViewStyle);
+ }
+
+ public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+ mVerticalCorrection = a.getDimensionPixelOffset(
+ R.styleable.KeyboardView_verticalCorrection, 0);
+ mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
+ a.recycle();
+
+ final Resources res = getResources();
+ final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
+ mKeyDetector = new KeyDetector(keyHysteresisDistance);
+
+ final boolean ignoreMultitouch = true;
+ mGestureDetector = new GestureDetector(
+ getContext(), new DoubleTapListener(), null, ignoreMultitouch);
+ mGestureDetector.setIsLongpressEnabled(false);
+
+ mHasDistinctMultitouch = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
+ mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
+
+ PointerTracker.init(mHasDistinctMultitouch, getContext());
+ }
+
+ public void startIgnoringDoubleTap() {
+ if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
+ mKeyTimerHandler.startIgnoringDoubleTap();
+ }
+
+ public void setKeyboardActionListener(KeyboardActionListener listener) {
+ mKeyboardActionListener = listener;
+ PointerTracker.setKeyboardActionListener(listener);
+ }
+
+ /**
+ * Returns the {@link KeyboardActionListener} object.
+ * @return the listener attached to this keyboard
+ */
+ @Override
+ public KeyboardActionListener getKeyboardActionListener() {
+ return mKeyboardActionListener;
+ }
+
+ @Override
+ public KeyDetector getKeyDetector() {
+ return mKeyDetector;
+ }
+
+ @Override
+ public DrawingProxy getDrawingProxy() {
+ return this;
+ }
+
+ @Override
+ public TimerProxy getTimerProxy() {
+ return mKeyTimerHandler;
+ }
+
+ @Override
+ public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard instanceof LatinKeyboard) {
+ final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
+ if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
+ // Phone and number keyboard never shows popup preview.
+ super.setKeyPreviewPopupEnabled(false, delay);
+ return;
+ }
+ }
+ super.setKeyPreviewPopupEnabled(previewEnabled, delay);
+ }
+
+ /**
+ * 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) {
+ // Remove any pending messages, except dismissing preview
+ mKeyTimerHandler.cancelKeyTimers();
+ super.setKeyboard(keyboard);
+ mKeyDetector.setKeyboard(
+ keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
+ mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+ PointerTracker.setKeyDetector(mKeyDetector);
+ mTouchScreenRegulator.setKeyboard(keyboard);
+ 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();
+ }
+
+ @Override
+ public void cancelAllMessages() {
+ mKeyTimerHandler.cancelAllMessages();
+ super.cancelAllMessages();
+ }
+
+ private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+ // Check if we have a popup layout specified first.
+ if (mPopupLayout == 0) {
+ return false;
+ }
+
+ // Check if we are already displaying popup panel.
+ if (mPopupPanel != null)
+ return false;
+ final Key parentKey = tracker.getKey(keyIndex);
+ if (parentKey == null)
+ return false;
+ return onLongPress(parentKey, tracker);
+ }
+
+ 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 mPointerQueue.
+ mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+ }
+
+ // This default implementation returns a popup mini keyboard panel.
+ 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);
+ final Keyboard parentKeyboard = getKeyboard();
+ final Keyboard miniKeyboard = new MiniKeyboard.Builder(
+ this, parentKeyboard.mPopupKeyboardResId, 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 mPopupPanel != null;
+ }
+
+ public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
+ final Keyboard keyboard = getKeyboard();
+ // We should not set text fade factor to the keyboard which does not display the language on
+ // its spacebar.
+ if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
+ ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
+ }
+ }
+
+ /**
+ * 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) {
+ final int primaryCode = parentKey.mCode;
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard instanceof LatinKeyboard) {
+ final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
+ if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
+ tracker.onLongPressed();
+ // Long pressing on 0 in phone number keypad gives you a '+'.
+ return invokeOnKey(Keyboard.CODE_PLUS);
+ }
+ if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
+ tracker.onLongPressed();
+ return invokeOnKey(Keyboard.CODE_CAPSLOCK);
+ }
+ }
+ if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
+ // Both long pressing settings key and space key invoke IME switcher dialog.
+ if (getKeyboardActionListener().onCustomRequest(
+ LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+ tracker.onLongPressed();
+ return true;
+ } else {
+ return openPopupPanel(parentKey, tracker);
+ }
+ } else {
+ return openPopupPanel(parentKey, tracker);
+ }
+ }
+
+ private boolean invokeOnKey(int primaryCode) {
+ getKeyboardActionListener().onCodeInput(primaryCode, null,
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ return true;
+ }
+
+ private boolean openPopupPanel(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);
+ }
+ mPopupPanel = popupPanel;
+ mPopupPanelPointerTrackerId = tracker.mPointerId;
+
+ popupPanel.showPopupPanel(this, parentKey, tracker, mPopupWindow);
+ final int translatedX = popupPanel.translateX(tracker.getLastX());
+ final int translatedY = popupPanel.translateY(tracker.getLastY());
+ tracker.onShowPopupPanel(translatedX, translatedY, SystemClock.uptimeMillis(), popupPanel);
+
+ invalidateAllKeys();
+ return true;
+ }
+
+ private PointerTracker getPointerTracker(final int id) {
+ return PointerTracker.getPointerTracker(id, this);
+ }
+
+ public boolean isInSlidingKeyInput() {
+ if (mPopupPanel != null) {
+ return true;
+ } else {
+ return PointerTracker.isAnyInSlidingKeyInput();
+ }
+ }
+
+ public int getPointerCount() {
+ return mOldPointerCount;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent me) {
+ return mTouchScreenRegulator.onTouchEvent(me);
+ }
+
+ @Override
+ public boolean processMotionEvent(MotionEvent me) {
+ final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
+ 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 (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
+ return true;
+ }
+
+ // Gesture detector must be enabled only when mini-keyboard is not on the screen.
+ if (mPopupPanel == null && mGestureDetector != null
+ && mGestureDetector.onTouchEvent(me)) {
+ PointerTracker.dismissAllKeyPreviews();
+ mKeyTimerHandler.cancelKeyTimers();
+ return true;
+ }
+
+ final long eventTime = me.getEventTime();
+ final int index = me.getActionIndex();
+ final int id = me.getPointerId(index);
+ final int x, y;
+ if (mPopupPanel != null && id == mPopupPanelPointerTrackerId) {
+ x = mPopupPanel.translateX((int)me.getX(index));
+ y = mPopupPanel.translateY((int)me.getY(index));
+ } else {
+ x = (int)me.getX(index);
+ y = (int)me.getY(index);
+ }
+
+ if (mKeyTimerHandler.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()) {
+ mKeyTimerHandler.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 (nonDistinctMultitouch) {
+ // 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, this);
+ if (action == MotionEvent.ACTION_UP)
+ tracker.onUpEvent(x, y, eventTime);
+ }
+ } 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);
+ } else if (pointerCount == 1 && oldPointerCount == 1) {
+ processMotionEvent(tracker, action, x, y, eventTime, this);
+ } else {
+ Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
+ + " (old " + oldPointerCount + ")");
+ }
+ return true;
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ for (int i = 0; i < pointerCount; i++) {
+ final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
+ final int px, py;
+ if (mPopupPanel != null && tracker.mPointerId == mPopupPanelPointerTrackerId) {
+ px = mPopupPanel.translateX((int)me.getX(i));
+ py = mPopupPanel.translateY((int)me.getY(i));
+ } else {
+ px = (int)me.getX(i);
+ py = (int)me.getY(i);
+ }
+ tracker.onMoveEvent(px, py, eventTime);
+ }
+ } else {
+ processMotionEvent(getPointerTracker(id), action, x, y, eventTime, this);
+ }
+
+ return true;
+ }
+
+ private static void processMotionEvent(PointerTracker tracker, int action, int x, int y,
+ long eventTime, PointerTracker.KeyEventHandler handler) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ tracker.onDownEvent(x, y, eventTime, handler);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ tracker.onUpEvent(x, y, eventTime);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ tracker.onMoveEvent(x, y, eventTime);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ tracker.onCancelEvent(x, y, eventTime);
+ break;
+ }
+ }
+
+ @Override
+ public void closing() {
+ super.closing();
+ dismissPopupPanel();
+ mPopupPanelCache.clear();
+ }
+
+ @Override
+ public boolean dismissPopupPanel() {
+ if (mPopupWindow != null && mPopupWindow.isShowing()) {
+ mPopupWindow.dismiss();
+ mPopupPanel = null;
+ mPopupPanelPointerTrackerId = -1;
+ invalidateAllKeys();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean handleBack() {
+ return dismissPopupPanel();
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ Utils.GCUtils.getInstance().reset();
+ boolean tryGC = true;
+ for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+ try {
+ super.draw(c);
+ tryGC = false;
+ } catch (OutOfMemoryError e) {
+ tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
+ }
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // Token is available from here.
+ VoiceProxy.getInstance().onAttachedToWindow();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ // Drop non-hover touch events when touch exploration is enabled.
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ 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);
+ }
+
+ /**
+ * Receives hover events from the input framework. This method overrides
+ * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
+ * lower SDK versions, this method is never called.
+ *
+ * @param event The motion event to be dispatched.
+ * @return {@code true} if the event was handled by the view, {@code false}
+ * otherwise
+ */
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ final PointerTracker tracker = getPointerTracker(0);
+ return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
+ }
+
+ // Reflection doesn't support calling superclass methods.
+ return false;
+ }
+}