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