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