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