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