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