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