• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
KeyboardView(final Context context, final AttributeSet attrs)117     public KeyboardView(final Context context, final AttributeSet attrs) {
118         this(context, attrs, R.attr.keyboardViewStyle);
119     }
120 
KeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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 
blendAlpha(final Paint paint, final int alpha)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      */
setKeyboard(final Keyboard keyboard)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      */
getKeyboard()178     public Keyboard getKeyboard() {
179         return mKeyboard;
180     }
181 
getVerticalCorrection()182     protected float getVerticalCorrection() {
183         return mVerticalCorrection;
184     }
185 
updateKeyDrawParams(final int keyHeight)186     protected void updateKeyDrawParams(final int keyHeight) {
187         mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
188     }
189 
190     @Override
onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)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
onDraw(final Canvas canvas)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 
maybeAllocateOffscreenBuffer()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 
freeOffscreenBuffer()236     private void freeOffscreenBuffer() {
237         if (mOffscreenBuffer != null) {
238             mOffscreenBuffer.recycle();
239             mOffscreenBuffer = null;
240         }
241     }
242 
onDrawKeyboard(final Canvas canvas)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 
onDrawKey(final Key key, final Canvas canvas, final Paint paint)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.
onDrawKeyBackground(final Key key, final Canvas canvas)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.
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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.
drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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 
drawIcon(final Canvas canvas, final Drawable icon, final int x, final int y, final int width, final int height)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 
drawHorizontalLine(final Canvas canvas, final float y, final float w, final int color, final Paint paint)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 
drawVerticalLine(final Canvas canvas, final float x, final float h, final int color, final Paint paint)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 
drawRectangle(final Canvas canvas, final float x, final float y, final float w, final float h, final int color, final Paint paint)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 
newLabelPaint(final Key key)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      */
invalidateAllKeys()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      */
invalidateKey(final Key key)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
onDetachedFromWindow()603     protected void onDetachedFromWindow() {
604         super.onDetachedFromWindow();
605         freeOffscreenBuffer();
606     }
607 }
608