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.Typeface;
     29 import android.graphics.drawable.Drawable;
     30 import android.graphics.drawable.NinePatchDrawable;
     31 import android.text.TextUtils;
     32 import android.util.AttributeSet;
     33 import android.view.View;
     34 
     35 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
     36 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
     37 import com.android.inputmethod.latin.R;
     38 import com.android.inputmethod.latin.common.Constants;
     39 import com.android.inputmethod.latin.utils.TypefaceUtils;
     40 
     41 import java.util.HashSet;
     42 
     43 import javax.annotation.Nonnull;
     44 import 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  */
     80 public 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