Home | History | Annotate | Download | only in keyboard
      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 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Canvas;
     23 import android.graphics.Color;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.Align;
     26 import android.graphics.PorterDuff;
     27 import android.graphics.Rect;
     28 import android.graphics.Region;
     29 import android.graphics.Typeface;
     30 import android.graphics.drawable.Drawable;
     31 import android.graphics.drawable.NinePatchDrawable;
     32 import android.text.TextUtils;
     33 import android.util.AttributeSet;
     34 import android.view.View;
     35 
     36 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
     37 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
     38 import com.android.inputmethod.latin.Constants;
     39 import com.android.inputmethod.latin.R;
     40 import com.android.inputmethod.latin.utils.TypefaceUtils;
     41 
     42 import java.util.HashSet;
     43 
     44 /**
     45  * A view that renders a virtual {@link Keyboard}.
     46  *
     47  * @attr ref R.styleable#KeyboardView_keyBackground
     48  * @attr ref R.styleable#KeyboardView_functionalKeyBackground
     49  * @attr ref R.styleable#KeyboardView_spacebarBackground
     50  * @attr ref R.styleable#KeyboardView_spacebarIconWidthRatio
     51  * @attr ref R.styleable#Keyboard_Key_keyLabelFlags
     52  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
     53  * @attr ref R.styleable#KeyboardView_keyPopupHintLetter
     54  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
     55  * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
     56  * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
     57  * @attr ref R.styleable#KeyboardView_verticalCorrection
     58  * @attr ref R.styleable#Keyboard_Key_keyTypeface
     59  * @attr ref R.styleable#Keyboard_Key_keyLetterSize
     60  * @attr ref R.styleable#Keyboard_Key_keyLabelSize
     61  * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
     62  * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
     63  * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
     64  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
     65  * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
     66  * @attr ref R.styleable#Keyboard_Key_keyLabelOffCenterRatio
     67  * @attr ref R.styleable#Keyboard_Key_keyHintLabelOffCenterRatio
     68  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
     69  * @attr ref R.styleable#Keyboard_Key_keyTextColor
     70  * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
     71  * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
     72  * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
     73  * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
     74  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
     75  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
     76  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
     77  */
     78 public class KeyboardView extends View {
     79     // XML attributes
     80     private final KeyVisualAttributes mKeyVisualAttributes;
     81     // Default keyLabelFlags from {@link KeyboardTheme}.
     82     // Currently only "alignHintLabelToBottom" is supported.
     83     private final int mDefaultKeyLabelFlags;
     84     private final float mKeyHintLetterPadding;
     85     private final String mKeyPopupHintLetter;
     86     private final float mKeyPopupHintLetterPadding;
     87     private final float mKeyShiftedLetterHintPadding;
     88     private final float mKeyTextShadowRadius;
     89     private final float mVerticalCorrection;
     90     private final Drawable mKeyBackground;
     91     private final Drawable mFunctionalKeyBackground;
     92     private final Drawable mSpacebarBackground;
     93     private final float mSpacebarIconWidthRatio;
     94     private final Rect mKeyBackgroundPadding = new Rect();
     95     private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
     96 
     97     // The maximum key label width in the proportion to the key width.
     98     private static final float MAX_LABEL_RATIO = 0.90f;
     99 
    100     // Main keyboard
    101     private Keyboard mKeyboard;
    102     protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
    103 
    104     // Drawing
    105     /** True if all keys should be drawn */
    106     private boolean mInvalidateAllKeys;
    107     /** The keys that should be drawn */
    108     private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
    109     /** The working rectangle variable */
    110     private final Rect mWorkingRect = new Rect();
    111     /** The keyboard bitmap buffer for faster updates */
    112     /** The clip region to draw keys */
    113     private final Region mClipRegion = new Region();
    114     private Bitmap mOffscreenBuffer;
    115     /** The canvas for the above mutable keyboard bitmap */
    116     private final Canvas mOffscreenCanvas = new Canvas();
    117     private final Paint mPaint = new Paint();
    118     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
    119     public KeyboardView(final Context context, final AttributeSet attrs) {
    120         this(context, attrs, R.attr.keyboardViewStyle);
    121     }
    122 
    123     public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
    124         super(context, attrs, defStyle);
    125 
    126         final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
    127                 R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
    128         mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
    129         mKeyBackground.getPadding(mKeyBackgroundPadding);
    130         final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
    131                 R.styleable.KeyboardView_functionalKeyBackground);
    132         mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
    133                 : mKeyBackground;
    134         final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
    135                 R.styleable.KeyboardView_spacebarBackground);
    136         mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground;
    137         mSpacebarIconWidthRatio = keyboardViewAttr.getFloat(
    138                 R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f);
    139         mKeyHintLetterPadding = keyboardViewAttr.getDimension(
    140                 R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
    141         mKeyPopupHintLetter = keyboardViewAttr.getString(
    142                 R.styleable.KeyboardView_keyPopupHintLetter);
    143         mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
    144                 R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f);
    145         mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
    146                 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
    147         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
    148                 R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
    149         mVerticalCorrection = keyboardViewAttr.getDimension(
    150                 R.styleable.KeyboardView_verticalCorrection, 0.0f);
    151         keyboardViewAttr.recycle();
    152 
    153         final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
    154                 R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
    155         mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
    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 int keyWidth = key.getDrawWidth();
    344         final int keyHeight = key.getHeight();
    345         final int bgWidth, bgHeight, bgX, bgY;
    346         if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)
    347                 // HACK: To disable expanding normal/functional key background.
    348                 && !key.hasCustomActionLabel()) {
    349             final int intrinsicWidth = background.getIntrinsicWidth();
    350             final int intrinsicHeight = background.getIntrinsicHeight();
    351             final float minScale = Math.min(
    352                     keyWidth / (float)intrinsicWidth, keyHeight / (float)intrinsicHeight);
    353             bgWidth = (int)(intrinsicWidth * minScale);
    354             bgHeight = (int)(intrinsicHeight * minScale);
    355             bgX = (keyWidth - bgWidth) / 2;
    356             bgY = (keyHeight - bgHeight) / 2;
    357         } else {
    358             final Rect padding = mKeyBackgroundPadding;
    359             bgWidth = keyWidth + padding.left + padding.right;
    360             bgHeight = keyHeight + padding.top + padding.bottom;
    361             bgX = -padding.left;
    362             bgY = -padding.top;
    363         }
    364         final Rect bounds = background.getBounds();
    365         if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
    366             background.setBounds(0, 0, bgWidth, bgHeight);
    367         }
    368         canvas.translate(bgX, bgY);
    369         background.draw(canvas);
    370         canvas.translate(-bgX, -bgY);
    371     }
    372 
    373     // Draw key top visuals.
    374     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
    375             final KeyDrawParams params) {
    376         final int keyWidth = key.getDrawWidth();
    377         final int keyHeight = key.getHeight();
    378         final float centerX = keyWidth * 0.5f;
    379         final float centerY = keyHeight * 0.5f;
    380 
    381         // Draw key label.
    382         final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
    383         float labelX = centerX;
    384         float labelBaseline = centerY;
    385         final String label = key.getLabel();
    386         if (label != null) {
    387             paint.setTypeface(key.selectTypeface(params));
    388             paint.setTextSize(key.selectTextSize(params));
    389             final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
    390             final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
    391 
    392             // Vertical label text alignment.
    393             labelBaseline = centerY + labelCharHeight / 2.0f;
    394 
    395             // Horizontal label text alignment
    396             if (key.isAlignLabelOffCenter()) {
    397                 // The label is placed off center of the key. Used mainly on "phone number" layout.
    398                 labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth;
    399                 paint.setTextAlign(Align.LEFT);
    400             } else {
    401                 labelX = 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(), labelX, labelBaseline, paint);
    430             // Turn off drop shadow and reset x-scale.
    431             paint.clearShadowLayer();
    432             paint.setTextScaleX(1.0f);
    433         }
    434 
    435         // Draw hint label.
    436         final String hintLabel = key.getHintLabel();
    437         if (hintLabel != null) {
    438             paint.setTextSize(key.selectHintTextSize(params));
    439             paint.setColor(key.selectHintTextColor(params));
    440             // TODO: Should add a way to specify type face for hint letters
    441             paint.setTypeface(Typeface.DEFAULT_BOLD);
    442             blendAlpha(paint, params.mAnimAlpha);
    443             final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
    444             final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
    445             final float hintX, hintBaseline;
    446             if (key.hasHintLabel()) {
    447                 // The hint label is placed just right of the key label. Used mainly on
    448                 // "phone number" layout.
    449                 hintX = labelX + params.mHintLabelOffCenterRatio * labelCharWidth;
    450                 if (key.isAlignHintLabelToBottom(mDefaultKeyLabelFlags)) {
    451                     hintBaseline = labelBaseline;
    452                 } else {
    453                     hintBaseline = centerY + labelCharHeight / 2.0f;
    454                 }
    455                 paint.setTextAlign(Align.LEFT);
    456             } else if (key.hasShiftedLetterHint()) {
    457                 // The hint label is placed at top-right corner of the key. Used mainly on tablet.
    458                 hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
    459                 paint.getFontMetrics(mFontMetrics);
    460                 hintBaseline = -mFontMetrics.top;
    461                 paint.setTextAlign(Align.CENTER);
    462             } else { // key.hasHintLetter()
    463                 // The hint letter is placed at top-right corner of the key. Used mainly on phone.
    464                 final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
    465                 final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
    466                 hintX = keyWidth - mKeyHintLetterPadding
    467                         - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
    468                 hintBaseline = -paint.ascent();
    469                 paint.setTextAlign(Align.CENTER);
    470             }
    471             final float adjustmentY = params.mHintLabelVerticalAdjustment * labelCharHeight;
    472             canvas.drawText(
    473                     hintLabel, 0, hintLabel.length(), hintX, hintBaseline + adjustmentY, paint);
    474         }
    475 
    476         // Draw key icon.
    477         if (label == null && icon != null) {
    478             final int iconWidth;
    479             if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
    480                 iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio);
    481             } else {
    482                 iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
    483             }
    484             final int iconHeight = icon.getIntrinsicHeight();
    485             final int iconY;
    486             if (key.isAlignIconToBottom()) {
    487                 iconY = keyHeight - iconHeight;
    488             } else {
    489                 iconY = (keyHeight - iconHeight) / 2; // Align vertically center.
    490             }
    491             final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center.
    492             drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
    493         }
    494 
    495         if (key.hasPopupHint() && key.getMoreKeys() != null) {
    496             drawKeyPopupHint(key, canvas, paint, params);
    497         }
    498     }
    499 
    500     // Draw popup hint "..." at the bottom right corner of the key.
    501     protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
    502             final KeyDrawParams params) {
    503         if (TextUtils.isEmpty(mKeyPopupHintLetter)) {
    504             return;
    505         }
    506         final int keyWidth = key.getDrawWidth();
    507         final int keyHeight = key.getHeight();
    508 
    509         paint.setTypeface(params.mTypeface);
    510         paint.setTextSize(params.mHintLetterSize);
    511         paint.setColor(params.mHintLabelColor);
    512         paint.setTextAlign(Align.CENTER);
    513         final float hintX = keyWidth - mKeyHintLetterPadding
    514                 - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
    515         final float hintY = keyHeight - mKeyPopupHintLetterPadding;
    516         canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint);
    517     }
    518 
    519     protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
    520             final int y, final int width, final int height) {
    521         canvas.translate(x, y);
    522         icon.setBounds(0, 0, width, height);
    523         icon.draw(canvas);
    524         canvas.translate(-x, -y);
    525     }
    526 
    527     public Paint newLabelPaint(final Key key) {
    528         final Paint paint = new Paint();
    529         paint.setAntiAlias(true);
    530         if (key == null) {
    531             paint.setTypeface(mKeyDrawParams.mTypeface);
    532             paint.setTextSize(mKeyDrawParams.mLabelSize);
    533         } else {
    534             paint.setColor(key.selectTextColor(mKeyDrawParams));
    535             paint.setTypeface(key.selectTypeface(mKeyDrawParams));
    536             paint.setTextSize(key.selectTextSize(mKeyDrawParams));
    537         }
    538         return paint;
    539     }
    540 
    541     /**
    542      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
    543      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
    544      * draws the cached buffer.
    545      * @see #invalidateKey(Key)
    546      */
    547     public void invalidateAllKeys() {
    548         mInvalidatedKeys.clear();
    549         mInvalidateAllKeys = true;
    550         invalidate();
    551     }
    552 
    553     /**
    554      * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
    555      * one key is changing it's content. Any changes that affect the position or size of the key
    556      * may not be honored.
    557      * @param key key in the attached {@link Keyboard}.
    558      * @see #invalidateAllKeys
    559      */
    560     public void invalidateKey(final Key key) {
    561         if (mInvalidateAllKeys) return;
    562         if (key == null) return;
    563         mInvalidatedKeys.add(key);
    564         final int x = key.getX() + getPaddingLeft();
    565         final int y = key.getY() + getPaddingTop();
    566         invalidate(x, y, x + key.getWidth(), y + key.getHeight());
    567     }
    568 
    569     @Override
    570     protected void onDetachedFromWindow() {
    571         super.onDetachedFromWindow();
    572         freeOffscreenBuffer();
    573     }
    574 
    575     public void deallocateMemory() {
    576         freeOffscreenBuffer();
    577     }
    578 }
    579