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.Resources;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.Paint;
     26 import android.graphics.Paint.Align;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.Rect;
     29 import android.graphics.Region.Op;
     30 import android.graphics.Typeface;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.Message;
     33 import android.util.AttributeSet;
     34 import android.util.TypedValue;
     35 import android.view.LayoutInflater;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.widget.RelativeLayout;
     39 import android.widget.TextView;
     40 
     41 import com.android.inputmethod.compat.FrameLayoutCompatUtils;
     42 import com.android.inputmethod.latin.LatinImeLogger;
     43 import com.android.inputmethod.latin.R;
     44 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
     45 
     46 import java.util.HashMap;
     47 
     48 /**
     49  * A view that renders a virtual {@link Keyboard}.
     50  *
     51  * @attr ref R.styleable#KeyboardView_backgroundDimAmount
     52  * @attr ref R.styleable#KeyboardView_keyBackground
     53  * @attr ref R.styleable#KeyboardView_keyLetterRatio
     54  * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
     55  * @attr ref R.styleable#KeyboardView_keyLabelRatio
     56  * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
     57  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterRatio
     58  * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
     59  * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
     60  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
     61  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
     62  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterPadding
     63  * @attr ref R.styleable#KeyboardView_keyTextStyle
     64  * @attr ref R.styleable#KeyboardView_keyPreviewLayout
     65  * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
     66  * @attr ref R.styleable#KeyboardView_keyPreviewOffset
     67  * @attr ref R.styleable#KeyboardView_keyPreviewHeight
     68  * @attr ref R.styleable#KeyboardView_keyTextColor
     69  * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
     70  * @attr ref R.styleable#KeyboardView_keyHintLetterColor
     71  * @attr ref R.styleable#KeyboardView_keyHintLabelColor
     72  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterInactivatedColor
     73  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterActivatedColor
     74  * @attr ref R.styleable#KeyboardView_shadowColor
     75  * @attr ref R.styleable#KeyboardView_shadowRadius
     76  */
     77 public class KeyboardView extends View implements PointerTracker.DrawingProxy {
     78     // Miscellaneous constants
     79     private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
     80 
     81     // XML attributes
     82     protected final float mVerticalCorrection;
     83     protected final int mMoreKeysLayout;
     84     private final float mBackgroundDimAmount;
     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     private final KeyDrawParams mKeyDrawParams;
    100 
    101     // Key preview
    102     private final int mKeyPreviewLayoutId;
    103     protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
    104     private boolean mShowKeyPreviewPopup = true;
    105     private final int mDelayBeforePreview;
    106     private int mDelayAfterPreview;
    107     private ViewGroup mPreviewPlacer;
    108 
    109     // Drawing
    110     /** True if the entire keyboard needs to be dimmed. */
    111     private boolean mNeedsToDimBackground;
    112     /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
    113     private boolean mBufferNeedsUpdate;
    114     /** The dirty region in the keyboard bitmap */
    115     private final Rect mDirtyRect = new Rect();
    116     /** The key to invalidate. */
    117     private Key mInvalidatedKey;
    118     /** The dirty region for single key drawing */
    119     private final Rect mInvalidatedKeyRect = new Rect();
    120     /** The keyboard bitmap buffer for faster updates */
    121     private Bitmap mBuffer;
    122     /** The canvas for the above mutable keyboard bitmap */
    123     private Canvas mCanvas;
    124     private final Paint mPaint = new Paint();
    125     // This map caches key label text height in pixel as value and key label text size as map key.
    126     private static final HashMap<Integer, Float> sTextHeightCache =
    127             new HashMap<Integer, Float>();
    128     // This map caches key label text width in pixel as value and key label text size as map key.
    129     private static final HashMap<Integer, Float> sTextWidthCache =
    130             new HashMap<Integer, Float>();
    131     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
    132     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
    133 
    134     private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
    135 
    136     public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
    137         private static final int MSG_SHOW_KEY_PREVIEW = 1;
    138         private static final int MSG_DISMISS_KEY_PREVIEW = 2;
    139 
    140         public DrawingHandler(KeyboardView outerInstance) {
    141             super(outerInstance);
    142         }
    143 
    144         @Override
    145         public void handleMessage(Message msg) {
    146             final KeyboardView keyboardView = getOuterInstance();
    147             if (keyboardView == null) return;
    148             final PointerTracker tracker = (PointerTracker) msg.obj;
    149             switch (msg.what) {
    150             case MSG_SHOW_KEY_PREVIEW:
    151                 keyboardView.showKey(msg.arg1, tracker);
    152                 break;
    153             case MSG_DISMISS_KEY_PREVIEW:
    154                 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
    155                 break;
    156             }
    157         }
    158 
    159         public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
    160             removeMessages(MSG_SHOW_KEY_PREVIEW);
    161             final KeyboardView keyboardView = getOuterInstance();
    162             if (keyboardView == null) return;
    163             if (tracker.getKeyPreviewText().getVisibility() == VISIBLE || delay == 0) {
    164                 // Show right away, if it's already visible and finger is moving around
    165                 keyboardView.showKey(keyIndex, tracker);
    166             } else {
    167                 sendMessageDelayed(
    168                         obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
    169             }
    170         }
    171 
    172         public void cancelShowKeyPreview(PointerTracker tracker) {
    173             removeMessages(MSG_SHOW_KEY_PREVIEW, tracker);
    174         }
    175 
    176         public void cancelAllShowKeyPreviews() {
    177             removeMessages(MSG_SHOW_KEY_PREVIEW);
    178         }
    179 
    180         public void dismissKeyPreview(long delay, PointerTracker tracker) {
    181             sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
    182         }
    183 
    184         public void cancelDismissKeyPreview(PointerTracker tracker) {
    185             removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
    186         }
    187 
    188         public void cancelAllDismissKeyPreviews() {
    189             removeMessages(MSG_DISMISS_KEY_PREVIEW);
    190         }
    191 
    192         public void cancelAllMessages() {
    193             cancelAllShowKeyPreviews();
    194             cancelAllDismissKeyPreviews();
    195         }
    196     }
    197 
    198     private static class KeyDrawParams {
    199         // XML attributes
    200         public final int mKeyTextColor;
    201         public final int mKeyTextInactivatedColor;
    202         public final Typeface mKeyTextStyle;
    203         public final float mKeyLabelHorizontalPadding;
    204         public final float mKeyHintLetterPadding;
    205         public final float mKeyPopupHintLetterPadding;
    206         public final float mKeyUppercaseLetterPadding;
    207         public final int mShadowColor;
    208         public final float mShadowRadius;
    209         public final Drawable mKeyBackground;
    210         public final int mKeyHintLetterColor;
    211         public final int mKeyHintLabelColor;
    212         public final int mKeyUppercaseLetterInactivatedColor;
    213         public final int mKeyUppercaseLetterActivatedColor;
    214 
    215         private final float mKeyLetterRatio;
    216         private final float mKeyLargeLetterRatio;
    217         private final float mKeyLabelRatio;
    218         private final float mKeyHintLetterRatio;
    219         private final float mKeyUppercaseLetterRatio;
    220         private final float mKeyHintLabelRatio;
    221         private static final float UNDEFINED_RATIO = -1.0f;
    222 
    223         public final Rect mPadding = new Rect();
    224         public int mKeyLetterSize;
    225         public int mKeyLargeLetterSize;
    226         public int mKeyLabelSize;
    227         public int mKeyHintLetterSize;
    228         public int mKeyUppercaseLetterSize;
    229         public int mKeyHintLabelSize;
    230 
    231         public KeyDrawParams(TypedArray a) {
    232             mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
    233             if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
    234                 mKeyLetterRatio = UNDEFINED_RATIO;
    235                 mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
    236             } else {
    237                 mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
    238             }
    239             if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
    240                 mKeyLabelRatio = UNDEFINED_RATIO;
    241                 mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
    242             } else {
    243                 mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
    244             }
    245             mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
    246             mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
    247             mKeyUppercaseLetterRatio = getRatio(a,
    248                     R.styleable.KeyboardView_keyUppercaseLetterRatio);
    249             mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
    250             mKeyLabelHorizontalPadding = a.getDimension(
    251                     R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
    252             mKeyHintLetterPadding = a.getDimension(
    253                     R.styleable.KeyboardView_keyHintLetterPadding, 0);
    254             mKeyPopupHintLetterPadding = a.getDimension(
    255                     R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
    256             mKeyUppercaseLetterPadding = a.getDimension(
    257                     R.styleable.KeyboardView_keyUppercaseLetterPadding, 0);
    258             mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
    259             mKeyTextInactivatedColor = a.getColor(
    260                     R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
    261             mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
    262             mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
    263             mKeyUppercaseLetterInactivatedColor = a.getColor(
    264                     R.styleable.KeyboardView_keyUppercaseLetterInactivatedColor, 0);
    265             mKeyUppercaseLetterActivatedColor = a.getColor(
    266                     R.styleable.KeyboardView_keyUppercaseLetterActivatedColor, 0);
    267             mKeyTextStyle = Typeface.defaultFromStyle(
    268                     a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
    269             mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
    270             mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
    271 
    272             mKeyBackground.getPadding(mPadding);
    273         }
    274 
    275         public void updateKeyHeight(int keyHeight) {
    276             if (mKeyLetterRatio >= 0.0f)
    277                 mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
    278             if (mKeyLabelRatio >= 0.0f)
    279                 mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
    280             mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
    281             mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
    282             mKeyUppercaseLetterSize = (int)(keyHeight * mKeyUppercaseLetterRatio);
    283             mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
    284         }
    285     }
    286 
    287     protected static class KeyPreviewDrawParams {
    288         // XML attributes.
    289         public final Drawable mPreviewBackground;
    290         public final Drawable mPreviewLeftBackground;
    291         public final Drawable mPreviewRightBackground;
    292         public final int mPreviewBackgroundWidth;
    293         public final int mPreviewBackgroundHeight;
    294         public final int mPreviewTextColor;
    295         public final int mPreviewOffset;
    296         public final int mPreviewHeight;
    297         public final Typeface mKeyTextStyle;
    298 
    299         private final float mPreviewTextRatio;
    300         private final float mKeyLetterRatio;
    301 
    302         public int mPreviewTextSize;
    303         public int mKeyLetterSize;
    304         public final int[] mCoordinates = new int[2];
    305 
    306         private static final int PREVIEW_ALPHA = 240;
    307 
    308         public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
    309             mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
    310             mPreviewLeftBackground = a.getDrawable(
    311                     R.styleable.KeyboardView_keyPreviewLeftBackground);
    312             mPreviewRightBackground = a.getDrawable(
    313                     R.styleable.KeyboardView_keyPreviewRightBackground);
    314             setAlpha(mPreviewBackground, PREVIEW_ALPHA);
    315             setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
    316             setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
    317             mPreviewBackgroundWidth = a.getDimensionPixelSize(
    318                     R.styleable.KeyboardView_keyPreviewBackgroundWidth, 0);
    319             mPreviewBackgroundHeight = a.getDimensionPixelSize(
    320                     R.styleable.KeyboardView_keyPreviewBackgroundHeight, 0);
    321             mPreviewOffset = a.getDimensionPixelOffset(
    322                     R.styleable.KeyboardView_keyPreviewOffset, 0);
    323             mPreviewHeight = a.getDimensionPixelSize(
    324                     R.styleable.KeyboardView_keyPreviewHeight, 80);
    325             mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
    326             mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
    327 
    328             mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
    329             mKeyTextStyle = keyDrawParams.mKeyTextStyle;
    330         }
    331 
    332         public void updateKeyHeight(int keyHeight) {
    333             mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
    334             mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
    335         }
    336 
    337         private static void setAlpha(Drawable drawable, int alpha) {
    338             if (drawable == null)
    339                 return;
    340             drawable.setAlpha(alpha);
    341         }
    342     }
    343 
    344     public KeyboardView(Context context, AttributeSet attrs) {
    345         this(context, attrs, R.attr.keyboardViewStyle);
    346     }
    347 
    348     public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
    349         super(context, attrs, defStyle);
    350 
    351         final TypedArray a = context.obtainStyledAttributes(
    352                 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
    353 
    354         mKeyDrawParams = new KeyDrawParams(a);
    355         mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
    356         mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
    357         if (mKeyPreviewLayoutId == 0) {
    358             mShowKeyPreviewPopup = false;
    359         }
    360         mVerticalCorrection = a.getDimensionPixelOffset(
    361                 R.styleable.KeyboardView_verticalCorrection, 0);
    362         mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
    363         mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f);
    364         a.recycle();
    365 
    366         final Resources res = getResources();
    367 
    368         mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
    369         mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
    370 
    371         mPaint.setAntiAlias(true);
    372         mPaint.setTextAlign(Align.CENTER);
    373         mPaint.setAlpha(255);
    374     }
    375 
    376     // Read fraction value in TypedArray as float.
    377     private static float getRatio(TypedArray a, int index) {
    378         return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
    379     }
    380 
    381     /**
    382      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
    383      * view will re-layout itself to accommodate the keyboard.
    384      * @see Keyboard
    385      * @see #getKeyboard()
    386      * @param keyboard the keyboard to display in this view
    387      */
    388     public void setKeyboard(Keyboard keyboard) {
    389         // Remove any pending dismissing preview
    390         mDrawingHandler.cancelAllShowKeyPreviews();
    391         if (mKeyboard != null) {
    392             PointerTracker.dismissAllKeyPreviews();
    393         }
    394         mKeyboard = keyboard;
    395         LatinImeLogger.onSetKeyboard(keyboard);
    396         requestLayout();
    397         mDirtyRect.set(0, 0, getWidth(), getHeight());
    398         mBufferNeedsUpdate = true;
    399         invalidateAllKeys();
    400         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
    401         mKeyDrawParams.updateKeyHeight(keyHeight);
    402         mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
    403     }
    404 
    405     /**
    406      * Returns the current keyboard being displayed by this view.
    407      * @return the currently attached keyboard
    408      * @see #setKeyboard(Keyboard)
    409      */
    410     public Keyboard getKeyboard() {
    411         return mKeyboard;
    412     }
    413 
    414     /**
    415      * Enables or disables the key feedback popup. This is a popup that shows a magnified
    416      * version of the depressed key. By default the preview is enabled.
    417      * @param previewEnabled whether or not to enable the key feedback preview
    418      * @param delay the delay after which the preview is dismissed
    419      * @see #isKeyPreviewPopupEnabled()
    420      */
    421     public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
    422         mShowKeyPreviewPopup = previewEnabled;
    423         mDelayAfterPreview = delay;
    424     }
    425 
    426     /**
    427      * Returns the enabled state of the key feedback preview
    428      * @return whether or not the key feedback preview is enabled
    429      * @see #setKeyPreviewPopupEnabled(boolean, int)
    430      */
    431     public boolean isKeyPreviewPopupEnabled() {
    432         return mShowKeyPreviewPopup;
    433     }
    434 
    435     @Override
    436     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    437         if (mKeyboard != null) {
    438             // The main keyboard expands to the display width.
    439             final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
    440             setMeasuredDimension(widthMeasureSpec, height);
    441         } else {
    442             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    443         }
    444     }
    445 
    446     @Override
    447     public void onDraw(Canvas canvas) {
    448         super.onDraw(canvas);
    449         if (mBufferNeedsUpdate || mBuffer == null) {
    450             mBufferNeedsUpdate = false;
    451             onBufferDraw();
    452         }
    453         canvas.drawBitmap(mBuffer, 0, 0, null);
    454     }
    455 
    456     private void onBufferDraw() {
    457         final int width = getWidth();
    458         final int height = getHeight();
    459         if (width == 0 || height == 0)
    460             return;
    461         if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) {
    462             if (mBuffer != null)
    463                 mBuffer.recycle();
    464             mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    465             mDirtyRect.union(0, 0, width, height);
    466             if (mCanvas != null) {
    467                 mCanvas.setBitmap(mBuffer);
    468             } else {
    469                 mCanvas = new Canvas(mBuffer);
    470             }
    471         }
    472         final Canvas canvas = mCanvas;
    473         canvas.clipRect(mDirtyRect, Op.REPLACE);
    474         canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
    475 
    476         if (mKeyboard == null) return;
    477 
    478         final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
    479         final KeyDrawParams params = mKeyDrawParams;
    480         if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
    481             // Draw a single key.
    482             final int keyDrawX = mInvalidatedKey.mX + mInvalidatedKey.mVisualInsetsLeft
    483                     + getPaddingLeft();
    484             final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
    485             canvas.translate(keyDrawX, keyDrawY);
    486             onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params,
    487                     isManualTemporaryUpperCase);
    488             canvas.translate(-keyDrawX, -keyDrawY);
    489         } else {
    490             // Draw all keys.
    491             for (final Key key : mKeyboard.mKeys) {
    492                 final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
    493                 final int keyDrawY = key.mY + getPaddingTop();
    494                 canvas.translate(keyDrawX, keyDrawY);
    495                 onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase);
    496                 canvas.translate(-keyDrawX, -keyDrawY);
    497             }
    498         }
    499 
    500         // Overlay a dark rectangle to dim the entire keyboard
    501         if (mNeedsToDimBackground) {
    502             mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
    503             canvas.drawRect(0, 0, width, height, mPaint);
    504         }
    505 
    506         mInvalidatedKey = null;
    507         mDirtyRect.setEmpty();
    508     }
    509 
    510     public void dimEntireKeyboard(boolean dimmed) {
    511         final boolean needsRedrawing = mNeedsToDimBackground != dimmed;
    512         mNeedsToDimBackground = dimmed;
    513         if (needsRedrawing) {
    514             invalidateAllKeys();
    515         }
    516     }
    517 
    518     private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas,
    519             Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) {
    520         final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG;
    521         // Draw key background.
    522         if (!key.isSpacer()) {
    523             final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
    524                     + params.mPadding.left + params.mPadding.right;
    525             final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
    526             final int bgX = -params.mPadding.left;
    527             final int bgY = -params.mPadding.top;
    528             final int[] drawableState = key.getCurrentDrawableState();
    529             final Drawable background = params.mKeyBackground;
    530             background.setState(drawableState);
    531             final Rect bounds = background.getBounds();
    532             if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
    533                 background.setBounds(0, 0, bgWidth, bgHeight);
    534             }
    535             canvas.translate(bgX, bgY);
    536             background.draw(canvas);
    537             if (debugShowAlign) {
    538                 drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
    539             }
    540             canvas.translate(-bgX, -bgY);
    541         }
    542 
    543         // Draw key top visuals.
    544         final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
    545         final int keyHeight = key.mHeight;
    546         final float centerX = keyWidth * 0.5f;
    547         final float centerY = keyHeight * 0.5f;
    548 
    549         if (debugShowAlign) {
    550             drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
    551         }
    552 
    553         // Draw key label.
    554         final Drawable icon = key.getIcon();
    555         float positionX = centerX;
    556         if (key.mLabel != null) {
    557             // Switch the character to uppercase if shift is pressed
    558             final CharSequence label = keyboard.adjustLabelCase(key.mLabel);
    559             // For characters, use large font. For labels like "Done", use smaller font.
    560             paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
    561             final int labelSize = key.selectTextSize(params.mKeyLetterSize,
    562                     params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyHintLabelSize);
    563             paint.setTextSize(labelSize);
    564             final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
    565             final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
    566 
    567             // Vertical label text alignment.
    568             final float baseline = centerY + labelCharHeight / 2;
    569 
    570             // Horizontal label text alignment
    571             float labelWidth = 0;
    572             if (key.isAlignLeft()) {
    573                 positionX = (int)params.mKeyLabelHorizontalPadding;
    574                 paint.setTextAlign(Align.LEFT);
    575             } else if (key.isAlignRight()) {
    576                 positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
    577                 paint.setTextAlign(Align.RIGHT);
    578             } else if (key.isAlignLeftOfCenter()) {
    579                 // TODO: Parameterise this?
    580                 positionX = centerX - labelCharWidth * 7 / 4;
    581                 paint.setTextAlign(Align.LEFT);
    582             } else if (key.hasLabelWithIconLeft() && icon != null) {
    583                 labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
    584                         + LABEL_ICON_MARGIN * keyWidth;
    585                 positionX = centerX + labelWidth / 2;
    586                 paint.setTextAlign(Align.RIGHT);
    587             } else if (key.hasLabelWithIconRight() && icon != null) {
    588                 labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
    589                         + LABEL_ICON_MARGIN * keyWidth;
    590                 positionX = centerX - labelWidth / 2;
    591                 paint.setTextAlign(Align.LEFT);
    592             } else {
    593                 positionX = centerX;
    594                 paint.setTextAlign(Align.CENTER);
    595             }
    596             if (key.needsXScale()) {
    597                 paint.setTextScaleX(
    598                         Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
    599             }
    600 
    601             if (key.hasUppercaseLetter() && isManualTemporaryUpperCase) {
    602                 paint.setColor(params.mKeyTextInactivatedColor);
    603             } else {
    604                 paint.setColor(params.mKeyTextColor);
    605             }
    606             if (key.isEnabled()) {
    607                 // Set a drop shadow for the text
    608                 paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
    609             } else {
    610                 // Make label invisible
    611                 paint.setColor(Color.TRANSPARENT);
    612             }
    613             canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
    614             // Turn off drop shadow and reset x-scale.
    615             paint.setShadowLayer(0, 0, 0, 0);
    616             paint.setTextScaleX(1.0f);
    617 
    618             if (icon != null) {
    619                 final int iconWidth = icon.getIntrinsicWidth();
    620                 final int iconHeight = icon.getIntrinsicHeight();
    621                 final int iconY = (keyHeight - iconHeight) / 2;
    622                 if (key.hasLabelWithIconLeft()) {
    623                     final int iconX = (int)(centerX - labelWidth / 2);
    624                     drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
    625                 } else if (key.hasLabelWithIconRight()) {
    626                     final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
    627                     drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
    628                 }
    629             }
    630 
    631             if (debugShowAlign) {
    632                 final Paint line = new Paint();
    633                 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
    634                 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
    635             }
    636         }
    637 
    638         // Draw hint label.
    639         if (key.mHintLabel != null) {
    640             final CharSequence hint = key.mHintLabel;
    641             final int hintColor;
    642             final int hintSize;
    643             if (key.hasHintLabel()) {
    644                 hintColor = params.mKeyHintLabelColor;
    645                 hintSize = params.mKeyHintLabelSize;
    646                 paint.setTypeface(Typeface.DEFAULT);
    647             } else if (key.hasUppercaseLetter()) {
    648                 hintColor = isManualTemporaryUpperCase
    649                         ? params.mKeyUppercaseLetterActivatedColor
    650                         : params.mKeyUppercaseLetterInactivatedColor;
    651                 hintSize = params.mKeyUppercaseLetterSize;
    652             } else { // key.hasHintLetter()
    653                 hintColor = params.mKeyHintLetterColor;
    654                 hintSize = params.mKeyHintLetterSize;
    655             }
    656             paint.setColor(hintColor);
    657             paint.setTextSize(hintSize);
    658             final float hintX, hintY;
    659             if (key.hasHintLabel()) {
    660                 // The hint label is placed just right of the key label. Used mainly on
    661                 // "phone number" layout.
    662                 // TODO: Generalize the following calculations.
    663                 hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
    664                 hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
    665                 paint.setTextAlign(Align.LEFT);
    666             } else if (key.hasUppercaseLetter()) {
    667                 // The hint label is placed at top-right corner of the key. Used mainly on tablet.
    668                 hintX = keyWidth - params.mKeyUppercaseLetterPadding
    669                         - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
    670                 hintY = -paint.ascent();
    671                 paint.setTextAlign(Align.CENTER);
    672             } else { // key.hasHintLetter()
    673                 // The hint label is placed at top-right corner of the key. Used mainly on phone.
    674                 hintX = keyWidth - params.mKeyHintLetterPadding
    675                         - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
    676                 hintY = -paint.ascent();
    677                 paint.setTextAlign(Align.CENTER);
    678             }
    679             canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
    680 
    681             if (debugShowAlign) {
    682                 final Paint line = new Paint();
    683                 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
    684                 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
    685             }
    686         }
    687 
    688         // Draw key icon.
    689         if (key.mLabel == null && icon != null) {
    690             final int iconWidth = icon.getIntrinsicWidth();
    691             final int iconHeight = icon.getIntrinsicHeight();
    692             final int iconX, alignX;
    693             final int iconY = (keyHeight - iconHeight) / 2;
    694             if (key.isAlignLeft()) {
    695                 iconX = (int)params.mKeyLabelHorizontalPadding;
    696                 alignX = iconX;
    697             } else if (key.isAlignRight()) {
    698                 iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
    699                 alignX = iconX + iconWidth;
    700             } else { // Align center
    701                 iconX = (keyWidth - iconWidth) / 2;
    702                 alignX = iconX + iconWidth / 2;
    703             }
    704             drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
    705 
    706             if (debugShowAlign) {
    707                 final Paint line = new Paint();
    708                 drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line);
    709                 drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line);
    710             }
    711         }
    712 
    713         // Draw popup hint "..." at the bottom right corner of the key.
    714         if ((key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0)
    715                 || key.needsSpecialPopupHint()) {
    716             paint.setTextSize(params.mKeyHintLetterSize);
    717             paint.setColor(params.mKeyHintLabelColor);
    718             paint.setTextAlign(Align.CENTER);
    719             final float hintX = keyWidth - params.mKeyHintLetterPadding
    720                     - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
    721             final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
    722             canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
    723 
    724             if (debugShowAlign) {
    725                 final Paint line = new Paint();
    726                 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
    727                 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
    728             }
    729         }
    730     }
    731 
    732     private static final Rect sTextBounds = new Rect();
    733 
    734     private static int getCharGeometryCacheKey(char reference, Paint paint) {
    735         final int labelSize = (int)paint.getTextSize();
    736         final Typeface face = paint.getTypeface();
    737         final int codePointOffset = reference << 15;
    738         if (face == Typeface.DEFAULT) {
    739             return codePointOffset + labelSize;
    740         } else if (face == Typeface.DEFAULT_BOLD) {
    741             return codePointOffset + labelSize + 0x1000;
    742         } else if (face == Typeface.MONOSPACE) {
    743             return codePointOffset + labelSize + 0x2000;
    744         } else {
    745             return codePointOffset + labelSize;
    746         }
    747     }
    748 
    749     private static float getCharHeight(char[] character, Paint paint) {
    750         final Integer key = getCharGeometryCacheKey(character[0], paint);
    751         final Float cachedValue = sTextHeightCache.get(key);
    752         if (cachedValue != null)
    753             return cachedValue;
    754 
    755         paint.getTextBounds(character, 0, 1, sTextBounds);
    756         final float height = sTextBounds.height();
    757         sTextHeightCache.put(key, height);
    758         return height;
    759     }
    760 
    761     private static float getCharWidth(char[] character, Paint paint) {
    762         final Integer key = getCharGeometryCacheKey(character[0], paint);
    763         final Float cachedValue = sTextWidthCache.get(key);
    764         if (cachedValue != null)
    765             return cachedValue;
    766 
    767         paint.getTextBounds(character, 0, 1, sTextBounds);
    768         final float width = sTextBounds.width();
    769         sTextWidthCache.put(key, width);
    770         return width;
    771     }
    772 
    773     private static float getLabelWidth(CharSequence label, Paint paint) {
    774         paint.getTextBounds(label.toString(), 0, label.length(), sTextBounds);
    775         return sTextBounds.width();
    776     }
    777 
    778     public float getDefaultLabelWidth(CharSequence label, Paint paint) {
    779         paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
    780         paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
    781         return getLabelWidth(label, paint);
    782     }
    783 
    784     private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
    785             int height) {
    786         canvas.translate(x, y);
    787         icon.setBounds(0, 0, width, height);
    788         icon.draw(canvas);
    789         canvas.translate(-x, -y);
    790     }
    791 
    792     private static void drawHorizontalLine(Canvas canvas, float y, float w, int color,
    793             Paint paint) {
    794         paint.setStyle(Paint.Style.STROKE);
    795         paint.setStrokeWidth(1.0f);
    796         paint.setColor(color);
    797         canvas.drawLine(0, y, w, y, paint);
    798     }
    799 
    800     private static void drawVerticalLine(Canvas canvas, float x, float h, int color, Paint paint) {
    801         paint.setStyle(Paint.Style.STROKE);
    802         paint.setStrokeWidth(1.0f);
    803         paint.setColor(color);
    804         canvas.drawLine(x, 0, x, h, paint);
    805     }
    806 
    807     private static void drawRectangle(Canvas canvas, float x, float y, float w, float h, int color,
    808             Paint paint) {
    809         paint.setStyle(Paint.Style.STROKE);
    810         paint.setStrokeWidth(1.0f);
    811         paint.setColor(color);
    812         canvas.translate(x, y);
    813         canvas.drawRect(0, 0, w, h, paint);
    814         canvas.translate(-x, -y);
    815     }
    816 
    817     public void cancelAllMessages() {
    818         mDrawingHandler.cancelAllMessages();
    819     }
    820 
    821     // Called by {@link PointerTracker} constructor to create a TextView.
    822     @Override
    823     public TextView inflateKeyPreviewText() {
    824         final Context context = getContext();
    825         if (mKeyPreviewLayoutId != 0) {
    826             return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
    827         } else {
    828             return new TextView(context);
    829         }
    830     }
    831 
    832     @Override
    833     public void showKeyPreview(int keyIndex, PointerTracker tracker) {
    834         if (mShowKeyPreviewPopup) {
    835             mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
    836         }
    837     }
    838 
    839     @Override
    840     public void cancelShowKeyPreview(PointerTracker tracker) {
    841         mDrawingHandler.cancelShowKeyPreview(tracker);
    842     }
    843 
    844     @Override
    845     public void dismissKeyPreview(PointerTracker tracker) {
    846         mDrawingHandler.cancelShowKeyPreview(tracker);
    847         mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
    848     }
    849 
    850     private void addKeyPreview(TextView keyPreview) {
    851         if (mPreviewPlacer == null) {
    852             mPreviewPlacer = new RelativeLayout(getContext());
    853             final ViewGroup windowContentView =
    854                     (ViewGroup)getRootView().findViewById(android.R.id.content);
    855             windowContentView.addView(mPreviewPlacer);
    856         }
    857         mPreviewPlacer.addView(
    858                 keyPreview, FrameLayoutCompatUtils.newLayoutParam(mPreviewPlacer, 0, 0));
    859     }
    860 
    861     private void showKey(final int keyIndex, PointerTracker tracker) {
    862         final TextView previewText = tracker.getKeyPreviewText();
    863         // If the key preview has no parent view yet, add it to the ViewGroup which can place
    864         // key preview absolutely in SoftInputWindow.
    865         if (previewText.getParent() == null) {
    866             addKeyPreview(previewText);
    867         }
    868 
    869         mDrawingHandler.cancelDismissKeyPreview(tracker);
    870         final Key key = tracker.getKey(keyIndex);
    871         // If keyIndex is invalid or IME is already closed, we must not show key preview.
    872         // Trying to show key preview while root window is closed causes
    873         // WindowManager.BadTokenException.
    874         if (key == null)
    875             return;
    876 
    877         final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
    878         final int keyDrawX = key.mX + key.mVisualInsetsLeft;
    879         final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
    880         // What we show as preview should match what we show on key top in onBufferDraw().
    881         if (key.mLabel != null) {
    882             // TODO Should take care of temporaryShiftLabel here.
    883             previewText.setCompoundDrawables(null, null, null, null);
    884             if (key.mLabel.length() > 1) {
    885                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
    886                 previewText.setTypeface(Typeface.DEFAULT_BOLD);
    887             } else {
    888                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
    889                 previewText.setTypeface(params.mKeyTextStyle);
    890             }
    891             previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
    892         } else {
    893             final Drawable previewIcon = key.getPreviewIcon();
    894             previewText.setCompoundDrawables(null, null, null,
    895                    previewIcon != null ? previewIcon : key.getIcon());
    896             previewText.setText(null);
    897         }
    898         previewText.setBackgroundDrawable(params.mPreviewBackground);
    899 
    900         previewText.measure(
    901                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    902         final int previewWidth = Math.max(previewText.getMeasuredWidth(), keyDrawWidth
    903                 + previewText.getPaddingLeft() + previewText.getPaddingRight());
    904         final int previewHeight = params.mPreviewHeight;
    905         getLocationInWindow(params.mCoordinates);
    906         int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0];
    907         final int previewY = key.mY - previewHeight
    908                 + params.mCoordinates[1] + params.mPreviewOffset;
    909         if (previewX < 0 && params.mPreviewLeftBackground != null) {
    910             previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
    911             previewX = 0;
    912         } else if (previewX + previewWidth > getWidth() && params.mPreviewRightBackground != null) {
    913             previewText.setBackgroundDrawable(params.mPreviewRightBackground);
    914             previewX = getWidth() - previewWidth;
    915         }
    916 
    917         // Set the preview background state
    918         previewText.getBackground().setState(
    919                 key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
    920         previewText.setTextColor(params.mPreviewTextColor);
    921         FrameLayoutCompatUtils.placeViewAt(
    922                 previewText, previewX, previewY, previewWidth, previewHeight);
    923         previewText.setVisibility(VISIBLE);
    924     }
    925 
    926     /**
    927      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
    928      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
    929      * draws the cached buffer.
    930      * @see #invalidateKey(Key)
    931      */
    932     public void invalidateAllKeys() {
    933         mDirtyRect.union(0, 0, getWidth(), getHeight());
    934         mBufferNeedsUpdate = true;
    935         invalidate();
    936     }
    937 
    938     /**
    939      * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
    940      * one key is changing it's content. Any changes that affect the position or size of the key
    941      * may not be honored.
    942      * @param key key in the attached {@link Keyboard}.
    943      * @see #invalidateAllKeys
    944      */
    945     @Override
    946     public void invalidateKey(Key key) {
    947         if (key == null)
    948             return;
    949         mInvalidatedKey = key;
    950         final int x = key.mX + getPaddingLeft();
    951         final int y = key.mY + getPaddingTop();
    952         mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
    953         mDirtyRect.union(mInvalidatedKeyRect);
    954         mBufferNeedsUpdate = true;
    955         invalidate(mInvalidatedKeyRect);
    956     }
    957 
    958     public void closing() {
    959         PointerTracker.dismissAllKeyPreviews();
    960         cancelAllMessages();
    961 
    962         mDirtyRect.union(0, 0, getWidth(), getHeight());
    963         requestLayout();
    964     }
    965 
    966     @Override
    967     public boolean dismissMoreKeysPanel() {
    968         return false;
    969     }
    970 
    971     public void purgeKeyboardAndClosing() {
    972         mKeyboard = null;
    973         closing();
    974     }
    975 
    976     @Override
    977     protected void onDetachedFromWindow() {
    978         super.onDetachedFromWindow();
    979         closing();
    980         if (mPreviewPlacer != null) {
    981             mPreviewPlacer.removeAllViews();
    982         }
    983         if (mBuffer != null) {
    984             mBuffer.recycle();
    985             mBuffer = null;
    986         }
    987     }
    988 }
    989