KeyboardView.java revision 51ee5a47b8af04a14e1756c900b42784f948fff5
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.graphics.drawable.NinePatchDrawable;
32import android.util.AttributeSet;
33import android.view.View;
34
35import com.android.inputmethod.keyboard.internal.KeyDrawParams;
36import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
37import com.android.inputmethod.latin.Constants;
38import com.android.inputmethod.latin.R;
39import com.android.inputmethod.latin.utils.TypefaceUtils;
40
41import java.util.HashSet;
42
43/**
44 * A view that renders a virtual {@link Keyboard}.
45 *
46 * @attr ref R.styleable#KeyboardView_keyBackground
47 * @attr ref R.styleable#KeyboardView_functionalKeyBackground
48 * @attr ref R.styleable#KeyboardView_spacebarBackground
49 * @attr ref R.styleable#KeyboardView_spacebarIconWidthRatio
50 * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
51 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
52 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
53 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
54 * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
55 * @attr ref R.styleable#KeyboardView_verticalCorrection
56 * @attr ref R.styleable#Keyboard_Key_keyTypeface
57 * @attr ref R.styleable#Keyboard_Key_keyLetterSize
58 * @attr ref R.styleable#Keyboard_Key_keyLabelSize
59 * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
60 * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
61 * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
62 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
63 * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
64 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
65 * @attr ref R.styleable#Keyboard_Key_keyTextColor
66 * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
67 * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
68 * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
69 * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
70 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
71 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
72 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
73 */
74public class KeyboardView extends View {
75    // XML attributes
76    private final KeyVisualAttributes mKeyVisualAttributes;
77    private final int mKeyLabelHorizontalPadding;
78    private final float mKeyHintLetterPadding;
79    private final float mKeyPopupHintLetterPadding;
80    private final float mKeyShiftedLetterHintPadding;
81    private final float mKeyTextShadowRadius;
82    private final float mVerticalCorrection;
83    private final Drawable mKeyBackground;
84    private final Drawable mFunctionalKeyBackground;
85    private final Drawable mSpacebarBackground;
86    private final float mSpacebarIconWidthRatio;
87    private final Rect mKeyBackgroundPadding = new Rect();
88    private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
89
90    // HORIZONTAL ELLIPSIS "...", character for popup hint.
91    private static final String POPUP_HINT_CHAR = "\u2026";
92
93    // Margin between the label and the icon on a key that has both of them.
94    // Specified by the fraction of the key width.
95    // TODO: Use resource parameter for this value.
96    private static final float LABEL_ICON_MARGIN = 0.05f;
97
98    // The maximum key label width in the proportion to the key width.
99    private static final float MAX_LABEL_RATIO = 0.90f;
100
101    // Main keyboard
102    private Keyboard mKeyboard;
103    protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
104
105    // Drawing
106    /** True if all keys should be drawn */
107    private boolean mInvalidateAllKeys;
108    /** The keys that should be drawn */
109    private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
110    /** The working rectangle variable */
111    private final Rect mWorkingRect = new Rect();
112    /** The keyboard bitmap buffer for faster updates */
113    /** The clip region to draw keys */
114    private final Region mClipRegion = new Region();
115    private Bitmap mOffscreenBuffer;
116    /** The canvas for the above mutable keyboard bitmap */
117    private final Canvas mOffscreenCanvas = new Canvas();
118    private final Paint mPaint = new Paint();
119    private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
120    public KeyboardView(final Context context, final AttributeSet attrs) {
121        this(context, attrs, R.attr.keyboardViewStyle);
122    }
123
124    public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
125        super(context, attrs, defStyle);
126
127        final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
128                R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
129        mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
130        mKeyBackground.getPadding(mKeyBackgroundPadding);
131        final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
132                R.styleable.KeyboardView_functionalKeyBackground);
133        mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
134                : mKeyBackground;
135        final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
136                R.styleable.KeyboardView_spacebarBackground);
137        mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground;
138        mSpacebarIconWidthRatio = keyboardViewAttr.getFloat(
139                R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f);
140        mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
141                R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
142        mKeyHintLetterPadding = keyboardViewAttr.getDimension(
143                R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
144        mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
145                R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f);
146        mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
147                R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
148        mKeyTextShadowRadius = keyboardViewAttr.getFloat(
149                R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
150        mVerticalCorrection = keyboardViewAttr.getDimension(
151                R.styleable.KeyboardView_verticalCorrection, 0.0f);
152        keyboardViewAttr.recycle();
153
154        final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
155                R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
156        mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
157        keyAttr.recycle();
158
159        mPaint.setAntiAlias(true);
160    }
161
162    public KeyVisualAttributes getKeyVisualAttribute() {
163        return mKeyVisualAttributes;
164    }
165
166    private static void blendAlpha(final Paint paint, final int alpha) {
167        final int color = paint.getColor();
168        paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
169                Color.red(color), Color.green(color), Color.blue(color));
170    }
171
172    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
173        if (!enabled) return;
174        // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
175        setLayerType(LAYER_TYPE_HARDWARE, null);
176    }
177
178    /**
179     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
180     * view will re-layout itself to accommodate the keyboard.
181     * @see Keyboard
182     * @see #getKeyboard()
183     * @param keyboard the keyboard to display in this view
184     */
185    public void setKeyboard(final Keyboard keyboard) {
186        mKeyboard = keyboard;
187        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
188        mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
189        mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
190        invalidateAllKeys();
191        requestLayout();
192    }
193
194    /**
195     * Returns the current keyboard being displayed by this view.
196     * @return the currently attached keyboard
197     * @see #setKeyboard(Keyboard)
198     */
199    public Keyboard getKeyboard() {
200        return mKeyboard;
201    }
202
203    protected float getVerticalCorrection() {
204        return mVerticalCorrection;
205    }
206
207    protected void updateKeyDrawParams(final int keyHeight) {
208        mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
209    }
210
211    @Override
212    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
213        if (mKeyboard == null) {
214            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
215            return;
216        }
217        // The main keyboard expands to the entire this {@link KeyboardView}.
218        final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
219        final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
220        setMeasuredDimension(width, height);
221    }
222
223    @Override
224    protected void onDraw(final Canvas canvas) {
225        super.onDraw(canvas);
226        if (canvas.isHardwareAccelerated()) {
227            onDrawKeyboard(canvas);
228            return;
229        }
230
231        final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
232        if (bufferNeedsUpdates || mOffscreenBuffer == null) {
233            if (maybeAllocateOffscreenBuffer()) {
234                mInvalidateAllKeys = true;
235                // TODO: Stop using the offscreen canvas even when in software rendering
236                mOffscreenCanvas.setBitmap(mOffscreenBuffer);
237            }
238            onDrawKeyboard(mOffscreenCanvas);
239        }
240        canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null);
241    }
242
243    private boolean maybeAllocateOffscreenBuffer() {
244        final int width = getWidth();
245        final int height = getHeight();
246        if (width == 0 || height == 0) {
247            return false;
248        }
249        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width
250                && mOffscreenBuffer.getHeight() == height) {
251            return false;
252        }
253        freeOffscreenBuffer();
254        mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
255        return true;
256    }
257
258    private void freeOffscreenBuffer() {
259        mOffscreenCanvas.setBitmap(null);
260        mOffscreenCanvas.setMatrix(null);
261        if (mOffscreenBuffer != null) {
262            mOffscreenBuffer.recycle();
263            mOffscreenBuffer = null;
264        }
265    }
266
267    private void onDrawKeyboard(final Canvas canvas) {
268        if (mKeyboard == null) return;
269
270        final int width = getWidth();
271        final int height = getHeight();
272        final Paint paint = mPaint;
273
274        // Calculate clip region and set.
275        final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
276        final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
277        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
278        if (drawAllKeys || isHardwareAccelerated) {
279            mClipRegion.set(0, 0, width, height);
280        } else {
281            mClipRegion.setEmpty();
282            for (final Key key : mInvalidatedKeys) {
283                if (mKeyboard.hasKey(key)) {
284                    final int x = key.getX() + getPaddingLeft();
285                    final int y = key.getY() + getPaddingTop();
286                    mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight());
287                    mClipRegion.union(mWorkingRect);
288                }
289            }
290        }
291        if (!isHardwareAccelerated) {
292            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
293            // Draw keyboard background.
294            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
295            final Drawable background = getBackground();
296            if (background != null) {
297                background.draw(canvas);
298            }
299        }
300
301        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
302        if (drawAllKeys || isHardwareAccelerated) {
303            // Draw all keys.
304            for (final Key key : mKeyboard.getSortedKeys()) {
305                onDrawKey(key, canvas, paint);
306            }
307        } else {
308            // Draw invalidated keys.
309            for (final Key key : mInvalidatedKeys) {
310                if (mKeyboard.hasKey(key)) {
311                    onDrawKey(key, canvas, paint);
312                }
313            }
314        }
315
316        mInvalidatedKeys.clear();
317        mInvalidateAllKeys = false;
318    }
319
320    private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
321        final int keyDrawX = key.getDrawX() + getPaddingLeft();
322        final int keyDrawY = key.getY() + getPaddingTop();
323        canvas.translate(keyDrawX, keyDrawY);
324
325        final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
326        final KeyVisualAttributes attr = key.getVisualAttributes();
327        final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
328        params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
329
330        if (!key.isSpacer()) {
331            final Drawable background = key.selectBackgroundDrawable(
332                    mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
333            onDrawKeyBackground(key, canvas, background);
334        }
335        onDrawKeyTopVisuals(key, canvas, paint, params);
336
337        canvas.translate(-keyDrawX, -keyDrawY);
338    }
339
340    // Draw key background.
341    protected void onDrawKeyBackground(final Key key, final Canvas canvas,
342            final Drawable background) {
343        final Rect padding = mKeyBackgroundPadding;
344        final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
345        final int bgHeight = key.getHeight() + padding.top + padding.bottom;
346        final int bgX = -padding.left;
347        final int bgY = -padding.top;
348        final Rect bounds = background.getBounds();
349        if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
350            background.setBounds(0, 0, bgWidth, bgHeight);
351        }
352        canvas.translate(bgX, bgY);
353        background.draw(canvas);
354        canvas.translate(-bgX, -bgY);
355    }
356
357    // Draw key top visuals.
358    protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
359            final KeyDrawParams params) {
360        final int keyWidth = key.getDrawWidth();
361        final int keyHeight = key.getHeight();
362        final float centerX = keyWidth * 0.5f;
363        final float centerY = keyHeight * 0.5f;
364
365        // Draw key label.
366        final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
367        float positionX = centerX;
368        final String label = key.getLabel();
369        if (label != null) {
370            paint.setTypeface(key.selectTypeface(params));
371            paint.setTextSize(key.selectTextSize(params));
372            final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
373            final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
374
375            // Vertical label text alignment.
376            final float baseline = centerY + labelCharHeight / 2.0f;
377
378            // Horizontal label text alignment
379            float labelWidth = 0.0f;
380            if (key.isAlignLeft()) {
381                positionX = mKeyLabelHorizontalPadding;
382                paint.setTextAlign(Align.LEFT);
383            } else if (key.isAlignRight()) {
384                positionX = keyWidth - mKeyLabelHorizontalPadding;
385                paint.setTextAlign(Align.RIGHT);
386            } else if (key.isAlignLeftOfCenter()) {
387                // TODO: Parameterise this?
388                positionX = centerX - labelCharWidth * 7.0f / 4.0f;
389                paint.setTextAlign(Align.LEFT);
390            } else if (key.hasLabelWithIconLeft() && icon != null) {
391                labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
392                        + LABEL_ICON_MARGIN * keyWidth;
393                positionX = centerX + labelWidth / 2.0f;
394                paint.setTextAlign(Align.RIGHT);
395            } else if (key.hasLabelWithIconRight() && icon != null) {
396                labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
397                        + LABEL_ICON_MARGIN * keyWidth;
398                positionX = centerX - labelWidth / 2.0f;
399                paint.setTextAlign(Align.LEFT);
400            } else {
401                positionX = centerX;
402                paint.setTextAlign(Align.CENTER);
403            }
404            if (key.needsAutoXScale()) {
405                final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
406                        TypefaceUtils.getStringWidth(label, paint));
407                if (key.needsAutoScale()) {
408                    final float autoSize = paint.getTextSize() * ratio;
409                    paint.setTextSize(autoSize);
410                } else {
411                    paint.setTextScaleX(ratio);
412                }
413            }
414
415            if (key.isEnabled()) {
416                paint.setColor(key.selectTextColor(params));
417                // Set a drop shadow for the text if the shadow radius is positive value.
418                if (mKeyTextShadowRadius > 0.0f) {
419                    paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
420                } else {
421                    paint.clearShadowLayer();
422                }
423            } else {
424                // Make label invisible
425                paint.setColor(Color.TRANSPARENT);
426                paint.clearShadowLayer();
427            }
428            blendAlpha(paint, params.mAnimAlpha);
429            canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
430            // Turn off drop shadow and reset x-scale.
431            paint.clearShadowLayer();
432            paint.setTextScaleX(1.0f);
433
434            if (icon != null) {
435                final int iconWidth = icon.getIntrinsicWidth();
436                final int iconHeight = icon.getIntrinsicHeight();
437                final int iconY = (keyHeight - iconHeight) / 2;
438                if (key.hasLabelWithIconLeft()) {
439                    final int iconX = (int)(centerX - labelWidth / 2.0f);
440                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
441                } else if (key.hasLabelWithIconRight()) {
442                    final int iconX = (int)(centerX + labelWidth / 2.0f - iconWidth);
443                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
444                }
445            }
446        }
447
448        // Draw hint label.
449        final String hintLabel = key.getHintLabel();
450        if (hintLabel != null) {
451            paint.setTextSize(key.selectHintTextSize(params));
452            paint.setColor(key.selectHintTextColor(params));
453            // TODO: Should add a way to specify type face for hint letters
454            paint.setTypeface(Typeface.DEFAULT_BOLD);
455            blendAlpha(paint, params.mAnimAlpha);
456            final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
457            final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
458            final KeyVisualAttributes visualAttr = key.getVisualAttributes();
459            final float adjustmentY = (visualAttr == null) ? 0.0f
460                    : visualAttr.mHintLabelVerticalAdjustment * labelCharHeight;
461            final float hintX, hintY;
462            if (key.hasHintLabel()) {
463                // The hint label is placed just right of the key label. Used mainly on
464                // "phone number" layout.
465                // TODO: Generalize the following calculations.
466                hintX = positionX + labelCharWidth * 2.0f;
467                hintY = centerY + labelCharHeight / 2.0f;
468                paint.setTextAlign(Align.LEFT);
469            } else if (key.hasShiftedLetterHint()) {
470                // The hint label is placed at top-right corner of the key. Used mainly on tablet.
471                hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
472                paint.getFontMetrics(mFontMetrics);
473                hintY = -mFontMetrics.top;
474                paint.setTextAlign(Align.CENTER);
475            } else { // key.hasHintLetter()
476                // The hint letter is placed at top-right corner of the key. Used mainly on phone.
477                final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
478                final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
479                hintX = keyWidth - mKeyHintLetterPadding
480                        - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
481                hintY = -paint.ascent();
482                paint.setTextAlign(Align.CENTER);
483            }
484            canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY + adjustmentY, paint);
485        }
486
487        // Draw key icon.
488        if (label == null && icon != null) {
489            final int iconWidth;
490            if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
491                iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio);
492            } else {
493                iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
494            }
495            final int iconHeight = icon.getIntrinsicHeight();
496            final int iconY = key.isAlignButtom() ? keyHeight - iconHeight
497                    : (keyHeight - iconHeight) / 2;
498            final int iconX;
499            if (key.isAlignLeft()) {
500                iconX = mKeyLabelHorizontalPadding;
501            } else if (key.isAlignRight()) {
502                iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth;
503            } else { // Align center
504                iconX = (keyWidth - iconWidth) / 2;
505            }
506            drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
507        }
508
509        if (key.hasPopupHint() && key.getMoreKeys() != null) {
510            drawKeyPopupHint(key, canvas, paint, params);
511        }
512    }
513
514    // Draw popup hint "..." at the bottom right corner of the key.
515    protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
516            final KeyDrawParams params) {
517        final int keyWidth = key.getDrawWidth();
518        final int keyHeight = key.getHeight();
519
520        paint.setTypeface(params.mTypeface);
521        paint.setTextSize(params.mHintLetterSize);
522        paint.setColor(params.mHintLabelColor);
523        paint.setTextAlign(Align.CENTER);
524        final float hintX = keyWidth - mKeyHintLetterPadding
525                - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
526        final float hintY = keyHeight - mKeyPopupHintLetterPadding;
527        canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
528    }
529
530    protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
531            final int y, final int width, final int height) {
532        canvas.translate(x, y);
533        icon.setBounds(0, 0, width, height);
534        icon.draw(canvas);
535        canvas.translate(-x, -y);
536    }
537
538    public Paint newLabelPaint(final Key key) {
539        final Paint paint = new Paint();
540        paint.setAntiAlias(true);
541        if (key == null) {
542            paint.setTypeface(mKeyDrawParams.mTypeface);
543            paint.setTextSize(mKeyDrawParams.mLabelSize);
544        } else {
545            paint.setColor(key.selectTextColor(mKeyDrawParams));
546            paint.setTypeface(key.selectTypeface(mKeyDrawParams));
547            paint.setTextSize(key.selectTextSize(mKeyDrawParams));
548        }
549        return paint;
550    }
551
552    /**
553     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
554     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
555     * draws the cached buffer.
556     * @see #invalidateKey(Key)
557     */
558    public void invalidateAllKeys() {
559        mInvalidatedKeys.clear();
560        mInvalidateAllKeys = true;
561        invalidate();
562    }
563
564    /**
565     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
566     * one key is changing it's content. Any changes that affect the position or size of the key
567     * may not be honored.
568     * @param key key in the attached {@link Keyboard}.
569     * @see #invalidateAllKeys
570     */
571    public void invalidateKey(final Key key) {
572        if (mInvalidateAllKeys) return;
573        if (key == null) return;
574        mInvalidatedKeys.add(key);
575        final int x = key.getX() + getPaddingLeft();
576        final int y = key.getY() + getPaddingTop();
577        invalidate(x, y, x + key.getWidth(), y + key.getHeight());
578    }
579
580    @Override
581    protected void onDetachedFromWindow() {
582        super.onDetachedFromWindow();
583        freeOffscreenBuffer();
584    }
585
586    public void deallocateMemory() {
587        freeOffscreenBuffer();
588    }
589}
590