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