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