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