KeyboardView.java revision 5f282ea9e4a4590fcbab6e27d5fca7dacbb40a6a
1/*
2 * Copyright (C) 2010 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.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Paint;
25import android.graphics.Paint.Align;
26import android.graphics.PorterDuff;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.graphics.Typeface;
30import android.graphics.drawable.Drawable;
31import android.os.Message;
32import android.util.AttributeSet;
33import android.util.Log;
34import android.util.SparseArray;
35import android.util.TypedValue;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.TextView;
40
41import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
42import com.android.inputmethod.latin.CollectionUtils;
43import com.android.inputmethod.latin.Constants;
44import com.android.inputmethod.latin.LatinImeLogger;
45import com.android.inputmethod.latin.R;
46import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
47import com.android.inputmethod.latin.StringUtils;
48import com.android.inputmethod.latin.define.ProductionFlag;
49import com.android.inputmethod.research.ResearchLogger;
50
51import java.util.HashSet;
52
53/**
54 * A view that renders a virtual {@link Keyboard}.
55 *
56 * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
57 * @attr ref R.styleable#KeyboardView_keyBackground
58 * @attr ref R.styleable#KeyboardView_keyLetterRatio
59 * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
60 * @attr ref R.styleable#KeyboardView_keyLabelRatio
61 * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
62 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio
63 * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
64 * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
65 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
66 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
67 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
68 * @attr ref R.styleable#KeyboardView_keyTextStyle
69 * @attr ref R.styleable#KeyboardView_keyPreviewLayout
70 * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
71 * @attr ref R.styleable#KeyboardView_keyPreviewOffset
72 * @attr ref R.styleable#KeyboardView_keyPreviewHeight
73 * @attr ref R.styleable#KeyboardView_keyTextColor
74 * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
75 * @attr ref R.styleable#KeyboardView_keyHintLetterColor
76 * @attr ref R.styleable#KeyboardView_keyHintLabelColor
77 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor
78 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor
79 * @attr ref R.styleable#KeyboardView_shadowColor
80 * @attr ref R.styleable#KeyboardView_shadowRadius
81 */
82public class KeyboardView extends View implements PointerTracker.DrawingProxy {
83    private static final String TAG = KeyboardView.class.getSimpleName();
84
85    // Miscellaneous constants
86    private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
87
88    // XML attributes
89    protected final float mVerticalCorrection;
90    protected final int mMoreKeysLayout;
91    private final int mBackgroundDimAlpha;
92
93    // HORIZONTAL ELLIPSIS "...", character for popup hint.
94    private static final String POPUP_HINT_CHAR = "\u2026";
95
96    // Margin between the label and the icon on a key that has both of them.
97    // Specified by the fraction of the key width.
98    // TODO: Use resource parameter for this value.
99    private static final float LABEL_ICON_MARGIN = 0.05f;
100
101    // The maximum key label width in the proportion to the key width.
102    private static final float MAX_LABEL_RATIO = 0.90f;
103
104    // Main keyboard
105    private Keyboard mKeyboard;
106    protected final KeyDrawParams mKeyDrawParams;
107
108    // Key preview
109    private final int mKeyPreviewLayoutId;
110    protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
111    private boolean mShowKeyPreviewPopup = true;
112    private int mDelayAfterPreview;
113    private final PreviewPlacerView mPreviewPlacerView;
114
115    // Drawing
116    /** True if the entire keyboard needs to be dimmed. */
117    private boolean mNeedsToDimEntireKeyboard;
118    /** True if all keys should be drawn */
119    private boolean mInvalidateAllKeys;
120    /** The keys that should be drawn */
121    private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
122    /** The working rectangle variable */
123    private final Rect mWorkingRect = new Rect();
124    /** The keyboard bitmap buffer for faster updates */
125    /** The clip region to draw keys */
126    private final Region mClipRegion = new Region();
127    private Bitmap mOffscreenBuffer;
128    /** The canvas for the above mutable keyboard bitmap */
129    private Canvas mOffscreenCanvas;
130    private final Paint mPaint = new Paint();
131    private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
132    // This sparse array caches key label text height in pixel indexed by key label text size.
133    private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
134    // This sparse array caches key label text width in pixel indexed by key label text size.
135    private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
136    private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
137    private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
138
139    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
140
141    public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
142        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
143
144        public DrawingHandler(KeyboardView outerInstance) {
145            super(outerInstance);
146        }
147
148        @Override
149        public void handleMessage(Message msg) {
150            final KeyboardView keyboardView = getOuterInstance();
151            if (keyboardView == null) return;
152            final PointerTracker tracker = (PointerTracker) msg.obj;
153            switch (msg.what) {
154            case MSG_DISMISS_KEY_PREVIEW:
155                tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
156                break;
157            }
158        }
159
160        public void dismissKeyPreview(long delay, PointerTracker tracker) {
161            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
162        }
163
164        public void cancelDismissKeyPreview(PointerTracker tracker) {
165            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
166        }
167
168        public void cancelAllDismissKeyPreviews() {
169            removeMessages(MSG_DISMISS_KEY_PREVIEW);
170        }
171
172        public void cancelAllMessages() {
173            cancelAllDismissKeyPreviews();
174        }
175    }
176
177    protected static class KeyDrawParams {
178        // XML attributes
179        public final int mKeyTextColor;
180        public final int mKeyTextInactivatedColor;
181        public final Typeface mKeyTextStyle;
182        public final float mKeyLabelHorizontalPadding;
183        public final float mKeyHintLetterPadding;
184        public final float mKeyPopupHintLetterPadding;
185        public final float mKeyShiftedLetterHintPadding;
186        public final int mShadowColor;
187        public final float mShadowRadius;
188        public final Drawable mKeyBackground;
189        public final int mKeyHintLetterColor;
190        public final int mKeyHintLabelColor;
191        public final int mKeyShiftedLetterHintInactivatedColor;
192        public final int mKeyShiftedLetterHintActivatedColor;
193
194        /* package */ final float mKeyLetterRatio;
195        private final float mKeyLargeLetterRatio;
196        private final float mKeyLabelRatio;
197        private final float mKeyLargeLabelRatio;
198        private final float mKeyHintLetterRatio;
199        private final float mKeyShiftedLetterHintRatio;
200        private final float mKeyHintLabelRatio;
201        private static final float UNDEFINED_RATIO = -1.0f;
202
203        public final Rect mPadding = new Rect();
204        public int mKeyLetterSize;
205        public int mKeyLargeLetterSize;
206        public int mKeyLabelSize;
207        public int mKeyLargeLabelSize;
208        public int mKeyHintLetterSize;
209        public int mKeyShiftedLetterHintSize;
210        public int mKeyHintLabelSize;
211        public int mAnimAlpha;
212
213        public KeyDrawParams(TypedArray a) {
214            mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
215            if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
216                mKeyLetterRatio = UNDEFINED_RATIO;
217                mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
218            } else {
219                mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
220            }
221            if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
222                mKeyLabelRatio = UNDEFINED_RATIO;
223                mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
224            } else {
225                mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
226            }
227            mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
228            mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
229            mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
230            mKeyShiftedLetterHintRatio = getRatio(a,
231                    R.styleable.KeyboardView_keyShiftedLetterHintRatio);
232            mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
233            mKeyLabelHorizontalPadding = a.getDimension(
234                    R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
235            mKeyHintLetterPadding = a.getDimension(
236                    R.styleable.KeyboardView_keyHintLetterPadding, 0);
237            mKeyPopupHintLetterPadding = a.getDimension(
238                    R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
239            mKeyShiftedLetterHintPadding = a.getDimension(
240                    R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
241            mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
242            mKeyTextInactivatedColor = a.getColor(
243                    R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
244            mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
245            mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
246            mKeyShiftedLetterHintInactivatedColor = a.getColor(
247                    R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
248            mKeyShiftedLetterHintActivatedColor = a.getColor(
249                    R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
250            mKeyTextStyle = Typeface.defaultFromStyle(
251                    a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
252            mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
253            mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
254
255            mKeyBackground.getPadding(mPadding);
256        }
257
258        public void updateKeyHeight(int keyHeight) {
259            if (mKeyLetterRatio >= 0.0f) {
260                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
261            }
262            if (mKeyLabelRatio >= 0.0f) {
263                mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
264            }
265            mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
266            mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
267            mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
268            mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
269            mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
270        }
271
272        public void blendAlpha(Paint paint) {
273            final int color = paint.getColor();
274            paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
275                    Color.red(color), Color.green(color), Color.blue(color));
276        }
277    }
278
279    /* package */ static class KeyPreviewDrawParams {
280        // XML attributes.
281        public final Drawable mPreviewBackground;
282        public final Drawable mPreviewLeftBackground;
283        public final Drawable mPreviewRightBackground;
284        public final int mPreviewTextColor;
285        public final int mPreviewOffset;
286        public final int mPreviewHeight;
287        public final Typeface mKeyTextStyle;
288        public final int mLingerTimeout;
289
290        private final float mPreviewTextRatio;
291        private final float mKeyLetterRatio;
292
293        // The graphical geometry of the key preview.
294        // <-width->
295        // +-------+   ^
296        // |       |   |
297        // |preview| height (visible)
298        // |       |   |
299        // +       + ^ v
300        //  \     /  |offset
301        // +-\   /-+ v
302        // |  +-+  |
303        // |parent |
304        // |    key|
305        // +-------+
306        // The background of a {@link TextView} being used for a key preview may have invisible
307        // paddings. To align the more keys keyboard panel's visible part with the visible part of
308        // the background, we need to record the width and height of key preview that don't include
309        // invisible paddings.
310        public int mPreviewVisibleWidth;
311        public int mPreviewVisibleHeight;
312        // The key preview may have an arbitrary offset and its background that may have a bottom
313        // padding. To align the more keys keyboard and the key preview we also need to record the
314        // offset between the top edge of parent key and the bottom of the visible part of key
315        // preview background.
316        public int mPreviewVisibleOffset;
317
318        public int mPreviewTextSize;
319        public int mKeyLetterSize;
320        public final int[] mCoordinates = new int[2];
321
322        private static final int PREVIEW_ALPHA = 240;
323
324        public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
325            mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
326            mPreviewLeftBackground = a.getDrawable(
327                    R.styleable.KeyboardView_keyPreviewLeftBackground);
328            mPreviewRightBackground = a.getDrawable(
329                    R.styleable.KeyboardView_keyPreviewRightBackground);
330            setAlpha(mPreviewBackground, PREVIEW_ALPHA);
331            setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
332            setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
333            mPreviewOffset = a.getDimensionPixelOffset(
334                    R.styleable.KeyboardView_keyPreviewOffset, 0);
335            mPreviewHeight = a.getDimensionPixelSize(
336                    R.styleable.KeyboardView_keyPreviewHeight, 80);
337            mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
338            mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
339            mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
340
341            mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
342            mKeyTextStyle = keyDrawParams.mKeyTextStyle;
343        }
344
345        public void updateKeyHeight(int keyHeight) {
346            if (mPreviewTextRatio >= 0.0f) {
347                mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
348            }
349            if (mKeyLetterRatio >= 0.0f) {
350                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
351            }
352        }
353
354        private static void setAlpha(Drawable drawable, int alpha) {
355            if (drawable == null) return;
356            drawable.setAlpha(alpha);
357        }
358    }
359
360    public KeyboardView(Context context, AttributeSet attrs) {
361        this(context, attrs, R.attr.keyboardViewStyle);
362    }
363
364    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
365        super(context, attrs, defStyle);
366
367        final TypedArray a = context.obtainStyledAttributes(
368                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
369        mKeyDrawParams = new KeyDrawParams(a);
370        mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
371        mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
372        mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
373        if (mKeyPreviewLayoutId == 0) {
374            mShowKeyPreviewPopup = false;
375        }
376        mVerticalCorrection = a.getDimensionPixelOffset(
377                R.styleable.KeyboardView_verticalCorrection, 0);
378        mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
379        mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
380        a.recycle();
381
382        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
383        mPaint.setAntiAlias(true);
384    }
385
386    // Read fraction value in TypedArray as float.
387    /* package */ static float getRatio(TypedArray a, int index) {
388        return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
389    }
390
391    /**
392     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
393     * view will re-layout itself to accommodate the keyboard.
394     * @see Keyboard
395     * @see #getKeyboard()
396     * @param keyboard the keyboard to display in this view
397     */
398    public void setKeyboard(Keyboard keyboard) {
399        mKeyboard = keyboard;
400        LatinImeLogger.onSetKeyboard(keyboard);
401        requestLayout();
402        invalidateAllKeys();
403        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
404        mKeyDrawParams.updateKeyHeight(keyHeight);
405        mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
406    }
407
408    /**
409     * Returns the current keyboard being displayed by this view.
410     * @return the currently attached keyboard
411     * @see #setKeyboard(Keyboard)
412     */
413    public Keyboard getKeyboard() {
414        return mKeyboard;
415    }
416
417    /**
418     * Enables or disables the key feedback popup. This is a popup that shows a magnified
419     * version of the depressed key. By default the preview is enabled.
420     * @param previewEnabled whether or not to enable the key feedback preview
421     * @param delay the delay after which the preview is dismissed
422     * @see #isKeyPreviewPopupEnabled()
423     */
424    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
425        mShowKeyPreviewPopup = previewEnabled;
426        mDelayAfterPreview = delay;
427    }
428
429    /**
430     * Returns the enabled state of the key feedback preview
431     * @return whether or not the key feedback preview is enabled
432     * @see #setKeyPreviewPopupEnabled(boolean, int)
433     */
434    public boolean isKeyPreviewPopupEnabled() {
435        return mShowKeyPreviewPopup;
436    }
437
438    public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
439            boolean drawsGestureFloatingPreviewText) {
440        mPreviewPlacerView.setGesturePreviewMode(
441                drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
442    }
443
444    @Override
445    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
446        if (mKeyboard != null) {
447            // The main keyboard expands to the display width.
448            final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
449            setMeasuredDimension(widthMeasureSpec, height);
450        } else {
451            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
452        }
453    }
454
455    @Override
456    public void onDraw(Canvas canvas) {
457        super.onDraw(canvas);
458        if (canvas.isHardwareAccelerated()) {
459            onDrawKeyboard(canvas);
460            return;
461        }
462
463        final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
464        if (bufferNeedsUpdates || mOffscreenBuffer == null) {
465            if (maybeAllocateOffscreenBuffer()) {
466                mInvalidateAllKeys = true;
467                maybeCreateOffscreenCanvas();
468            }
469            onDrawKeyboard(mOffscreenCanvas);
470        }
471        canvas.drawBitmap(mOffscreenBuffer, 0, 0, null);
472    }
473
474    private boolean maybeAllocateOffscreenBuffer() {
475        final int width = getWidth();
476        final int height = getHeight();
477        if (width == 0 || height == 0) {
478            return false;
479        }
480        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width
481                && mOffscreenBuffer.getHeight() == height) {
482            return false;
483        }
484        freeOffscreenBuffer();
485        mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
486        return true;
487    }
488
489    private void freeOffscreenBuffer() {
490        if (mOffscreenBuffer != null) {
491            mOffscreenBuffer.recycle();
492            mOffscreenBuffer = null;
493        }
494    }
495
496    private void maybeCreateOffscreenCanvas() {
497        // TODO: Stop using the offscreen canvas even when in software rendering
498        if (mOffscreenCanvas != null) {
499            mOffscreenCanvas.setBitmap(mOffscreenBuffer);
500        } else {
501            mOffscreenCanvas = new Canvas(mOffscreenBuffer);
502        }
503    }
504
505    private void onDrawKeyboard(final Canvas canvas) {
506        if (mKeyboard == null) return;
507
508        final int width = getWidth();
509        final int height = getHeight();
510        final Paint paint = mPaint;
511        final KeyDrawParams params = mKeyDrawParams;
512
513        // Calculate clip region and set.
514        final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
515        final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
516        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
517        if (drawAllKeys || isHardwareAccelerated) {
518            mClipRegion.set(0, 0, width, height);
519        } else {
520            mClipRegion.setEmpty();
521            for (final Key key : mInvalidatedKeys) {
522                if (mKeyboard.hasKey(key)) {
523                    final int x = key.mX + getPaddingLeft();
524                    final int y = key.mY + getPaddingTop();
525                    mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
526                    mClipRegion.union(mWorkingRect);
527                }
528            }
529        }
530        if (!isHardwareAccelerated) {
531            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
532            // Draw keyboard background.
533            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
534            final Drawable background = getBackground();
535            if (background != null) {
536                background.draw(canvas);
537            }
538        }
539
540        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
541        if (drawAllKeys || isHardwareAccelerated) {
542            // Draw all keys.
543            for (final Key key : mKeyboard.mKeys) {
544                onDrawKey(key, canvas, paint, params);
545            }
546        } else {
547            // Draw invalidated keys.
548            for (final Key key : mInvalidatedKeys) {
549                if (mKeyboard.hasKey(key)) {
550                    onDrawKey(key, canvas, paint, params);
551                }
552            }
553        }
554
555        // Overlay a dark rectangle to dim.
556        if (mNeedsToDimEntireKeyboard) {
557            paint.setColor(Color.BLACK);
558            paint.setAlpha(mBackgroundDimAlpha);
559            // Note: clipRegion() above is in effect if it was called.
560            canvas.drawRect(0, 0, width, height, paint);
561        }
562
563        // ResearchLogging indicator.
564        // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
565        // and remove this call.
566        if (ProductionFlag.IS_EXPERIMENTAL) {
567            ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height);
568        }
569
570        mInvalidatedKeys.clear();
571        mInvalidateAllKeys = false;
572    }
573
574    public void dimEntireKeyboard(boolean dimmed) {
575        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
576        mNeedsToDimEntireKeyboard = dimmed;
577        if (needsRedrawing) {
578            invalidateAllKeys();
579        }
580    }
581
582    private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
583        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
584        final int keyDrawY = key.mY + getPaddingTop();
585        canvas.translate(keyDrawX, keyDrawY);
586
587        params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
588        if (!key.isSpacer()) {
589            onDrawKeyBackground(key, canvas, params);
590        }
591        onDrawKeyTopVisuals(key, canvas, paint, params);
592
593        canvas.translate(-keyDrawX, -keyDrawY);
594    }
595
596    // Draw key background.
597    protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
598        final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
599                + params.mPadding.left + params.mPadding.right;
600        final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
601        final int bgX = -params.mPadding.left;
602        final int bgY = -params.mPadding.top;
603        final int[] drawableState = key.getCurrentDrawableState();
604        final Drawable background = params.mKeyBackground;
605        background.setState(drawableState);
606        final Rect bounds = background.getBounds();
607        if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
608            background.setBounds(0, 0, bgWidth, bgHeight);
609        }
610        canvas.translate(bgX, bgY);
611        background.draw(canvas);
612        if (LatinImeLogger.sVISUALDEBUG) {
613            drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
614        }
615        canvas.translate(-bgX, -bgY);
616    }
617
618    // Draw key top visuals.
619    protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
620        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
621        final int keyHeight = key.mHeight;
622        final float centerX = keyWidth * 0.5f;
623        final float centerY = keyHeight * 0.5f;
624
625        if (LatinImeLogger.sVISUALDEBUG) {
626            drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
627        }
628
629        // Draw key label.
630        final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
631        float positionX = centerX;
632        if (key.mLabel != null) {
633            final String label = key.mLabel;
634            // For characters, use large font. For labels like "Done", use smaller font.
635            paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
636            final int labelSize = key.selectTextSize(params.mKeyLetterSize,
637                    params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize,
638                    params.mKeyHintLabelSize);
639            paint.setTextSize(labelSize);
640            final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
641            final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
642
643            // Vertical label text alignment.
644            final float baseline = centerY + labelCharHeight / 2;
645
646            // Horizontal label text alignment
647            float labelWidth = 0;
648            if (key.isAlignLeft()) {
649                positionX = (int)params.mKeyLabelHorizontalPadding;
650                paint.setTextAlign(Align.LEFT);
651            } else if (key.isAlignRight()) {
652                positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
653                paint.setTextAlign(Align.RIGHT);
654            } else if (key.isAlignLeftOfCenter()) {
655                // TODO: Parameterise this?
656                positionX = centerX - labelCharWidth * 7 / 4;
657                paint.setTextAlign(Align.LEFT);
658            } else if (key.hasLabelWithIconLeft() && icon != null) {
659                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
660                        + LABEL_ICON_MARGIN * keyWidth;
661                positionX = centerX + labelWidth / 2;
662                paint.setTextAlign(Align.RIGHT);
663            } else if (key.hasLabelWithIconRight() && icon != null) {
664                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
665                        + LABEL_ICON_MARGIN * keyWidth;
666                positionX = centerX - labelWidth / 2;
667                paint.setTextAlign(Align.LEFT);
668            } else {
669                positionX = centerX;
670                paint.setTextAlign(Align.CENTER);
671            }
672            if (key.needsXScale()) {
673                paint.setTextScaleX(
674                        Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
675            }
676
677            paint.setColor(key.isShiftedLetterActivated()
678                    ? params.mKeyTextInactivatedColor : params.mKeyTextColor);
679            if (key.isEnabled()) {
680                // Set a drop shadow for the text
681                paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
682            } else {
683                // Make label invisible
684                paint.setColor(Color.TRANSPARENT);
685            }
686            params.blendAlpha(paint);
687            canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
688            // Turn off drop shadow and reset x-scale.
689            paint.setShadowLayer(0, 0, 0, 0);
690            paint.setTextScaleX(1.0f);
691
692            if (icon != null) {
693                final int iconWidth = icon.getIntrinsicWidth();
694                final int iconHeight = icon.getIntrinsicHeight();
695                final int iconY = (keyHeight - iconHeight) / 2;
696                if (key.hasLabelWithIconLeft()) {
697                    final int iconX = (int)(centerX - labelWidth / 2);
698                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
699                } else if (key.hasLabelWithIconRight()) {
700                    final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
701                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
702                }
703            }
704
705            if (LatinImeLogger.sVISUALDEBUG) {
706                final Paint line = new Paint();
707                drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
708                drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
709            }
710        }
711
712        // Draw hint label.
713        if (key.mHintLabel != null) {
714            final String hint = key.mHintLabel;
715            final int hintColor;
716            final int hintSize;
717            if (key.hasHintLabel()) {
718                hintColor = params.mKeyHintLabelColor;
719                hintSize = params.mKeyHintLabelSize;
720                paint.setTypeface(Typeface.DEFAULT);
721            } else if (key.hasShiftedLetterHint()) {
722                hintColor = key.isShiftedLetterActivated()
723                        ? params.mKeyShiftedLetterHintActivatedColor
724                        : params.mKeyShiftedLetterHintInactivatedColor;
725                hintSize = params.mKeyShiftedLetterHintSize;
726            } else { // key.hasHintLetter()
727                hintColor = params.mKeyHintLetterColor;
728                hintSize = params.mKeyHintLetterSize;
729            }
730            paint.setColor(hintColor);
731            params.blendAlpha(paint);
732            paint.setTextSize(hintSize);
733            final float hintX, hintY;
734            if (key.hasHintLabel()) {
735                // The hint label is placed just right of the key label. Used mainly on
736                // "phone number" layout.
737                // TODO: Generalize the following calculations.
738                hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
739                hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
740                paint.setTextAlign(Align.LEFT);
741            } else if (key.hasShiftedLetterHint()) {
742                // The hint label is placed at top-right corner of the key. Used mainly on tablet.
743                hintX = keyWidth - params.mKeyShiftedLetterHintPadding
744                        - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
745                paint.getFontMetrics(mFontMetrics);
746                hintY = -mFontMetrics.top;
747                paint.setTextAlign(Align.CENTER);
748            } else { // key.hasHintLetter()
749                // The hint letter is placed at top-right corner of the key. Used mainly on phone.
750                hintX = keyWidth - params.mKeyHintLetterPadding
751                        - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
752                hintY = -paint.ascent();
753                paint.setTextAlign(Align.CENTER);
754            }
755            canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
756
757            if (LatinImeLogger.sVISUALDEBUG) {
758                final Paint line = new Paint();
759                drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
760                drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
761            }
762        }
763
764        // Draw key icon.
765        if (key.mLabel == null && icon != null) {
766            final int iconWidth = icon.getIntrinsicWidth();
767            final int iconHeight = icon.getIntrinsicHeight();
768            final int iconX, alignX;
769            final int iconY = (keyHeight - iconHeight) / 2;
770            if (key.isAlignLeft()) {
771                iconX = (int)params.mKeyLabelHorizontalPadding;
772                alignX = iconX;
773            } else if (key.isAlignRight()) {
774                iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
775                alignX = iconX + iconWidth;
776            } else { // Align center
777                iconX = (keyWidth - iconWidth) / 2;
778                alignX = iconX + iconWidth / 2;
779            }
780            drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
781
782            if (LatinImeLogger.sVISUALDEBUG) {
783                final Paint line = new Paint();
784                drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line);
785                drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line);
786            }
787        }
788
789        if (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) {
790            drawKeyPopupHint(key, canvas, paint, params);
791        }
792    }
793
794    // Draw popup hint "..." at the bottom right corner of the key.
795    protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
796        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
797        final int keyHeight = key.mHeight;
798
799        paint.setTypeface(params.mKeyTextStyle);
800        paint.setTextSize(params.mKeyHintLetterSize);
801        paint.setColor(params.mKeyHintLabelColor);
802        paint.setTextAlign(Align.CENTER);
803        final float hintX = keyWidth - params.mKeyHintLetterPadding
804                - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
805        final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
806        canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
807
808        if (LatinImeLogger.sVISUALDEBUG) {
809            final Paint line = new Paint();
810            drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
811            drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
812        }
813    }
814
815    private static int getCharGeometryCacheKey(char referenceChar, Paint paint) {
816        final int labelSize = (int)paint.getTextSize();
817        final Typeface face = paint.getTypeface();
818        final int codePointOffset = referenceChar << 15;
819        if (face == Typeface.DEFAULT) {
820            return codePointOffset + labelSize;
821        } else if (face == Typeface.DEFAULT_BOLD) {
822            return codePointOffset + labelSize + 0x1000;
823        } else if (face == Typeface.MONOSPACE) {
824            return codePointOffset + labelSize + 0x2000;
825        } else {
826            return codePointOffset + labelSize;
827        }
828    }
829
830    // Working variable for the following methods.
831    private final Rect mTextBounds = new Rect();
832
833    private float getCharHeight(char[] referenceChar, Paint paint) {
834        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
835        final Float cachedValue = sTextHeightCache.get(key);
836        if (cachedValue != null)
837            return cachedValue;
838
839        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
840        final float height = mTextBounds.height();
841        sTextHeightCache.put(key, height);
842        return height;
843    }
844
845    private float getCharWidth(char[] referenceChar, Paint paint) {
846        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
847        final Float cachedValue = sTextWidthCache.get(key);
848        if (cachedValue != null)
849            return cachedValue;
850
851        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
852        final float width = mTextBounds.width();
853        sTextWidthCache.put(key, width);
854        return width;
855    }
856
857    public float getLabelWidth(String label, Paint paint) {
858        paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds);
859        return mTextBounds.width();
860    }
861
862    protected static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
863            int height) {
864        canvas.translate(x, y);
865        icon.setBounds(0, 0, width, height);
866        icon.draw(canvas);
867        canvas.translate(-x, -y);
868    }
869
870    private static void drawHorizontalLine(Canvas canvas, float y, float w, int color,
871            Paint paint) {
872        paint.setStyle(Paint.Style.STROKE);
873        paint.setStrokeWidth(1.0f);
874        paint.setColor(color);
875        canvas.drawLine(0, y, w, y, paint);
876    }
877
878    private static void drawVerticalLine(Canvas canvas, float x, float h, int color, Paint paint) {
879        paint.setStyle(Paint.Style.STROKE);
880        paint.setStrokeWidth(1.0f);
881        paint.setColor(color);
882        canvas.drawLine(x, 0, x, h, paint);
883    }
884
885    private static void drawRectangle(Canvas canvas, float x, float y, float w, float h, int color,
886            Paint paint) {
887        paint.setStyle(Paint.Style.STROKE);
888        paint.setStrokeWidth(1.0f);
889        paint.setColor(color);
890        canvas.translate(x, y);
891        canvas.drawRect(0, 0, w, h, paint);
892        canvas.translate(-x, -y);
893    }
894
895    public Paint newDefaultLabelPaint() {
896        final Paint paint = new Paint();
897        paint.setAntiAlias(true);
898        paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
899        paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
900        return paint;
901    }
902
903    public void cancelAllMessages() {
904        mDrawingHandler.cancelAllMessages();
905        if (mPreviewPlacerView != null) {
906            mPreviewPlacerView.cancelAllMessages();
907        }
908    }
909
910    // Called by {@link PointerTracker} constructor to create a TextView.
911    @Override
912    public TextView inflateKeyPreviewText() {
913        final Context context = getContext();
914        if (mKeyPreviewLayoutId != 0) {
915            return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
916        } else {
917            return new TextView(context);
918        }
919    }
920
921    @Override
922    public void dismissKeyPreview(PointerTracker tracker) {
923        mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
924    }
925
926    private void addKeyPreview(TextView keyPreview) {
927        locatePreviewPlacerView();
928        mPreviewPlacerView.addView(
929                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
930    }
931
932    private void locatePreviewPlacerView() {
933        if (mPreviewPlacerView.getParent() != null) {
934            return;
935        }
936        final int[] viewOrigin = new int[2];
937        getLocationInWindow(viewOrigin);
938        mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
939        final View rootView = getRootView();
940        if (rootView == null) {
941            Log.w(TAG, "Cannot find root view");
942            return;
943        }
944        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
945        // Note: It'd be very weird if we get null by android.R.id.content.
946        if (windowContentView == null) {
947            Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
948        } else {
949            windowContentView.addView(mPreviewPlacerView);
950        }
951    }
952
953    public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
954        locatePreviewPlacerView();
955        mPreviewPlacerView.setGestureFloatingPreviewText(gestureFloatingPreviewText);
956    }
957
958    public void dismissGestureFloatingPreviewText() {
959        locatePreviewPlacerView();
960        mPreviewPlacerView.dismissGestureFloatingPreviewText();
961    }
962
963    @Override
964    public void showGestureTrail(PointerTracker tracker) {
965        locatePreviewPlacerView();
966        mPreviewPlacerView.invalidatePointer(tracker);
967    }
968
969    @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
970    @Override
971    public void showKeyPreview(PointerTracker tracker) {
972        if (!mShowKeyPreviewPopup) return;
973
974        final TextView previewText = tracker.getKeyPreviewText();
975        // If the key preview has no parent view yet, add it to the ViewGroup which can place
976        // key preview absolutely in SoftInputWindow.
977        if (previewText.getParent() == null) {
978            addKeyPreview(previewText);
979        }
980
981        mDrawingHandler.cancelDismissKeyPreview(tracker);
982        final Key key = tracker.getKey();
983        // If key is invalid or IME is already closed, we must not show key preview.
984        // Trying to show key preview while root window is closed causes
985        // WindowManager.BadTokenException.
986        if (key == null)
987            return;
988
989        final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
990        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
991        // What we show as preview should match what we show on a key top in onBufferDraw().
992        if (label != null) {
993            // TODO Should take care of temporaryShiftLabel here.
994            previewText.setCompoundDrawables(null, null, null, null);
995            if (StringUtils.codePointCount(label) > 1) {
996                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
997                previewText.setTypeface(Typeface.DEFAULT_BOLD);
998            } else {
999                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
1000                previewText.setTypeface(params.mKeyTextStyle);
1001            }
1002            previewText.setText(label);
1003        } else {
1004            previewText.setCompoundDrawables(null, null, null,
1005                    key.getPreviewIcon(mKeyboard.mIconsSet));
1006            previewText.setText(null);
1007        }
1008        previewText.setBackgroundDrawable(params.mPreviewBackground);
1009
1010        previewText.measure(
1011                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
1012        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
1013        final int previewWidth = previewText.getMeasuredWidth();
1014        final int previewHeight = params.mPreviewHeight;
1015        // The width and height of visible part of the key preview background. The content marker
1016        // of the background 9-patch have to cover the visible part of the background.
1017        params.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
1018                - previewText.getPaddingRight();
1019        params.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
1020                - previewText.getPaddingBottom();
1021        // The distance between the top edge of the parent key and the bottom of the visible part
1022        // of the key preview background.
1023        params.mPreviewVisibleOffset = params.mPreviewOffset - previewText.getPaddingBottom();
1024        getLocationInWindow(params.mCoordinates);
1025        // The key preview is horizontally aligned with the center of the visible part of the
1026        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
1027        // the left/right background is used if such background is specified.
1028        int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
1029                + params.mCoordinates[0];
1030        if (previewX < 0) {
1031            previewX = 0;
1032            if (params.mPreviewLeftBackground != null) {
1033                previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
1034            }
1035        } else if (previewX > getWidth() - previewWidth) {
1036            previewX = getWidth() - previewWidth;
1037            if (params.mPreviewRightBackground != null) {
1038                previewText.setBackgroundDrawable(params.mPreviewRightBackground);
1039            }
1040        }
1041        // The key preview is placed vertically above the top edge of the parent key with an
1042        // arbitrary offset.
1043        final int previewY = key.mY - previewHeight + params.mPreviewOffset
1044                + params.mCoordinates[1];
1045
1046        // Set the preview background state
1047        previewText.getBackground().setState(
1048                key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
1049        previewText.setTextColor(params.mPreviewTextColor);
1050        ViewLayoutUtils.placeViewAt(
1051                previewText, previewX, previewY, previewWidth, previewHeight);
1052        previewText.setVisibility(VISIBLE);
1053    }
1054
1055    /**
1056     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
1057     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
1058     * draws the cached buffer.
1059     * @see #invalidateKey(Key)
1060     */
1061    public void invalidateAllKeys() {
1062        mInvalidatedKeys.clear();
1063        mInvalidateAllKeys = true;
1064        invalidate();
1065    }
1066
1067    /**
1068     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1069     * one key is changing it's content. Any changes that affect the position or size of the key
1070     * may not be honored.
1071     * @param key key in the attached {@link Keyboard}.
1072     * @see #invalidateAllKeys
1073     */
1074    @Override
1075    public void invalidateKey(Key key) {
1076        if (mInvalidateAllKeys) return;
1077        if (key == null) return;
1078        mInvalidatedKeys.add(key);
1079        final int x = key.mX + getPaddingLeft();
1080        final int y = key.mY + getPaddingTop();
1081        invalidate(x, y, x + key.mWidth, y + key.mHeight);
1082    }
1083
1084    public void closing() {
1085        PointerTracker.dismissAllKeyPreviews();
1086        cancelAllMessages();
1087
1088        mInvalidateAllKeys = true;
1089        requestLayout();
1090    }
1091
1092    @Override
1093    public boolean dismissMoreKeysPanel() {
1094        return false;
1095    }
1096
1097    public void purgeKeyboardAndClosing() {
1098        mKeyboard = null;
1099        closing();
1100    }
1101
1102    @Override
1103    protected void onDetachedFromWindow() {
1104        super.onDetachedFromWindow();
1105        closing();
1106        mPreviewPlacerView.removeAllViews();
1107        freeOffscreenBuffer();
1108    }
1109}
1110