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