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