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