KeyboardView.java revision 6c63f712655efe7e2d72cb2a0af9a0d8fdf0c730
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
17package com.android.inputmethod.keyboard;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Paint;
25import android.graphics.Paint.Align;
26import android.graphics.PorterDuff;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.graphics.Typeface;
30import android.graphics.drawable.Drawable;
31import android.os.Message;
32import android.util.AttributeSet;
33import android.util.SparseArray;
34import android.util.TypedValue;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.TextView;
39
40import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
41import com.android.inputmethod.latin.Constants;
42import com.android.inputmethod.latin.LatinImeLogger;
43import com.android.inputmethod.latin.R;
44import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
45import com.android.inputmethod.latin.StringUtils;
46import com.android.inputmethod.latin.define.ProductionFlag;
47import com.android.inputmethod.research.ResearchLogger;
48
49import java.util.HashSet;
50
51/**
52 * A view that renders a virtual {@link Keyboard}.
53 *
54 * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
55 * @attr ref R.styleable#KeyboardView_keyBackground
56 * @attr ref R.styleable#KeyboardView_keyLetterRatio
57 * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
58 * @attr ref R.styleable#KeyboardView_keyLabelRatio
59 * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
60 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio
61 * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
62 * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
63 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
64 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
65 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
66 * @attr ref R.styleable#KeyboardView_keyTextStyle
67 * @attr ref R.styleable#KeyboardView_keyPreviewLayout
68 * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
69 * @attr ref R.styleable#KeyboardView_keyPreviewOffset
70 * @attr ref R.styleable#KeyboardView_keyPreviewHeight
71 * @attr ref R.styleable#KeyboardView_keyTextColor
72 * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
73 * @attr ref R.styleable#KeyboardView_keyHintLetterColor
74 * @attr ref R.styleable#KeyboardView_keyHintLabelColor
75 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor
76 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor
77 * @attr ref R.styleable#KeyboardView_shadowColor
78 * @attr ref R.styleable#KeyboardView_shadowRadius
79 */
80public class KeyboardView extends View implements PointerTracker.DrawingProxy {
81    // Miscellaneous constants
82    private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
83
84    // XML attributes
85    protected final float mVerticalCorrection;
86    protected final int mMoreKeysLayout;
87    private final int mBackgroundDimAlpha;
88
89    // HORIZONTAL ELLIPSIS "...", character for popup hint.
90    private static final String POPUP_HINT_CHAR = "\u2026";
91
92    // Margin between the label and the icon on a key that has both of them.
93    // Specified by the fraction of the key width.
94    // TODO: Use resource parameter for this value.
95    private static final float LABEL_ICON_MARGIN = 0.05f;
96
97    // The maximum key label width in the proportion to the key width.
98    private static final float MAX_LABEL_RATIO = 0.90f;
99
100    // Main keyboard
101    private Keyboard mKeyboard;
102    protected final KeyDrawParams mKeyDrawParams;
103
104    // Key preview
105    private final int mKeyPreviewLayoutId;
106    protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
107    private boolean mShowKeyPreviewPopup = true;
108    private int mDelayAfterPreview;
109    private final PreviewPlacerView mPreviewPlacerView;
110
111    // Drawing
112    /** True if the entire keyboard needs to be dimmed. */
113    private boolean mNeedsToDimEntireKeyboard;
114    /** True if all keys should be drawn */
115    private boolean mInvalidateAllKeys;
116    /** The keys that should be drawn */
117    private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
118    /** The working rectangle variable */
119    private final Rect mWorkingRect = new Rect();
120    /** The keyboard bitmap buffer for faster updates */
121    /** The clip region to draw keys */
122    private final Region mClipRegion = new Region();
123    private Bitmap mOffscreenBuffer;
124    /** The canvas for the above mutable keyboard bitmap */
125    private Canvas mOffscreenCanvas;
126    private final Paint mPaint = new Paint();
127    private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
128    // This sparse array caches key label text height in pixel indexed by key label text size.
129    private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
130    // This sparse array caches key label text width in pixel indexed by key label text size.
131    private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
132    private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
133    private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
134
135    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
136
137    public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
138        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
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_DISMISS_KEY_PREVIEW:
151                tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
152                break;
153            }
154        }
155
156        public void dismissKeyPreview(long delay, PointerTracker tracker) {
157            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
158        }
159
160        public void cancelDismissKeyPreview(PointerTracker tracker) {
161            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
162        }
163
164        public void cancelAllDismissKeyPreviews() {
165            removeMessages(MSG_DISMISS_KEY_PREVIEW);
166        }
167
168        public void cancelAllMessages() {
169            cancelAllDismissKeyPreviews();
170        }
171    }
172
173    protected static class KeyDrawParams {
174        // XML attributes
175        public final int mKeyTextColor;
176        public final int mKeyTextInactivatedColor;
177        public final Typeface mKeyTextStyle;
178        public final float mKeyLabelHorizontalPadding;
179        public final float mKeyHintLetterPadding;
180        public final float mKeyPopupHintLetterPadding;
181        public final float mKeyShiftedLetterHintPadding;
182        public final int mShadowColor;
183        public final float mShadowRadius;
184        public final Drawable mKeyBackground;
185        public final int mKeyHintLetterColor;
186        public final int mKeyHintLabelColor;
187        public final int mKeyShiftedLetterHintInactivatedColor;
188        public final int mKeyShiftedLetterHintActivatedColor;
189
190        /* package */ final float mKeyLetterRatio;
191        private final float mKeyLargeLetterRatio;
192        private final float mKeyLabelRatio;
193        private final float mKeyLargeLabelRatio;
194        private final float mKeyHintLetterRatio;
195        private final float mKeyShiftedLetterHintRatio;
196        private final float mKeyHintLabelRatio;
197        private static final float UNDEFINED_RATIO = -1.0f;
198
199        public final Rect mPadding = new Rect();
200        public int mKeyLetterSize;
201        public int mKeyLargeLetterSize;
202        public int mKeyLabelSize;
203        public int mKeyLargeLabelSize;
204        public int mKeyHintLetterSize;
205        public int mKeyShiftedLetterHintSize;
206        public int mKeyHintLabelSize;
207        public int mAnimAlpha;
208
209        public KeyDrawParams(TypedArray a) {
210            mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
211            if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
212                mKeyLetterRatio = UNDEFINED_RATIO;
213                mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
214            } else {
215                mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
216            }
217            if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
218                mKeyLabelRatio = UNDEFINED_RATIO;
219                mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
220            } else {
221                mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
222            }
223            mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
224            mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
225            mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
226            mKeyShiftedLetterHintRatio = getRatio(a,
227                    R.styleable.KeyboardView_keyShiftedLetterHintRatio);
228            mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
229            mKeyLabelHorizontalPadding = a.getDimension(
230                    R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
231            mKeyHintLetterPadding = a.getDimension(
232                    R.styleable.KeyboardView_keyHintLetterPadding, 0);
233            mKeyPopupHintLetterPadding = a.getDimension(
234                    R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
235            mKeyShiftedLetterHintPadding = a.getDimension(
236                    R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
237            mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
238            mKeyTextInactivatedColor = a.getColor(
239                    R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
240            mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
241            mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
242            mKeyShiftedLetterHintInactivatedColor = a.getColor(
243                    R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
244            mKeyShiftedLetterHintActivatedColor = a.getColor(
245                    R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
246            mKeyTextStyle = Typeface.defaultFromStyle(
247                    a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
248            mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
249            mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
250
251            mKeyBackground.getPadding(mPadding);
252        }
253
254        public void updateKeyHeight(int keyHeight) {
255            if (mKeyLetterRatio >= 0.0f) {
256                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
257            }
258            if (mKeyLabelRatio >= 0.0f) {
259                mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
260            }
261            mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
262            mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
263            mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
264            mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
265            mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
266        }
267
268        public void blendAlpha(Paint paint) {
269            final int color = paint.getColor();
270            paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
271                    Color.red(color), Color.green(color), Color.blue(color));
272        }
273    }
274
275    /* package */ static class KeyPreviewDrawParams {
276        // XML attributes.
277        public final Drawable mPreviewBackground;
278        public final Drawable mPreviewLeftBackground;
279        public final Drawable mPreviewRightBackground;
280        public final int mPreviewTextColor;
281        public final int mPreviewOffset;
282        public final int mPreviewHeight;
283        public final Typeface mKeyTextStyle;
284        public final int mLingerTimeout;
285
286        private final float mPreviewTextRatio;
287        private final float mKeyLetterRatio;
288
289        // The graphical geometry of the key preview.
290        // <-width->
291        // +-------+   ^
292        // |       |   |
293        // |preview| height (visible)
294        // |       |   |
295        // +       + ^ v
296        //  \     /  |offset
297        // +-\   /-+ v
298        // |  +-+  |
299        // |parent |
300        // |    key|
301        // +-------+
302        // The background of a {@link TextView} being used for a key preview may have invisible
303        // paddings. To align the more keys keyboard panel's visible part with the visible part of
304        // the background, we need to record the width and height of key preview that don't include
305        // invisible paddings.
306        public int mPreviewVisibleWidth;
307        public int mPreviewVisibleHeight;
308        // The key preview may have an arbitrary offset and its background that may have a bottom
309        // padding. To align the more keys keyboard and the key preview we also need to record the
310        // offset between the top edge of parent key and the bottom of the visible part of key
311        // preview background.
312        public int mPreviewVisibleOffset;
313
314        public int mPreviewTextSize;
315        public int mKeyLetterSize;
316        public final int[] mCoordinates = new int[2];
317
318        private static final int PREVIEW_ALPHA = 240;
319
320        public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
321            mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
322            mPreviewLeftBackground = a.getDrawable(
323                    R.styleable.KeyboardView_keyPreviewLeftBackground);
324            mPreviewRightBackground = a.getDrawable(
325                    R.styleable.KeyboardView_keyPreviewRightBackground);
326            setAlpha(mPreviewBackground, PREVIEW_ALPHA);
327            setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
328            setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
329            mPreviewOffset = a.getDimensionPixelOffset(
330                    R.styleable.KeyboardView_keyPreviewOffset, 0);
331            mPreviewHeight = a.getDimensionPixelSize(
332                    R.styleable.KeyboardView_keyPreviewHeight, 80);
333            mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
334            mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
335            mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
336
337            mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
338            mKeyTextStyle = keyDrawParams.mKeyTextStyle;
339        }
340
341        public void updateKeyHeight(int keyHeight) {
342            if (mPreviewTextRatio >= 0.0f) {
343                mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
344            }
345            if (mKeyLetterRatio >= 0.0f) {
346                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
347            }
348        }
349
350        private static void setAlpha(Drawable drawable, int alpha) {
351            if (drawable == null) return;
352            drawable.setAlpha(alpha);
353        }
354    }
355
356    public KeyboardView(Context context, AttributeSet attrs) {
357        this(context, attrs, R.attr.keyboardViewStyle);
358    }
359
360    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
361        super(context, attrs, defStyle);
362
363        final TypedArray a = context.obtainStyledAttributes(
364                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
365        mKeyDrawParams = new KeyDrawParams(a);
366        mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
367        mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
368        mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
369        if (mKeyPreviewLayoutId == 0) {
370            mShowKeyPreviewPopup = false;
371        }
372        mVerticalCorrection = a.getDimensionPixelOffset(
373                R.styleable.KeyboardView_verticalCorrection, 0);
374        mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
375        mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
376        a.recycle();
377
378        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
379        mPaint.setAntiAlias(true);
380    }
381
382    // Read fraction value in TypedArray as float.
383    /* package */ static float getRatio(TypedArray a, int index) {
384        return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
385    }
386
387    /**
388     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
389     * view will re-layout itself to accommodate the keyboard.
390     * @see Keyboard
391     * @see #getKeyboard()
392     * @param keyboard the keyboard to display in this view
393     */
394    public void setKeyboard(Keyboard keyboard) {
395        mKeyboard = keyboard;
396        LatinImeLogger.onSetKeyboard(keyboard);
397        requestLayout();
398        invalidateAllKeys();
399        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
400        mKeyDrawParams.updateKeyHeight(keyHeight);
401        mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
402    }
403
404    /**
405     * Returns the current keyboard being displayed by this view.
406     * @return the currently attached keyboard
407     * @see #setKeyboard(Keyboard)
408     */
409    public Keyboard getKeyboard() {
410        return mKeyboard;
411    }
412
413    /**
414     * Enables or disables the key feedback popup. This is a popup that shows a magnified
415     * version of the depressed key. By default the preview is enabled.
416     * @param previewEnabled whether or not to enable the key feedback preview
417     * @param delay the delay after which the preview is dismissed
418     * @see #isKeyPreviewPopupEnabled()
419     */
420    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
421        mShowKeyPreviewPopup = previewEnabled;
422        mDelayAfterPreview = delay;
423    }
424
425    /**
426     * Returns the enabled state of the key feedback preview
427     * @return whether or not the key feedback preview is enabled
428     * @see #setKeyPreviewPopupEnabled(boolean, int)
429     */
430    public boolean isKeyPreviewPopupEnabled() {
431        return mShowKeyPreviewPopup;
432    }
433
434    public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
435            boolean drawsGestureFloatingPreviewText) {
436        mPreviewPlacerView.setGesturePreviewMode(
437                drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
438    }
439
440    @Override
441    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
442        if (mKeyboard != null) {
443            // The main keyboard expands to the display width.
444            final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
445            setMeasuredDimension(widthMeasureSpec, height);
446        } else {
447            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
448        }
449    }
450
451    @Override
452    public void onDraw(Canvas canvas) {
453        super.onDraw(canvas);
454        if (canvas.isHardwareAccelerated()) {
455            onDrawKeyboard(canvas);
456            return;
457        }
458
459        final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
460        if (bufferNeedsUpdates || mOffscreenBuffer == null) {
461            if (maybeAllocateOffscreenBuffer()) {
462                mInvalidateAllKeys = true;
463                maybeCreateOffscreenCanvas();
464            }
465            onDrawKeyboard(mOffscreenCanvas);
466        }
467        canvas.drawBitmap(mOffscreenBuffer, 0, 0, null);
468    }
469
470    private boolean maybeAllocateOffscreenBuffer() {
471        final int width = getWidth();
472        final int height = getHeight();
473        if (width == 0 || height == 0) {
474            return false;
475        }
476        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width
477                && mOffscreenBuffer.getHeight() == height) {
478            return false;
479        }
480        freeOffscreenBuffer();
481        mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
482        return true;
483    }
484
485    private void freeOffscreenBuffer() {
486        if (mOffscreenBuffer != null) {
487            mOffscreenBuffer.recycle();
488            mOffscreenBuffer = null;
489        }
490    }
491
492    private void maybeCreateOffscreenCanvas() {
493        // TODO: Stop using the offscreen canvas even when in software rendering
494        if (mOffscreenCanvas != null) {
495            mOffscreenCanvas.setBitmap(mOffscreenBuffer);
496        } else {
497            mOffscreenCanvas = new Canvas(mOffscreenBuffer);
498        }
499    }
500
501    private void onDrawKeyboard(final Canvas canvas) {
502        if (mKeyboard == null) return;
503
504        final int width = getWidth();
505        final int height = getHeight();
506        final Paint paint = mPaint;
507        final KeyDrawParams params = mKeyDrawParams;
508
509        // Calculate clip region and set.
510        final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
511        final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
512        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
513        if (drawAllKeys || isHardwareAccelerated) {
514            mClipRegion.set(0, 0, width, height);
515        } else {
516            mClipRegion.setEmpty();
517            for (final Key key : mInvalidatedKeys) {
518                if (mKeyboard.hasKey(key)) {
519                    final int x = key.mX + getPaddingLeft();
520                    final int y = key.mY + getPaddingTop();
521                    mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
522                    mClipRegion.union(mWorkingRect);
523                }
524            }
525        }
526        if (!isHardwareAccelerated) {
527            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
528            // Draw keyboard background.
529            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
530            final Drawable background = getBackground();
531            if (background != null) {
532                background.draw(canvas);
533            }
534        }
535
536        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
537        if (drawAllKeys || isHardwareAccelerated) {
538            // Draw all keys.
539            for (final Key key : mKeyboard.mKeys) {
540                onDrawKey(key, canvas, paint, params);
541            }
542        } else {
543            // Draw invalidated keys.
544            for (final Key key : mInvalidatedKeys) {
545                if (mKeyboard.hasKey(key)) {
546                    onDrawKey(key, canvas, paint, params);
547                }
548            }
549        }
550
551        // Overlay a dark rectangle to dim.
552        if (mNeedsToDimEntireKeyboard) {
553            paint.setColor(Color.BLACK);
554            paint.setAlpha(mBackgroundDimAlpha);
555            // Note: clipRegion() above is in effect if it was called.
556            canvas.drawRect(0, 0, width, height, paint);
557        }
558
559        // ResearchLogging indicator.
560        // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
561        // and remove this call.
562        if (ProductionFlag.IS_EXPERIMENTAL) {
563            ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height);
564        }
565
566        mInvalidatedKeys.clear();
567        mInvalidateAllKeys = false;
568    }
569
570    public void dimEntireKeyboard(boolean dimmed) {
571        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
572        mNeedsToDimEntireKeyboard = dimmed;
573        if (needsRedrawing) {
574            invalidateAllKeys();
575        }
576    }
577
578    private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
579        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
580        final int keyDrawY = key.mY + getPaddingTop();
581        canvas.translate(keyDrawX, keyDrawY);
582
583        params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
584        if (!key.isSpacer()) {
585            onDrawKeyBackground(key, canvas, params);
586        }
587        onDrawKeyTopVisuals(key, canvas, paint, params);
588
589        canvas.translate(-keyDrawX, -keyDrawY);
590    }
591
592    // Draw key background.
593    protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
594        final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
595                + params.mPadding.left + params.mPadding.right;
596        final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
597        final int bgX = -params.mPadding.left;
598        final int bgY = -params.mPadding.top;
599        final int[] drawableState = key.getCurrentDrawableState();
600        final Drawable background = params.mKeyBackground;
601        background.setState(drawableState);
602        final Rect bounds = background.getBounds();
603        if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
604            background.setBounds(0, 0, bgWidth, bgHeight);
605        }
606        canvas.translate(bgX, bgY);
607        background.draw(canvas);
608        if (LatinImeLogger.sVISUALDEBUG) {
609            drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
610        }
611        canvas.translate(-bgX, -bgY);
612    }
613
614    // Draw key top visuals.
615    protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
616        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
617        final int keyHeight = key.mHeight;
618        final float centerX = keyWidth * 0.5f;
619        final float centerY = keyHeight * 0.5f;
620
621        if (LatinImeLogger.sVISUALDEBUG) {
622            drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
623        }
624
625        // Draw key label.
626        final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
627        float positionX = centerX;
628        if (key.mLabel != null) {
629            final String label = key.mLabel;
630            // For characters, use large font. For labels like "Done", use smaller font.
631            paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
632            final int labelSize = key.selectTextSize(params.mKeyLetterSize,
633                    params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize,
634                    params.mKeyHintLabelSize);
635            paint.setTextSize(labelSize);
636            final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
637            final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
638
639            // Vertical label text alignment.
640            final float baseline = centerY + labelCharHeight / 2;
641
642            // Horizontal label text alignment
643            float labelWidth = 0;
644            if (key.isAlignLeft()) {
645                positionX = (int)params.mKeyLabelHorizontalPadding;
646                paint.setTextAlign(Align.LEFT);
647            } else if (key.isAlignRight()) {
648                positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
649                paint.setTextAlign(Align.RIGHT);
650            } else if (key.isAlignLeftOfCenter()) {
651                // TODO: Parameterise this?
652                positionX = centerX - labelCharWidth * 7 / 4;
653                paint.setTextAlign(Align.LEFT);
654            } else if (key.hasLabelWithIconLeft() && icon != null) {
655                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
656                        + LABEL_ICON_MARGIN * keyWidth;
657                positionX = centerX + labelWidth / 2;
658                paint.setTextAlign(Align.RIGHT);
659            } else if (key.hasLabelWithIconRight() && icon != null) {
660                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
661                        + LABEL_ICON_MARGIN * keyWidth;
662                positionX = centerX - labelWidth / 2;
663                paint.setTextAlign(Align.LEFT);
664            } else {
665                positionX = centerX;
666                paint.setTextAlign(Align.CENTER);
667            }
668            if (key.needsXScale()) {
669                paint.setTextScaleX(
670                        Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
671            }
672
673            paint.setColor(key.isShiftedLetterActivated()
674                    ? params.mKeyTextInactivatedColor : params.mKeyTextColor);
675            if (key.isEnabled()) {
676                // Set a drop shadow for the text
677                paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
678            } else {
679                // Make label invisible
680                paint.setColor(Color.TRANSPARENT);
681            }
682            params.blendAlpha(paint);
683            canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
684            // Turn off drop shadow and reset x-scale.
685            paint.setShadowLayer(0, 0, 0, 0);
686            paint.setTextScaleX(1.0f);
687
688            if (icon != null) {
689                final int iconWidth = icon.getIntrinsicWidth();
690                final int iconHeight = icon.getIntrinsicHeight();
691                final int iconY = (keyHeight - iconHeight) / 2;
692                if (key.hasLabelWithIconLeft()) {
693                    final int iconX = (int)(centerX - labelWidth / 2);
694                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
695                } else if (key.hasLabelWithIconRight()) {
696                    final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
697                    drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
698                }
699            }
700
701            if (LatinImeLogger.sVISUALDEBUG) {
702                final Paint line = new Paint();
703                drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
704                drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
705            }
706        }
707
708        // Draw hint label.
709        if (key.mHintLabel != null) {
710            final String hint = key.mHintLabel;
711            final int hintColor;
712            final int hintSize;
713            if (key.hasHintLabel()) {
714                hintColor = params.mKeyHintLabelColor;
715                hintSize = params.mKeyHintLabelSize;
716                paint.setTypeface(Typeface.DEFAULT);
717            } else if (key.hasShiftedLetterHint()) {
718                hintColor = key.isShiftedLetterActivated()
719                        ? params.mKeyShiftedLetterHintActivatedColor
720                        : params.mKeyShiftedLetterHintInactivatedColor;
721                hintSize = params.mKeyShiftedLetterHintSize;
722            } else { // key.hasHintLetter()
723                hintColor = params.mKeyHintLetterColor;
724                hintSize = params.mKeyHintLetterSize;
725            }
726            paint.setColor(hintColor);
727            params.blendAlpha(paint);
728            paint.setTextSize(hintSize);
729            final float hintX, hintY;
730            if (key.hasHintLabel()) {
731                // The hint label is placed just right of the key label. Used mainly on
732                // "phone number" layout.
733                // TODO: Generalize the following calculations.
734                hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
735                hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
736                paint.setTextAlign(Align.LEFT);
737            } else if (key.hasShiftedLetterHint()) {
738                // The hint label is placed at top-right corner of the key. Used mainly on tablet.
739                hintX = keyWidth - params.mKeyShiftedLetterHintPadding
740                        - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
741                paint.getFontMetrics(mFontMetrics);
742                hintY = -mFontMetrics.top;
743                paint.setTextAlign(Align.CENTER);
744            } else { // key.hasHintLetter()
745                // The hint letter is placed at top-right corner of the key. Used mainly on phone.
746                hintX = keyWidth - params.mKeyHintLetterPadding
747                        - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
748                hintY = -paint.ascent();
749                paint.setTextAlign(Align.CENTER);
750            }
751            canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
752
753            if (LatinImeLogger.sVISUALDEBUG) {
754                final Paint line = new Paint();
755                drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
756                drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
757            }
758        }
759
760        // Draw key icon.
761        if (key.mLabel == null && icon != null) {
762            final int iconWidth = icon.getIntrinsicWidth();
763            final int iconHeight = icon.getIntrinsicHeight();
764            final int iconX, alignX;
765            final int iconY = (keyHeight - iconHeight) / 2;
766            if (key.isAlignLeft()) {
767                iconX = (int)params.mKeyLabelHorizontalPadding;
768                alignX = iconX;
769            } else if (key.isAlignRight()) {
770                iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
771                alignX = iconX + iconWidth;
772            } else { // Align center
773                iconX = (keyWidth - iconWidth) / 2;
774                alignX = iconX + iconWidth / 2;
775            }
776            drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
777
778            if (LatinImeLogger.sVISUALDEBUG) {
779                final Paint line = new Paint();
780                drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line);
781                drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line);
782            }
783        }
784
785        if (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) {
786            drawKeyPopupHint(key, canvas, paint, params);
787        }
788    }
789
790    // Draw popup hint "..." at the bottom right corner of the key.
791    protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
792        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
793        final int keyHeight = key.mHeight;
794
795        paint.setTypeface(params.mKeyTextStyle);
796        paint.setTextSize(params.mKeyHintLetterSize);
797        paint.setColor(params.mKeyHintLabelColor);
798        paint.setTextAlign(Align.CENTER);
799        final float hintX = keyWidth - params.mKeyHintLetterPadding
800                - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
801        final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
802        canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
803
804        if (LatinImeLogger.sVISUALDEBUG) {
805            final Paint line = new Paint();
806            drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
807            drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
808        }
809    }
810
811    private static int getCharGeometryCacheKey(char referenceChar, Paint paint) {
812        final int labelSize = (int)paint.getTextSize();
813        final Typeface face = paint.getTypeface();
814        final int codePointOffset = referenceChar << 15;
815        if (face == Typeface.DEFAULT) {
816            return codePointOffset + labelSize;
817        } else if (face == Typeface.DEFAULT_BOLD) {
818            return codePointOffset + labelSize + 0x1000;
819        } else if (face == Typeface.MONOSPACE) {
820            return codePointOffset + labelSize + 0x2000;
821        } else {
822            return codePointOffset + labelSize;
823        }
824    }
825
826    // Working variable for the following methods.
827    private final Rect mTextBounds = new Rect();
828
829    private float getCharHeight(char[] referenceChar, Paint paint) {
830        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
831        final Float cachedValue = sTextHeightCache.get(key);
832        if (cachedValue != null)
833            return cachedValue;
834
835        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
836        final float height = mTextBounds.height();
837        sTextHeightCache.put(key, height);
838        return height;
839    }
840
841    private float getCharWidth(char[] referenceChar, Paint paint) {
842        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
843        final Float cachedValue = sTextWidthCache.get(key);
844        if (cachedValue != null)
845            return cachedValue;
846
847        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
848        final float width = mTextBounds.width();
849        sTextWidthCache.put(key, width);
850        return width;
851    }
852
853    public float getLabelWidth(String label, Paint paint) {
854        paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds);
855        return mTextBounds.width();
856    }
857
858    protected static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
859            int height) {
860        canvas.translate(x, y);
861        icon.setBounds(0, 0, width, height);
862        icon.draw(canvas);
863        canvas.translate(-x, -y);
864    }
865
866    private static void drawHorizontalLine(Canvas canvas, float y, float w, int color,
867            Paint paint) {
868        paint.setStyle(Paint.Style.STROKE);
869        paint.setStrokeWidth(1.0f);
870        paint.setColor(color);
871        canvas.drawLine(0, y, w, y, paint);
872    }
873
874    private static void drawVerticalLine(Canvas canvas, float x, float h, int color, Paint paint) {
875        paint.setStyle(Paint.Style.STROKE);
876        paint.setStrokeWidth(1.0f);
877        paint.setColor(color);
878        canvas.drawLine(x, 0, x, h, paint);
879    }
880
881    private static void drawRectangle(Canvas canvas, float x, float y, float w, float h, int color,
882            Paint paint) {
883        paint.setStyle(Paint.Style.STROKE);
884        paint.setStrokeWidth(1.0f);
885        paint.setColor(color);
886        canvas.translate(x, y);
887        canvas.drawRect(0, 0, w, h, paint);
888        canvas.translate(-x, -y);
889    }
890
891    public Paint newDefaultLabelPaint() {
892        final Paint paint = new Paint();
893        paint.setAntiAlias(true);
894        paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
895        paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
896        return paint;
897    }
898
899    public void cancelAllMessages() {
900        mDrawingHandler.cancelAllMessages();
901        if (mPreviewPlacerView != null) {
902            mPreviewPlacerView.cancelAllMessages();
903        }
904    }
905
906    // Called by {@link PointerTracker} constructor to create a TextView.
907    @Override
908    public TextView inflateKeyPreviewText() {
909        final Context context = getContext();
910        if (mKeyPreviewLayoutId != 0) {
911            return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
912        } else {
913            return new TextView(context);
914        }
915    }
916
917    @Override
918    public void dismissKeyPreview(PointerTracker tracker) {
919        mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
920    }
921
922    private void addKeyPreview(TextView keyPreview) {
923        locatePreviewPlacerView();
924        mPreviewPlacerView.addView(
925                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
926    }
927
928    private void locatePreviewPlacerView() {
929        if (mPreviewPlacerView.getParent() != null) {
930            return;
931        }
932        final int[] viewOrigin = new int[2];
933        getLocationInWindow(viewOrigin);
934        mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
935        final ViewGroup windowContentView =
936                (ViewGroup)getRootView().findViewById(android.R.id.content);
937        windowContentView.addView(mPreviewPlacerView);
938    }
939
940    public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
941        locatePreviewPlacerView();
942        mPreviewPlacerView.setGestureFloatingPreviewText(gestureFloatingPreviewText);
943    }
944
945    public void dismissGestureFloatingPreviewText() {
946        locatePreviewPlacerView();
947        mPreviewPlacerView.dismissGestureFloatingPreviewText();
948    }
949
950    @Override
951    public void showGestureTrail(PointerTracker tracker) {
952        locatePreviewPlacerView();
953        mPreviewPlacerView.invalidatePointer(tracker);
954    }
955
956    @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
957    @Override
958    public void showKeyPreview(PointerTracker tracker) {
959        if (!mShowKeyPreviewPopup) return;
960
961        final TextView previewText = tracker.getKeyPreviewText();
962        // If the key preview has no parent view yet, add it to the ViewGroup which can place
963        // key preview absolutely in SoftInputWindow.
964        if (previewText.getParent() == null) {
965            addKeyPreview(previewText);
966        }
967
968        mDrawingHandler.cancelDismissKeyPreview(tracker);
969        final Key key = tracker.getKey();
970        // If key is invalid or IME is already closed, we must not show key preview.
971        // Trying to show key preview while root window is closed causes
972        // WindowManager.BadTokenException.
973        if (key == null)
974            return;
975
976        final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
977        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
978        // What we show as preview should match what we show on a key top in onBufferDraw().
979        if (label != null) {
980            // TODO Should take care of temporaryShiftLabel here.
981            previewText.setCompoundDrawables(null, null, null, null);
982            if (StringUtils.codePointCount(label) > 1) {
983                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
984                previewText.setTypeface(Typeface.DEFAULT_BOLD);
985            } else {
986                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
987                previewText.setTypeface(params.mKeyTextStyle);
988            }
989            previewText.setText(label);
990        } else {
991            previewText.setCompoundDrawables(null, null, null,
992                    key.getPreviewIcon(mKeyboard.mIconsSet));
993            previewText.setText(null);
994        }
995        previewText.setBackgroundDrawable(params.mPreviewBackground);
996
997        previewText.measure(
998                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
999        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
1000        final int previewWidth = previewText.getMeasuredWidth();
1001        final int previewHeight = params.mPreviewHeight;
1002        // The width and height of visible part of the key preview background. The content marker
1003        // of the background 9-patch have to cover the visible part of the background.
1004        params.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
1005                - previewText.getPaddingRight();
1006        params.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
1007                - previewText.getPaddingBottom();
1008        // The distance between the top edge of the parent key and the bottom of the visible part
1009        // of the key preview background.
1010        params.mPreviewVisibleOffset = params.mPreviewOffset - previewText.getPaddingBottom();
1011        getLocationInWindow(params.mCoordinates);
1012        // The key preview is horizontally aligned with the center of the visible part of the
1013        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
1014        // the left/right background is used if such background is specified.
1015        int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
1016                + params.mCoordinates[0];
1017        if (previewX < 0) {
1018            previewX = 0;
1019            if (params.mPreviewLeftBackground != null) {
1020                previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
1021            }
1022        } else if (previewX > getWidth() - previewWidth) {
1023            previewX = getWidth() - previewWidth;
1024            if (params.mPreviewRightBackground != null) {
1025                previewText.setBackgroundDrawable(params.mPreviewRightBackground);
1026            }
1027        }
1028        // The key preview is placed vertically above the top edge of the parent key with an
1029        // arbitrary offset.
1030        final int previewY = key.mY - previewHeight + params.mPreviewOffset
1031                + params.mCoordinates[1];
1032
1033        // Set the preview background state
1034        previewText.getBackground().setState(
1035                key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
1036        previewText.setTextColor(params.mPreviewTextColor);
1037        ViewLayoutUtils.placeViewAt(
1038                previewText, previewX, previewY, previewWidth, previewHeight);
1039        previewText.setVisibility(VISIBLE);
1040    }
1041
1042    /**
1043     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
1044     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
1045     * draws the cached buffer.
1046     * @see #invalidateKey(Key)
1047     */
1048    public void invalidateAllKeys() {
1049        mInvalidatedKeys.clear();
1050        mInvalidateAllKeys = true;
1051        invalidate();
1052    }
1053
1054    /**
1055     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
1056     * one key is changing it's content. Any changes that affect the position or size of the key
1057     * may not be honored.
1058     * @param key key in the attached {@link Keyboard}.
1059     * @see #invalidateAllKeys
1060     */
1061    @Override
1062    public void invalidateKey(Key key) {
1063        if (mInvalidateAllKeys) return;
1064        if (key == null) return;
1065        mInvalidatedKeys.add(key);
1066        final int x = key.mX + getPaddingLeft();
1067        final int y = key.mY + getPaddingTop();
1068        invalidate(x, y, x + key.mWidth, y + key.mHeight);
1069    }
1070
1071    public void closing() {
1072        PointerTracker.dismissAllKeyPreviews();
1073        cancelAllMessages();
1074
1075        mInvalidateAllKeys = true;
1076        requestLayout();
1077    }
1078
1079    @Override
1080    public boolean dismissMoreKeysPanel() {
1081        return false;
1082    }
1083
1084    public void purgeKeyboardAndClosing() {
1085        mKeyboard = null;
1086        closing();
1087    }
1088
1089    @Override
1090    protected void onDetachedFromWindow() {
1091        super.onDetachedFromWindow();
1092        closing();
1093        mPreviewPlacerView.removeAllViews();
1094        freeOffscreenBuffer();
1095    }
1096}
1097