diff options
Diffstat (limited to 'java/src')
16 files changed, 1120 insertions, 1201 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index 3dca9aae6..8185619f9 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -35,7 +35,7 @@ import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.LatinKeyboardBaseView; +import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.keyboard.PointerTracker; public class AccessibleKeyboardViewProxy { @@ -47,7 +47,7 @@ public class AccessibleKeyboardViewProxy { private InputMethodService mInputMethod; private FlickGestureDetector mGestureDetector; - private LatinKeyboardBaseView mView; + private LatinKeyboardView mView; private AccessibleKeyboardActionListener mListener; private AudioManagerCompatWrapper mAudioManager; @@ -65,7 +65,7 @@ public class AccessibleKeyboardViewProxy { return sInstance; } - public static void setView(LatinKeyboardBaseView view) { + public static void setView(LatinKeyboardView view) { sInstance.mView = view; } diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 397b7b16b..f56b52388 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -295,12 +295,8 @@ public class Key { mY = y; mWidth = keyWidth - mHorizontalGap; - CharSequence[] popupCharacters = style.getTextArray( + final CharSequence[] popupCharacters = style.getTextArray( keyAttr, R.styleable.Keyboard_Key_popupCharacters); - if (params.mId.mPasswordInput) { - popupCharacters = PopupCharactersParser.filterOut( - res, popupCharacters, PopupCharactersParser.NON_ASCII_FILTER); - } // In Arabic symbol layouts, we'd like to keep digits in popup characters regardless of // config_digit_popup_characters_enabled. if (params.mId.isAlphabetKeyboard() && !res.getBoolean( diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index 0a3acb48b..3298c41cf 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -91,6 +91,10 @@ public class KeyDetector { mProximityThresholdSquare = threshold * threshold; } + public boolean alwaysAllowsSlidingInput() { + return false; + } + /** * Computes maximum size of the array that can contain all nearby key indices returned by * {@link #getKeyIndexAndNearbyCodes}. diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index 3b3e1f87e..1b6f57b92 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -141,7 +141,7 @@ public class LatinKeyboard extends Keyboard { } } - public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardBaseView view) { + public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) { mSpacebarTextFadeFactor = fadeFactor; updateSpacebarForLocale(false); if (view != null) diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java deleted file mode 100644 index 4a7b2bd60..000000000 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java +++ /dev/null @@ -1,681 +0,0 @@ -/* - * 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.keyboard.internal.MiniKeyboardBuilder; -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 LatinKeyboardBaseView extends KeyboardView implements PointerTracker.KeyEventHandler { - private static final String TAG = LatinKeyboardBaseView.class.getSimpleName(); - - private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; - - // 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<LatinKeyboardBaseView> - 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(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; - } - } - - @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 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); - 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); - 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 MiniKeyboardBuilder( - 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) { - 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; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 42ce7c440..be04b5a52 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -1,152 +1,691 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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 + * 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 + * 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. + * 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.latin.LatinImeLogger; +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; -// TODO: We should remove this class -public class LatinKeyboardView extends LatinKeyboardBaseView { +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 boolean DEBUG_MODE = LatinImeLogger.sDBG; - /** Whether we've started dropping move events because we found a big jump */ - private boolean mDroppingEvents; - /** - * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has - * occured - */ - private boolean mDisableDisambiguation; - /** The distance threshold at which we start treating the touch session as a multi-touch */ - private int mJumpThresholdSquare = Integer.MAX_VALUE; - private int mLastX; - private int mLastY; + 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) { - super(context, 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 setKeyboard(Keyboard newKeyboard) { - super.setKeyboard(newKeyboard); - // One-seventh of the keyboard width seems like a reasonable threshold - final int jumpThreshold = newKeyboard.mOccupiedWidth / 7; - mJumpThresholdSquare = jumpThreshold * jumpThreshold; + 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(); } /** - * This function checks to see if we need to handle any sudden jumps in the pointer location - * that could be due to a multi-touch being treated as a move by the firmware or hardware. - * Once a sudden jump is detected, all subsequent move events are discarded - * until an UP is received.<P> - * When a sudden jump is detected, an UP event is simulated at the last position and when - * the sudden moves subside, a DOWN event is simulated for the second key. - * @param me the motion event - * @return true if the event was consumed, so that it doesn't continue to be handled by - * {@link LatinKeyboardBaseView}. + * Returns whether the device has distinct multi-touch panel. + * @return true if the device has distinct multi-touch panel. */ - private boolean handleSuddenJump(MotionEvent me) { - // If device has distinct multi touch panel, there is no need to check sudden jump. - if (hasDistinctMultitouch()) + 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 int action = me.getAction(); - final int x = (int) me.getX(); - final int y = (int) me.getY(); - boolean result = false; - - // Real multi-touch event? Stop looking for sudden jumps - if (me.getPointerCount() > 1) { - mDisableDisambiguation = true; - } - if (mDisableDisambiguation) { - // If UP, reset the multi-touch flag - if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = 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: - // Reset the "session" - mDroppingEvents = false; - mDisableDisambiguation = false; + 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: - // Is this a big jump? - final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); - // Check the distance. - if (distanceSquare > mJumpThresholdSquare) { - // If we're not yet dropping events, start dropping and send an UP event - if (!mDroppingEvents) { - mDroppingEvents = true; - // Send an up event - MotionEvent translated = MotionEvent.obtain( - me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_UP, - mLastX, mLastY, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - } - result = true; - } else if (mDroppingEvents) { - // If moves are small and we're already dropping events, continue dropping - result = true; - } + tracker.onMoveEvent(x, y, eventTime); break; - case MotionEvent.ACTION_UP: - if (mDroppingEvents) { - // Send a down event first, as we dropped a bunch of sudden jumps and assume that - // the user is releasing the touch on the second key. - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_DOWN, - x, y, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - mDroppingEvents = false; - // Let the up event get processed as well, result = false - } + case MotionEvent.ACTION_CANCEL: + tracker.onCancelEvent(x, y, eventTime); break; } - // Track the previous coordinate - mLastX = x; - mLastY = y; - return result; } @Override - public boolean onTouchEvent(MotionEvent me) { - if (getKeyboard() == null) return true; + public void closing() { + super.closing(); + dismissPopupPanel(); + mPopupPanelCache.clear(); + } - // If there was a sudden jump, return without processing the actual motion event. - if (handleSuddenJump(me)) { - if (DEBUG_MODE) - Log.w(TAG, "onTouchEvent: ignore sudden jump " + me); + @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); + } - return super.onTouchEvent(me); + // Reflection doesn't support calling superclass methods. + return false; } } diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java index 08e7d7e19..17c253963 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java @@ -16,12 +16,18 @@ package com.android.inputmethod.keyboard; -import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardParams; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.android.inputmethod.keyboard.internal.KeyboardBuilder; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.keyboard.internal.PopupCharactersParser; +import com.android.inputmethod.latin.R; public class MiniKeyboard extends Keyboard { private final int mDefaultKeyCoordX; - public MiniKeyboard(MiniKeyboardParams params) { + private MiniKeyboard(Builder.MiniKeyboardParams params) { super(params); mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; } @@ -29,4 +35,244 @@ public class MiniKeyboard extends Keyboard { public int getDefaultCoordX() { return mDefaultKeyCoordX; } + + public static class Builder extends KeyboardBuilder<Builder.MiniKeyboardParams> { + private final CharSequence[] mPopupCharacters; + + public static class MiniKeyboardParams extends KeyboardParams { + /* package */int mTopRowAdjustment; + public int mNumRows; + public int mNumColumns; + public int mLeftKeys; + public int mRightKeys; // includes default key. + + public MiniKeyboardParams() { + super(); + } + + /* package for test */MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth, + int rowHeight, int coordXInParent, int parentKeyboardWidth) { + super(); + setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, + parentKeyboardWidth); + } + + /** + * Set keyboard parameters of mini keyboard. + * + * @param numKeys number of keys in this mini keyboard. + * @param maxColumns number of maximum columns of this mini keyboard. + * @param keyWidth mini keyboard key width in pixel, including horizontal gap. + * @param rowHeight mini keyboard row height in pixel, including vertical gap. + * @param coordXInParent coordinate x of the popup key in parent keyboard. + * @param parentKeyboardWidth parent keyboard width in pixel. + */ + public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight, + int coordXInParent, int parentKeyboardWidth) { + if (parentKeyboardWidth / keyWidth < maxColumns) { + throw new IllegalArgumentException( + "Keyboard is too small to hold mini keyboard: " + parentKeyboardWidth + + " " + keyWidth + " " + maxColumns); + } + mDefaultKeyWidth = keyWidth; + mDefaultRowHeight = rowHeight; + + final int numRows = (numKeys + maxColumns - 1) / maxColumns; + mNumRows = numRows; + final int numColumns = getOptimizedColumns(numKeys, maxColumns); + mNumColumns = numColumns; + + final int numLeftKeys = (numColumns - 1) / 2; + final int numRightKeys = numColumns - numLeftKeys; // including default key. + final int maxLeftKeys = coordXInParent / keyWidth; + final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent) + / keyWidth); + int leftKeys, rightKeys; + if (numLeftKeys > maxLeftKeys) { + leftKeys = maxLeftKeys; + rightKeys = numColumns - maxLeftKeys; + } else if (numRightKeys > maxRightKeys) { + leftKeys = numColumns - maxRightKeys; + rightKeys = maxRightKeys; + } else { + leftKeys = numLeftKeys; + rightKeys = numRightKeys; + } + // Shift right if the left edge of mini keyboard is on the edge of parent keyboard + // unless the parent key is on the left edge. + if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) { + leftKeys--; + rightKeys++; + } + // Shift left if the right edge of mini keyboard is on the edge of parent keyboard + // unless the parent key is on the right edge. + if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) { + leftKeys++; + rightKeys--; + } + mLeftKeys = leftKeys; + mRightKeys = rightKeys; + + // Centering of the top row. + final boolean onEdge = (leftKeys == 0 || rightKeys == 1); + if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) { + mTopRowAdjustment = 0; + } else if (mLeftKeys < mRightKeys - 1) { + mTopRowAdjustment = 1; + } else { + mTopRowAdjustment = -1; + } + + mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth; + mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; + } + + // Return key position according to column count (0 is default). + /* package */int getColumnPos(int n) { + final int col = n % mNumColumns; + if (col == 0) { + // default position. + return 0; + } + int pos = 0; + int right = 1; // include default position key. + int left = 0; + int i = 0; + while (true) { + // Assign right key if available. + if (right < mRightKeys) { + pos = right; + right++; + i++; + } + if (i >= col) + break; + // Assign left key if available. + if (left < mLeftKeys) { + left++; + pos = -left; + i++; + } + if (i >= col) + break; + } + return pos; + } + + private static int getTopRowEmptySlots(int numKeys, int numColumns) { + final int remainingKeys = numKeys % numColumns; + if (remainingKeys == 0) { + return 0; + } else { + return numColumns - remainingKeys; + } + } + + private int getOptimizedColumns(int numKeys, int maxColumns) { + int numColumns = Math.min(numKeys, maxColumns); + while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { + numColumns--; + } + return numColumns; + } + + public int getDefaultKeyCoordX() { + return mLeftKeys * mDefaultKeyWidth; + } + + public int getX(int n, int row) { + final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX(); + if (isTopRow(row)) { + return x + mTopRowAdjustment * (mDefaultKeyWidth / 2); + } + return x; + } + + public int getY(int row) { + return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; + } + + public int getRowFlags(int row) { + int rowFlags = 0; + if (row == 0) + rowFlags |= Keyboard.EDGE_TOP; + if (isTopRow(row)) + rowFlags |= Keyboard.EDGE_BOTTOM; + return rowFlags; + } + + private boolean isTopRow(int rowCount) { + return rowCount == mNumRows - 1; + } + } + + public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) { + super(view.getContext(), new MiniKeyboardParams()); + load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId)); + + // HACK: Current mini keyboard design totally relies on the 9-patch + // padding about horizontal + // and vertical key spacing. To keep the visual of mini keyboard as + // is, these hacks are + // needed to keep having the same horizontal and vertical key + // spacing. + mParams.mHorizontalGap = 0; + mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; + // TODO: When we have correctly padded key background 9-patch + // drawables for mini keyboard, + // revert the above hacks and uncomment the following lines. + // mParams.mHorizontalGap = parentKeyboard.mHorizontalGap; + // mParams.mVerticalGap = parentKeyboard.mVerticalGap; + + mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard; + mPopupCharacters = parentKey.mPopupCharacters; + + final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth); + mParams.setParameters(mPopupCharacters.length, parentKey.mMaxPopupColumn, keyWidth, + parentKeyboard.mDefaultRowHeight, parentKey.mX + + (mParams.mDefaultKeyWidth - keyWidth) / 2, view.getMeasuredWidth()); + } + + private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters, + int minKeyWidth) { + Paint paint = null; + Rect bounds = null; + int maxWidth = 0; + for (CharSequence popupSpec : popupCharacters) { + final CharSequence label = PopupCharactersParser.getLabel(popupSpec.toString()); + // If the label is single letter, minKeyWidth is enough to hold + // the label. + if (label != null && label.length() > 1) { + if (paint == null) { + paint = new Paint(); + paint.setAntiAlias(true); + } + final int labelSize = view.getDefaultLabelSizeAndSetPaint(paint); + paint.setTextSize(labelSize); + if (bounds == null) + bounds = new Rect(); + paint.getTextBounds(label.toString(), 0, label.length(), bounds); + if (maxWidth < bounds.width()) + maxWidth = bounds.width(); + } + } + final int horizontalPadding = (int) view.getContext().getResources() + .getDimension(R.dimen.mini_keyboard_key_horizontal_padding); + return Math.max(minKeyWidth, maxWidth + horizontalPadding); + } + + @Override + public MiniKeyboard build() { + final MiniKeyboardParams params = mParams; + for (int n = 0; n < mPopupCharacters.length; n++) { + final CharSequence label = mPopupCharacters[n]; + final int row = n / params.mNumColumns; + final Key key = new Key(mResources, params, label, params.getX(n, row), + params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight, + params.getRowFlags(row)); + params.onAddKey(key); + } + return new MiniKeyboard(params); + } + } } diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java deleted file mode 100644 index 84bd44c30..000000000 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2010 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 java.util.List; - -public class MiniKeyboardKeyDetector extends KeyDetector { - private final int mSlideAllowanceSquare; - private final int mSlideAllowanceSquareTop; - - public MiniKeyboardKeyDetector(float slideAllowance) { - super(/* keyHysteresisDistance */0); - mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); - // Top slide allowance is slightly longer (sqrt(2) times) than other edges. - mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; - } - - @Override - protected int getMaxNearbyKeys() { - // No nearby key will be returned. - return 1; - } - - @Override - public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { - final List<Key> keys = getKeyboard().mKeys; - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - - int nearestIndex = NOT_A_KEY; - int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; - final int keyCount = keys.size(); - for (int index = 0; index < keyCount; index++) { - final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY); - if (dist < nearestDist) { - nearestIndex = index; - nearestDist = dist; - } - } - - if (allCodes != null && nearestIndex != NOT_A_KEY) - allCodes[0] = keys.get(nearestIndex).mCode; - return nearestIndex; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 1f8119a0f..d33cb442b 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -438,9 +438,9 @@ public class PointerTracker { private void onDownEventInternal(int x, int y, long eventTime) { int keyIndex = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding - // from modifier key, or 3) this pointer is on mini-keyboard. + // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) - || mKeyDetector instanceof MiniKeyboardKeyDetector; + || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; mKeyAlreadyProcessed = false; mIsRepeatableKey = false; diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java index fb932e3e8..2396222bc 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java @@ -28,6 +28,8 @@ import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.latin.R; +import java.util.List; + /** * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting * key presses and touch movements. @@ -39,10 +41,55 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { private final KeyDetector mKeyDetector; private final int mVerticalCorrection; - private LatinKeyboardBaseView mParentKeyboardView; + private LatinKeyboardView mParentKeyboardView; private int mOriginX; private int mOriginY; + private static class MiniKeyboardKeyDetector extends KeyDetector { + private final int mSlideAllowanceSquare; + private final int mSlideAllowanceSquareTop; + + public MiniKeyboardKeyDetector(float slideAllowance) { + super(/* keyHysteresisDistance */0); + mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); + // Top slide allowance is slightly longer (sqrt(2) times) than other edges. + mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; + } + + @Override + public boolean alwaysAllowsSlidingInput() { + return true; + } + + @Override + protected int getMaxNearbyKeys() { + // No nearby key will be returned. + return 1; + } + + @Override + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { + final List<Key> keys = getKeyboard().mKeys; + final int touchX = getTouchX(x); + final int touchY = getTouchY(y); + + int nearestIndex = NOT_A_KEY; + int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; + final int keyCount = keys.size(); + for (int index = 0; index < keyCount; index++) { + final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY); + if (dist < nearestDist) { + nearestIndex = index; + nearestDist = dist; + } + } + + if (allCodes != null && nearestIndex != NOT_A_KEY) + allCodes[0] = keys.get(nearestIndex).mCode; + return nearestIndex; + } + } + private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy() { @Override public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {} @@ -146,11 +193,6 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - // Do nothing for the mini keyboard. - } - - @Override public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { // Mini keyboard needs no pop-up key preview displayed, so we pass always false with a // delay of 0. The delay does not matter actually since the popup is not shown anyway. @@ -158,7 +200,7 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { } @Override - public void showPopupPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey, + public void showPopupPanel(LatinKeyboardView parentKeyboardView, Key parentKey, PointerTracker tracker, PopupWindow window) { mParentKeyboardView = parentKeyboardView; final View container = (View)getParent(); diff --git a/java/src/com/android/inputmethod/keyboard/PopupPanel.java b/java/src/com/android/inputmethod/keyboard/PopupPanel.java index dc526e74f..db637c50b 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupPanel.java +++ b/java/src/com/android/inputmethod/keyboard/PopupPanel.java @@ -26,7 +26,7 @@ public interface PopupPanel extends PointerTracker.KeyEventHandler { * @param tracker the pointer tracker that pressesd the parent key * @param window PopupWindow to be used to show this popup panel */ - public void showPopupPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey, + public void showPopupPanel(LatinKeyboardView parentKeyboardView, Key parentKey, PointerTracker tracker, PopupWindow window); /** diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java new file mode 100644 index 000000000..c4251ccfc --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java @@ -0,0 +1,158 @@ +/* + * 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.os.Build; +import android.util.Log; +import android.view.MotionEvent; + +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; + +public class SuddenJumpingTouchEventHandler { + private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName(); + private static boolean DEBUG_MODE = LatinImeLogger.sDBG; + + public interface ProcessMotionEvent { + public boolean processMotionEvent(MotionEvent me); + } + + private final ProcessMotionEvent mView; + private final boolean mNeedsSuddenJumpingHack; + + /** Whether we've started dropping move events because we found a big jump */ + private boolean mDroppingEvents; + /** + * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has + * occured + */ + private boolean mDisableDisambiguation; + /** The distance threshold at which we start treating the touch session as a multi-touch */ + private int mJumpThresholdSquare = Integer.MAX_VALUE; + private int mLastX; + private int mLastY; + + public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) { + mView = view; + final String[] deviceList = context.getResources().getStringArray( + R.array.sudden_jumping_touch_event_device_list); + mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.DEVICE, deviceList); + } + + private static boolean needsSuddenJumpingHack(String deviceName, String[] deviceList) { + for (String device : deviceList) { + if (device.equalsIgnoreCase(deviceName)) { + return true; + } + } + return false; + } + + public void setKeyboard(Keyboard newKeyboard) { + // One-seventh of the keyboard width seems like a reasonable threshold + final int jumpThreshold = newKeyboard.mOccupiedWidth / 7; + mJumpThresholdSquare = jumpThreshold * jumpThreshold; + } + + /** + * This function checks to see if we need to handle any sudden jumps in the pointer location + * that could be due to a multi-touch being treated as a move by the firmware or hardware. + * Once a sudden jump is detected, all subsequent move events are discarded + * until an UP is received.<P> + * When a sudden jump is detected, an UP event is simulated at the last position and when + * the sudden moves subside, a DOWN event is simulated for the second key. + * @param me the motion event + * @return true if the event was consumed, so that it doesn't continue to be handled by + * {@link LatinKeyboardView}. + */ + private boolean handleSuddenJumping(MotionEvent me) { + if (!mNeedsSuddenJumpingHack) + return false; + final int action = me.getAction(); + final int x = (int) me.getX(); + final int y = (int) me.getY(); + boolean result = false; + + // Real multi-touch event? Stop looking for sudden jumps + if (me.getPointerCount() > 1) { + mDisableDisambiguation = true; + } + if (mDisableDisambiguation) { + // If UP, reset the multi-touch flag + if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false; + return false; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + // Reset the "session" + mDroppingEvents = false; + mDisableDisambiguation = false; + break; + case MotionEvent.ACTION_MOVE: + // Is this a big jump? + final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); + // Check the distance. + if (distanceSquare > mJumpThresholdSquare) { + // If we're not yet dropping events, start dropping and send an UP event + if (!mDroppingEvents) { + mDroppingEvents = true; + // Send an up event + MotionEvent translated = MotionEvent.obtain( + me.getEventTime(), me.getEventTime(), + MotionEvent.ACTION_UP, + mLastX, mLastY, me.getMetaState()); + mView.processMotionEvent(translated); + translated.recycle(); + } + result = true; + } else if (mDroppingEvents) { + // If moves are small and we're already dropping events, continue dropping + result = true; + } + break; + case MotionEvent.ACTION_UP: + if (mDroppingEvents) { + // Send a down event first, as we dropped a bunch of sudden jumps and assume that + // the user is releasing the touch on the second key. + MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), + MotionEvent.ACTION_DOWN, + x, y, me.getMetaState()); + mView.processMotionEvent(translated); + translated.recycle(); + mDroppingEvents = false; + // Let the up event get processed as well, result = false + } + break; + } + // Track the previous coordinate + mLastX = x; + mLastY = y; + return result; + } + + public boolean onTouchEvent(MotionEvent me) { + // If there was a sudden jump, return without processing the actual motion event. + if (handleSuddenJumping(me)) { + if (DEBUG_MODE) + Log.w(TAG, "onTouchEvent: ignore sudden jump " + me); + return true; + } + return mView.processMotionEvent(me); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java deleted file mode 100644 index 31a291cef..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2010 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.internal; - -import android.graphics.Paint; -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.MiniKeyboard; -import com.android.inputmethod.latin.R; - -public class MiniKeyboardBuilder extends - KeyboardBuilder<MiniKeyboardBuilder.MiniKeyboardParams> { - private final CharSequence[] mPopupCharacters; - - public static class MiniKeyboardParams extends KeyboardParams { - /* package */ int mTopRowAdjustment; - public int mNumRows; - public int mNumColumns; - public int mLeftKeys; - public int mRightKeys; // includes default key. - - public MiniKeyboardParams() { - super(); - } - - /* package for test */ MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth, - int rowHeight, int coordXInParent, int parentKeyboardWidth) { - super(); - setParameters( - numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, parentKeyboardWidth); - } - - /** - * Set keyboard parameters of mini keyboard. - * - * @param numKeys number of keys in this mini keyboard. - * @param maxColumns number of maximum columns of this mini keyboard. - * @param keyWidth mini keyboard key width in pixel, including horizontal gap. - * @param rowHeight mini keyboard row height in pixel, including vertical gap. - * @param coordXInParent coordinate x of the popup key in parent keyboard. - * @param parentKeyboardWidth parent keyboard width in pixel. - */ - public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight, - int coordXInParent, int parentKeyboardWidth) { - if (parentKeyboardWidth / keyWidth < maxColumns) { - throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: " - + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); - } - mDefaultKeyWidth = keyWidth; - mDefaultRowHeight = rowHeight; - - final int numRows = (numKeys + maxColumns - 1) / maxColumns; - mNumRows = numRows; - final int numColumns = getOptimizedColumns(numKeys, maxColumns); - mNumColumns = numColumns; - - final int numLeftKeys = (numColumns - 1) / 2; - final int numRightKeys = numColumns - numLeftKeys; // including default key. - final int maxLeftKeys = coordXInParent / keyWidth; - final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent) / keyWidth); - int leftKeys, rightKeys; - if (numLeftKeys > maxLeftKeys) { - leftKeys = maxLeftKeys; - rightKeys = numColumns - maxLeftKeys; - } else if (numRightKeys > maxRightKeys) { - leftKeys = numColumns - maxRightKeys; - rightKeys = maxRightKeys; - } else { - leftKeys = numLeftKeys; - rightKeys = numRightKeys; - } - // Shift right if the left edge of mini keyboard is on the edge of parent keyboard - // unless the parent key is on the left edge. - if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) { - leftKeys--; - rightKeys++; - } - // Shift left if the right edge of mini keyboard is on the edge of parent keyboard - // unless the parent key is on the right edge. - if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) { - leftKeys++; - rightKeys--; - } - mLeftKeys = leftKeys; - mRightKeys = rightKeys; - - // Centering of the top row. - final boolean onEdge = (leftKeys == 0 || rightKeys == 1); - if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) { - mTopRowAdjustment = 0; - } else if (mLeftKeys < mRightKeys - 1) { - mTopRowAdjustment = 1; - } else { - mTopRowAdjustment = -1; - } - - mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth; - mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - } - - // Return key position according to column count (0 is default). - /* package */ int getColumnPos(int n) { - final int col = n % mNumColumns; - if (col == 0) { - // default position. - return 0; - } - int pos = 0; - int right = 1; // include default position key. - int left = 0; - int i = 0; - while (true) { - // Assign right key if available. - if (right < mRightKeys) { - pos = right; - right++; - i++; - } - if (i >= col) - break; - // Assign left key if available. - if (left < mLeftKeys) { - left++; - pos = -left; - i++; - } - if (i >= col) - break; - } - return pos; - } - - private static int getTopRowEmptySlots(int numKeys, int numColumns) { - final int remainingKeys = numKeys % numColumns; - if (remainingKeys == 0) { - return 0; - } else { - return numColumns - remainingKeys; - } - } - - private int getOptimizedColumns(int numKeys, int maxColumns) { - int numColumns = Math.min(numKeys, maxColumns); - while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { - numColumns--; - } - return numColumns; - } - - public int getDefaultKeyCoordX() { - return mLeftKeys * mDefaultKeyWidth; - } - - public int getX(int n, int row) { - final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX(); - if (isTopRow(row)) { - return x + mTopRowAdjustment * (mDefaultKeyWidth / 2); - } - return x; - } - - public int getY(int row) { - return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; - } - - public int getRowFlags(int row) { - int rowFlags = 0; - if (row == 0) rowFlags |= Keyboard.EDGE_TOP; - if (isTopRow(row)) rowFlags |= Keyboard.EDGE_BOTTOM; - return rowFlags; - } - - private boolean isTopRow(int rowCount) { - return rowCount == mNumRows - 1; - } - } - - public MiniKeyboardBuilder(KeyboardView view, int xmlId, Key parentKey, - Keyboard parentKeyboard) { - super(view.getContext(), new MiniKeyboardParams()); - load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId)); - - // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal - // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are - // needed to keep having the same horizontal and vertical key spacing. - mParams.mHorizontalGap = 0; - mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; - // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard, - // revert the above hacks and uncomment the following lines. - //mParams.mHorizontalGap = parentKeyboard.mHorizontalGap; - //mParams.mVerticalGap = parentKeyboard.mVerticalGap; - - mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard; - mPopupCharacters = parentKey.mPopupCharacters; - - final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth); - mParams.setParameters( - mPopupCharacters.length, parentKey.mMaxPopupColumn, - keyWidth, parentKeyboard.mDefaultRowHeight, - parentKey.mX + (mParams.mDefaultKeyWidth - keyWidth) / 2, - view.getMeasuredWidth()); - } - - private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters, - int minKeyWidth) { - Paint paint = null; - Rect bounds = null; - int maxWidth = 0; - for (CharSequence popupSpec : popupCharacters) { - final CharSequence label = PopupCharactersParser.getLabel(popupSpec.toString()); - // If the label is single letter, minKeyWidth is enough to hold the label. - if (label != null && label.length() > 1) { - if (paint == null) { - paint = new Paint(); - paint.setAntiAlias(true); - } - final int labelSize = view.getDefaultLabelSizeAndSetPaint(paint); - paint.setTextSize(labelSize); - if (bounds == null) bounds = new Rect(); - paint.getTextBounds(label.toString(), 0, label.length(), bounds); - if (maxWidth < bounds.width()) - maxWidth = bounds.width(); - } - } - final int horizontalPadding = (int)view.getContext().getResources().getDimension( - R.dimen.mini_keyboard_key_horizontal_padding); - return Math.max(minKeyWidth, maxWidth + horizontalPadding); - } - - @Override - public MiniKeyboard build() { - final MiniKeyboardParams params = mParams; - for (int n = 0; n < mPopupCharacters.length; n++) { - final CharSequence label = mPopupCharacters[n]; - final int row = n / params.mNumColumns; - final Key key = new Key(mResources, params, label, params.getX(n, row), params.getY(row), - params.mDefaultKeyWidth, params.mDefaultRowHeight, params.getRowFlags(row)); - params.onAddKey(key); - } - return new MiniKeyboard(params); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java index 032489e66..7c5abe32a 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java @@ -196,13 +196,6 @@ public class PopupCharactersParser { } }; - public static final CodeFilter NON_ASCII_FILTER = new CodeFilter() { - @Override - public boolean shouldFilterOut(int code) { - return code < 0x20 || code > 0x7e; - } - }; - public static CharSequence[] filterOut(Resources res, CharSequence[] popupCharacters, CodeFilter filter) { if (popupCharacters == null || popupCharacters.length < 1) { diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index ffd204dac..9642151d7 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -64,24 +64,10 @@ public class DictionaryFactory { } } - // null == dictList is not supposed to be possible, but better safe than sorry and it's - // safer for future extension. In this case, rather than returning null, it should be safer - // to return an empty DictionaryCollection. - if (null == dictList) { - return new DictionaryCollection(); - } else { - if (dictList.isEmpty()) { - // The list may be empty if no dictionaries have been added. The getter should not - // return an empty list, but if it does we end up here. Likewise, if the files - // we found could not be opened by the native code for any reason (format mismatch, - // file too big to fit in memory, etc) then we could have an empty list. In this - // case we want to fall back on the resource. - return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, - locale)); - } else { - return new DictionaryCollection(dictList); - } - } + // If the list is empty, that means we should not use any dictionary (for example, the user + // explicitly disabled the main dictionary), so the following is okay. dictList is never + // null, but if for some reason it is, DictionaryCollection handles it gracefully. + return new DictionaryCollection(dictList); } /** diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index afbdd36a9..394414d03 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -113,7 +113,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; - private static final int SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY = 2; + private static final int START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED = 10; private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20; private static final int RESTORE_KEYBOARD_STATE_DELAY = 500; @@ -198,8 +198,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Member variables for remembering the current device orientation. private int mDisplayOrientation; - private int mDisplayWidth; - private int mDisplayHeight; // Object for reacting to adding/removing a dictionary pack. private BroadcastReceiver mDictionaryPackInstallReceiver = @@ -219,31 +217,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5; private static final int MSG_SPACE_TYPED = 6; private static final int MSG_SET_BIGRAM_PREDICTIONS = 7; - private static final int MSG_CONFIRM_ORIENTATION_CHANGE = 8; + private static final int MSG_START_ORIENTATION_CHANGE = 8; private static final int MSG_START_INPUT_VIEW = 9; private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 10; - private static class OrientationChangeArgs { - public final int mOldWidth; - public final int mOldHeight; - private int mRetryCount; - - public OrientationChangeArgs(int oldw, int oldh) { - mOldWidth = oldw; - mOldHeight = oldh; - mRetryCount = 0; - } - - public boolean hasTimedOut() { - mRetryCount++; - return mRetryCount >= 10; - } - - public boolean hasOrientationChangeFinished(DisplayMetrics dm) { - return dm.widthPixels != mOldWidth && dm.heightPixels != mOldHeight; - } - } - public UIHandler(LatinIME outerInstance) { super(outerInstance); } @@ -291,18 +268,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar (LatinKeyboard)msg.obj); } break; - case MSG_CONFIRM_ORIENTATION_CHANGE: { - final OrientationChangeArgs args = (OrientationChangeArgs)msg.obj; - final Resources res = latinIme.mResources; - final DisplayMetrics dm = res.getDisplayMetrics(); - if (args.hasTimedOut() || args.hasOrientationChangeFinished(dm)) { - latinIme.setDisplayGeometry(res.getConfiguration(), dm); - } else { - // It seems orientation changing is on going. - postConfirmOrientationChange(args); - } - break; - } case MSG_START_INPUT_VIEW: latinIme.onStartInputView((EditorInfo)msg.obj, false); break; @@ -411,22 +376,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } - private void postConfirmOrientationChange(OrientationChangeArgs args) { - removeMessages(MSG_CONFIRM_ORIENTATION_CHANGE); - // Will confirm whether orientation change has finished or not again. - sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args), - SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY); - } - - public void startOrientationChanging(int oldw, int oldh) { - postConfirmOrientationChange(new OrientationChangeArgs(oldw, oldh)); + public void startOrientationChanging() { + sendMessageDelayed(obtainMessage(MSG_START_ORIENTATION_CHANGE), + START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED); final LatinIME latinIme = getOuterInstance(); latinIme.mKeyboardSwitcher.getKeyboardState().save(); postRestoreKeyboardLayout(); } public boolean postStartInputView(EditorInfo attribute) { - if (hasMessages(MSG_CONFIRM_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) { + if (hasMessages(MSG_START_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) { removeMessages(MSG_START_INPUT_VIEW); // Postpone onStartInputView by ACCUMULATE_START_INPUT_VIEW_DELAY and see if // orientation change has finished. @@ -438,12 +397,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } - private void setDisplayGeometry(Configuration conf, DisplayMetrics metric) { - mDisplayOrientation = conf.orientation; - mDisplayWidth = metric.widthPixels; - mDisplayHeight = metric.heightPixels; - } - @Override public void onCreate() { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -481,7 +434,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } - setDisplayGeometry(res.getConfiguration(), res.getDisplayMetrics()); + mDisplayOrientation = res.getConfiguration().orientation; // Register to receive ringer mode change and network state change. // Also receive installation and removal of a dictionary pack. @@ -609,8 +562,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void onConfigurationChanged(Configuration conf) { mSubtypeSwitcher.onConfigurationChanged(conf); // If orientation changed while predicting, commit the change - if (conf.orientation != mDisplayOrientation) { - mHandler.startOrientationChanging(mDisplayWidth, mDisplayHeight); + if (mDisplayOrientation != conf.orientation) { + mDisplayOrientation = conf.orientation; + mHandler.startOrientationChanging(); final InputConnection ic = getCurrentInputConnection(); commitTyped(ic); if (ic != null) ic.finishComposingText(); // For voice input |