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 base 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 
KeyboardView(final Context context, final AttributeSet attrs)125     public KeyboardView(final Context context, final AttributeSet attrs) {
126         this(context, attrs, R.attr.keyboardViewStyle);
127     }
128 
KeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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
getKeyVisualAttribute()169     public KeyVisualAttributes getKeyVisualAttribute() {
170         return mKeyVisualAttributes;
171     }
172 
blendAlpha(@onnull final Paint paint, final int alpha)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 
setHardwareAcceleratedDrawingEnabled(final boolean enabled)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      */
setKeyboard(@onnull final Keyboard keyboard)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
getKeyboard()207     public Keyboard getKeyboard() {
208         return mKeyboard;
209     }
210 
getVerticalCorrection()211     protected float getVerticalCorrection() {
212         return mVerticalCorrection;
213     }
214 
215     @Nonnull
getKeyDrawParams()216     protected KeyDrawParams getKeyDrawParams() {
217         return mKeyDrawParams;
218     }
219 
updateKeyDrawParams(final int keyHeight)220     protected void updateKeyDrawParams(final int keyHeight) {
221         mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
222     }
223 
224     @Override
onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)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
onDraw(final Canvas canvas)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 
maybeAllocateOffscreenBuffer()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 
freeOffscreenBuffer()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 
onDrawKeyboard(@onnull final Canvas canvas)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 
onDrawKey(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint)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.
onDrawKeyBackground(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Drawable 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.
onDrawKeyTopVisuals(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final KeyDrawParams params)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.
drawKeyPopupHint(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final KeyDrawParams params)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 
drawIcon(@onnull final Canvas canvas,@Nonnull final Drawable icon, final int x, final int y, final int width, final int height)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 
newLabelPaint(@ullable final Key key)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      */
invalidateAllKeys()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      */
invalidateKey(@ullable final Key key)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
onDetachedFromWindow()582     protected void onDetachedFromWindow() {
583         super.onDetachedFromWindow();
584         freeOffscreenBuffer();
585     }
586 
deallocateMemory()587     public void deallocateMemory() {
588         freeOffscreenBuffer();
589     }
590 }
591