From c8e45ddb032554f4e9d4411d8ef47d98db62d77b Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Mon, 23 Jul 2012 10:27:14 +0900 Subject: Rename LatinKeyboardView to MainKeyboardView Change-Id: I8f3123b8d727c7beb72e226f80c17794f89c8b60 --- .../accessibility/AccessibleKeyboardViewProxy.java | 6 +- .../inputmethod/keyboard/KeyboardSwitcher.java | 16 +- .../inputmethod/keyboard/LatinKeyboardView.java | 1036 -------------------- .../inputmethod/keyboard/MainKeyboardView.java | 1036 ++++++++++++++++++++ .../inputmethod/keyboard/PointerTracker.java | 6 +- .../keyboard/SuddenJumpingTouchEventHandler.java | 2 +- .../com/android/inputmethod/latin/LatinIME.java | 10 +- .../inputmethod/research/ResearchLogger.java | 34 +- 8 files changed, 1073 insertions(+), 1073 deletions(-) delete mode 100644 java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java create mode 100644 java/src/com/android/inputmethod/keyboard/MainKeyboardView.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index f6376d5f4..4ecbf827a 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -29,7 +29,7 @@ import android.view.ViewConfiguration; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.R; @@ -37,7 +37,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy(); private InputMethodService mInputMethod; - private LatinKeyboardView mView; + private MainKeyboardView mView; private AccessibilityEntityProvider mAccessibilityNodeProvider; private Key mLastHoverKey = null; @@ -70,7 +70,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { * * @param view The view to wrap. */ - public void setView(LatinKeyboardView view) { + public void setView(MainKeyboardView view) { if (view == null) { // Ignore null views. return; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 38d78da93..d637ab5f1 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -71,7 +71,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { private boolean mForceNonDistinctMultitouch; private InputView mCurrentInputView; - private LatinKeyboardView mKeyboardView; + private MainKeyboardView mKeyboardView; private LatinIME mLatinIME; private Resources mResources; private SettingsValues mCurrentSettingsValues; @@ -268,7 +268,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void startDoubleTapTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.startDoubleTapTimer(); @@ -278,7 +278,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void cancelDoubleTapTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.cancelDoubleTapTimer(); @@ -288,7 +288,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public boolean isInDoubleTapTimeout() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); return (keyboardView != null) ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false; } @@ -296,7 +296,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void startLongPressTimer(int code) { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.startLongPressTimer(code); @@ -306,7 +306,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void cancelLongPressTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.cancelLongPressTimer(); @@ -346,7 +346,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); } - public LatinKeyboardView getKeyboardView() { + public MainKeyboardView getKeyboardView() { return mKeyboardView; } @@ -372,7 +372,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); + mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); mKeyboardView.setKeyboardActionListener(mLatinIME); if (mForceNonDistinctMultitouch) { mKeyboardView.setDistinctMultitouch(false); diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java deleted file mode 100644 index 0a7e7a2e3..000000000 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ /dev/null @@ -1,1036 +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.animation.AnimatorInflater; -import android.animation.ObjectAnimator; -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.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Message; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodSubtype; -import android.widget.PopupWindow; - -import com.android.inputmethod.accessibility.AccessibilityUtils; -import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; -import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; -import com.android.inputmethod.latin.StringUtils; -import com.android.inputmethod.latin.SubtypeLocale; -import com.android.inputmethod.latin.Utils; -import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.research.ResearchLogger; - -import java.util.Locale; -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(); - - // TODO: Kill process when the usability study mode was changed. - private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; - - /** Listener for {@link KeyboardActionListener}. */ - private KeyboardActionListener mKeyboardActionListener; - - /* Space key and its icons */ - private Key mSpaceKey; - private Drawable mSpaceIcon; - // Stuff to draw language name on spacebar. - private final int mLanguageOnSpacebarFinalAlpha; - private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; - private boolean mNeedsToDisplayLanguage; - private boolean mHasMultipleEnabledIMEsOrSubtypes; - private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; - private final float mSpacebarTextRatio; - private float mSpacebarTextSize; - private final int mSpacebarTextColor; - private final int mSpacebarTextShadowColor; - // The minimum x-scale to fit the language name on spacebar. - private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; - // Stuff to draw auto correction LED on spacebar. - private boolean mAutoCorrectionSpacebarLedOn; - private final boolean mAutoCorrectionSpacebarLedEnabled; - private final Drawable mAutoCorrectionSpacebarLedIcon; - private static final int SPACE_LED_LENGTH_PERCENT = 80; - - // Stuff to draw altCodeWhileTyping keys. - private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; - private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; - private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; - - // More keys keyboard - private PopupWindow mMoreKeysWindow; - private MoreKeysPanel mMoreKeysPanel; - private int mMoreKeysPanelPointerTrackerId; - private final WeakHashMap mMoreKeysPanelCache = - new WeakHashMap(); - private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; - - private final PointerTrackerParams mPointerTrackerParams; - private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; - - protected KeyDetector mKeyDetector; - private boolean mHasDistinctMultitouch; - private int mOldPointerCount = 1; - private Key mOldKey; - - private final KeyTimerHandler mKeyTimerHandler; - - private static class KeyTimerHandler extends StaticInnerHandlerWrapper - implements TimerProxy { - private static final int MSG_REPEAT_KEY = 1; - private static final int MSG_LONGPRESS_KEY = 2; - private static final int MSG_DOUBLE_TAP = 3; - private static final int MSG_TYPING_STATE_EXPIRED = 4; - - private final KeyTimerParams mParams; - - public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) { - super(outerInstance); - mParams = params; - } - - @Override - public void handleMessage(Message msg) { - final LatinKeyboardView keyboardView = getOuterInstance(); - final PointerTracker tracker = (PointerTracker) msg.obj; - switch (msg.what) { - case MSG_REPEAT_KEY: - final Key currentKey = tracker.getKey(); - if (currentKey != null && currentKey.mCode == msg.arg1) { - tracker.onRegisterKey(currentKey); - startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); - } - break; - case MSG_LONGPRESS_KEY: - if (tracker != null) { - keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker); - } else { - KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); - } - break; - case MSG_TYPING_STATE_EXPIRED: - cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, - keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); - break; - } - } - - private void startKeyRepeatTimer(PointerTracker tracker, long delay) { - final Key key = tracker.getKey(); - if (key == null) return; - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); - } - - @Override - public void startKeyRepeatTimer(PointerTracker tracker) { - startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout); - } - - public void cancelKeyRepeatTimer() { - removeMessages(MSG_REPEAT_KEY); - } - - // TODO: Suppress layout changes in key repeat mode - public boolean isInKeyRepeat() { - return hasMessages(MSG_REPEAT_KEY); - } - - @Override - public void startLongPressTimer(int code) { - cancelLongPressTimer(); - final int delay; - switch (code) { - case Keyboard.CODE_SHIFT: - delay = mParams.mLongPressShiftKeyTimeout; - break; - default: - delay = 0; - break; - } - if (delay > 0) { - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); - } - } - - @Override - public void startLongPressTimer(PointerTracker tracker) { - cancelLongPressTimer(); - if (tracker == null) { - return; - } - final Key key = tracker.getKey(); - final int delay; - switch (key.mCode) { - case Keyboard.CODE_SHIFT: - delay = mParams.mLongPressShiftKeyTimeout; - break; - default: - if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { - // We use longer timeout for sliding finger input started from the symbols - // mode key. - delay = mParams.mLongPressKeyTimeout * 3; - } else { - delay = mParams.mLongPressKeyTimeout; - } - break; - } - if (delay > 0) { - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); - } - } - - @Override - public void cancelLongPressTimer() { - removeMessages(MSG_LONGPRESS_KEY); - } - - public static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, - final ObjectAnimator animatorToStart) { - float startFraction = 0.0f; - if (animatorToCancel.isStarted()) { - animatorToCancel.cancel(); - startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); - } - final long startTime = (long)(animatorToStart.getDuration() * startFraction); - animatorToStart.start(); - animatorToStart.setCurrentPlayTime(startTime); - } - - @Override - public void startTypingStateTimer() { - final boolean isTyping = isTypingState(); - removeMessages(MSG_TYPING_STATE_EXPIRED); - sendMessageDelayed( - obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout); - if (isTyping) { - return; - } - final LatinKeyboardView keyboardView = getOuterInstance(); - cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, - keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); - } - - @Override - public boolean isTypingState() { - return hasMessages(MSG_TYPING_STATE_EXPIRED); - } - - @Override - public void startDoubleTapTimer() { - sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP), - ViewConfiguration.getDoubleTapTimeout()); - } - - @Override - public void cancelDoubleTapTimer() { - removeMessages(MSG_DOUBLE_TAP); - } - - @Override - public boolean isInDoubleTapTimeout() { - return hasMessages(MSG_DOUBLE_TAP); - } - - @Override - public void cancelKeyTimers() { - cancelKeyRepeatTimer(); - cancelLongPressTimer(); - } - - public void cancelAllMessages() { - cancelKeyTimers(); - } - } - - public static class PointerTrackerParams { - public final boolean mSlidingKeyInputEnabled; - public final int mTouchNoiseThresholdTime; - public final float mTouchNoiseThresholdDistance; - - public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); - - private PointerTrackerParams() { - mSlidingKeyInputEnabled = false; - mTouchNoiseThresholdTime =0; - mTouchNoiseThresholdDistance = 0; - } - - public PointerTrackerParams(TypedArray latinKeyboardViewAttr) { - mSlidingKeyInputEnabled = latinKeyboardViewAttr.getBoolean( - R.styleable.LatinKeyboardView_slidingKeyInputEnable, false); - mTouchNoiseThresholdTime = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_touchNoiseThresholdTime, 0); - mTouchNoiseThresholdDistance = latinKeyboardViewAttr.getDimension( - R.styleable.LatinKeyboardView_touchNoiseThresholdDistance, 0); - } - } - - static class KeyTimerParams { - public final int mKeyRepeatStartTimeout; - public final int mKeyRepeatInterval; - public final int mLongPressKeyTimeout; - public final int mLongPressShiftKeyTimeout; - public final int mIgnoreAltCodeKeyTimeout; - - public KeyTimerParams(TypedArray latinKeyboardViewAttr) { - mKeyRepeatStartTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_keyRepeatStartTimeout, 0); - mKeyRepeatInterval = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_keyRepeatInterval, 0); - mLongPressKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_longPressKeyTimeout, 0); - mLongPressShiftKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_longPressShiftKeyTimeout, 0); - mIgnoreAltCodeKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_ignoreAltCodeKeyTimeout, 0); - } - } - - public LatinKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.latinKeyboardViewStyle); - } - - public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); - - mHasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - final Resources res = getResources(); - final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( - Utils.getDeviceOverrideValue(res, - R.array.phantom_sudden_move_event_device_list, "false")); - PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); - - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.LatinKeyboardView, defStyle, R.style.LatinKeyboardView); - mAutoCorrectionSpacebarLedEnabled = a.getBoolean( - R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedEnabled, false); - mAutoCorrectionSpacebarLedIcon = a.getDrawable( - R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedIcon); - mSpacebarTextRatio = a.getFraction(R.styleable.LatinKeyboardView_spacebarTextRatio, - 1000, 1000, 1) / 1000.0f; - mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0); - mSpacebarTextShadowColor = a.getColor( - R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0); - mLanguageOnSpacebarFinalAlpha = a.getInt( - R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, - Constants.Color.ALPHA_OPAQUE); - final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0); - final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); - final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); - - final KeyTimerParams keyTimerParams = new KeyTimerParams(a); - mPointerTrackerParams = new PointerTrackerParams(a); - - final float keyHysteresisDistance = a.getDimension( - R.styleable.LatinKeyboardView_keyHysteresisDistance, 0); - mKeyDetector = new KeyDetector(keyHysteresisDistance); - mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams); - mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( - R.styleable.LatinKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); - a.recycle(); - - PointerTracker.setParameters(mPointerTrackerParams); - - mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( - languageOnSpacebarFadeoutAnimatorResId, this); - mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( - altCodeKeyWhileTypingFadeoutAnimatorResId, this); - mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( - altCodeKeyWhileTypingFadeinAnimatorResId, this); - } - - private ObjectAnimator loadObjectAnimator(int resId, Object target) { - if (resId == 0) return null; - final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( - getContext(), resId); - if (animator != null) { - animator.setTarget(target); - } - return animator; - } - - // Getter/setter methods for {@link ObjectAnimator}. - public int getLanguageOnSpacebarAnimAlpha() { - return mLanguageOnSpacebarAnimAlpha; - } - - public void setLanguageOnSpacebarAnimAlpha(int alpha) { - mLanguageOnSpacebarAnimAlpha = alpha; - invalidateKey(mSpaceKey); - } - - public int getAltCodeKeyWhileTypingAnimAlpha() { - return mAltCodeKeyWhileTypingAnimAlpha; - } - - public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) { - mAltCodeKeyWhileTypingAnimAlpha = alpha; - updateAltCodeKeyWhileTyping(); - } - - 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; - } - - /** - * 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 and key repeat. - mKeyTimerHandler.cancelLongPressTimer(); - super.setKeyboard(keyboard); - mKeyDetector.setKeyboard( - keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); - PointerTracker.setKeyDetector(mKeyDetector, mGestureInputEnabled); - mTouchScreenRegulator.setKeyboard(keyboard); - mMoreKeysPanelCache.clear(); - - mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); - mSpaceIcon = (mSpaceKey != null) - ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; - final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; - mSpacebarTextSize = keyHeight * mSpacebarTextRatio; - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_setKeyboard(keyboard); - } - - // This always needs to be set since the accessibility state can - // potentially change without the keyboard being set again. - AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard); - } - - /** - * Returns whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - - public void setDistinctMultitouch(boolean hasDistinctMultitouch) { - mHasDistinctMultitouch = hasDistinctMultitouch; - } - - /** - * 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 - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - // Notify the research logger that the keyboard view has been attached. This is needed - // to properly show the splash screen, which requires that the window token of the - // KeyboardView be non-null. - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().latinKeyboardView_onAttachedToWindow(); - } - } - - @Override - public void cancelAllMessages() { - mKeyTimerHandler.cancelAllMessages(); - super.cancelAllMessages(); - } - - private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) { - // Check if we have a popup layout specified first. - if (mMoreKeysLayout == 0) { - return false; - } - - // Check if we are already displaying popup panel. - if (mMoreKeysPanel != null) - return false; - if (parentKey == null) - return false; - return onLongPress(parentKey, tracker); - } - - // This default implementation returns a more keys panel. - protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) { - if (parentKey.mMoreKeys == null) - return null; - - final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); - if (container == null) - throw new NullPointerException(); - - final MoreKeysKeyboardView moreKeysKeyboardView = - (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); - final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this) - .build(); - moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - return moreKeysKeyboardView; - } - - /** - * Called when a key is long pressed. By default this will open more keys 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) { - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_onLongPress(); - } - final int primaryCode = parentKey.mCode; - if (parentKey.hasEmbeddedMoreKey()) { - final int embeddedCode = parentKey.mMoreKeys[0].mCode; - tracker.onLongPressed(); - invokeCodeInput(embeddedCode); - invokeReleaseKey(primaryCode); - KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); - return true; - } - if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) { - // Long pressing the space key invokes IME switcher dialog. - if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { - tracker.onLongPressed(); - invokeReleaseKey(primaryCode); - return true; - } - } - return openMoreKeysPanel(parentKey, tracker); - } - - private boolean invokeCustomRequest(int code) { - return mKeyboardActionListener.onCustomRequest(code); - } - - private void invokeCodeInput(int primaryCode) { - mKeyboardActionListener.onCodeInput(primaryCode, - KeyboardActionListener.NOT_A_TOUCH_COORDINATE, - KeyboardActionListener.NOT_A_TOUCH_COORDINATE); - } - - private void invokeReleaseKey(int primaryCode) { - mKeyboardActionListener.onReleaseKey(primaryCode, false); - } - - private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) { - MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); - if (moreKeysPanel == null) { - moreKeysPanel = onCreateMoreKeysPanel(parentKey); - if (moreKeysPanel == null) - return false; - mMoreKeysPanelCache.put(parentKey, moreKeysPanel); - } - if (mMoreKeysWindow == null) { - mMoreKeysWindow = new PopupWindow(getContext()); - mMoreKeysWindow.setBackgroundDrawable(null); - mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation); - } - mMoreKeysPanel = moreKeysPanel; - mMoreKeysPanelPointerTrackerId = tracker.mPointerId; - - final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview(); - // The more keys keyboard is usually horizontally aligned with the center of the parent key. - // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more - // keys keyboard is placed at the touch point of the parent key. - final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) - ? tracker.getLastX() - : parentKey.mX + parentKey.mWidth / 2; - // The more keys keyboard is usually vertically aligned with the top edge of the parent key - // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically - // aligned with the bottom edge of the visible part of the key preview. - final int pointY = parentKey.mY + (keyPreviewEnabled - ? mKeyPreviewDrawParams.mPreviewVisibleOffset - : -parentKey.mVerticalGap); - moreKeysPanel.showMoreKeysPanel( - this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener); - final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); - final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); - tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); - dimEntireKeyboard(true); - return true; - } - - public boolean isInSlidingKeyInput() { - if (mMoreKeysPanel != null) { - return true; - } else { - return PointerTracker.isAnyInSlidingKeyInput(); - } - } - - public int getPointerCount() { - return mOldPointerCount; - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - if (getKeyboard() == null) { - return false; - } - 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; - } - - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final int x, y; - if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) { - x = mMoreKeysPanel.translateX((int)me.getX(index)); - y = mMoreKeysPanel.translateY((int)me.getY(index)); - } else { - x = (int)me.getX(index); - y = (int)me.getY(index); - } - if (ENABLE_USABILITY_STUDY_LOG) { - final String eventTag; - switch (action) { - case MotionEvent.ACTION_UP: - eventTag = "[Up]"; - break; - case MotionEvent.ACTION_DOWN: - eventTag = "[Down]"; - break; - case MotionEvent.ACTION_POINTER_UP: - eventTag = "[PointerUp]"; - break; - case MotionEvent.ACTION_POINTER_DOWN: - eventTag = "[PointerDown]"; - break; - case MotionEvent.ACTION_MOVE: // Skip this as being logged below - eventTag = ""; - break; - default: - eventTag = "[Action" + action + "]"; - break; - } - if (!TextUtils.isEmpty(eventTag)) { - final float size = me.getSize(index); - final float pressure = me.getPressure(index); - UsabilityStudyLogUtils.getInstance().write( - eventTag + eventTime + "," + id + "," + x + "," + y + "," - + size + "," + pressure); - } - } - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, index, id, - x, y); - } - - if (mKeyTimerHandler.isInKeyRepeat()) { - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - // 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. - final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); - 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 Key newKey = tracker.getKeyOn(x, y); - if (mOldKey != newKey) { - 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(); - mOldKey = tracker.getKeyOn(lastX, lastY); - tracker.onUpEvent(lastX, lastY, eventTime); - } else if (pointerCount == 1 && oldPointerCount == 1) { - tracker.processMotionEvent(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 int pointerId = me.getPointerId(i); - final PointerTracker tracker = PointerTracker.getPointerTracker( - pointerId, this); - final int px, py; - final MotionEvent motionEvent; - if (mMoreKeysPanel != null - && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { - px = mMoreKeysPanel.translateX((int)me.getX(i)); - py = mMoreKeysPanel.translateY((int)me.getY(i)); - motionEvent = null; - } else { - px = (int)me.getX(i); - py = (int)me.getY(i); - motionEvent = me; - } - tracker.onMoveEvent(px, py, eventTime, motionEvent); - if (ENABLE_USABILITY_STUDY_LOG) { - final float pointerSize = me.getSize(i); - final float pointerPressure = me.getPressure(i); - UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + "," - + pointerId + "," + px + "," + py + "," - + pointerSize + "," + pointerPressure); - } - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, - i, pointerId, px, py); - } - } - } else { - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - tracker.processMotionEvent(action, x, y, eventTime, this); - } - - return true; - } - - @Override - public void closing() { - super.closing(); - dismissMoreKeysPanel(); - mMoreKeysPanelCache.clear(); - } - - @Override - public boolean dismissMoreKeysPanel() { - if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { - mMoreKeysWindow.dismiss(); - mMoreKeysPanel = null; - mMoreKeysPanelPointerTrackerId = -1; - dimEntireKeyboard(false); - return true; - } - return false; - } - - @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(TAG, e); - } - } - } - - /** - * Receives hover events from the input framework. - * - * @param event The motion event to be dispatched. - * @return {@code true} if the event was handled by the view, {@code false} - * otherwise - */ - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); - return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); - } - - // Reflection doesn't support calling superclass methods. - return false; - } - - public void updateShortcutKey(boolean available) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; - final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT); - if (shortcutKey == null) return; - shortcutKey.setEnabled(available); - invalidateKey(shortcutKey); - } - - private void updateAltCodeKeyWhileTyping() { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; - for (final Key key : keyboard.mAltCodeKeysWhileTyping) { - invalidateKey(key); - } - } - - public void startDisplayLanguageOnSpacebar(boolean subtypeChanged, - boolean needsToDisplayLanguage, boolean hasMultipleEnabledIMEsOrSubtypes) { - mNeedsToDisplayLanguage = needsToDisplayLanguage; - mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; - final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; - if (animator == null) { - mNeedsToDisplayLanguage = false; - } else { - if (subtypeChanged && needsToDisplayLanguage) { - setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); - if (animator.isStarted()) { - animator.cancel(); - } - animator.start(); - } else { - if (!animator.isStarted()) { - mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; - } - } - } - invalidateKey(mSpaceKey); - } - - public void updateAutoCorrectionState(boolean isAutoCorrection) { - if (!mAutoCorrectionSpacebarLedEnabled) return; - mAutoCorrectionSpacebarLedOn = isAutoCorrection; - invalidateKey(mSpaceKey); - } - - @Override - protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { - if (key.altCodeWhileTyping() && key.isEnabled()) { - params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; - } - if (key.mCode == Keyboard.CODE_SPACE) { - drawSpacebar(key, canvas, paint); - // Whether space key needs to show the "..." popup hint for special purposes - if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { - drawKeyPopupHint(key, canvas, paint, params); - } - } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) { - super.onDrawKeyTopVisuals(key, canvas, paint, params); - drawKeyPopupHint(key, canvas, paint, params); - } else { - super.onDrawKeyTopVisuals(key, canvas, paint, params); - } - } - - private boolean fitsTextIntoWidth(final int width, String text, Paint paint) { - paint.setTextScaleX(1.0f); - final float textWidth = getLabelWidth(text, paint); - if (textWidth < width) return true; - - final float scaleX = width / textWidth; - if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false; - - paint.setTextScaleX(scaleX); - return getLabelWidth(text, paint) < width; - } - - // Layout language name on spacebar. - private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype, - final int width) { - // Choose appropriate language name to fit into the width. - String text = getFullDisplayName(subtype, getResources()); - if (fitsTextIntoWidth(width, text, paint)) { - return text; - } - - text = getMiddleDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; - } - - text = getShortDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; - } - - return ""; - } - - private void drawSpacebar(Key key, Canvas canvas, Paint paint) { - final int width = key.mWidth; - final int height = key.mHeight; - - // If input language are explicitly selected. - if (mNeedsToDisplayLanguage) { - paint.setTextAlign(Align.CENTER); - paint.setTypeface(Typeface.DEFAULT); - paint.setTextSize(mSpacebarTextSize); - final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; - final String language = layoutLanguageOnSpacebar(paint, subtype, width); - // Draw language text with shadow - final float descent = paint.descent(); - final float textHeight = -paint.ascent() + descent; - final float baseline = height / 2 + textHeight / 2; - paint.setColor(mSpacebarTextShadowColor); - paint.setAlpha(mLanguageOnSpacebarAnimAlpha); - canvas.drawText(language, width / 2, baseline - descent - 1, paint); - paint.setColor(mSpacebarTextColor); - paint.setAlpha(mLanguageOnSpacebarAnimAlpha); - canvas.drawText(language, width / 2, baseline - descent, paint); - } - - // Draw the spacebar icon at the bottom - if (mAutoCorrectionSpacebarLedOn) { - final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; - final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); - } else if (mSpaceIcon != null) { - final int iconWidth = mSpaceIcon.getIntrinsicWidth(); - final int iconHeight = mSpaceIcon.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); - } - } - - // InputMethodSubtype's display name for spacebar text in its locale. - // isAdditionalSubtype (T=true, F=false) - // locale layout | Short Middle Full - // ------ ------ - ---- --------- ---------------------- - // en_US qwerty F En English English (US) exception - // en_GB qwerty F En English English (UK) exception - // fr azerty F Fr Français Français - // fr_CA qwerty F Fr Français Français (Canada) - // de qwertz F De Deutsch Deutsch - // zz qwerty F QWERTY QWERTY - // fr qwertz T Fr Français Français (QWERTZ) - // de qwerty T De Deutsch Deutsch (QWERTY) - // en_US azerty T En English English (US) (AZERTY) - // zz azerty T AZERTY AZERTY - - // Get InputMethodSubtype's full display name in its locale. - static String getFullDisplayName(InputMethodSubtype subtype, Resources res) { - if (SubtypeLocale.isNoLanguage(subtype)) { - return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); - } - - return SubtypeLocale.getSubtypeDisplayName(subtype, res); - } - - // Get InputMethodSubtype's short display name in its locale. - static String getShortDisplayName(InputMethodSubtype subtype) { - if (SubtypeLocale.isNoLanguage(subtype)) { - return ""; - } - final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); - return StringUtils.toTitleCase(locale.getLanguage(), locale); - } - - // Get InputMethodSubtype's middle display name in its locale. - static String getMiddleDisplayName(InputMethodSubtype subtype) { - if (SubtypeLocale.isNoLanguage(subtype)) { - return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); - } - final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); - return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java new file mode 100644 index 000000000..8c234e4e6 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -0,0 +1,1036 @@ +/* + * 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.animation.AnimatorInflater; +import android.animation.ObjectAnimator; +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.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Message; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.PopupWindow; + +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; +import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.SubtypeLocale; +import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; +import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; + +import java.util.Locale; +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 MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, + SuddenJumpingTouchEventHandler.ProcessMotionEvent { + private static final String TAG = MainKeyboardView.class.getSimpleName(); + + // TODO: Kill process when the usability study mode was changed. + private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; + + /** Listener for {@link KeyboardActionListener}. */ + private KeyboardActionListener mKeyboardActionListener; + + /* Space key and its icons */ + private Key mSpaceKey; + private Drawable mSpaceIcon; + // Stuff to draw language name on spacebar. + private final int mLanguageOnSpacebarFinalAlpha; + private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; + private boolean mNeedsToDisplayLanguage; + private boolean mHasMultipleEnabledIMEsOrSubtypes; + private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; + private final float mSpacebarTextRatio; + private float mSpacebarTextSize; + private final int mSpacebarTextColor; + private final int mSpacebarTextShadowColor; + // The minimum x-scale to fit the language name on spacebar. + private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; + // Stuff to draw auto correction LED on spacebar. + private boolean mAutoCorrectionSpacebarLedOn; + private final boolean mAutoCorrectionSpacebarLedEnabled; + private final Drawable mAutoCorrectionSpacebarLedIcon; + private static final int SPACE_LED_LENGTH_PERCENT = 80; + + // Stuff to draw altCodeWhileTyping keys. + private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; + private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; + private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; + + // More keys keyboard + private PopupWindow mMoreKeysWindow; + private MoreKeysPanel mMoreKeysPanel; + private int mMoreKeysPanelPointerTrackerId; + private final WeakHashMap mMoreKeysPanelCache = + new WeakHashMap(); + private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; + + private final PointerTrackerParams mPointerTrackerParams; + private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; + + protected KeyDetector mKeyDetector; + private boolean mHasDistinctMultitouch; + private int mOldPointerCount = 1; + private Key mOldKey; + + private final KeyTimerHandler mKeyTimerHandler; + + private static class KeyTimerHandler extends StaticInnerHandlerWrapper + implements TimerProxy { + private static final int MSG_REPEAT_KEY = 1; + private static final int MSG_LONGPRESS_KEY = 2; + private static final int MSG_DOUBLE_TAP = 3; + private static final int MSG_TYPING_STATE_EXPIRED = 4; + + private final KeyTimerParams mParams; + + public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) { + super(outerInstance); + mParams = params; + } + + @Override + public void handleMessage(Message msg) { + final MainKeyboardView keyboardView = getOuterInstance(); + final PointerTracker tracker = (PointerTracker) msg.obj; + switch (msg.what) { + case MSG_REPEAT_KEY: + final Key currentKey = tracker.getKey(); + if (currentKey != null && currentKey.mCode == msg.arg1) { + tracker.onRegisterKey(currentKey); + startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); + } + break; + case MSG_LONGPRESS_KEY: + if (tracker != null) { + keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker); + } else { + KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); + } + break; + case MSG_TYPING_STATE_EXPIRED: + cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, + keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); + break; + } + } + + private void startKeyRepeatTimer(PointerTracker tracker, long delay) { + final Key key = tracker.getKey(); + if (key == null) return; + sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); + } + + @Override + public void startKeyRepeatTimer(PointerTracker tracker) { + startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout); + } + + public void cancelKeyRepeatTimer() { + removeMessages(MSG_REPEAT_KEY); + } + + // TODO: Suppress layout changes in key repeat mode + public boolean isInKeyRepeat() { + return hasMessages(MSG_REPEAT_KEY); + } + + @Override + public void startLongPressTimer(int code) { + cancelLongPressTimer(); + final int delay; + switch (code) { + case Keyboard.CODE_SHIFT: + delay = mParams.mLongPressShiftKeyTimeout; + break; + default: + delay = 0; + break; + } + if (delay > 0) { + sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); + } + } + + @Override + public void startLongPressTimer(PointerTracker tracker) { + cancelLongPressTimer(); + if (tracker == null) { + return; + } + final Key key = tracker.getKey(); + final int delay; + switch (key.mCode) { + case Keyboard.CODE_SHIFT: + delay = mParams.mLongPressShiftKeyTimeout; + break; + default: + if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { + // We use longer timeout for sliding finger input started from the symbols + // mode key. + delay = mParams.mLongPressKeyTimeout * 3; + } else { + delay = mParams.mLongPressKeyTimeout; + } + break; + } + if (delay > 0) { + sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); + } + } + + @Override + public void cancelLongPressTimer() { + removeMessages(MSG_LONGPRESS_KEY); + } + + public static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, + final ObjectAnimator animatorToStart) { + float startFraction = 0.0f; + if (animatorToCancel.isStarted()) { + animatorToCancel.cancel(); + startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); + } + final long startTime = (long)(animatorToStart.getDuration() * startFraction); + animatorToStart.start(); + animatorToStart.setCurrentPlayTime(startTime); + } + + @Override + public void startTypingStateTimer() { + final boolean isTyping = isTypingState(); + removeMessages(MSG_TYPING_STATE_EXPIRED); + sendMessageDelayed( + obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout); + if (isTyping) { + return; + } + final MainKeyboardView keyboardView = getOuterInstance(); + cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, + keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); + } + + @Override + public boolean isTypingState() { + return hasMessages(MSG_TYPING_STATE_EXPIRED); + } + + @Override + public void startDoubleTapTimer() { + sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP), + ViewConfiguration.getDoubleTapTimeout()); + } + + @Override + public void cancelDoubleTapTimer() { + removeMessages(MSG_DOUBLE_TAP); + } + + @Override + public boolean isInDoubleTapTimeout() { + return hasMessages(MSG_DOUBLE_TAP); + } + + @Override + public void cancelKeyTimers() { + cancelKeyRepeatTimer(); + cancelLongPressTimer(); + } + + public void cancelAllMessages() { + cancelKeyTimers(); + } + } + + public static class PointerTrackerParams { + public final boolean mSlidingKeyInputEnabled; + public final int mTouchNoiseThresholdTime; + public final float mTouchNoiseThresholdDistance; + + public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); + + private PointerTrackerParams() { + mSlidingKeyInputEnabled = false; + mTouchNoiseThresholdTime =0; + mTouchNoiseThresholdDistance = 0; + } + + public PointerTrackerParams(TypedArray mainKeyboardViewAttr) { + mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean( + R.styleable.MainKeyboardView_slidingKeyInputEnable, false); + mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); + mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); + } + } + + static class KeyTimerParams { + public final int mKeyRepeatStartTimeout; + public final int mKeyRepeatInterval; + public final int mLongPressKeyTimeout; + public final int mLongPressShiftKeyTimeout; + public final int mIgnoreAltCodeKeyTimeout; + + public KeyTimerParams(TypedArray mainKeyboardViewAttr) { + mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); + mKeyRepeatInterval = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatInterval, 0); + mLongPressKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_longPressKeyTimeout, 0); + mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0); + mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); + } + } + + public MainKeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.mainKeyboardViewStyle); + } + + public MainKeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); + + mHasDistinctMultitouch = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + final Resources res = getResources(); + final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( + Utils.getDeviceOverrideValue(res, + R.array.phantom_sudden_move_event_device_list, "false")); + PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); + mAutoCorrectionSpacebarLedEnabled = a.getBoolean( + R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); + mAutoCorrectionSpacebarLedIcon = a.getDrawable( + R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); + mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio, + 1000, 1000, 1) / 1000.0f; + mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0); + mSpacebarTextShadowColor = a.getColor( + R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); + mLanguageOnSpacebarFinalAlpha = a.getInt( + R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, + Constants.Color.ALPHA_OPAQUE); + final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId( + R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); + final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId( + R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); + final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( + R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); + + final KeyTimerParams keyTimerParams = new KeyTimerParams(a); + mPointerTrackerParams = new PointerTrackerParams(a); + + final float keyHysteresisDistance = a.getDimension( + R.styleable.MainKeyboardView_keyHysteresisDistance, 0); + mKeyDetector = new KeyDetector(keyHysteresisDistance); + mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams); + mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( + R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); + a.recycle(); + + PointerTracker.setParameters(mPointerTrackerParams); + + mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( + languageOnSpacebarFadeoutAnimatorResId, this); + mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( + altCodeKeyWhileTypingFadeoutAnimatorResId, this); + mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( + altCodeKeyWhileTypingFadeinAnimatorResId, this); + } + + private ObjectAnimator loadObjectAnimator(int resId, Object target) { + if (resId == 0) return null; + final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( + getContext(), resId); + if (animator != null) { + animator.setTarget(target); + } + return animator; + } + + // Getter/setter methods for {@link ObjectAnimator}. + public int getLanguageOnSpacebarAnimAlpha() { + return mLanguageOnSpacebarAnimAlpha; + } + + public void setLanguageOnSpacebarAnimAlpha(int alpha) { + mLanguageOnSpacebarAnimAlpha = alpha; + invalidateKey(mSpaceKey); + } + + public int getAltCodeKeyWhileTypingAnimAlpha() { + return mAltCodeKeyWhileTypingAnimAlpha; + } + + public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) { + mAltCodeKeyWhileTypingAnimAlpha = alpha; + updateAltCodeKeyWhileTyping(); + } + + 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; + } + + /** + * 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 and key repeat. + mKeyTimerHandler.cancelLongPressTimer(); + super.setKeyboard(keyboard); + mKeyDetector.setKeyboard( + keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); + PointerTracker.setKeyDetector(mKeyDetector, mGestureInputEnabled); + mTouchScreenRegulator.setKeyboard(keyboard); + mMoreKeysPanelCache.clear(); + + mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); + mSpaceIcon = (mSpaceKey != null) + ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; + final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; + mSpacebarTextSize = keyHeight * mSpacebarTextRatio; + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.mainKeyboardView_setKeyboard(keyboard); + } + + // This always needs to be set since the accessibility state can + // potentially change without the keyboard being set again. + AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard); + } + + /** + * Returns whether the device has distinct multi-touch panel. + * @return true if the device has distinct multi-touch panel. + */ + public boolean hasDistinctMultitouch() { + return mHasDistinctMultitouch; + } + + public void setDistinctMultitouch(boolean hasDistinctMultitouch) { + mHasDistinctMultitouch = hasDistinctMultitouch; + } + + /** + * 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 + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // Notify the research logger that the keyboard view has been attached. This is needed + // to properly show the splash screen, which requires that the window token of the + // KeyboardView be non-null. + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(); + } + } + + @Override + public void cancelAllMessages() { + mKeyTimerHandler.cancelAllMessages(); + super.cancelAllMessages(); + } + + private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) { + // Check if we have a popup layout specified first. + if (mMoreKeysLayout == 0) { + return false; + } + + // Check if we are already displaying popup panel. + if (mMoreKeysPanel != null) + return false; + if (parentKey == null) + return false; + return onLongPress(parentKey, tracker); + } + + // This default implementation returns a more keys panel. + protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) { + if (parentKey.mMoreKeys == null) + return null; + + final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); + if (container == null) + throw new NullPointerException(); + + final MoreKeysKeyboardView moreKeysKeyboardView = + (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); + final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this) + .build(); + moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); + container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + return moreKeysKeyboardView; + } + + /** + * Called when a key is long pressed. By default this will open more keys 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) { + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.mainKeyboardView_onLongPress(); + } + final int primaryCode = parentKey.mCode; + if (parentKey.hasEmbeddedMoreKey()) { + final int embeddedCode = parentKey.mMoreKeys[0].mCode; + tracker.onLongPressed(); + invokeCodeInput(embeddedCode); + invokeReleaseKey(primaryCode); + KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); + return true; + } + if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) { + // Long pressing the space key invokes IME switcher dialog. + if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { + tracker.onLongPressed(); + invokeReleaseKey(primaryCode); + return true; + } + } + return openMoreKeysPanel(parentKey, tracker); + } + + private boolean invokeCustomRequest(int code) { + return mKeyboardActionListener.onCustomRequest(code); + } + + private void invokeCodeInput(int primaryCode) { + mKeyboardActionListener.onCodeInput(primaryCode, + KeyboardActionListener.NOT_A_TOUCH_COORDINATE, + KeyboardActionListener.NOT_A_TOUCH_COORDINATE); + } + + private void invokeReleaseKey(int primaryCode) { + mKeyboardActionListener.onReleaseKey(primaryCode, false); + } + + private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) { + MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); + if (moreKeysPanel == null) { + moreKeysPanel = onCreateMoreKeysPanel(parentKey); + if (moreKeysPanel == null) + return false; + mMoreKeysPanelCache.put(parentKey, moreKeysPanel); + } + if (mMoreKeysWindow == null) { + mMoreKeysWindow = new PopupWindow(getContext()); + mMoreKeysWindow.setBackgroundDrawable(null); + mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation); + } + mMoreKeysPanel = moreKeysPanel; + mMoreKeysPanelPointerTrackerId = tracker.mPointerId; + + final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview(); + // The more keys keyboard is usually horizontally aligned with the center of the parent key. + // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more + // keys keyboard is placed at the touch point of the parent key. + final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) + ? tracker.getLastX() + : parentKey.mX + parentKey.mWidth / 2; + // The more keys keyboard is usually vertically aligned with the top edge of the parent key + // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically + // aligned with the bottom edge of the visible part of the key preview. + final int pointY = parentKey.mY + (keyPreviewEnabled + ? mKeyPreviewDrawParams.mPreviewVisibleOffset + : -parentKey.mVerticalGap); + moreKeysPanel.showMoreKeysPanel( + this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener); + final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); + final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); + tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); + dimEntireKeyboard(true); + return true; + } + + public boolean isInSlidingKeyInput() { + if (mMoreKeysPanel != null) { + return true; + } else { + return PointerTracker.isAnyInSlidingKeyInput(); + } + } + + public int getPointerCount() { + return mOldPointerCount; + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + if (getKeyboard() == null) { + return false; + } + 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; + } + + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final int x, y; + if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) { + x = mMoreKeysPanel.translateX((int)me.getX(index)); + y = mMoreKeysPanel.translateY((int)me.getY(index)); + } else { + x = (int)me.getX(index); + y = (int)me.getY(index); + } + if (ENABLE_USABILITY_STUDY_LOG) { + final String eventTag; + switch (action) { + case MotionEvent.ACTION_UP: + eventTag = "[Up]"; + break; + case MotionEvent.ACTION_DOWN: + eventTag = "[Down]"; + break; + case MotionEvent.ACTION_POINTER_UP: + eventTag = "[PointerUp]"; + break; + case MotionEvent.ACTION_POINTER_DOWN: + eventTag = "[PointerDown]"; + break; + case MotionEvent.ACTION_MOVE: // Skip this as being logged below + eventTag = ""; + break; + default: + eventTag = "[Action" + action + "]"; + break; + } + if (!TextUtils.isEmpty(eventTag)) { + final float size = me.getSize(index); + final float pressure = me.getPressure(index); + UsabilityStudyLogUtils.getInstance().write( + eventTag + eventTime + "," + id + "," + x + "," + y + "," + + size + "," + pressure); + } + } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, index, id, + x, y); + } + + if (mKeyTimerHandler.isInKeyRepeat()) { + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); + // 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. + final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); + 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 Key newKey = tracker.getKeyOn(x, y); + if (mOldKey != newKey) { + 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(); + mOldKey = tracker.getKeyOn(lastX, lastY); + tracker.onUpEvent(lastX, lastY, eventTime); + } else if (pointerCount == 1 && oldPointerCount == 1) { + tracker.processMotionEvent(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 int pointerId = me.getPointerId(i); + final PointerTracker tracker = PointerTracker.getPointerTracker( + pointerId, this); + final int px, py; + final MotionEvent motionEvent; + if (mMoreKeysPanel != null + && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { + px = mMoreKeysPanel.translateX((int)me.getX(i)); + py = mMoreKeysPanel.translateY((int)me.getY(i)); + motionEvent = null; + } else { + px = (int)me.getX(i); + py = (int)me.getY(i); + motionEvent = me; + } + tracker.onMoveEvent(px, py, eventTime, motionEvent); + if (ENABLE_USABILITY_STUDY_LOG) { + final float pointerSize = me.getSize(i); + final float pointerPressure = me.getPressure(i); + UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + "," + + pointerId + "," + px + "," + py + "," + + pointerSize + "," + pointerPressure); + } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, + i, pointerId, px, py); + } + } + } else { + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); + tracker.processMotionEvent(action, x, y, eventTime, this); + } + + return true; + } + + @Override + public void closing() { + super.closing(); + dismissMoreKeysPanel(); + mMoreKeysPanelCache.clear(); + } + + @Override + public boolean dismissMoreKeysPanel() { + if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { + mMoreKeysWindow.dismiss(); + mMoreKeysPanel = null; + mMoreKeysPanelPointerTrackerId = -1; + dimEntireKeyboard(false); + return true; + } + return false; + } + + @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(TAG, e); + } + } + } + + /** + * Receives hover events from the input framework. + * + * @param event The motion event to be dispatched. + * @return {@code true} if the event was handled by the view, {@code false} + * otherwise + */ + @Override + public boolean dispatchHoverEvent(MotionEvent event) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); + return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); + } + + // Reflection doesn't support calling superclass methods. + return false; + } + + public void updateShortcutKey(boolean available) { + final Keyboard keyboard = getKeyboard(); + if (keyboard == null) return; + final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT); + if (shortcutKey == null) return; + shortcutKey.setEnabled(available); + invalidateKey(shortcutKey); + } + + private void updateAltCodeKeyWhileTyping() { + final Keyboard keyboard = getKeyboard(); + if (keyboard == null) return; + for (final Key key : keyboard.mAltCodeKeysWhileTyping) { + invalidateKey(key); + } + } + + public void startDisplayLanguageOnSpacebar(boolean subtypeChanged, + boolean needsToDisplayLanguage, boolean hasMultipleEnabledIMEsOrSubtypes) { + mNeedsToDisplayLanguage = needsToDisplayLanguage; + mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; + final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; + if (animator == null) { + mNeedsToDisplayLanguage = false; + } else { + if (subtypeChanged && needsToDisplayLanguage) { + setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); + if (animator.isStarted()) { + animator.cancel(); + } + animator.start(); + } else { + if (!animator.isStarted()) { + mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; + } + } + } + invalidateKey(mSpaceKey); + } + + public void updateAutoCorrectionState(boolean isAutoCorrection) { + if (!mAutoCorrectionSpacebarLedEnabled) return; + mAutoCorrectionSpacebarLedOn = isAutoCorrection; + invalidateKey(mSpaceKey); + } + + @Override + protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { + if (key.altCodeWhileTyping() && key.isEnabled()) { + params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; + } + if (key.mCode == Keyboard.CODE_SPACE) { + drawSpacebar(key, canvas, paint); + // Whether space key needs to show the "..." popup hint for special purposes + if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { + drawKeyPopupHint(key, canvas, paint, params); + } + } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) { + super.onDrawKeyTopVisuals(key, canvas, paint, params); + drawKeyPopupHint(key, canvas, paint, params); + } else { + super.onDrawKeyTopVisuals(key, canvas, paint, params); + } + } + + private boolean fitsTextIntoWidth(final int width, String text, Paint paint) { + paint.setTextScaleX(1.0f); + final float textWidth = getLabelWidth(text, paint); + if (textWidth < width) return true; + + final float scaleX = width / textWidth; + if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false; + + paint.setTextScaleX(scaleX); + return getLabelWidth(text, paint) < width; + } + + // Layout language name on spacebar. + private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype, + final int width) { + // Choose appropriate language name to fit into the width. + String text = getFullDisplayName(subtype, getResources()); + if (fitsTextIntoWidth(width, text, paint)) { + return text; + } + + text = getMiddleDisplayName(subtype); + if (fitsTextIntoWidth(width, text, paint)) { + return text; + } + + text = getShortDisplayName(subtype); + if (fitsTextIntoWidth(width, text, paint)) { + return text; + } + + return ""; + } + + private void drawSpacebar(Key key, Canvas canvas, Paint paint) { + final int width = key.mWidth; + final int height = key.mHeight; + + // If input language are explicitly selected. + if (mNeedsToDisplayLanguage) { + paint.setTextAlign(Align.CENTER); + paint.setTypeface(Typeface.DEFAULT); + paint.setTextSize(mSpacebarTextSize); + final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; + final String language = layoutLanguageOnSpacebar(paint, subtype, width); + // Draw language text with shadow + final float descent = paint.descent(); + final float textHeight = -paint.ascent() + descent; + final float baseline = height / 2 + textHeight / 2; + paint.setColor(mSpacebarTextShadowColor); + paint.setAlpha(mLanguageOnSpacebarAnimAlpha); + canvas.drawText(language, width / 2, baseline - descent - 1, paint); + paint.setColor(mSpacebarTextColor); + paint.setAlpha(mLanguageOnSpacebarAnimAlpha); + canvas.drawText(language, width / 2, baseline - descent, paint); + } + + // Draw the spacebar icon at the bottom + if (mAutoCorrectionSpacebarLedOn) { + final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; + final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); + int x = (width - iconWidth) / 2; + int y = height - iconHeight; + drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); + } else if (mSpaceIcon != null) { + final int iconWidth = mSpaceIcon.getIntrinsicWidth(); + final int iconHeight = mSpaceIcon.getIntrinsicHeight(); + int x = (width - iconWidth) / 2; + int y = height - iconHeight; + drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); + } + } + + // InputMethodSubtype's display name for spacebar text in its locale. + // isAdditionalSubtype (T=true, F=false) + // locale layout | Short Middle Full + // ------ ------ - ---- --------- ---------------------- + // en_US qwerty F En English English (US) exception + // en_GB qwerty F En English English (UK) exception + // fr azerty F Fr Français Français + // fr_CA qwerty F Fr Français Français (Canada) + // de qwertz F De Deutsch Deutsch + // zz qwerty F QWERTY QWERTY + // fr qwertz T Fr Français Français (QWERTZ) + // de qwerty T De Deutsch Deutsch (QWERTY) + // en_US azerty T En English English (US) (AZERTY) + // zz azerty T AZERTY AZERTY + + // Get InputMethodSubtype's full display name in its locale. + static String getFullDisplayName(InputMethodSubtype subtype, Resources res) { + if (SubtypeLocale.isNoLanguage(subtype)) { + return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); + } + + return SubtypeLocale.getSubtypeDisplayName(subtype, res); + } + + // Get InputMethodSubtype's short display name in its locale. + static String getShortDisplayName(InputMethodSubtype subtype) { + if (SubtypeLocale.isNoLanguage(subtype)) { + return ""; + } + final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); + return StringUtils.toTitleCase(locale.getLanguage(), locale); + } + + // Get InputMethodSubtype's middle display name in its locale. + static String getMiddleDisplayName(InputMethodSubtype subtype) { + if (SubtypeLocale.isNoLanguage(subtype)) { + return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); + } + final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); + return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 9542ee7c4..4a5ecf986 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -119,7 +119,7 @@ public class PointerTracker { } // Parameters for pointer handling. - private static LatinKeyboardView.PointerTrackerParams sParams; + private static MainKeyboardView.PointerTrackerParams sParams; private static int sTouchNoiseThresholdDistanceSquared; private static boolean sNeedsPhantomSuddenMoveEventHack; @@ -198,11 +198,11 @@ public class PointerTracker { } sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; - setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT); + setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT); updateGestureInputEnabledState(null, false /* gestureInputEnabled */); } - public static void setParameters(LatinKeyboardView.PointerTrackerParams params) { + public static void setParameters(MainKeyboardView.PointerTrackerParams params) { sParams = params; sTouchNoiseThresholdDistanceSquared = (int)( params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java index 617207389..2398c0850 100644 --- a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java +++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java @@ -70,7 +70,7 @@ public class SuddenJumpingTouchEventHandler { * 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}. + * {@link MainKeyboardView}. */ private boolean handleSuddenJumping(MotionEvent me) { if (!mNeedsSuddenJumpingHack) diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 4248921f4..49c247660 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -67,7 +67,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.SuggestionsView; @@ -598,7 +598,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) { super.onStartInputView(editorInfo, restarting); final KeyboardSwitcher switcher = mKeyboardSwitcher; - LatinKeyboardView inputView = switcher.getKeyboardView(); + MainKeyboardView inputView = switcher.getKeyboardView(); if (editorInfo == null) { Log.e(TAG, "Null EditorInfo in onStartInputView()"); @@ -889,7 +889,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { // TODO: Modify this if we support suggestions with hard keyboard if (onEvaluateInputViewShown() && mSuggestionsContainer != null) { - final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); + final MainKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); final boolean inputViewShown = (keyboardView != null) ? keyboardView.isShown() : false; final boolean shouldShowSuggestions = shown && (needsInputViewShown ? inputViewShown : true); @@ -950,7 +950,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int extraHeight = extractHeight + backingHeight + suggestionsHeight; int touchY = extraHeight; // Need to set touchable region only if input view is being shown - final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); + final MainKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); if (keyboardView != null && keyboardView.isShown()) { if (mSuggestionsContainer.getVisibility() == View.VISIBLE) { touchY -= suggestionsHeight; @@ -1643,7 +1643,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void handleClose() { commitTyped(LastComposedWord.NOT_A_SEPARATOR); requestHideSelf(0); - LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + MainKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); } diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index d097ea436..d0523f66e 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -27,11 +27,11 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.inputmethodservice.InputMethodService; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.inputmethodservice.InputMethodService; import android.os.Build; import android.os.IBinder; import android.text.TextUtils; @@ -53,7 +53,7 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; @@ -192,7 +192,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - public void latinKeyboardView_onAttachedToWindow() { + public void mainKeyboardView_onAttachedToWindow() { maybeShowSplashScreen(); } @@ -552,9 +552,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang int height) { // TODO: Reimplement using a keyboard background image specific to the ResearchLogger // and remove this method. - // The check for LatinKeyboardView ensures that a red border is only placed around + // The check for MainKeyboardView ensures that a red border is only placed around // the main keyboard, not every keyboard. - if (IS_SHOWING_INDICATOR && isAllowedToLog() && view instanceof LatinKeyboardView) { + if (IS_SHOWING_INDICATOR && isAllowedToLog() && view instanceof MainKeyboardView) { final int savedColor = paint.getColor(); paint.setColor(Color.RED); final Style savedStyle = paint.getStyle(); @@ -876,11 +876,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // Regular logging methods - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = { - "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = { + "MainKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", "pressure" }; - public static void latinKeyboardView_processMotionEvent(final MotionEvent me, final int action, + public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action, final long eventTime, final int index, final int id, final int x, final int y) { if (me != null) { final String actionString; @@ -900,7 +900,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang actionString, eventTime, id, x, y, size, pressure }; getInstance().enqueuePotentiallyPrivateEvent( - EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); + EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); } } @@ -1129,20 +1129,20 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang EVENTKEYS_NULLVALUES); } - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS = { - "LatinKeyboardViewOnLongPress" + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = { + "MainKeyboardViewOnLongPress" }; - public static void latinKeyboardView_onLongPress() { - getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); + public static void mainKeyboardView_onLongPress() { + getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); } - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD = { - "LatinKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width", + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD = { + "MainKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width", "modeName", "action", "navigateNext", "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled", "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th", "keys" }; - public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) { + public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) { if (keyboard != null) { final KeyboardId kid = keyboard.mId; final boolean isPasswordView = kid.passwordInput(); @@ -1166,7 +1166,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang keyboard.mOccupiedHeight, keyboard.mKeys }; - getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD, values); + getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values); getInstance().setIsPasswordView(isPasswordView); } } -- cgit v1.2.3-83-g751a