Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.animation.AnimatorInflater;
     20 import android.animation.ObjectAnimator;
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.content.pm.PackageManager;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Canvas;
     26 import android.graphics.Color;
     27 import android.graphics.Paint;
     28 import android.graphics.Paint.Align;
     29 import android.graphics.Typeface;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Message;
     32 import android.os.SystemClock;
     33 import android.preference.PreferenceManager;
     34 import android.util.AttributeSet;
     35 import android.util.DisplayMetrics;
     36 import android.util.Log;
     37 import android.util.SparseArray;
     38 import android.util.TypedValue;
     39 import android.view.LayoutInflater;
     40 import android.view.MotionEvent;
     41 import android.view.View;
     42 import android.view.ViewConfiguration;
     43 import android.view.ViewGroup;
     44 import android.view.inputmethod.InputMethodSubtype;
     45 import android.widget.TextView;
     46 
     47 import com.android.inputmethod.accessibility.AccessibilityUtils;
     48 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
     49 import com.android.inputmethod.annotations.ExternallyReferenced;
     50 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
     51 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
     52 import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
     53 import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
     54 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
     55 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
     56 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
     57 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
     58 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
     59 import com.android.inputmethod.latin.Constants;
     60 import com.android.inputmethod.latin.LatinImeLogger;
     61 import com.android.inputmethod.latin.R;
     62 import com.android.inputmethod.latin.SuggestedWords;
     63 import com.android.inputmethod.latin.define.ProductionFlag;
     64 import com.android.inputmethod.latin.settings.DebugSettings;
     65 import com.android.inputmethod.latin.utils.CollectionUtils;
     66 import com.android.inputmethod.latin.utils.CoordinateUtils;
     67 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
     68 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     69 import com.android.inputmethod.latin.utils.TypefaceUtils;
     70 import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
     71 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
     72 import com.android.inputmethod.research.ResearchLogger;
     73 
     74 import java.util.WeakHashMap;
     75 
     76 /**
     77  * A view that is responsible for detecting key presses and touch movements.
     78  *
     79  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
     80  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
     81  * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
     82  * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
     83  * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
     84  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
     85  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
     86  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
     87  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
     88  * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
     89  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
     90  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
     91  * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
     92  * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
     93  * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
     94  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
     95  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
     96  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
     97  * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
     98  * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
     99  * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
    100  * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
    101  * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
    102  * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
    103  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
    104  * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
    105  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
    106  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
    107  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
    108  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
    109  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
    110  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
    111  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
    112  * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
    113  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
    114  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
    115  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
    116  */
    117 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
    118         PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
    119     private static final String TAG = MainKeyboardView.class.getSimpleName();
    120 
    121     /** Listener for {@link KeyboardActionListener}. */
    122     private KeyboardActionListener mKeyboardActionListener;
    123 
    124     /* Space key and its icons */
    125     private Key mSpaceKey;
    126     private Drawable mSpaceIcon;
    127     // Stuff to draw language name on spacebar.
    128     private final int mLanguageOnSpacebarFinalAlpha;
    129     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
    130     private boolean mNeedsToDisplayLanguage;
    131     private boolean mHasMultipleEnabledIMEsOrSubtypes;
    132     private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
    133     private final float mSpacebarTextRatio;
    134     private float mSpacebarTextSize;
    135     private final int mSpacebarTextColor;
    136     private final int mSpacebarTextShadowColor;
    137     // The minimum x-scale to fit the language name on spacebar.
    138     private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
    139     // Stuff to draw auto correction LED on spacebar.
    140     private boolean mAutoCorrectionSpacebarLedOn;
    141     private final boolean mAutoCorrectionSpacebarLedEnabled;
    142     private final Drawable mAutoCorrectionSpacebarLedIcon;
    143     private static final int SPACE_LED_LENGTH_PERCENT = 80;
    144 
    145     // Stuff to draw altCodeWhileTyping keys.
    146     private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
    147     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
    148     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
    149 
    150     // Preview placer view
    151     private final PreviewPlacerView mPreviewPlacerView;
    152     private final int[] mOriginCoords = CoordinateUtils.newInstance();
    153     private final GestureFloatingPreviewText mGestureFloatingPreviewText;
    154     private final GestureTrailsPreview mGestureTrailsPreview;
    155     private final SlidingKeyInputPreview mSlidingKeyInputPreview;
    156 
    157     // Key preview
    158     private final int mKeyPreviewLayoutId;
    159     private final int mKeyPreviewOffset;
    160     private final int mKeyPreviewHeight;
    161     private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
    162     private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
    163     private boolean mShowKeyPreviewPopup = true;
    164     private int mKeyPreviewLingerTimeout;
    165 
    166     // More keys keyboard
    167     private final Paint mBackgroundDimAlphaPaint = new Paint();
    168     private boolean mNeedsToDimEntireKeyboard;
    169     private final View mMoreKeysKeyboardContainer;
    170     private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache =
    171             CollectionUtils.newWeakHashMap();
    172     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
    173     // More keys panel (used by both more keys keyboard and more suggestions view)
    174     // TODO: Consider extending to support multiple more keys panels
    175     private MoreKeysPanel mMoreKeysPanel;
    176 
    177     // Gesture floating preview text
    178     // TODO: Make this parameter customizable by user via settings.
    179     private int mGestureFloatingPreviewTextLingerTimeout;
    180 
    181     private KeyDetector mKeyDetector;
    182     private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
    183 
    184     private final KeyTimerHandler mKeyTimerHandler;
    185     private final int mLanguageOnSpacebarHorizontalMargin;
    186 
    187     private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
    188             implements TimerProxy {
    189         private static final int MSG_TYPING_STATE_EXPIRED = 0;
    190         private static final int MSG_REPEAT_KEY = 1;
    191         private static final int MSG_LONGPRESS_KEY = 2;
    192         private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
    193         private static final int MSG_UPDATE_BATCH_INPUT = 4;
    194 
    195         private final int mIgnoreAltCodeKeyTimeout;
    196         private final int mGestureRecognitionUpdateTime;
    197 
    198         public KeyTimerHandler(final MainKeyboardView outerInstance,
    199                 final TypedArray mainKeyboardViewAttr) {
    200             super(outerInstance);
    201 
    202             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
    203                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
    204             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
    205                     R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
    206         }
    207 
    208         @Override
    209         public void handleMessage(final Message msg) {
    210             final MainKeyboardView keyboardView = getOuterInstance();
    211             if (keyboardView == null) {
    212                 return;
    213             }
    214             final PointerTracker tracker = (PointerTracker) msg.obj;
    215             switch (msg.what) {
    216             case MSG_TYPING_STATE_EXPIRED:
    217                 startWhileTypingFadeinAnimation(keyboardView);
    218                 break;
    219             case MSG_REPEAT_KEY:
    220                 tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
    221                 break;
    222             case MSG_LONGPRESS_KEY:
    223                 keyboardView.onLongPress(tracker);
    224                 break;
    225             case MSG_UPDATE_BATCH_INPUT:
    226                 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
    227                 startUpdateBatchInputTimer(tracker);
    228                 break;
    229             }
    230         }
    231 
    232         @Override
    233         public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount,
    234                 final int delay) {
    235             final Key key = tracker.getKey();
    236             if (key == null || delay == 0) {
    237                 return;
    238             }
    239             sendMessageDelayed(
    240                     obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
    241         }
    242 
    243         public void cancelKeyRepeatTimer() {
    244             removeMessages(MSG_REPEAT_KEY);
    245         }
    246 
    247         // TODO: Suppress layout changes in key repeat mode
    248         public boolean isInKeyRepeat() {
    249             return hasMessages(MSG_REPEAT_KEY);
    250         }
    251 
    252         @Override
    253         public void startLongPressTimer(final PointerTracker tracker, final int delay) {
    254             cancelLongPressTimer();
    255             if (delay <= 0) return;
    256             sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
    257         }
    258 
    259         @Override
    260         public void cancelLongPressTimer() {
    261             removeMessages(MSG_LONGPRESS_KEY);
    262         }
    263 
    264         private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
    265                 final ObjectAnimator animatorToStart) {
    266             if (animatorToCancel == null || animatorToStart == null) {
    267                 // TODO: Stop using null as a no-operation animator.
    268                 return;
    269             }
    270             float startFraction = 0.0f;
    271             if (animatorToCancel.isStarted()) {
    272                 animatorToCancel.cancel();
    273                 startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
    274             }
    275             final long startTime = (long)(animatorToStart.getDuration() * startFraction);
    276             animatorToStart.start();
    277             animatorToStart.setCurrentPlayTime(startTime);
    278         }
    279 
    280         private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
    281             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
    282                     keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
    283         }
    284 
    285         private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
    286             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
    287                     keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
    288         }
    289 
    290         @Override
    291         public void startTypingStateTimer(final Key typedKey) {
    292             if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
    293                 return;
    294             }
    295 
    296             final boolean isTyping = isTypingState();
    297             removeMessages(MSG_TYPING_STATE_EXPIRED);
    298             final MainKeyboardView keyboardView = getOuterInstance();
    299 
    300             // When user hits the space or the enter key, just cancel the while-typing timer.
    301             final int typedCode = typedKey.getCode();
    302             if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
    303                 if (isTyping) {
    304                     startWhileTypingFadeinAnimation(keyboardView);
    305                 }
    306                 return;
    307             }
    308 
    309             sendMessageDelayed(
    310                     obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
    311             if (isTyping) {
    312                 return;
    313             }
    314             startWhileTypingFadeoutAnimation(keyboardView);
    315         }
    316 
    317         @Override
    318         public boolean isTypingState() {
    319             return hasMessages(MSG_TYPING_STATE_EXPIRED);
    320         }
    321 
    322         @Override
    323         public void startDoubleTapShiftKeyTimer() {
    324             sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
    325                     ViewConfiguration.getDoubleTapTimeout());
    326         }
    327 
    328         @Override
    329         public void cancelDoubleTapShiftKeyTimer() {
    330             removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
    331         }
    332 
    333         @Override
    334         public boolean isInDoubleTapShiftKeyTimeout() {
    335             return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
    336         }
    337 
    338         @Override
    339         public void cancelKeyTimers() {
    340             cancelKeyRepeatTimer();
    341             cancelLongPressTimer();
    342         }
    343 
    344         @Override
    345         public void startUpdateBatchInputTimer(final PointerTracker tracker) {
    346             if (mGestureRecognitionUpdateTime <= 0) {
    347                 return;
    348             }
    349             removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
    350             sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
    351                     mGestureRecognitionUpdateTime);
    352         }
    353 
    354         @Override
    355         public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
    356             removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
    357         }
    358 
    359         @Override
    360         public void cancelAllUpdateBatchInputTimers() {
    361             removeMessages(MSG_UPDATE_BATCH_INPUT);
    362         }
    363 
    364         public void cancelAllMessages() {
    365             cancelKeyTimers();
    366             cancelAllUpdateBatchInputTimers();
    367         }
    368     }
    369 
    370     private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
    371 
    372     public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
    373         private static final int MSG_DISMISS_KEY_PREVIEW = 0;
    374         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
    375 
    376         public DrawingHandler(final MainKeyboardView outerInstance) {
    377             super(outerInstance);
    378         }
    379 
    380         @Override
    381         public void handleMessage(final Message msg) {
    382             final MainKeyboardView mainKeyboardView = getOuterInstance();
    383             if (mainKeyboardView == null) return;
    384             final PointerTracker tracker = (PointerTracker) msg.obj;
    385             switch (msg.what) {
    386             case MSG_DISMISS_KEY_PREVIEW:
    387                 final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
    388                         tracker.mPointerId);
    389                 if (previewText != null) {
    390                     previewText.setVisibility(INVISIBLE);
    391                 }
    392                 break;
    393             case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
    394                 mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
    395                 break;
    396             }
    397         }
    398 
    399         public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
    400             sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
    401         }
    402 
    403         public void cancelDismissKeyPreview(final PointerTracker tracker) {
    404             removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
    405         }
    406 
    407         private void cancelAllDismissKeyPreviews() {
    408             removeMessages(MSG_DISMISS_KEY_PREVIEW);
    409         }
    410 
    411         public void dismissGestureFloatingPreviewText(final long delay) {
    412             sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
    413         }
    414 
    415         public void cancelAllMessages() {
    416             cancelAllDismissKeyPreviews();
    417         }
    418     }
    419 
    420     public MainKeyboardView(final Context context, final AttributeSet attrs) {
    421         this(context, attrs, R.attr.mainKeyboardViewStyle);
    422     }
    423 
    424     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
    425         super(context, attrs, defStyle);
    426 
    427         PointerTracker.init(getResources());
    428         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    429         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
    430                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
    431         final boolean hasDistinctMultitouch = context.getPackageManager()
    432                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
    433                 && !forceNonDistinctMultitouch;
    434         mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
    435                 : new NonDistinctMultitouchHelper();
    436 
    437         mPreviewPlacerView = new PreviewPlacerView(context, attrs);
    438 
    439         final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
    440                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
    441         final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
    442                 R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
    443         mBackgroundDimAlphaPaint.setColor(Color.BLACK);
    444         mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
    445         mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
    446                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
    447         mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
    448                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
    449         mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
    450                 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
    451         mSpacebarTextColor = mainKeyboardViewAttr.getColor(
    452                 R.styleable.MainKeyboardView_spacebarTextColor, 0);
    453         mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
    454                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
    455         mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
    456                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
    457                 Constants.Color.ALPHA_OPAQUE);
    458         final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
    459                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
    460         final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
    461                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
    462         final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
    463                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
    464 
    465         final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
    466                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
    467         final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
    468                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
    469         mKeyDetector = new KeyDetector(
    470                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
    471         mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
    472         mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
    473                 R.styleable.MainKeyboardView_keyPreviewOffset, 0);
    474         mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
    475                 R.styleable.MainKeyboardView_keyPreviewHeight, 0);
    476         mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
    477                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
    478         mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
    479                 R.styleable.MainKeyboardView_keyPreviewLayout, 0);
    480         if (mKeyPreviewLayoutId == 0) {
    481             mShowKeyPreviewPopup = false;
    482         }
    483         final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
    484                 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
    485         mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
    486                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
    487 
    488         mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
    489                 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
    490         PointerTracker.setParameters(mainKeyboardViewAttr);
    491 
    492         mGestureFloatingPreviewText = new GestureFloatingPreviewText(
    493                 mPreviewPlacerView, mainKeyboardViewAttr);
    494         mPreviewPlacerView.addPreview(mGestureFloatingPreviewText);
    495 
    496         mGestureTrailsPreview = new GestureTrailsPreview(
    497                 mPreviewPlacerView, mainKeyboardViewAttr);
    498         mPreviewPlacerView.addPreview(mGestureTrailsPreview);
    499 
    500         mSlidingKeyInputPreview = new SlidingKeyInputPreview(
    501                 mPreviewPlacerView, mainKeyboardViewAttr);
    502         mPreviewPlacerView.addPreview(mSlidingKeyInputPreview);
    503         mainKeyboardViewAttr.recycle();
    504 
    505         mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
    506                 .inflate(moreKeysKeyboardLayoutId, null);
    507         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
    508                 languageOnSpacebarFadeoutAnimatorResId, this);
    509         mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
    510                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
    511         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
    512                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
    513 
    514         mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
    515 
    516         mLanguageOnSpacebarHorizontalMargin =
    517                 (int) getResources().getDimension(R.dimen.language_on_spacebar_horizontal_margin);
    518     }
    519 
    520     @Override
    521     public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
    522         super.setHardwareAcceleratedDrawingEnabled(enabled);
    523         mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
    524     }
    525 
    526     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
    527         if (resId == 0) {
    528             // TODO: Stop returning null.
    529             return null;
    530         }
    531         final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
    532                 getContext(), resId);
    533         if (animator != null) {
    534             animator.setTarget(target);
    535         }
    536         return animator;
    537     }
    538 
    539     @ExternallyReferenced
    540     public int getLanguageOnSpacebarAnimAlpha() {
    541         return mLanguageOnSpacebarAnimAlpha;
    542     }
    543 
    544     @ExternallyReferenced
    545     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
    546         mLanguageOnSpacebarAnimAlpha = alpha;
    547         invalidateKey(mSpaceKey);
    548     }
    549 
    550     @ExternallyReferenced
    551     public int getAltCodeKeyWhileTypingAnimAlpha() {
    552         return mAltCodeKeyWhileTypingAnimAlpha;
    553     }
    554 
    555     @ExternallyReferenced
    556     public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
    557         if (mAltCodeKeyWhileTypingAnimAlpha == alpha) {
    558             return;
    559         }
    560         // Update the visual of alt-code-key-while-typing.
    561         mAltCodeKeyWhileTypingAnimAlpha = alpha;
    562         final Keyboard keyboard = getKeyboard();
    563         if (keyboard == null) {
    564             return;
    565         }
    566         for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
    567             invalidateKey(key);
    568         }
    569     }
    570 
    571     public void setKeyboardActionListener(final KeyboardActionListener listener) {
    572         mKeyboardActionListener = listener;
    573         PointerTracker.setKeyboardActionListener(listener);
    574     }
    575 
    576     /**
    577      * Returns the {@link KeyboardActionListener} object.
    578      * @return the listener attached to this keyboard
    579      */
    580     @Override
    581     public KeyboardActionListener getKeyboardActionListener() {
    582         return mKeyboardActionListener;
    583     }
    584 
    585     @Override
    586     public KeyDetector getKeyDetector() {
    587         return mKeyDetector;
    588     }
    589 
    590     @Override
    591     public DrawingProxy getDrawingProxy() {
    592         return this;
    593     }
    594 
    595     @Override
    596     public TimerProxy getTimerProxy() {
    597         return mKeyTimerHandler;
    598     }
    599 
    600     /**
    601      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
    602      * view will re-layout itself to accommodate the keyboard.
    603      * @see Keyboard
    604      * @see #getKeyboard()
    605      * @param keyboard the keyboard to display in this view
    606      */
    607     @Override
    608     public void setKeyboard(final Keyboard keyboard) {
    609         // Remove any pending messages, except dismissing preview and key repeat.
    610         mKeyTimerHandler.cancelLongPressTimer();
    611         super.setKeyboard(keyboard);
    612         mKeyDetector.setKeyboard(
    613                 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
    614         PointerTracker.setKeyDetector(mKeyDetector);
    615         mMoreKeysKeyboardCache.clear();
    616 
    617         mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
    618         mSpaceIcon = (mSpaceKey != null)
    619                 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
    620         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
    621         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
    622         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
    623             final int orientation = getContext().getResources().getConfiguration().orientation;
    624             ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
    625         }
    626 
    627         // This always needs to be set since the accessibility state can
    628         // potentially change without the keyboard being set again.
    629         AccessibleKeyboardViewProxy.getInstance().setKeyboard();
    630     }
    631 
    632     /**
    633      * Enables or disables the key feedback popup. This is a popup that shows a magnified
    634      * version of the depressed key. By default the preview is enabled.
    635      * @param previewEnabled whether or not to enable the key feedback preview
    636      * @param delay the delay after which the preview is dismissed
    637      * @see #isKeyPreviewPopupEnabled()
    638      */
    639     public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
    640         mShowKeyPreviewPopup = previewEnabled;
    641         mKeyPreviewLingerTimeout = delay;
    642     }
    643 
    644     private void locatePreviewPlacerView() {
    645         if (mPreviewPlacerView.getParent() != null) {
    646             return;
    647         }
    648         final int width = getWidth();
    649         final int height = getHeight();
    650         if (width == 0 || height == 0) {
    651             // In transient state.
    652             return;
    653         }
    654         getLocationInWindow(mOriginCoords);
    655         final DisplayMetrics dm = getResources().getDisplayMetrics();
    656         if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
    657             // In transient state.
    658             return;
    659         }
    660         final View rootView = getRootView();
    661         if (rootView == null) {
    662             Log.w(TAG, "Cannot find root view");
    663             return;
    664         }
    665         final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
    666         // Note: It'd be very weird if we get null by android.R.id.content.
    667         if (windowContentView == null) {
    668             Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
    669         } else {
    670             windowContentView.addView(mPreviewPlacerView);
    671             mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
    672         }
    673     }
    674 
    675     /**
    676      * Returns the enabled state of the key feedback preview
    677      * @return whether or not the key feedback preview is enabled
    678      * @see #setKeyPreviewPopupEnabled(boolean, int)
    679      */
    680     public boolean isKeyPreviewPopupEnabled() {
    681         return mShowKeyPreviewPopup;
    682     }
    683 
    684     private void addKeyPreview(final TextView keyPreview) {
    685         locatePreviewPlacerView();
    686         mPreviewPlacerView.addView(
    687                 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
    688     }
    689 
    690     private TextView getKeyPreviewText(final int pointerId) {
    691         TextView previewText = mKeyPreviewTexts.get(pointerId);
    692         if (previewText != null) {
    693             return previewText;
    694         }
    695         final Context context = getContext();
    696         if (mKeyPreviewLayoutId != 0) {
    697             previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
    698         } else {
    699             previewText = new TextView(context);
    700         }
    701         mKeyPreviewTexts.put(pointerId, previewText);
    702         return previewText;
    703     }
    704 
    705     private void dismissAllKeyPreviews() {
    706         final int pointerCount = mKeyPreviewTexts.size();
    707         for (int id = 0; id < pointerCount; id++) {
    708             final TextView previewText = mKeyPreviewTexts.get(id);
    709             if (previewText != null) {
    710                 previewText.setVisibility(INVISIBLE);
    711             }
    712         }
    713         PointerTracker.setReleasedKeyGraphicsToAllKeys();
    714     }
    715 
    716     // Background state set
    717     private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
    718         { // STATE_MIDDLE
    719             EMPTY_STATE_SET,
    720             { R.attr.state_has_morekeys }
    721         },
    722         { // STATE_LEFT
    723             { R.attr.state_left_edge },
    724             { R.attr.state_left_edge, R.attr.state_has_morekeys }
    725         },
    726         { // STATE_RIGHT
    727             { R.attr.state_right_edge },
    728             { R.attr.state_right_edge, R.attr.state_has_morekeys }
    729         }
    730     };
    731     private static final int STATE_MIDDLE = 0;
    732     private static final int STATE_LEFT = 1;
    733     private static final int STATE_RIGHT = 2;
    734     private static final int STATE_NORMAL = 0;
    735     private static final int STATE_HAS_MOREKEYS = 1;
    736 
    737     @Override
    738     public void showKeyPreview(final PointerTracker tracker) {
    739         final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
    740         final Keyboard keyboard = getKeyboard();
    741         if (!mShowKeyPreviewPopup) {
    742             previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
    743             return;
    744         }
    745 
    746         final TextView previewText = getKeyPreviewText(tracker.mPointerId);
    747         // If the key preview has no parent view yet, add it to the ViewGroup which can place
    748         // key preview absolutely in SoftInputWindow.
    749         if (previewText.getParent() == null) {
    750             addKeyPreview(previewText);
    751         }
    752 
    753         mDrawingHandler.cancelDismissKeyPreview(tracker);
    754         final Key key = tracker.getKey();
    755         // If key is invalid or IME is already closed, we must not show key preview.
    756         // Trying to show key preview while root window is closed causes
    757         // WindowManager.BadTokenException.
    758         if (key == null) {
    759             return;
    760         }
    761 
    762         final KeyDrawParams drawParams = mKeyDrawParams;
    763         previewText.setTextColor(drawParams.mPreviewTextColor);
    764         final Drawable background = previewText.getBackground();
    765         final String label = key.getPreviewLabel();
    766         // What we show as preview should match what we show on a key top in onDraw().
    767         if (label != null) {
    768             // TODO Should take care of temporaryShiftLabel here.
    769             previewText.setCompoundDrawables(null, null, null, null);
    770             previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
    771                     key.selectPreviewTextSize(drawParams));
    772             previewText.setTypeface(key.selectPreviewTypeface(drawParams));
    773             previewText.setText(label);
    774         } else {
    775             previewText.setCompoundDrawables(null, null, null,
    776                     key.getPreviewIcon(keyboard.mIconsSet));
    777             previewText.setText(null);
    778         }
    779 
    780         previewText.measure(
    781                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    782         final int keyDrawWidth = key.getDrawWidth();
    783         final int previewWidth = previewText.getMeasuredWidth();
    784         final int previewHeight = mKeyPreviewHeight;
    785         // The width and height of visible part of the key preview background. The content marker
    786         // of the background 9-patch have to cover the visible part of the background.
    787         previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
    788                 - previewText.getPaddingRight();
    789         previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
    790                 - previewText.getPaddingBottom();
    791         // The distance between the top edge of the parent key and the bottom of the visible part
    792         // of the key preview background.
    793         previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
    794         getLocationInWindow(mOriginCoords);
    795         // The key preview is horizontally aligned with the center of the visible part of the
    796         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
    797         // the left/right background is used if such background is specified.
    798         final int statePosition;
    799         int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
    800                 + CoordinateUtils.x(mOriginCoords);
    801         if (previewX < 0) {
    802             previewX = 0;
    803             statePosition = STATE_LEFT;
    804         } else if (previewX > getWidth() - previewWidth) {
    805             previewX = getWidth() - previewWidth;
    806             statePosition = STATE_RIGHT;
    807         } else {
    808             statePosition = STATE_MIDDLE;
    809         }
    810         // The key preview is placed vertically above the top edge of the parent key with an
    811         // arbitrary offset.
    812         final int previewY = key.getY() - previewHeight + mKeyPreviewOffset
    813                 + CoordinateUtils.y(mOriginCoords);
    814 
    815         if (background != null) {
    816             final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
    817             background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
    818         }
    819         ViewLayoutUtils.placeViewAt(
    820                 previewText, previewX, previewY, previewWidth, previewHeight);
    821         previewText.setVisibility(VISIBLE);
    822     }
    823 
    824     @Override
    825     public void dismissKeyPreview(final PointerTracker tracker) {
    826         mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
    827     }
    828 
    829     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
    830         mSlidingKeyInputPreview.setPreviewEnabled(enabled);
    831     }
    832 
    833     @Override
    834     public void showSlidingKeyInputPreview(final PointerTracker tracker) {
    835         locatePreviewPlacerView();
    836         mSlidingKeyInputPreview.setPreviewPosition(tracker);
    837     }
    838 
    839     @Override
    840     public void dismissSlidingKeyInputPreview() {
    841         mSlidingKeyInputPreview.dismissSlidingKeyInputPreview();
    842     }
    843 
    844     private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
    845             final boolean isGestureFloatingPreviewTextEnabled) {
    846         mGestureFloatingPreviewText.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
    847         mGestureTrailsPreview.setPreviewEnabled(isGestureTrailEnabled);
    848     }
    849 
    850     public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
    851         locatePreviewPlacerView();
    852         mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
    853     }
    854 
    855     public void dismissGestureFloatingPreviewText() {
    856         locatePreviewPlacerView();
    857         mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
    858     }
    859 
    860     @Override
    861     public void showGestureTrail(final PointerTracker tracker,
    862             final boolean showsFloatingPreviewText) {
    863         locatePreviewPlacerView();
    864         if (showsFloatingPreviewText) {
    865             mGestureFloatingPreviewText.setPreviewPosition(tracker);
    866         }
    867         mGestureTrailsPreview.setPreviewPosition(tracker);
    868     }
    869 
    870     // Note that this method is called from a non-UI thread.
    871     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
    872         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
    873     }
    874 
    875     public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser,
    876             final boolean isGestureTrailEnabled,
    877             final boolean isGestureFloatingPreviewTextEnabled) {
    878         PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser);
    879         setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled,
    880                 isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled);
    881     }
    882 
    883     @Override
    884     protected void onAttachedToWindow() {
    885         super.onAttachedToWindow();
    886         // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
    887         // been attached.  This is needed to properly show the splash screen, which requires that
    888         // the window token of the KeyboardView be non-null.
    889         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
    890             ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
    891         }
    892     }
    893 
    894     @Override
    895     protected void onDetachedFromWindow() {
    896         super.onDetachedFromWindow();
    897         mPreviewPlacerView.removeAllViews();
    898         // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
    899         // been detached.  This is needed to invalidate the reference of {@link MainKeyboardView}
    900         // to null.
    901         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
    902             ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
    903         }
    904     }
    905 
    906     private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
    907         if (key.getMoreKeys() == null) {
    908             return null;
    909         }
    910         Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
    911         if (moreKeysKeyboard == null) {
    912             moreKeysKeyboard = new MoreKeysKeyboard.Builder(
    913                     context, key, this, mKeyPreviewDrawParams).build();
    914             mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
    915         }
    916 
    917         final View container = mMoreKeysKeyboardContainer;
    918         final MoreKeysKeyboardView moreKeysKeyboardView =
    919                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
    920         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
    921         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    922         return moreKeysKeyboardView;
    923     }
    924 
    925     /**
    926      * Called when a key is long pressed.
    927      * @param tracker the pointer tracker which pressed the parent key
    928      */
    929     private void onLongPress(final PointerTracker tracker) {
    930         if (isShowingMoreKeysPanel()) {
    931             return;
    932         }
    933         final Key key = tracker.getKey();
    934         if (key == null) {
    935             return;
    936         }
    937         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
    938             ResearchLogger.mainKeyboardView_onLongPress();
    939         }
    940         final KeyboardActionListener listener = mKeyboardActionListener;
    941         if (key.hasNoPanelAutoMoreKey()) {
    942             final int moreKeyCode = key.getMoreKeys()[0].mCode;
    943             tracker.onLongPressed();
    944             listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
    945             listener.onCodeInput(moreKeyCode,
    946                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
    947             listener.onReleaseKey(moreKeyCode, false /* withSliding */);
    948             return;
    949         }
    950         final int code = key.getCode();
    951         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
    952             // Long pressing the space key invokes IME switcher dialog.
    953             if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
    954                 tracker.onLongPressed();
    955                 listener.onReleaseKey(code, false /* withSliding */);
    956                 return;
    957             }
    958         }
    959         openMoreKeysPanel(key, tracker);
    960     }
    961 
    962     private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
    963         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
    964         if (moreKeysPanel == null) {
    965             return;
    966         }
    967 
    968         final int[] lastCoords = CoordinateUtils.newInstance();
    969         tracker.getLastCoordinates(lastCoords);
    970         final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview();
    971         // The more keys keyboard is usually horizontally aligned with the center of the parent key.
    972         // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
    973         // keys keyboard is placed at the touch point of the parent key.
    974         final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
    975                 ? CoordinateUtils.x(lastCoords)
    976                 : key.getX() + key.getWidth() / 2;
    977         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
    978         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
    979         // aligned with the bottom edge of the visible part of the key preview.
    980         // {@code mPreviewVisibleOffset} has been set appropriately in
    981         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
    982         final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset;
    983         moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
    984         tracker.onShowMoreKeysPanel(moreKeysPanel);
    985     }
    986 
    987     public boolean isInSlidingKeyInput() {
    988         if (isShowingMoreKeysPanel()) {
    989             return true;
    990         }
    991         return PointerTracker.isAnyInSlidingKeyInput();
    992     }
    993 
    994     @Override
    995     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
    996         locatePreviewPlacerView();
    997         // TODO: Remove this check
    998         if (panel.isShowingInParent()) {
    999             panel.dismissMoreKeysPanel();
   1000         }
   1001         mPreviewPlacerView.addView(panel.getContainerView());
   1002         mMoreKeysPanel = panel;
   1003         dimEntireKeyboard(true /* dimmed */);
   1004     }
   1005 
   1006     public boolean isShowingMoreKeysPanel() {
   1007         return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
   1008     }
   1009 
   1010     @Override
   1011     public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
   1012         PointerTracker.dismissAllMoreKeysPanels();
   1013     }
   1014 
   1015     @Override
   1016     public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
   1017         dimEntireKeyboard(false /* dimmed */);
   1018         if (isShowingMoreKeysPanel()) {
   1019             mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
   1020             mMoreKeysPanel = null;
   1021         }
   1022     }
   1023 
   1024     public void startDoubleTapShiftKeyTimer() {
   1025         mKeyTimerHandler.startDoubleTapShiftKeyTimer();
   1026     }
   1027 
   1028     public void cancelDoubleTapShiftKeyTimer() {
   1029         mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
   1030     }
   1031 
   1032     public boolean isInDoubleTapShiftKeyTimeout() {
   1033         return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
   1034     }
   1035 
   1036     @Override
   1037     public boolean dispatchTouchEvent(MotionEvent event) {
   1038         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
   1039             return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event);
   1040         }
   1041         return super.dispatchTouchEvent(event);
   1042     }
   1043 
   1044     @Override
   1045     public boolean onTouchEvent(final MotionEvent me) {
   1046         if (getKeyboard() == null) {
   1047             return false;
   1048         }
   1049         if (mNonDistinctMultitouchHelper != null) {
   1050             if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
   1051                 // Key repeating timer will be canceled if 2 or more keys are in action.
   1052                 mKeyTimerHandler.cancelKeyRepeatTimer();
   1053             }
   1054             // Non distinct multitouch screen support
   1055             mNonDistinctMultitouchHelper.processMotionEvent(me, this);
   1056             return true;
   1057         }
   1058         return processMotionEvent(me);
   1059     }
   1060 
   1061     public boolean processMotionEvent(final MotionEvent me) {
   1062         if (LatinImeLogger.sUsabilityStudy) {
   1063             UsabilityStudyLogUtils.writeMotionEvent(me);
   1064         }
   1065         // Currently the same "move" event is being logged twice.
   1066         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
   1067             ResearchLogger.mainKeyboardView_processMotionEvent(me);
   1068         }
   1069 
   1070         final int index = me.getActionIndex();
   1071         final int id = me.getPointerId(index);
   1072         final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
   1073         tracker.processMotionEvent(me, this);
   1074         return true;
   1075     }
   1076 
   1077     public void cancelAllOngoingEvents() {
   1078         mKeyTimerHandler.cancelAllMessages();
   1079         mDrawingHandler.cancelAllMessages();
   1080         dismissAllKeyPreviews();
   1081         dismissGestureFloatingPreviewText();
   1082         dismissSlidingKeyInputPreview();
   1083         PointerTracker.dismissAllMoreKeysPanels();
   1084         PointerTracker.cancelAllPointerTrackers();
   1085     }
   1086 
   1087     public void closing() {
   1088         cancelAllOngoingEvents();
   1089         mMoreKeysKeyboardCache.clear();
   1090     }
   1091 
   1092     /**
   1093      * Receives hover events from the input framework.
   1094      *
   1095      * @param event The motion event to be dispatched.
   1096      * @return {@code true} if the event was handled by the view, {@code false}
   1097      *         otherwise
   1098      */
   1099     @Override
   1100     public boolean dispatchHoverEvent(final MotionEvent event) {
   1101         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
   1102             final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
   1103             return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
   1104         }
   1105 
   1106         // Reflection doesn't support calling superclass methods.
   1107         return false;
   1108     }
   1109 
   1110     public void updateShortcutKey(final boolean available) {
   1111         final Keyboard keyboard = getKeyboard();
   1112         if (keyboard == null) {
   1113             return;
   1114         }
   1115         final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT);
   1116         if (shortcutKey == null) {
   1117             return;
   1118         }
   1119         shortcutKey.setEnabled(available);
   1120         invalidateKey(shortcutKey);
   1121     }
   1122 
   1123     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
   1124             final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
   1125         mNeedsToDisplayLanguage = needsToDisplayLanguage;
   1126         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
   1127         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
   1128         if (animator == null) {
   1129             mNeedsToDisplayLanguage = false;
   1130         } else {
   1131             if (subtypeChanged && needsToDisplayLanguage) {
   1132                 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
   1133                 if (animator.isStarted()) {
   1134                     animator.cancel();
   1135                 }
   1136                 animator.start();
   1137             } else {
   1138                 if (!animator.isStarted()) {
   1139                     mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
   1140                 }
   1141             }
   1142         }
   1143         invalidateKey(mSpaceKey);
   1144     }
   1145 
   1146     public void updateAutoCorrectionState(final boolean isAutoCorrection) {
   1147         if (!mAutoCorrectionSpacebarLedEnabled) {
   1148             return;
   1149         }
   1150         mAutoCorrectionSpacebarLedOn = isAutoCorrection;
   1151         invalidateKey(mSpaceKey);
   1152     }
   1153 
   1154     private void dimEntireKeyboard(final boolean dimmed) {
   1155         final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
   1156         mNeedsToDimEntireKeyboard = dimmed;
   1157         if (needsRedrawing) {
   1158             invalidateAllKeys();
   1159         }
   1160     }
   1161 
   1162     @Override
   1163     protected void onDraw(final Canvas canvas) {
   1164         super.onDraw(canvas);
   1165 
   1166         // Overlay a dark rectangle to dim.
   1167         if (mNeedsToDimEntireKeyboard) {
   1168             canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
   1169         }
   1170     }
   1171 
   1172     @Override
   1173     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
   1174             final KeyDrawParams params) {
   1175         if (key.altCodeWhileTyping() && key.isEnabled()) {
   1176             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
   1177         }
   1178         final int code = key.getCode();
   1179         if (code == Constants.CODE_SPACE) {
   1180             drawSpacebar(key, canvas, paint);
   1181             // Whether space key needs to show the "..." popup hint for special purposes
   1182             if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
   1183                 drawKeyPopupHint(key, canvas, paint, params);
   1184             }
   1185         } else if (code == Constants.CODE_LANGUAGE_SWITCH) {
   1186             super.onDrawKeyTopVisuals(key, canvas, paint, params);
   1187             drawKeyPopupHint(key, canvas, paint, params);
   1188         } else {
   1189             super.onDrawKeyTopVisuals(key, canvas, paint, params);
   1190         }
   1191     }
   1192 
   1193     private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
   1194         final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
   1195         paint.setTextScaleX(1.0f);
   1196         final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
   1197         if (textWidth < width) {
   1198             return true;
   1199         }
   1200 
   1201         final float scaleX = maxTextWidth / textWidth;
   1202         if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
   1203             return false;
   1204         }
   1205 
   1206         paint.setTextScaleX(scaleX);
   1207         return TypefaceUtils.getLabelWidth(text, paint) < maxTextWidth;
   1208     }
   1209 
   1210     // Layout language name on spacebar.
   1211     private String layoutLanguageOnSpacebar(final Paint paint,
   1212             final InputMethodSubtype subtype, final int width) {
   1213 
   1214         // Choose appropriate language name to fit into the width.
   1215         final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
   1216         if (fitsTextIntoWidth(width, fullText, paint)) {
   1217             return fullText;
   1218         }
   1219 
   1220         final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
   1221         if (fitsTextIntoWidth(width, middleText, paint)) {
   1222             return middleText;
   1223         }
   1224 
   1225         final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype);
   1226         if (fitsTextIntoWidth(width, shortText, paint)) {
   1227             return shortText;
   1228         }
   1229 
   1230         return "";
   1231     }
   1232 
   1233     private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
   1234         final int width = key.getWidth();
   1235         final int height = key.getHeight();
   1236 
   1237         // If input language are explicitly selected.
   1238         if (mNeedsToDisplayLanguage) {
   1239             paint.setTextAlign(Align.CENTER);
   1240             paint.setTypeface(Typeface.DEFAULT);
   1241             paint.setTextSize(mSpacebarTextSize);
   1242             final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
   1243             final String language = layoutLanguageOnSpacebar(paint, subtype, width);
   1244             // Draw language text with shadow
   1245             final float descent = paint.descent();
   1246             final float textHeight = -paint.ascent() + descent;
   1247             final float baseline = height / 2 + textHeight / 2;
   1248             paint.setColor(mSpacebarTextShadowColor);
   1249             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
   1250             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
   1251             paint.setColor(mSpacebarTextColor);
   1252             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
   1253             canvas.drawText(language, width / 2, baseline - descent, paint);
   1254         }
   1255 
   1256         // Draw the spacebar icon at the bottom
   1257         if (mAutoCorrectionSpacebarLedOn) {
   1258             final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
   1259             final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
   1260             int x = (width - iconWidth) / 2;
   1261             int y = height - iconHeight;
   1262             drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
   1263         } else if (mSpaceIcon != null) {
   1264             final int iconWidth = mSpaceIcon.getIntrinsicWidth();
   1265             final int iconHeight = mSpaceIcon.getIntrinsicHeight();
   1266             int x = (width - iconWidth) / 2;
   1267             int y = height - iconHeight;
   1268             drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
   1269         }
   1270     }
   1271 
   1272     @Override
   1273     public void deallocateMemory() {
   1274         super.deallocateMemory();
   1275         mGestureTrailsPreview.deallocateMemory();
   1276     }
   1277 }
   1278