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