MainKeyboardView.java revision 9364d46ac3590d23b8117a66efc8756454cef772
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.AnimatorInflater;
20import android.animation.ObjectAnimator;
21import android.content.Context;
22import android.content.SharedPreferences;
23import android.content.pm.PackageManager;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.Paint.Align;
29import android.graphics.Typeface;
30import android.graphics.drawable.Drawable;
31import android.preference.PreferenceManager;
32import android.util.AttributeSet;
33import android.util.DisplayMetrics;
34import android.util.Log;
35import android.view.LayoutInflater;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.inputmethod.InputMethodSubtype;
40import android.widget.TextView;
41
42import com.android.inputmethod.accessibility.AccessibilityUtils;
43import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
44import com.android.inputmethod.annotations.ExternallyReferenced;
45import com.android.inputmethod.keyboard.internal.DrawingHandler;
46import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
47import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview;
48import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview;
49import com.android.inputmethod.keyboard.internal.KeyDrawParams;
50import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
51import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
52import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
53import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
54import com.android.inputmethod.keyboard.internal.TimerHandler;
55import com.android.inputmethod.latin.Constants;
56import com.android.inputmethod.latin.LatinImeLogger;
57import com.android.inputmethod.latin.R;
58import com.android.inputmethod.latin.SuggestedWords;
59import com.android.inputmethod.latin.define.ProductionFlag;
60import com.android.inputmethod.latin.settings.DebugSettings;
61import com.android.inputmethod.latin.utils.CollectionUtils;
62import com.android.inputmethod.latin.utils.CoordinateUtils;
63import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
64import com.android.inputmethod.latin.utils.TypefaceUtils;
65import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
66import com.android.inputmethod.research.ResearchLogger;
67
68import java.util.WeakHashMap;
69
70/**
71 * A view that is responsible for detecting key presses and touch movements.
72 *
73 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
74 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
75 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
76 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
77 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
78 * @attr ref R.styleable#MainKeyboardView_spacebarBackground
79 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
80 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
81 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
82 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
83 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
84 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
85 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
86 * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger
87 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
88 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
89 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
90 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
91 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
92 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
93 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
94 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
95 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
96 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
97 * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
98 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
99 * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
100 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
101 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
102 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
103 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
104 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
105 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
106 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
107 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
108 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
109 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
110 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
111 */
112public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy,
113        MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks {
114    private static final String TAG = MainKeyboardView.class.getSimpleName();
115
116    /** Listener for {@link KeyboardActionListener}. */
117    private KeyboardActionListener mKeyboardActionListener;
118
119    /* Space key and its icon and background. */
120    private Key mSpaceKey;
121    private Drawable mSpacebarIcon;
122    private final Drawable mSpacebarBackground;
123    // Stuff to draw language name on spacebar.
124    private final int mLanguageOnSpacebarFinalAlpha;
125    private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
126    private boolean mNeedsToDisplayLanguage;
127    private boolean mHasMultipleEnabledIMEsOrSubtypes;
128    private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
129    private final float mLanguageOnSpacebarTextRatio;
130    private float mLanguageOnSpacebarTextSize;
131    private final int mLanguageOnSpacebarTextColor;
132    private final int mLanguageOnSpacebarTextShadowColor;
133    // The minimum x-scale to fit the language name on spacebar.
134    private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
135    // Stuff to draw auto correction LED on spacebar.
136    private boolean mAutoCorrectionSpacebarLedOn;
137    private final boolean mAutoCorrectionSpacebarLedEnabled;
138    private final Drawable mAutoCorrectionSpacebarLedIcon;
139    private static final int SPACE_LED_LENGTH_PERCENT = 80;
140
141    // Stuff to draw altCodeWhileTyping keys.
142    private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
143    private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
144    private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
145
146    // Drawing preview placer view
147    private final DrawingPreviewPlacerView mDrawingPreviewPlacerView;
148    private final int[] mOriginCoords = CoordinateUtils.newInstance();
149    private final GestureFloatingTextDrawingPreview mGestureFloatingTextDrawingPreview;
150    private final GestureTrailsDrawingPreview mGestureTrailsDrawingPreview;
151    private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview;
152
153    // Key preview
154    private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false;
155    private final KeyPreviewDrawParams mKeyPreviewDrawParams;
156    private final KeyPreviewChoreographer mKeyPreviewChoreographer;
157
158    // More keys keyboard
159    private final Paint mBackgroundDimAlphaPaint = new Paint();
160    private boolean mNeedsToDimEntireKeyboard;
161    private final View mMoreKeysKeyboardContainer;
162    private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache =
163            CollectionUtils.newWeakHashMap();
164    private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
165    // More keys panel (used by both more keys keyboard and more suggestions view)
166    // TODO: Consider extending to support multiple more keys panels
167    private MoreKeysPanel mMoreKeysPanel;
168
169    // Gesture floating preview text
170    // TODO: Make this parameter customizable by user via settings.
171    private int mGestureFloatingPreviewTextLingerTimeout;
172
173    private final KeyDetector mKeyDetector;
174    private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
175
176    private final TimerHandler mKeyTimerHandler;
177    private final int mLanguageOnSpacebarHorizontalMargin;
178
179    private final DrawingHandler mDrawingHandler =
180            new DrawingHandler(this);
181
182    public MainKeyboardView(final Context context, final AttributeSet attrs) {
183        this(context, attrs, R.attr.mainKeyboardViewStyle);
184    }
185
186    public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
187        super(context, attrs, defStyle);
188
189        mDrawingPreviewPlacerView = new DrawingPreviewPlacerView(context, attrs);
190
191        final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
192                attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
193        final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
194                R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
195        final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
196                R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
197        mKeyTimerHandler = new TimerHandler(
198                this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime);
199
200        final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
201                R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
202        final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
203                R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
204        mKeyDetector = new KeyDetector(
205                keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
206
207        PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */);
208
209        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
210        final boolean forceNonDistinctMultitouch = prefs.getBoolean(
211                DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
212        final boolean hasDistinctMultitouch = context.getPackageManager()
213                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
214                && !forceNonDistinctMultitouch;
215        mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
216                : new NonDistinctMultitouchHelper();
217
218        final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
219                R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
220        mBackgroundDimAlphaPaint.setColor(Color.BLACK);
221        mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
222        mSpacebarBackground = mainKeyboardViewAttr.getDrawable(
223                R.styleable.MainKeyboardView_spacebarBackground);
224        mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
225                R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
226        mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
227                R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
228        mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
229                R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
230        mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
231                R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
232        mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
233                R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
234        mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
235                R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
236                Constants.Color.ALPHA_OPAQUE);
237        final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
238                R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
239        final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
240                R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
241        final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
242                R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
243
244        mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr);
245        mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams);
246
247        final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
248                R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
249        mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
250                R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
251
252        mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
253                R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
254
255        mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
256                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
257        mDrawingPreviewPlacerView.addPreview(mGestureFloatingTextDrawingPreview);
258
259        mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(
260                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
261        mDrawingPreviewPlacerView.addPreview(mGestureTrailsDrawingPreview);
262
263        mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(
264                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
265        mDrawingPreviewPlacerView.addPreview(mSlidingKeyInputDrawingPreview);
266        mainKeyboardViewAttr.recycle();
267
268        mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
269                .inflate(moreKeysKeyboardLayoutId, null);
270        mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
271                languageOnSpacebarFadeoutAnimatorResId, this);
272        mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
273                altCodeKeyWhileTypingFadeoutAnimatorResId, this);
274        mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
275                altCodeKeyWhileTypingFadeinAnimatorResId, this);
276
277        mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
278
279        mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
280                R.dimen.config_language_on_spacebar_horizontal_margin);
281    }
282
283    @Override
284    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
285        super.setHardwareAcceleratedDrawingEnabled(enabled);
286        mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
287    }
288
289    private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
290        if (resId == 0) {
291            // TODO: Stop returning null.
292            return null;
293        }
294        final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
295                getContext(), resId);
296        if (animator != null) {
297            animator.setTarget(target);
298        }
299        return animator;
300    }
301
302    private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
303            final ObjectAnimator animatorToStart) {
304        if (animatorToCancel == null || animatorToStart == null) {
305            // TODO: Stop using null as a no-operation animator.
306            return;
307        }
308        float startFraction = 0.0f;
309        if (animatorToCancel.isStarted()) {
310            animatorToCancel.cancel();
311            startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
312        }
313        final long startTime = (long)(animatorToStart.getDuration() * startFraction);
314        animatorToStart.start();
315        animatorToStart.setCurrentPlayTime(startTime);
316    }
317
318    // Implements {@link TimerHander.Callbacks} method.
319    @Override
320    public void startWhileTypingFadeinAnimation() {
321        cancelAndStartAnimators(
322                mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
323    }
324
325    @Override
326    public void startWhileTypingFadeoutAnimation() {
327        cancelAndStartAnimators(
328                mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
329    }
330
331    @ExternallyReferenced
332    public int getLanguageOnSpacebarAnimAlpha() {
333        return mLanguageOnSpacebarAnimAlpha;
334    }
335
336    @ExternallyReferenced
337    public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
338        mLanguageOnSpacebarAnimAlpha = alpha;
339        invalidateKey(mSpaceKey);
340    }
341
342    @ExternallyReferenced
343    public int getAltCodeKeyWhileTypingAnimAlpha() {
344        return mAltCodeKeyWhileTypingAnimAlpha;
345    }
346
347    @ExternallyReferenced
348    public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
349        if (mAltCodeKeyWhileTypingAnimAlpha == alpha) {
350            return;
351        }
352        // Update the visual of alt-code-key-while-typing.
353        mAltCodeKeyWhileTypingAnimAlpha = alpha;
354        final Keyboard keyboard = getKeyboard();
355        if (keyboard == null) {
356            return;
357        }
358        for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
359            invalidateKey(key);
360        }
361    }
362
363    public void setKeyboardActionListener(final KeyboardActionListener listener) {
364        mKeyboardActionListener = listener;
365        PointerTracker.setKeyboardActionListener(listener);
366    }
367
368    // TODO: We should reconsider which coordinate system should be used to represent keyboard
369    // event.
370    public int getKeyX(final int x) {
371        return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x;
372    }
373
374    // TODO: We should reconsider which coordinate system should be used to represent keyboard
375    // event.
376    public int getKeyY(final int y) {
377        return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y;
378    }
379
380    /**
381     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
382     * view will re-layout itself to accommodate the keyboard.
383     * @see Keyboard
384     * @see #getKeyboard()
385     * @param keyboard the keyboard to display in this view
386     */
387    @Override
388    public void setKeyboard(final Keyboard keyboard) {
389        // Remove any pending messages, except dismissing preview and key repeat.
390        mKeyTimerHandler.cancelLongPressTimers();
391        super.setKeyboard(keyboard);
392        mKeyDetector.setKeyboard(
393                keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
394        PointerTracker.setKeyDetector(mKeyDetector);
395        mMoreKeysKeyboardCache.clear();
396
397        mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
398        mSpacebarIcon = (mSpaceKey != null)
399                ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
400        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
401        mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
402        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
403            final int orientation = getContext().getResources().getConfiguration().orientation;
404            ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
405        }
406
407        // This always needs to be set since the accessibility state can
408        // potentially change without the keyboard being set again.
409        AccessibleKeyboardViewProxy.getInstance().setKeyboard();
410    }
411
412    /**
413     * Enables or disables the key feedback popup. This is a popup that shows a magnified
414     * version of the depressed key. By default the preview is enabled.
415     * @param previewEnabled whether or not to enable the key feedback preview
416     * @param delay the delay after which the preview is dismissed
417     * @see #isKeyPreviewPopupEnabled()
418     */
419    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
420        mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
421    }
422
423    public void setKeyPreviewAnimationParams(final float showUpStartScale, final int showUpDuration,
424            final float dismissEndScale, final int dismissDuration) {
425        mKeyPreviewDrawParams.setAnimationParams(
426                showUpStartScale, showUpDuration, dismissEndScale, dismissDuration);
427    }
428
429    private void locatePreviewPlacerView() {
430        if (mDrawingPreviewPlacerView.getParent() != null) {
431            return;
432        }
433        final int width = getWidth();
434        final int height = getHeight();
435        if (width == 0 || height == 0) {
436            // In transient state.
437            return;
438        }
439        getLocationInWindow(mOriginCoords);
440        final DisplayMetrics dm = getResources().getDisplayMetrics();
441        if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
442            // In transient state.
443            return;
444        }
445        final View rootView = getRootView();
446        if (rootView == null) {
447            Log.w(TAG, "Cannot find root view");
448            return;
449        }
450        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
451        // Note: It'd be very weird if we get null by android.R.id.content.
452        if (windowContentView == null) {
453            Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
454        } else {
455            windowContentView.addView(mDrawingPreviewPlacerView);
456            mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
457        }
458    }
459
460    /**
461     * Returns the enabled state of the key feedback preview
462     * @return whether or not the key feedback preview is enabled
463     * @see #setKeyPreviewPopupEnabled(boolean, int)
464     */
465    public boolean isKeyPreviewPopupEnabled() {
466        return mKeyPreviewDrawParams.isPopupEnabled();
467    }
468
469    // Implements {@link DrawingHandler.Callbacks} method.
470    @Override
471    public void dismissAllKeyPreviews() {
472        mKeyPreviewChoreographer.dismissAllKeyPreviews();
473        PointerTracker.setReleasedKeyGraphicsToAllKeys();
474    }
475
476    @Override
477    public void showKeyPreview(final Key key) {
478        // If key is invalid or IME is already closed, we must not show key preview.
479        // Trying to show key preview while root window is closed causes
480        // WindowManager.BadTokenException.
481        if (key == null) {
482            return;
483        }
484
485        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
486        final Keyboard keyboard = getKeyboard();
487        if (!previewParams.isPopupEnabled()) {
488            previewParams.setVisibleOffset(-keyboard.mVerticalGap);
489            return;
490        }
491
492        locatePreviewPlacerView();
493        final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
494                key, mDrawingPreviewPlacerView);
495        getLocationInWindow(mOriginCoords);
496        mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet,
497                mKeyDrawParams, getWidth(), mOriginCoords);
498        mKeyPreviewChoreographer.showKeyPreview(key, previewTextView, isHardwareAccelerated());
499    }
500
501    // Implements {@link TimerHandler.Callbacks} method.
502    @Override
503    public void dismissKeyPreviewWithoutDelay(final Key key) {
504        mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
505        // To redraw key top letter.
506        invalidateKey(key);
507    }
508
509    @Override
510    public void dismissKeyPreview(final Key key) {
511        if (!isHardwareAccelerated()) {
512            // TODO: Implement preference option to control key preview method and duration.
513            mDrawingHandler.dismissKeyPreview(mKeyPreviewDrawParams.getLingerTimeout(), key);
514            return;
515        }
516        mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
517    }
518
519    public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
520        mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
521    }
522
523    @Override
524    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
525        locatePreviewPlacerView();
526        mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
527    }
528
529    @Override
530    public void dismissSlidingKeyInputPreview() {
531        mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
532    }
533
534    private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
535            final boolean isGestureFloatingPreviewTextEnabled) {
536        mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
537        mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
538    }
539
540    // Implements {@link DrawingHandler.Callbacks} method.
541    @Override
542    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
543        locatePreviewPlacerView();
544        mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
545    }
546
547    public void dismissGestureFloatingPreviewText() {
548        locatePreviewPlacerView();
549        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
550    }
551
552    @Override
553    public void showGestureTrail(final PointerTracker tracker,
554            final boolean showsFloatingPreviewText) {
555        locatePreviewPlacerView();
556        if (showsFloatingPreviewText) {
557            mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker);
558        }
559        mGestureTrailsDrawingPreview.setPreviewPosition(tracker);
560    }
561
562    // Note that this method is called from a non-UI thread.
563    public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
564        PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
565    }
566
567    public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser,
568            final boolean isGestureTrailEnabled,
569            final boolean isGestureFloatingPreviewTextEnabled) {
570        PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser);
571        setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled,
572                isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled);
573    }
574
575    @Override
576    protected void onAttachedToWindow() {
577        super.onAttachedToWindow();
578        // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
579        // been attached.  This is needed to properly show the splash screen, which requires that
580        // the window token of the KeyboardView be non-null.
581        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
582            ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
583        }
584    }
585
586    @Override
587    protected void onDetachedFromWindow() {
588        super.onDetachedFromWindow();
589        mDrawingPreviewPlacerView.removeAllViews();
590        // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
591        // been detached.  This is needed to invalidate the reference of {@link MainKeyboardView}
592        // to null.
593        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
594            ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
595        }
596    }
597
598    private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
599        if (key.getMoreKeys() == null) {
600            return null;
601        }
602        Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
603        if (moreKeysKeyboard == null) {
604            moreKeysKeyboard = new MoreKeysKeyboard.Builder(
605                    context, key, this, mKeyPreviewDrawParams).build();
606            mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
607        }
608
609        final View container = mMoreKeysKeyboardContainer;
610        final MoreKeysKeyboardView moreKeysKeyboardView =
611                (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
612        moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
613        container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
614        return moreKeysKeyboardView;
615    }
616
617    // Implements {@link TimerHandler.Callbacks} method.
618    /**
619     * Called when a key is long pressed.
620     * @param tracker the pointer tracker which pressed the parent key
621     */
622    @Override
623    public void onLongPress(final PointerTracker tracker) {
624        if (isShowingMoreKeysPanel()) {
625            return;
626        }
627        final Key key = tracker.getKey();
628        if (key == null) {
629            return;
630        }
631        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
632            ResearchLogger.mainKeyboardView_onLongPress();
633        }
634        final KeyboardActionListener listener = mKeyboardActionListener;
635        if (key.hasNoPanelAutoMoreKey()) {
636            final int moreKeyCode = key.getMoreKeys()[0].mCode;
637            tracker.onLongPressed();
638            listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
639            listener.onCodeInput(moreKeyCode,
640                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
641            listener.onReleaseKey(moreKeyCode, false /* withSliding */);
642            return;
643        }
644        final int code = key.getCode();
645        if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
646            // Long pressing the space key invokes IME switcher dialog.
647            if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
648                tracker.onLongPressed();
649                listener.onReleaseKey(code, false /* withSliding */);
650                return;
651            }
652        }
653        openMoreKeysPanel(key, tracker);
654    }
655
656    private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
657        final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
658        if (moreKeysPanel == null) {
659            return;
660        }
661
662        final int[] lastCoords = CoordinateUtils.newInstance();
663        tracker.getLastCoordinates(lastCoords);
664        final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview();
665        // The more keys keyboard is usually horizontally aligned with the center of the parent key.
666        // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
667        // keys keyboard is placed at the touch point of the parent key.
668        final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
669                ? CoordinateUtils.x(lastCoords)
670                : key.getX() + key.getWidth() / 2;
671        // The more keys keyboard is usually vertically aligned with the top edge of the parent key
672        // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
673        // aligned with the bottom edge of the visible part of the key preview.
674        // {@code mPreviewVisibleOffset} has been set appropriately in
675        // {@link KeyboardView#showKeyPreview(PointerTracker)}.
676        final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset();
677        moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
678        tracker.onShowMoreKeysPanel(moreKeysPanel);
679        // TODO: Implement zoom in animation of more keys panel.
680        dismissKeyPreviewWithoutDelay(key);
681    }
682
683    public boolean isInDraggingFinger() {
684        if (isShowingMoreKeysPanel()) {
685            return true;
686        }
687        return PointerTracker.isAnyInDraggingFinger();
688    }
689
690    @Override
691    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
692        locatePreviewPlacerView();
693        panel.showInParent(mDrawingPreviewPlacerView);
694        mMoreKeysPanel = panel;
695        dimEntireKeyboard(true /* dimmed */);
696    }
697
698    public boolean isShowingMoreKeysPanel() {
699        return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
700    }
701
702    @Override
703    public void onCancelMoreKeysPanel() {
704        PointerTracker.dismissAllMoreKeysPanels();
705    }
706
707    @Override
708    public void onDismissMoreKeysPanel() {
709        dimEntireKeyboard(false /* dimmed */);
710        if (isShowingMoreKeysPanel()) {
711            mMoreKeysPanel.removeFromParent();
712            mMoreKeysPanel = null;
713        }
714    }
715
716    public void startDoubleTapShiftKeyTimer() {
717        mKeyTimerHandler.startDoubleTapShiftKeyTimer();
718    }
719
720    public void cancelDoubleTapShiftKeyTimer() {
721        mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
722    }
723
724    public boolean isInDoubleTapShiftKeyTimeout() {
725        return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
726    }
727
728    @Override
729    public boolean onTouchEvent(final MotionEvent me) {
730        if (getKeyboard() == null) {
731            return false;
732        }
733        if (mNonDistinctMultitouchHelper != null) {
734            if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
735                // Key repeating timer will be canceled if 2 or more keys are in action.
736                mKeyTimerHandler.cancelKeyRepeatTimers();
737            }
738            // Non distinct multitouch screen support
739            mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
740            return true;
741        }
742        return processMotionEvent(me);
743    }
744
745    public boolean processMotionEvent(final MotionEvent me) {
746        if (LatinImeLogger.sUsabilityStudy) {
747            UsabilityStudyLogUtils.writeMotionEvent(me);
748        }
749        // Currently the same "move" event is being logged twice.
750        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
751            ResearchLogger.mainKeyboardView_processMotionEvent(me);
752        }
753
754        final int index = me.getActionIndex();
755        final int id = me.getPointerId(index);
756        final PointerTracker tracker = PointerTracker.getPointerTracker(id);
757        // When a more keys panel is showing, we should ignore other fingers' single touch events
758        // other than the finger that is showing the more keys panel.
759        if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
760                && PointerTracker.getActivePointerTrackerCount() == 1) {
761            return true;
762        }
763        tracker.processMotionEvent(me, mKeyDetector);
764        return true;
765    }
766
767    public void cancelAllOngoingEvents() {
768        mKeyTimerHandler.cancelAllMessages();
769        mDrawingHandler.cancelAllMessages();
770        dismissAllKeyPreviews();
771        dismissGestureFloatingPreviewText();
772        dismissSlidingKeyInputPreview();
773        PointerTracker.dismissAllMoreKeysPanels();
774        PointerTracker.cancelAllPointerTrackers();
775    }
776
777    public void closing() {
778        cancelAllOngoingEvents();
779        mMoreKeysKeyboardCache.clear();
780    }
781
782    /**
783     * Receives hover events from the input framework.
784     *
785     * @param event The motion event to be dispatched.
786     * @return {@code true} if the event was handled by the view, {@code false}
787     *         otherwise
788     */
789    @Override
790    public boolean dispatchHoverEvent(final MotionEvent event) {
791        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
792            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(
793                    event, mKeyDetector);
794        }
795
796        // Reflection doesn't support calling superclass methods.
797        return false;
798    }
799
800    public void updateShortcutKey(final boolean available) {
801        final Keyboard keyboard = getKeyboard();
802        if (keyboard == null) {
803            return;
804        }
805        final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT);
806        if (shortcutKey == null) {
807            return;
808        }
809        shortcutKey.setEnabled(available);
810        invalidateKey(shortcutKey);
811    }
812
813    public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
814            final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
815        mNeedsToDisplayLanguage = needsToDisplayLanguage;
816        mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
817        final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
818        if (animator == null) {
819            mNeedsToDisplayLanguage = false;
820        } else {
821            if (subtypeChanged && needsToDisplayLanguage) {
822                setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
823                if (animator.isStarted()) {
824                    animator.cancel();
825                }
826                animator.start();
827            } else {
828                if (!animator.isStarted()) {
829                    mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
830                }
831            }
832        }
833        invalidateKey(mSpaceKey);
834    }
835
836    public void updateAutoCorrectionState(final boolean isAutoCorrection) {
837        if (!mAutoCorrectionSpacebarLedEnabled) {
838            return;
839        }
840        mAutoCorrectionSpacebarLedOn = isAutoCorrection;
841        invalidateKey(mSpaceKey);
842    }
843
844    private void dimEntireKeyboard(final boolean dimmed) {
845        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
846        mNeedsToDimEntireKeyboard = dimmed;
847        if (needsRedrawing) {
848            invalidateAllKeys();
849        }
850    }
851
852    @Override
853    protected void onDraw(final Canvas canvas) {
854        super.onDraw(canvas);
855
856        // Overlay a dark rectangle to dim.
857        if (mNeedsToDimEntireKeyboard) {
858            canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
859        }
860    }
861
862    // Draw key background.
863    @Override
864    protected void onDrawKeyBackground(final Key key, final Canvas canvas,
865            final Drawable background) {
866        if (key.getCode() == Constants.CODE_SPACE) {
867            super.onDrawKeyBackground(key, canvas, mSpacebarBackground);
868            return;
869        }
870        super.onDrawKeyBackground(key, canvas, background);
871    }
872
873    @Override
874    protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
875            final KeyDrawParams params) {
876        if (key.altCodeWhileTyping() && key.isEnabled()) {
877            params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
878        }
879        // Don't draw key top letter when key preview is showing.
880        if (FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED
881                && mKeyPreviewChoreographer.isShowingKeyPreview(key)) {
882            // TODO: Fade out animation for the key top letter, and fade in animation for the key
883            // background color when the user presses the key.
884            return;
885        }
886        final int code = key.getCode();
887        if (code == Constants.CODE_SPACE) {
888            drawSpacebar(key, canvas, paint);
889            // Whether space key needs to show the "..." popup hint for special purposes
890            if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
891                drawKeyPopupHint(key, canvas, paint, params);
892            }
893        } else if (code == Constants.CODE_LANGUAGE_SWITCH) {
894            super.onDrawKeyTopVisuals(key, canvas, paint, params);
895            drawKeyPopupHint(key, canvas, paint, params);
896        } else {
897            super.onDrawKeyTopVisuals(key, canvas, paint, params);
898        }
899    }
900
901    private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
902        final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
903        paint.setTextScaleX(1.0f);
904        final float textWidth = TypefaceUtils.getStringWidth(text, paint);
905        if (textWidth < width) {
906            return true;
907        }
908
909        final float scaleX = maxTextWidth / textWidth;
910        if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
911            return false;
912        }
913
914        paint.setTextScaleX(scaleX);
915        return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
916    }
917
918    // Layout language name on spacebar.
919    private String layoutLanguageOnSpacebar(final Paint paint,
920            final InputMethodSubtype subtype, final int width) {
921        // Choose appropriate language name to fit into the width.
922        final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
923        if (fitsTextIntoWidth(width, fullText, paint)) {
924            return fullText;
925        }
926
927        final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
928        if (fitsTextIntoWidth(width, middleText, paint)) {
929            return middleText;
930        }
931
932        return "";
933    }
934
935    private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
936        final int width = key.getWidth();
937        final int height = key.getHeight();
938
939        // If input language are explicitly selected.
940        if (mNeedsToDisplayLanguage) {
941            paint.setTextAlign(Align.CENTER);
942            paint.setTypeface(Typeface.DEFAULT);
943            paint.setTextSize(mLanguageOnSpacebarTextSize);
944            final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
945            final String language = layoutLanguageOnSpacebar(paint, subtype, width);
946            // Draw language text with shadow
947            final float descent = paint.descent();
948            final float textHeight = -paint.ascent() + descent;
949            final float baseline = height / 2 + textHeight / 2;
950            paint.setColor(mLanguageOnSpacebarTextShadowColor);
951            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
952            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
953            paint.setColor(mLanguageOnSpacebarTextColor);
954            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
955            canvas.drawText(language, width / 2, baseline - descent, paint);
956        }
957
958        // Draw the spacebar icon at the bottom
959        if (mAutoCorrectionSpacebarLedOn) {
960            final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
961            final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
962            int x = (width - iconWidth) / 2;
963            int y = height - iconHeight;
964            drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
965        } else if (mSpacebarIcon != null) {
966            final int iconWidth = mSpacebarIcon.getIntrinsicWidth();
967            final int iconHeight = mSpacebarIcon.getIntrinsicHeight();
968            int x = (width - iconWidth) / 2;
969            int y = height - iconHeight;
970            drawIcon(canvas, mSpacebarIcon, x, y, iconWidth, iconHeight);
971        }
972    }
973
974    @Override
975    public void deallocateMemory() {
976        super.deallocateMemory();
977        mDrawingPreviewPlacerView.deallocateMemory();
978    }
979}
980