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