MainKeyboardView.java revision 9647d7fbee4cbd72876e949e6544dc43fadbd148
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;
38import android.widget.TextView;
39
40import com.android.inputmethod.accessibility.AccessibilityUtils;
41import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
42import com.android.inputmethod.annotations.ExternallyReferenced;
43import com.android.inputmethod.keyboard.internal.DrawingHandler;
44import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
45import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview;
46import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview;
47import com.android.inputmethod.keyboard.internal.KeyDrawParams;
48import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
49import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
50import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
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.LatinImeLogger;
56import com.android.inputmethod.latin.R;
57import com.android.inputmethod.latin.SuggestedWords;
58import com.android.inputmethod.latin.define.ProductionFlag;
59import com.android.inputmethod.latin.settings.DebugSettings;
60import com.android.inputmethod.latin.utils.CoordinateUtils;
61import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
62import com.android.inputmethod.latin.utils.TypefaceUtils;
63import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
64import com.android.inputmethod.research.ResearchLogger;
65
66import java.util.WeakHashMap;
67
68/**
69 * A view that is responsible for detecting key presses and touch movements.
70 *
71 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
72 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
73 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius
74 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
75 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
76 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
77 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
78 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
79 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
80 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
81 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
82 * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger
83 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
84 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
85 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
86 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
87 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
88 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
89 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
90 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
91 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
92 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
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 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 =
169            new DrawingHandler(this);
170
171    private final 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        mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
238                R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
239
240        mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
241                R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
242
243        mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
244                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
245        mDrawingPreviewPlacerView.addPreview(mGestureFloatingTextDrawingPreview);
246
247        mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(
248                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
249        mDrawingPreviewPlacerView.addPreview(mGestureTrailsDrawingPreview);
250
251        mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(
252                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
253        mDrawingPreviewPlacerView.addPreview(mSlidingKeyInputDrawingPreview);
254        mainKeyboardViewAttr.recycle();
255
256        mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
257                .inflate(moreKeysKeyboardLayoutId, null);
258        mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
259                languageOnSpacebarFadeoutAnimatorResId, this);
260        mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
261                altCodeKeyWhileTypingFadeoutAnimatorResId, this);
262        mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
263                altCodeKeyWhileTypingFadeinAnimatorResId, this);
264
265        mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
266
267        mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
268                R.dimen.config_language_on_spacebar_horizontal_margin);
269
270        mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector);
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        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
391            final int orientation = getContext().getResources().getConfiguration().orientation;
392            ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
393        }
394
395        mAccessibilityDelegate.setKeyboard(keyboard);
396    }
397
398    /**
399     * Enables or disables the key feedback popup. This is a popup that shows a magnified
400     * version of the depressed key. By default the preview is enabled.
401     * @param previewEnabled whether or not to enable the key feedback preview
402     * @param delay the delay after which the preview is dismissed
403     * @see #isKeyPreviewPopupEnabled()
404     */
405    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
406        mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
407    }
408
409    public void setKeyPreviewAnimationParams(final float showUpStartScale, final int showUpDuration,
410            final float dismissEndScale, final int dismissDuration) {
411        mKeyPreviewDrawParams.setAnimationParams(
412                showUpStartScale, showUpDuration, dismissEndScale, dismissDuration);
413    }
414
415    private void locatePreviewPlacerView() {
416        getLocationInWindow(mOriginCoords);
417        mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight());
418    }
419
420    private void installPreviewPlacerView() {
421        final View rootView = getRootView();
422        if (rootView == null) {
423            Log.w(TAG, "Cannot find root view");
424            return;
425        }
426        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
427        // Note: It'd be very weird if we get null by android.R.id.content.
428        if (windowContentView == null) {
429            Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
430            return;
431        }
432        windowContentView.addView(mDrawingPreviewPlacerView);
433    }
434
435    /**
436     * Returns the enabled state of the key feedback preview
437     * @return whether or not the key feedback preview is enabled
438     * @see #setKeyPreviewPopupEnabled(boolean, int)
439     */
440    public boolean isKeyPreviewPopupEnabled() {
441        return mKeyPreviewDrawParams.isPopupEnabled();
442    }
443
444    // Implements {@link DrawingHandler.Callbacks} method.
445    @Override
446    public void dismissAllKeyPreviews() {
447        mKeyPreviewChoreographer.dismissAllKeyPreviews();
448        PointerTracker.setReleasedKeyGraphicsToAllKeys();
449    }
450
451    @Override
452    public void showKeyPreview(final Key key) {
453        // If key is invalid or IME is already closed, we must not show key preview.
454        // Trying to show key preview while root window is closed causes
455        // WindowManager.BadTokenException.
456        if (key == null) {
457            return;
458        }
459
460        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
461        final Keyboard keyboard = getKeyboard();
462        if (!previewParams.isPopupEnabled()) {
463            previewParams.setVisibleOffset(-keyboard.mVerticalGap);
464            return;
465        }
466
467        locatePreviewPlacerView();
468        final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
469                key, mDrawingPreviewPlacerView);
470        getLocationInWindow(mOriginCoords);
471        mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet,
472                mKeyDrawParams, getWidth(), mOriginCoords);
473        mKeyPreviewChoreographer.showKeyPreview(key, previewTextView, isHardwareAccelerated());
474    }
475
476    // Implements {@link TimerHandler.Callbacks} method.
477    @Override
478    public void dismissKeyPreviewWithoutDelay(final Key key) {
479        mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
480        // To redraw key top letter.
481        invalidateKey(key);
482    }
483
484    @Override
485    public void dismissKeyPreview(final Key key) {
486        if (!isHardwareAccelerated()) {
487            // TODO: Implement preference option to control key preview method and duration.
488            mDrawingHandler.dismissKeyPreview(mKeyPreviewDrawParams.getLingerTimeout(), key);
489            return;
490        }
491        mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
492    }
493
494    public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
495        mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
496    }
497
498    @Override
499    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
500        locatePreviewPlacerView();
501        mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
502    }
503
504    @Override
505    public void dismissSlidingKeyInputPreview() {
506        mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
507    }
508
509    private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
510            final boolean isGestureFloatingPreviewTextEnabled) {
511        mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
512        mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
513    }
514
515    // Implements {@link DrawingHandler.Callbacks} method.
516    @Override
517    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
518        locatePreviewPlacerView();
519        mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
520    }
521
522    public void dismissGestureFloatingPreviewText() {
523        locatePreviewPlacerView();
524        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
525    }
526
527    @Override
528    public void showGestureTrail(final PointerTracker tracker,
529            final boolean showsFloatingPreviewText) {
530        locatePreviewPlacerView();
531        if (showsFloatingPreviewText) {
532            mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker);
533        }
534        mGestureTrailsDrawingPreview.setPreviewPosition(tracker);
535    }
536
537    // Note that this method is called from a non-UI thread.
538    @SuppressWarnings("static-method")
539    public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
540        PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
541    }
542
543    public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser,
544            final boolean isGestureTrailEnabled,
545            final boolean isGestureFloatingPreviewTextEnabled) {
546        PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser);
547        setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled,
548                isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled);
549    }
550
551    @Override
552    protected void onAttachedToWindow() {
553        super.onAttachedToWindow();
554        installPreviewPlacerView();
555        // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
556        // been attached.  This is needed to properly show the splash screen, which requires that
557        // the window token of the KeyboardView be non-null.
558        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
559            ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
560        }
561    }
562
563    @Override
564    protected void onDetachedFromWindow() {
565        super.onDetachedFromWindow();
566        mDrawingPreviewPlacerView.removeAllViews();
567        // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
568        // been detached.  This is needed to invalidate the reference of {@link MainKeyboardView}
569        // to null.
570        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
571            ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
572        }
573    }
574
575    private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
576        if (key.getMoreKeys() == null) {
577            return null;
578        }
579        Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
580        if (moreKeysKeyboard == null) {
581            moreKeysKeyboard = new MoreKeysKeyboard.Builder(
582                    context, key, this, mKeyPreviewDrawParams).build();
583            mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
584        }
585
586        final View container = mMoreKeysKeyboardContainer;
587        final MoreKeysKeyboardView moreKeysKeyboardView =
588                (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
589        moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
590        container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
591        return moreKeysKeyboardView;
592    }
593
594    // Implements {@link TimerHandler.Callbacks} method.
595    /**
596     * Called when a key is long pressed.
597     * @param tracker the pointer tracker which pressed the parent key
598     */
599    @Override
600    public void onLongPress(final PointerTracker tracker) {
601        if (isShowingMoreKeysPanel()) {
602            return;
603        }
604        final Key key = tracker.getKey();
605        if (key == null) {
606            return;
607        }
608        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
609            ResearchLogger.mainKeyboardView_onLongPress();
610        }
611        final KeyboardActionListener listener = mKeyboardActionListener;
612        if (key.hasNoPanelAutoMoreKey()) {
613            final int moreKeyCode = key.getMoreKeys()[0].mCode;
614            tracker.onLongPressed();
615            listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
616            listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
617                    Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
618            listener.onReleaseKey(moreKeyCode, false /* withSliding */);
619            return;
620        }
621        final int code = key.getCode();
622        if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
623            // Long pressing the space key invokes IME switcher dialog.
624            if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
625                tracker.onLongPressed();
626                listener.onReleaseKey(code, false /* withSliding */);
627                return;
628            }
629        }
630        openMoreKeysPanel(key, tracker);
631    }
632
633    private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
634        final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
635        if (moreKeysPanel == null) {
636            return;
637        }
638
639        final int[] lastCoords = CoordinateUtils.newInstance();
640        tracker.getLastCoordinates(lastCoords);
641        final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview();
642        // The more keys keyboard is usually horizontally aligned with the center of the parent key.
643        // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
644        // keys keyboard is placed at the touch point of the parent key.
645        final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
646                ? CoordinateUtils.x(lastCoords)
647                : key.getX() + key.getWidth() / 2;
648        // The more keys keyboard is usually vertically aligned with the top edge of the parent key
649        // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
650        // aligned with the bottom edge of the visible part of the key preview.
651        // {@code mPreviewVisibleOffset} has been set appropriately in
652        // {@link KeyboardView#showKeyPreview(PointerTracker)}.
653        final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset();
654        moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
655        tracker.onShowMoreKeysPanel(moreKeysPanel);
656        // TODO: Implement zoom in animation of more keys panel.
657        dismissKeyPreviewWithoutDelay(key);
658    }
659
660    public boolean isInDraggingFinger() {
661        if (isShowingMoreKeysPanel()) {
662            return true;
663        }
664        return PointerTracker.isAnyInDraggingFinger();
665    }
666
667    @Override
668    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
669        locatePreviewPlacerView();
670        panel.showInParent(mDrawingPreviewPlacerView);
671        mMoreKeysPanel = panel;
672        dimEntireKeyboard(true /* dimmed */);
673    }
674
675    public boolean isShowingMoreKeysPanel() {
676        return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
677    }
678
679    @Override
680    public void onCancelMoreKeysPanel() {
681        PointerTracker.dismissAllMoreKeysPanels();
682    }
683
684    @Override
685    public void onDismissMoreKeysPanel() {
686        dimEntireKeyboard(false /* dimmed */);
687        if (isShowingMoreKeysPanel()) {
688            mMoreKeysPanel.removeFromParent();
689            mMoreKeysPanel = null;
690        }
691    }
692
693    public void startDoubleTapShiftKeyTimer() {
694        mKeyTimerHandler.startDoubleTapShiftKeyTimer();
695    }
696
697    public void cancelDoubleTapShiftKeyTimer() {
698        mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
699    }
700
701    public boolean isInDoubleTapShiftKeyTimeout() {
702        return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
703    }
704
705    @Override
706    public boolean onTouchEvent(final MotionEvent me) {
707        if (getKeyboard() == null) {
708            return false;
709        }
710        if (mNonDistinctMultitouchHelper != null) {
711            if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
712                // Key repeating timer will be canceled if 2 or more keys are in action.
713                mKeyTimerHandler.cancelKeyRepeatTimers();
714            }
715            // Non distinct multitouch screen support
716            mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
717            return true;
718        }
719        return processMotionEvent(me);
720    }
721
722    public boolean processMotionEvent(final MotionEvent me) {
723        if (LatinImeLogger.sUsabilityStudy) {
724            UsabilityStudyLogUtils.writeMotionEvent(me);
725        }
726        // Currently the same "move" event is being logged twice.
727        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
728            ResearchLogger.mainKeyboardView_processMotionEvent(me);
729        }
730
731        final int index = me.getActionIndex();
732        final int id = me.getPointerId(index);
733        final PointerTracker tracker = PointerTracker.getPointerTracker(id);
734        // When a more keys panel is showing, we should ignore other fingers' single touch events
735        // other than the finger that is showing the more keys panel.
736        if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
737                && PointerTracker.getActivePointerTrackerCount() == 1) {
738            return true;
739        }
740        tracker.processMotionEvent(me, mKeyDetector);
741        return true;
742    }
743
744    public void cancelAllOngoingEvents() {
745        mKeyTimerHandler.cancelAllMessages();
746        mDrawingHandler.cancelAllMessages();
747        dismissAllKeyPreviews();
748        dismissGestureFloatingPreviewText();
749        dismissSlidingKeyInputPreview();
750        PointerTracker.dismissAllMoreKeysPanels();
751        PointerTracker.cancelAllPointerTrackers();
752    }
753
754    public void closing() {
755        cancelAllOngoingEvents();
756        mMoreKeysKeyboardCache.clear();
757    }
758
759    public void onHideWindow() {
760        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
761            mAccessibilityDelegate.onHideWindow();
762        }
763    }
764
765    /**
766     * {@inheritDoc}
767     */
768    @Override
769    public boolean onHoverEvent(final MotionEvent event) {
770        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
771            return mAccessibilityDelegate.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        mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
793        mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
794        final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
795        if (animator == null) {
796            mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
797        } else {
798            if (subtypeChanged
799                    && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
800                setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
801                if (animator.isStarted()) {
802                    animator.cancel();
803                }
804                animator.start();
805            } else {
806                if (!animator.isStarted()) {
807                    mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
808                }
809            }
810        }
811        invalidateKey(mSpaceKey);
812    }
813
814    private void dimEntireKeyboard(final boolean dimmed) {
815        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
816        mNeedsToDimEntireKeyboard = dimmed;
817        if (needsRedrawing) {
818            invalidateAllKeys();
819        }
820    }
821
822    @Override
823    protected void onDraw(final Canvas canvas) {
824        super.onDraw(canvas);
825
826        // Overlay a dark rectangle to dim.
827        if (mNeedsToDimEntireKeyboard) {
828            canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
829        }
830    }
831
832    @Override
833    protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
834            final KeyDrawParams params) {
835        if (key.altCodeWhileTyping() && key.isEnabled()) {
836            params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
837        }
838        super.onDrawKeyTopVisuals(key, canvas, paint, params);
839        final int code = key.getCode();
840        if (code == Constants.CODE_SPACE) {
841            // If input language are explicitly selected.
842            if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
843                drawLanguageOnSpacebar(key, canvas, paint);
844            }
845            // Whether space key needs to show the "..." popup hint for special purposes
846            if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
847                drawKeyPopupHint(key, canvas, paint, params);
848            }
849        } else if (code == Constants.CODE_LANGUAGE_SWITCH) {
850            drawKeyPopupHint(key, canvas, paint, params);
851        }
852    }
853
854    private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
855        final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
856        paint.setTextScaleX(1.0f);
857        final float textWidth = TypefaceUtils.getStringWidth(text, paint);
858        if (textWidth < width) {
859            return true;
860        }
861
862        final float scaleX = maxTextWidth / textWidth;
863        if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
864            return false;
865        }
866
867        paint.setTextScaleX(scaleX);
868        return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
869    }
870
871    // Layout language name on spacebar.
872    private String layoutLanguageOnSpacebar(final Paint paint,
873            final InputMethodSubtype subtype, final int width) {
874        // Choose appropriate language name to fit into the width.
875        if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
876            final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
877            if (fitsTextIntoWidth(width, fullText, paint)) {
878                return fullText;
879            }
880        }
881
882        final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
883        if (fitsTextIntoWidth(width, middleText, paint)) {
884            return middleText;
885        }
886
887        return "";
888    }
889
890    private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
891        final int width = key.getWidth();
892        final int height = key.getHeight();
893        paint.setTextAlign(Align.CENTER);
894        paint.setTypeface(Typeface.DEFAULT);
895        paint.setTextSize(mLanguageOnSpacebarTextSize);
896        final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
897        final String language = layoutLanguageOnSpacebar(paint, subtype, width);
898        // Draw language text with shadow
899        final float descent = paint.descent();
900        final float textHeight = -paint.ascent() + descent;
901        final float baseline = height / 2 + textHeight / 2;
902        if (mLanguageOnSpacebarTextShadowRadius > 0.0f) {
903            paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0,
904                    mLanguageOnSpacebarTextShadowColor);
905        } else {
906            paint.clearShadowLayer();
907        }
908        paint.setColor(mLanguageOnSpacebarTextColor);
909        paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
910        canvas.drawText(language, width / 2, baseline - descent, paint);
911        paint.clearShadowLayer();
912        paint.setTextScaleX(1.0f);
913    }
914
915    @Override
916    public void deallocateMemory() {
917        super.deallocateMemory();
918        mDrawingPreviewPlacerView.deallocateMemory();
919    }
920}
921