KeyboardView.java revision ff082d081f3ea18ff0b9b22126ee4a86504cf83c
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 com.android.inputmethod.latin.LatinImeLogger; 20import com.android.inputmethod.latin.R; 21 22import android.content.Context; 23import android.content.pm.PackageManager; 24import android.content.res.Resources; 25import android.content.res.TypedArray; 26import android.graphics.Bitmap; 27import android.graphics.Canvas; 28import android.graphics.Color; 29import android.graphics.Paint; 30import android.graphics.Paint.Align; 31import android.graphics.PorterDuff; 32import android.graphics.Rect; 33import android.graphics.Region.Op; 34import android.graphics.Typeface; 35import android.graphics.drawable.Drawable; 36import android.os.Handler; 37import android.os.Message; 38import android.util.AttributeSet; 39import android.util.Log; 40import android.util.TypedValue; 41import android.view.GestureDetector; 42import android.view.LayoutInflater; 43import android.view.MotionEvent; 44import android.view.View; 45import android.view.ViewConfiguration; 46import android.view.ViewGroup; 47import android.view.ViewGroup.MarginLayoutParams; 48import android.widget.FrameLayout; 49import android.widget.LinearLayout; 50import android.widget.PopupWindow; 51import android.widget.TextView; 52 53import java.util.ArrayList; 54import java.util.HashMap; 55import java.util.WeakHashMap; 56 57/** 58 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key 59 * presses and touch movements. 60 * 61 * @attr ref R.styleable#KeyboardView_keyBackground 62 * @attr ref R.styleable#KeyboardView_keyPreviewLayout 63 * @attr ref R.styleable#KeyboardView_keyPreviewOffset 64 * @attr ref R.styleable#KeyboardView_labelTextSize 65 * @attr ref R.styleable#KeyboardView_keyTextSize 66 * @attr ref R.styleable#KeyboardView_keyTextColor 67 * @attr ref R.styleable#KeyboardView_verticalCorrection 68 * @attr ref R.styleable#KeyboardView_popupLayout 69 */ 70public class KeyboardView extends View implements PointerTracker.UIProxy { 71 private static final String TAG = KeyboardView.class.getSimpleName(); 72 private static final boolean DEBUG_SHOW_ALIGN = false; 73 private static final boolean DEBUG_KEYBOARD_GRID = false; 74 75 private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true; 76 private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; 77 78 public static final int COLOR_SCHEME_WHITE = 0; 79 public static final int COLOR_SCHEME_BLACK = 1; 80 81 // Timing constants 82 private final int mKeyRepeatInterval; 83 84 // Miscellaneous constants 85 private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; 86 private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1; 87 88 // XML attribute 89 private int mKeyLetterSize; 90 private int mKeyTextColor; 91 private int mKeyTextColorDisabled; 92 private Typeface mKeyLetterStyle = Typeface.DEFAULT; 93 private int mLabelTextSize; 94 private int mColorScheme = COLOR_SCHEME_WHITE; 95 private int mShadowColor; 96 private float mShadowRadius; 97 private Drawable mKeyBackground; 98 private float mBackgroundDimAmount; 99 private float mKeyHysteresisDistance; 100 private float mVerticalCorrection; 101 private int mPreviewOffset; 102 private int mPreviewHeight; 103 private int mPopupLayout; 104 105 // Main keyboard 106 private Keyboard mKeyboard; 107 108 // Key preview 109 private boolean mInForeground; 110 private TextView mPreviewText; 111 private int mPreviewTextSizeLarge; 112 private boolean mShowKeyPreview = true; 113 private int mKeyPreviewDisplayedY; 114 private final int mDelayBeforePreview; 115 private final int mDelayAfterPreview; 116 private ViewGroup mPreviewPlacer; 117 private final int[] mCoordinates = new int[2]; 118 119 // Mini keyboard 120 private PopupWindow mPopupWindow; 121 private PopupPanel mPopupMiniKeyboardPanel; 122 private final WeakHashMap<Key, PopupPanel> mPopupPanelCache = 123 new WeakHashMap<Key, PopupPanel>(); 124 125 /** Listener for {@link KeyboardActionListener}. */ 126 private KeyboardActionListener mKeyboardActionListener; 127 128 private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); 129 130 // TODO: Let the PointerTracker class manage this pointer queue 131 private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue(); 132 133 private final boolean mHasDistinctMultitouch; 134 private int mOldPointerCount = 1; 135 private int mOldKeyIndex; 136 137 // Accessibility 138 private boolean mIsAccessibilityEnabled; 139 140 protected KeyDetector mKeyDetector = new KeyDetector(); 141 142 // Swipe gesture detector 143 protected GestureDetector mGestureDetector; 144 private final SwipeTracker mSwipeTracker = new SwipeTracker(); 145 private final int mSwipeThreshold; 146 private final boolean mDisambiguateSwipe; 147 148 // Drawing 149 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ 150 private boolean mDrawPending; 151 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ 152 private boolean mKeyboardChanged; 153 /** The dirty region in the keyboard bitmap */ 154 private final Rect mDirtyRect = new Rect(); 155 /** The key to invalidate. */ 156 private Key mInvalidatedKey; 157 /** The dirty region for single key drawing */ 158 private final Rect mInvalidatedKeyRect = new Rect(); 159 /** The keyboard bitmap for faster updates */ 160 private Bitmap mBuffer; 161 /** The canvas for the above mutable keyboard bitmap */ 162 private Canvas mCanvas; 163 private final Paint mPaint; 164 private final Rect mPadding; 165 // This map caches key label text height in pixel as value and key label text size as map key. 166 private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>(); 167 // Distance from horizontal center of the key, proportional to key label text height and width. 168 private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER = 0.45f; 169 private final float KEY_LABEL_VERTICAL_PADDING_FACTOR = 1.60f; 170 private final String KEY_LABEL_REFERENCE_CHAR = "H"; 171 private final int KEY_LABEL_OPTION_ALIGN_LEFT = 1; 172 private final int KEY_LABEL_OPTION_ALIGN_RIGHT = 2; 173 private final int KEY_LABEL_OPTION_ALIGN_BOTTOM = 8; 174 private final int KEY_LABEL_OPTION_FONT_NORMAL = 16; 175 private final int mKeyLabelHorizontalPadding; 176 177 private final UIHandler mHandler = new UIHandler(); 178 179 class UIHandler extends Handler { 180 private static final int MSG_SHOW_KEY_PREVIEW = 1; 181 private static final int MSG_DISMISS_KEY_PREVIEW = 2; 182 private static final int MSG_REPEAT_KEY = 3; 183 private static final int MSG_LONGPRESS_KEY = 4; 184 private static final int MSG_LONGPRESS_SHIFT_KEY = 5; 185 private static final int MSG_IGNORE_DOUBLE_TAP = 6; 186 187 private boolean mInKeyRepeat; 188 189 @Override 190 public void handleMessage(Message msg) { 191 final PointerTracker tracker = (PointerTracker) msg.obj; 192 switch (msg.what) { 193 case MSG_SHOW_KEY_PREVIEW: 194 showKey(msg.arg1, tracker); 195 break; 196 case MSG_DISMISS_KEY_PREVIEW: 197 mPreviewText.setVisibility(View.INVISIBLE); 198 break; 199 case MSG_REPEAT_KEY: 200 tracker.onRepeatKey(msg.arg1); 201 startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker); 202 break; 203 case MSG_LONGPRESS_KEY: 204 openMiniKeyboardIfRequired(msg.arg1, tracker); 205 break; 206 case MSG_LONGPRESS_SHIFT_KEY: 207 onLongPressShiftKey(tracker); 208 break; 209 } 210 } 211 212 public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) { 213 removeMessages(MSG_SHOW_KEY_PREVIEW); 214 if (mPreviewText.getVisibility() == VISIBLE || delay == 0) { 215 // Show right away, if it's already visible and finger is moving around 216 showKey(keyIndex, tracker); 217 } else { 218 sendMessageDelayed( 219 obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay); 220 } 221 } 222 223 public void cancelShowKeyPreview(PointerTracker tracker) { 224 removeMessages(MSG_SHOW_KEY_PREVIEW, tracker); 225 } 226 227 public void cancelAllShowKeyPreviews() { 228 removeMessages(MSG_SHOW_KEY_PREVIEW); 229 } 230 231 public void dismissKeyPreview(long delay, PointerTracker tracker) { 232 sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay); 233 } 234 235 public void cancelDismissKeyPreview(PointerTracker tracker) { 236 removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); 237 } 238 239 public void cancelAllDismissKeyPreviews() { 240 removeMessages(MSG_DISMISS_KEY_PREVIEW); 241 } 242 243 public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { 244 mInKeyRepeat = true; 245 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); 246 } 247 248 public void cancelKeyRepeatTimer() { 249 mInKeyRepeat = false; 250 removeMessages(MSG_REPEAT_KEY); 251 } 252 253 public boolean isInKeyRepeat() { 254 return mInKeyRepeat; 255 } 256 257 public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { 258 cancelLongPressTimers(); 259 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); 260 } 261 262 public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { 263 cancelLongPressTimers(); 264 if (ENABLE_CAPSLOCK_BY_LONGPRESS) { 265 sendMessageDelayed( 266 obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); 267 } 268 } 269 270 public void cancelLongPressTimers() { 271 removeMessages(MSG_LONGPRESS_KEY); 272 removeMessages(MSG_LONGPRESS_SHIFT_KEY); 273 } 274 275 public void cancelKeyTimers() { 276 cancelKeyRepeatTimer(); 277 cancelLongPressTimers(); 278 removeMessages(MSG_IGNORE_DOUBLE_TAP); 279 } 280 281 public void startIgnoringDoubleTap() { 282 sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP), 283 ViewConfiguration.getDoubleTapTimeout()); 284 } 285 286 public boolean isIgnoringDoubleTap() { 287 return hasMessages(MSG_IGNORE_DOUBLE_TAP); 288 } 289 290 public void cancelAllMessages() { 291 cancelKeyTimers(); 292 cancelAllShowKeyPreviews(); 293 cancelAllDismissKeyPreviews(); 294 } 295 } 296 297 public KeyboardView(Context context, AttributeSet attrs) { 298 this(context, attrs, R.attr.keyboardViewStyle); 299 } 300 301 public KeyboardView(Context context, AttributeSet attrs, int defStyle) { 302 super(context, attrs, defStyle); 303 304 final TypedArray a = context.obtainStyledAttributes( 305 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 306 int previewLayout = 0; 307 int keyTextSize = 0; 308 309 int n = a.getIndexCount(); 310 311 for (int i = 0; i < n; i++) { 312 int attr = a.getIndex(i); 313 314 switch (attr) { 315 case R.styleable.KeyboardView_keyBackground: 316 mKeyBackground = a.getDrawable(attr); 317 break; 318 case R.styleable.KeyboardView_keyHysteresisDistance: 319 mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0); 320 break; 321 case R.styleable.KeyboardView_verticalCorrection: 322 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); 323 break; 324 case R.styleable.KeyboardView_keyPreviewLayout: 325 previewLayout = a.getResourceId(attr, 0); 326 break; 327 case R.styleable.KeyboardView_keyPreviewOffset: 328 mPreviewOffset = a.getDimensionPixelOffset(attr, 0); 329 break; 330 case R.styleable.KeyboardView_keyPreviewHeight: 331 mPreviewHeight = a.getDimensionPixelSize(attr, 80); 332 break; 333 case R.styleable.KeyboardView_keyLetterSize: 334 mKeyLetterSize = a.getDimensionPixelSize(attr, 18); 335 break; 336 case R.styleable.KeyboardView_keyTextColor: 337 mKeyTextColor = a.getColor(attr, 0xFF000000); 338 break; 339 case R.styleable.KeyboardView_keyTextColorDisabled: 340 mKeyTextColorDisabled = a.getColor(attr, 0xFF000000); 341 break; 342 case R.styleable.KeyboardView_labelTextSize: 343 mLabelTextSize = a.getDimensionPixelSize(attr, 14); 344 break; 345 case R.styleable.KeyboardView_popupLayout: 346 mPopupLayout = a.getResourceId(attr, 0); 347 break; 348 case R.styleable.KeyboardView_shadowColor: 349 mShadowColor = a.getColor(attr, 0); 350 break; 351 case R.styleable.KeyboardView_shadowRadius: 352 mShadowRadius = a.getFloat(attr, 0f); 353 break; 354 // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) 355 case R.styleable.KeyboardView_backgroundDimAmount: 356 mBackgroundDimAmount = a.getFloat(attr, 0.5f); 357 break; 358 case R.styleable.KeyboardView_keyLetterStyle: 359 mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL)); 360 break; 361 case R.styleable.KeyboardView_colorScheme: 362 mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE); 363 break; 364 } 365 } 366 367 final Resources res = getResources(); 368 369 if (previewLayout != 0) { 370 mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null); 371 mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); 372 } else { 373 mShowKeyPreview = false; 374 } 375 mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview); 376 mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview); 377 mKeyLabelHorizontalPadding = (int)res.getDimension( 378 R.dimen.key_label_horizontal_alignment_padding); 379 380 mPaint = new Paint(); 381 mPaint.setAntiAlias(true); 382 mPaint.setTextSize(keyTextSize); 383 mPaint.setTextAlign(Align.CENTER); 384 mPaint.setAlpha(255); 385 386 mPadding = new Rect(0, 0, 0, 0); 387 mKeyBackground.getPadding(mPadding); 388 389 mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); 390 // TODO: Refer to frameworks/base/core/res/res/values/config.xml 391 mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); 392 393 GestureDetector.SimpleOnGestureListener listener = 394 new GestureDetector.SimpleOnGestureListener() { 395 private boolean mProcessingShiftDoubleTapEvent = false; 396 397 @Override 398 public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, 399 float velocityY) { 400 final float absX = Math.abs(velocityX); 401 final float absY = Math.abs(velocityY); 402 float deltaY = me2.getY() - me1.getY(); 403 int travelY = getHeight() / 2; // Half the keyboard height 404 mSwipeTracker.computeCurrentVelocity(1000); 405 final float endingVelocityY = mSwipeTracker.getYVelocity(); 406 if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { 407 if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { 408 onSwipeDown(); 409 return true; 410 } 411 } 412 return false; 413 } 414 415 @Override 416 public boolean onDoubleTap(MotionEvent firstDown) { 417 if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard 418 && ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) { 419 final int pointerIndex = firstDown.getActionIndex(); 420 final int id = firstDown.getPointerId(pointerIndex); 421 final PointerTracker tracker = getPointerTracker(id); 422 // If the first down event is on shift key. 423 if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) { 424 mProcessingShiftDoubleTapEvent = true; 425 return true; 426 } 427 } 428 mProcessingShiftDoubleTapEvent = false; 429 return false; 430 } 431 432 @Override 433 public boolean onDoubleTapEvent(MotionEvent secondTap) { 434 if (mProcessingShiftDoubleTapEvent 435 && secondTap.getAction() == MotionEvent.ACTION_DOWN) { 436 final MotionEvent secondDown = secondTap; 437 final int pointerIndex = secondDown.getActionIndex(); 438 final int id = secondDown.getPointerId(pointerIndex); 439 final PointerTracker tracker = getPointerTracker(id); 440 // If the second down event is also on shift key. 441 if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) { 442 // Detected a double tap on shift key. If we are in the ignoring double tap 443 // mode, it means we have already turned off caps lock in 444 // {@link KeyboardSwitcher#onReleaseShift} . 445 final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap(); 446 if (!ignoringDoubleTap) 447 onDoubleTapShiftKey(tracker); 448 return true; 449 } 450 // Otherwise these events should not be handled as double tap. 451 mProcessingShiftDoubleTapEvent = false; 452 } 453 return mProcessingShiftDoubleTapEvent; 454 } 455 }; 456 457 final boolean ignoreMultitouch = true; 458 mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); 459 mGestureDetector.setIsLongpressEnabled(false); 460 461 mHasDistinctMultitouch = context.getPackageManager() 462 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 463 mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); 464 } 465 466 public void startIgnoringDoubleTap() { 467 if (ENABLE_CAPSLOCK_BY_DOUBLETAP) 468 mHandler.startIgnoringDoubleTap(); 469 } 470 471 public void setOnKeyboardActionListener(KeyboardActionListener listener) { 472 mKeyboardActionListener = listener; 473 for (PointerTracker tracker : mPointerTrackers) { 474 tracker.setOnKeyboardActionListener(listener); 475 } 476 } 477 478 /** 479 * Returns the {@link KeyboardActionListener} object. 480 * @return the listener attached to this keyboard 481 */ 482 protected KeyboardActionListener getOnKeyboardActionListener() { 483 return mKeyboardActionListener; 484 } 485 486 /** 487 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 488 * view will re-layout itself to accommodate the keyboard. 489 * @see Keyboard 490 * @see #getKeyboard() 491 * @param keyboard the keyboard to display in this view 492 */ 493 public void setKeyboard(Keyboard keyboard) { 494 if (mKeyboard != null) { 495 dismissAllKeyPreviews(); 496 } 497 // Remove any pending messages, except dismissing preview 498 mHandler.cancelKeyTimers(); 499 mHandler.cancelAllShowKeyPreviews(); 500 mKeyboard = keyboard; 501 LatinImeLogger.onSetKeyboard(keyboard); 502 mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), 503 -getPaddingTop() + mVerticalCorrection); 504 for (PointerTracker tracker : mPointerTrackers) { 505 tracker.setKeyboard(keyboard, mKeyHysteresisDistance); 506 } 507 requestLayout(); 508 mKeyboardChanged = true; 509 invalidateAllKeys(); 510 mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth()); 511 mPopupPanelCache.clear(); 512 } 513 514 /** 515 * Returns the current keyboard being displayed by this view. 516 * @return the currently attached keyboard 517 * @see #setKeyboard(Keyboard) 518 */ 519 public Keyboard getKeyboard() { 520 return mKeyboard; 521 } 522 523 /** 524 * Returns whether the device has distinct multi-touch panel. 525 * @return true if the device has distinct multi-touch panel. 526 */ 527 @Override 528 public boolean hasDistinctMultitouch() { 529 return mHasDistinctMultitouch; 530 } 531 532 /** 533 * Enables or disables accessibility. 534 * @param accessibilityEnabled whether or not to enable accessibility 535 */ 536 public void setAccessibilityEnabled(boolean accessibilityEnabled) { 537 mIsAccessibilityEnabled = accessibilityEnabled; 538 539 // Propagate this change to all existing pointer trackers. 540 for (PointerTracker tracker : mPointerTrackers) { 541 tracker.setAccessibilityEnabled(accessibilityEnabled); 542 } 543 } 544 545 /** 546 * Returns whether the device has accessibility enabled. 547 * @return true if the device has accessibility enabled. 548 */ 549 @Override 550 public boolean isAccessibilityEnabled() { 551 return mIsAccessibilityEnabled; 552 } 553 554 /** 555 * Enables or disables the key feedback preview. This is a preview that shows a magnified 556 * version of the depressed key. By default the preview is enabled. 557 * @param previewEnabled whether or not to enable the key feedback preview 558 * @see #isKeyPreviewEnabled() 559 */ 560 public void setKeyPreviewEnabled(boolean previewEnabled) { 561 mShowKeyPreview = previewEnabled; 562 } 563 564 /** 565 * Returns the enabled state of the key feedback preview 566 * @return whether or not the key feedback preview is enabled 567 * @see #setKeyPreviewEnabled(boolean) 568 */ 569 public boolean isKeyPreviewEnabled() { 570 return mShowKeyPreview; 571 } 572 573 public int getColorScheme() { 574 return mColorScheme; 575 } 576 577 /** 578 * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key 579 * codes for adjacent keys. When disabled, only the primary key code will be 580 * reported. 581 * @param enabled whether or not the proximity correction is enabled 582 */ 583 public void setProximityCorrectionEnabled(boolean enabled) { 584 mKeyDetector.setProximityCorrectionEnabled(enabled); 585 } 586 587 /** 588 * Returns true if proximity correction is enabled. 589 */ 590 public boolean isProximityCorrectionEnabled() { 591 return mKeyDetector.isProximityCorrectionEnabled(); 592 } 593 594 protected CharSequence adjustCase(CharSequence label) { 595 if (mKeyboard.isShiftedOrShiftLocked() && label != null && label.length() < 3 596 && Character.isLowerCase(label.charAt(0))) { 597 return label.toString().toUpperCase(); 598 } 599 return label; 600 } 601 602 @Override 603 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 604 // Round up a little 605 if (mKeyboard == null) { 606 setMeasuredDimension( 607 getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); 608 } else { 609 int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); 610 if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { 611 width = MeasureSpec.getSize(widthMeasureSpec); 612 } 613 setMeasuredDimension( 614 width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); 615 } 616 } 617 618 @Override 619 public void onDraw(Canvas canvas) { 620 super.onDraw(canvas); 621 if (mDrawPending || mBuffer == null || mKeyboardChanged) { 622 onBufferDraw(); 623 } 624 canvas.drawBitmap(mBuffer, 0, 0, null); 625 } 626 627 private void onBufferDraw() { 628 final int width = getWidth(); 629 final int height = getHeight(); 630 if (width == 0 || height == 0) 631 return; 632 if (mBuffer == null || mKeyboardChanged) { 633 mKeyboardChanged = false; 634 mDirtyRect.union(0, 0, width, height); 635 } 636 if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) { 637 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 638 mCanvas = new Canvas(mBuffer); 639 } 640 final Canvas canvas = mCanvas; 641 canvas.clipRect(mDirtyRect, Op.REPLACE); 642 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 643 644 if (mKeyboard == null) return; 645 646 if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) { 647 // Draw a single key. 648 onBufferDrawKey(canvas, mInvalidatedKey); 649 } else { 650 // Draw all keys. 651 for (final Key key : mKeyboard.getKeys()) { 652 onBufferDrawKey(canvas, key); 653 } 654 } 655 656 // TODO: Move this function to ProximityInfo for getting rid of 657 // public declarations for 658 // GRID_WIDTH and GRID_HEIGHT 659 if (DEBUG_KEYBOARD_GRID) { 660 Paint p = new Paint(); 661 p.setStyle(Paint.Style.STROKE); 662 p.setStrokeWidth(1.0f); 663 p.setColor(0x800000c0); 664 int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1) 665 / mKeyboard.GRID_WIDTH; 666 int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1) 667 / mKeyboard.GRID_HEIGHT; 668 for (int i = 0; i <= mKeyboard.GRID_WIDTH; i++) 669 canvas.drawLine(i * cw, 0, i * cw, ch * mKeyboard.GRID_HEIGHT, p); 670 for (int i = 0; i <= mKeyboard.GRID_HEIGHT; i++) 671 canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p); 672 } 673 674 // Overlay a dark rectangle to dim the keyboard 675 if (mPopupMiniKeyboardPanel != null) { 676 mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); 677 canvas.drawRect(0, 0, width, height, mPaint); 678 } 679 680 mInvalidatedKey = null; 681 mDrawPending = false; 682 mDirtyRect.setEmpty(); 683 } 684 685 private void onBufferDrawKey(final Canvas canvas, final Key key) { 686 final Paint paint = mPaint; 687 final Drawable keyBackground = mKeyBackground; 688 final Rect padding = mPadding; 689 final int kbdPaddingLeft = getPaddingLeft(); 690 final int kbdPaddingTop = getPaddingTop(); 691 final int keyDrawX = key.mX + key.mVisualInsetsLeft; 692 final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 693 final int rowHeight = padding.top + key.mHeight; 694 final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase(); 695 696 canvas.translate(keyDrawX + kbdPaddingLeft, key.mY + kbdPaddingTop); 697 698 // Draw key background. 699 final int[] drawableState = key.getCurrentDrawableState(); 700 keyBackground.setState(drawableState); 701 final Rect bounds = keyBackground.getBounds(); 702 if (keyDrawWidth != bounds.right || key.mHeight != bounds.bottom) { 703 keyBackground.setBounds(0, 0, keyDrawWidth, key.mHeight); 704 } 705 keyBackground.draw(canvas); 706 707 // Draw key label. 708 if (key.mLabel != null) { 709 // Switch the character to uppercase if shift is pressed 710 final String label = key.mLabel == null ? null : adjustCase(key.mLabel).toString(); 711 // For characters, use large font. For labels like "Done", use small font. 712 final int labelSize = getLabelSizeAndSetPaint(label, key.mLabelOption, paint); 713 final int labelCharHeight = getLabelCharHeight(labelSize, paint); 714 715 // Vertical label text alignment. 716 final float baseline; 717 if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) { 718 baseline = key.mHeight - labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR; 719 if (DEBUG_SHOW_ALIGN) 720 drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000, 721 new Paint()); 722 } else { // Align center 723 final float centerY = (key.mHeight + padding.top - padding.bottom) / 2; 724 baseline = centerY + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER; 725 if (DEBUG_SHOW_ALIGN) 726 drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000, 727 new Paint()); 728 } 729 // Horizontal label text alignment 730 final int positionX; 731 if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { 732 positionX = mKeyLabelHorizontalPadding + padding.left; 733 paint.setTextAlign(Align.LEFT); 734 if (DEBUG_SHOW_ALIGN) 735 drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint()); 736 } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { 737 positionX = keyDrawWidth - mKeyLabelHorizontalPadding - padding.right; 738 paint.setTextAlign(Align.RIGHT); 739 if (DEBUG_SHOW_ALIGN) 740 drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint()); 741 } else { 742 positionX = (keyDrawWidth + padding.left - padding.right) / 2; 743 paint.setTextAlign(Align.CENTER); 744 if (DEBUG_SHOW_ALIGN) { 745 if (label.length() > 1) 746 drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint()); 747 } 748 } 749 if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) { 750 paint.setColor(mKeyTextColorDisabled); 751 } else { 752 paint.setColor(mKeyTextColor); 753 } 754 if (key.mEnabled) { 755 // Set a drop shadow for the text 756 paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); 757 } else { 758 // Make label invisible 759 paint.setColor(Color.TRANSPARENT); 760 } 761 canvas.drawText(label, positionX, baseline, paint); 762 // Turn off drop shadow 763 paint.setShadowLayer(0, 0, 0, 0); 764 } 765 766 // Draw key icon. 767 final Drawable icon = key.getIcon(); 768 if (key.mLabel == null && icon != null) { 769 final int drawableWidth = icon.getIntrinsicWidth(); 770 final int drawableHeight = icon.getIntrinsicHeight(); 771 final int drawableX; 772 final int drawableY = (key.mHeight + padding.top - padding.bottom - drawableHeight) / 2; 773 if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) { 774 drawableX = padding.left + mKeyLabelHorizontalPadding; 775 if (DEBUG_SHOW_ALIGN) 776 drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint()); 777 } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) { 778 drawableX = keyDrawWidth - padding.right - mKeyLabelHorizontalPadding 779 - drawableWidth; 780 if (DEBUG_SHOW_ALIGN) 781 drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight, 782 0xc0808000, new Paint()); 783 } else { // Align center 784 drawableX = (keyDrawWidth + padding.left - padding.right - drawableWidth) / 2; 785 if (DEBUG_SHOW_ALIGN) 786 drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight, 787 0xc0008080, new Paint()); 788 } 789 drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight); 790 if (DEBUG_SHOW_ALIGN) 791 drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, 792 0x80c00000, new Paint()); 793 } 794 795 // Draw hint icon. 796 if (key.mHintIcon != null) { 797 final int drawableWidth = keyDrawWidth; 798 final int drawableHeight = key.mHeight; 799 final int drawableX = 0; 800 final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL; 801 Drawable hintIcon = (isManualTemporaryUpperCase 802 && key.mManualTemporaryUpperCaseHintIcon != null) 803 ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon; 804 drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight); 805 if (DEBUG_SHOW_ALIGN) 806 drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight, 807 0x80c0c000, new Paint()); 808 } 809 810 canvas.translate(-keyDrawX - kbdPaddingLeft, -key.mY - kbdPaddingTop); 811 } 812 813 public int getLabelSizeAndSetPaint(CharSequence label, int keyLabelOption, Paint paint) { 814 // For characters, use large font. For labels like "Done", use small font. 815 final int labelSize; 816 final Typeface labelStyle; 817 if (label.length() > 1) { 818 labelSize = mLabelTextSize; 819 if ((keyLabelOption & KEY_LABEL_OPTION_FONT_NORMAL) != 0) { 820 labelStyle = Typeface.DEFAULT; 821 } else { 822 labelStyle = Typeface.DEFAULT_BOLD; 823 } 824 } else { 825 labelSize = mKeyLetterSize; 826 labelStyle = mKeyLetterStyle; 827 } 828 paint.setTextSize(labelSize); 829 paint.setTypeface(labelStyle); 830 return labelSize; 831 } 832 833 private int getLabelCharHeight(int labelSize, Paint paint) { 834 Integer labelHeightValue = mTextHeightCache.get(labelSize); 835 final int labelCharHeight; 836 if (labelHeightValue != null) { 837 labelCharHeight = labelHeightValue; 838 } else { 839 Rect textBounds = new Rect(); 840 paint.getTextBounds(KEY_LABEL_REFERENCE_CHAR, 0, 1, textBounds); 841 labelCharHeight = textBounds.height(); 842 mTextHeightCache.put(labelSize, labelCharHeight); 843 } 844 return labelCharHeight; 845 } 846 847 private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, 848 int height) { 849 canvas.translate(x, y); 850 icon.setBounds(0, 0, width, height); 851 icon.draw(canvas); 852 canvas.translate(-x, -y); 853 } 854 855 private static void drawHorizontalLine(Canvas canvas, int y, int w, int color, Paint paint) { 856 paint.setStyle(Paint.Style.STROKE); 857 paint.setStrokeWidth(1.0f); 858 paint.setColor(color); 859 canvas.drawLine(0, y, w, y, paint); 860 } 861 862 private static void drawVerticalLine(Canvas canvas, int x, int h, int color, Paint paint) { 863 paint.setStyle(Paint.Style.STROKE); 864 paint.setStrokeWidth(1.0f); 865 paint.setColor(color); 866 canvas.drawLine(x, 0, x, h, paint); 867 } 868 869 private static void drawRectangle(Canvas canvas, int x, int y, int w, int h, int color, 870 Paint paint) { 871 paint.setStyle(Paint.Style.STROKE); 872 paint.setStrokeWidth(1.0f); 873 paint.setColor(color); 874 canvas.translate(x, y); 875 canvas.drawRect(0, 0, w, h, paint); 876 canvas.translate(-x, -y); 877 } 878 879 public void setForeground(boolean foreground) { 880 mInForeground = foreground; 881 } 882 883 // TODO: clean up this method. 884 private void dismissAllKeyPreviews() { 885 for (PointerTracker tracker : mPointerTrackers) { 886 tracker.setReleasedKeyGraphics(); 887 dismissKeyPreview(tracker); 888 } 889 } 890 891 @Override 892 public void showKeyPreview(int keyIndex, PointerTracker tracker) { 893 if (mShowKeyPreview) { 894 mHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker); 895 } else if (mKeyboard.needSpacebarPreview(keyIndex)) { 896 // Show key preview (in this case, slide language switcher) without any delay. 897 showKey(keyIndex, tracker); 898 } 899 } 900 901 @Override 902 public void dismissKeyPreview(PointerTracker tracker) { 903 if (mShowKeyPreview) { 904 mHandler.cancelShowKeyPreview(tracker); 905 mHandler.dismissKeyPreview(mDelayAfterPreview, tracker); 906 } else if (mKeyboard.needSpacebarPreview(KeyDetector.NOT_A_KEY)) { 907 // Dismiss key preview (in this case, slide language switcher) without any delay. 908 mPreviewText.setVisibility(View.INVISIBLE); 909 } 910 } 911 912 private void addKeyPreview(TextView keyPreview) { 913 ViewGroup placer = mPreviewPlacer; 914 if (placer == null) { 915 final FrameLayout screenContent = (FrameLayout) getRootView().findViewById( 916 android.R.id.content); 917 if (android.os.Build.VERSION.SDK_INT >= /* HONEYCOMB */11) { 918 placer = screenContent; 919 } else { 920 // Insert LinearLayout to be able to setMargin because pre-Honeycomb FrameLayout 921 // could not handle setMargin properly. 922 placer = new LinearLayout(getContext()); 923 screenContent.addView(placer); 924 } 925 mPreviewPlacer = placer; 926 } 927 if (placer instanceof FrameLayout) { 928 // Honeycomb or later. 929 placer.addView(keyPreview, new FrameLayout.LayoutParams(0, 0)); 930 } else { 931 // Gingerbread or ealier. 932 placer.addView(keyPreview, new LinearLayout.LayoutParams(0, 0)); 933 } 934 } 935 936 // TODO: Introduce minimum duration for displaying key previews 937 // TODO: Display up to two key previews when the user presses two keys at the same time 938 private void showKey(final int keyIndex, PointerTracker tracker) { 939 final TextView previewText = mPreviewText; 940 // If the key preview has no parent view yet, add it to the ViewGroup which can place 941 // key preview absolutely in SoftInputWindow. 942 if (previewText.getParent() == null) { 943 addKeyPreview(previewText); 944 } 945 946 final Key key = tracker.getKey(keyIndex); 947 // If keyIndex is invalid or IME is already closed, we must not show key preview. 948 // Trying to show key preview while root window is closed causes 949 // WindowManager.BadTokenException. 950 if (key == null || !mInForeground) 951 return; 952 953 mHandler.cancelAllDismissKeyPreviews(); 954 955 final int keyDrawX = key.mX + key.mVisualInsetsLeft; 956 final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 957 // What we show as preview should match what we show on key top in onBufferDraw(). 958 if (key.mLabel != null) { 959 // TODO Should take care of temporaryShiftLabel here. 960 previewText.setCompoundDrawables(null, null, null, null); 961 previewText.setText(adjustCase(tracker.getPreviewText(key))); 962 if (key.mLabel.length() > 1) { 963 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize); 964 previewText.setTypeface(Typeface.DEFAULT_BOLD); 965 } else { 966 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); 967 previewText.setTypeface(mKeyLetterStyle); 968 } 969 } else { 970 final Drawable previewIcon = key.getPreviewIcon(); 971 previewText.setCompoundDrawables(null, null, null, 972 previewIcon != null ? previewIcon : key.getIcon()); 973 previewText.setText(null); 974 } 975 // Set the preview background state 976 previewText.getBackground().setState( 977 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); 978 979 previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 980 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 981 final int previewWidth = Math.max(previewText.getMeasuredWidth(), keyDrawWidth 982 + previewText.getPaddingLeft() + previewText.getPaddingRight()); 983 final int previewHeight = mPreviewHeight; 984 getLocationInWindow(mCoordinates); 985 final int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + mCoordinates[0]; 986 final int previewY = key.mY - previewHeight + mCoordinates[1] + mPreviewOffset; 987 // Record key preview position to display mini-keyboard later at the same position 988 mKeyPreviewDisplayedY = previewY; 989 990 // Place the key preview. 991 // TODO: Adjust position of key previews which touch screen edges 992 final MarginLayoutParams lp = (MarginLayoutParams)previewText.getLayoutParams(); 993 lp.width = previewWidth; 994 lp.height = previewHeight; 995 lp.setMargins(previewX, previewY, 0, 0); 996 previewText.setVisibility(VISIBLE); 997 } 998 999 /** 1000 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 1001 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 1002 * draws the cached buffer. 1003 * @see #invalidateKey(Key) 1004 */ 1005 public void invalidateAllKeys() { 1006 mDirtyRect.union(0, 0, getWidth(), getHeight()); 1007 mDrawPending = true; 1008 invalidate(); 1009 } 1010 1011 /** 1012 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 1013 * one key is changing it's content. Any changes that affect the position or size of the key 1014 * may not be honored. 1015 * @param key key in the attached {@link Keyboard}. 1016 * @see #invalidateAllKeys 1017 */ 1018 @Override 1019 public void invalidateKey(Key key) { 1020 if (key == null) 1021 return; 1022 mInvalidatedKey = key; 1023 final int x = key.mX + getPaddingLeft(); 1024 final int y = key.mY + getPaddingTop(); 1025 mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight); 1026 mDirtyRect.union(mInvalidatedKeyRect); 1027 onBufferDraw(); 1028 invalidate(mInvalidatedKeyRect); 1029 } 1030 1031 private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) { 1032 // Check if we have a popup layout specified first. 1033 if (mPopupLayout == 0) { 1034 return false; 1035 } 1036 1037 final Key parentKey = tracker.getKey(keyIndex); 1038 if (parentKey == null) 1039 return false; 1040 boolean result = onLongPress(parentKey, tracker); 1041 if (result) { 1042 dismissAllKeyPreviews(); 1043 tracker.onLongPressed(mPointerQueue); 1044 } 1045 return result; 1046 } 1047 1048 private void onLongPressShiftKey(PointerTracker tracker) { 1049 tracker.onLongPressed(mPointerQueue); 1050 mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); 1051 } 1052 1053 private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) { 1054 // When shift key is double tapped, the first tap is correctly processed as usual tap. And 1055 // the second tap is treated as this double tap event, so that we need not mark tracker 1056 // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue. 1057 mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); 1058 } 1059 1060 // This default implementation returns a popup mini keyboard panel. 1061 // A derived class may return a language switcher popup panel, for instance. 1062 protected PopupPanel onCreatePopupPanel(Key parentKey) { 1063 if (parentKey.mPopupCharacters == null) 1064 return null; 1065 1066 final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null); 1067 if (container == null) 1068 throw new NullPointerException(); 1069 1070 final PopupMiniKeyboardView miniKeyboardView = 1071 (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); 1072 miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() { 1073 @Override 1074 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { 1075 mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y); 1076 dismissMiniKeyboard(); 1077 } 1078 1079 @Override 1080 public void onTextInput(CharSequence text) { 1081 mKeyboardActionListener.onTextInput(text); 1082 dismissMiniKeyboard(); 1083 } 1084 1085 @Override 1086 public void onCancelInput() { 1087 mKeyboardActionListener.onCancelInput(); 1088 dismissMiniKeyboard(); 1089 } 1090 1091 @Override 1092 public void onSwipeDown() { 1093 // Nothing to do. 1094 } 1095 @Override 1096 public void onPress(int primaryCode, boolean withSliding) { 1097 mKeyboardActionListener.onPress(primaryCode, withSliding); 1098 } 1099 @Override 1100 public void onRelease(int primaryCode, boolean withSliding) { 1101 mKeyboardActionListener.onRelease(primaryCode, withSliding); 1102 } 1103 }); 1104 1105 final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(), 1106 parentKey).build(); 1107 miniKeyboardView.setKeyboard(keyboard); 1108 1109 container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), 1110 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); 1111 1112 return miniKeyboardView; 1113 } 1114 1115 /** 1116 * Called when a key is long pressed. By default this will open mini keyboard associated 1117 * with this key. 1118 * @param parentKey the key that was long pressed 1119 * @param tracker the pointer tracker which pressed the parent key 1120 * @return true if the long press is handled, false otherwise. Subclasses should call the 1121 * method on the base class if the subclass doesn't wish to handle the call. 1122 */ 1123 protected boolean onLongPress(Key parentKey, PointerTracker tracker) { 1124 PopupPanel popupPanel = mPopupPanelCache.get(parentKey); 1125 if (popupPanel == null) { 1126 popupPanel = onCreatePopupPanel(parentKey); 1127 if (popupPanel == null) 1128 return false; 1129 mPopupPanelCache.put(parentKey, popupPanel); 1130 } 1131 if (mPopupWindow == null) { 1132 mPopupWindow = new PopupWindow(getContext()); 1133 mPopupWindow.setBackgroundDrawable(null); 1134 mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation); 1135 // Allow popup window to be drawn off the screen. 1136 mPopupWindow.setClippingEnabled(false); 1137 } 1138 mPopupMiniKeyboardPanel = popupPanel; 1139 popupPanel.showPanel(this, parentKey, tracker, mKeyPreviewDisplayedY, mPopupWindow); 1140 1141 invalidateAllKeys(); 1142 return true; 1143 } 1144 1145 private PointerTracker getPointerTracker(final int id) { 1146 final ArrayList<PointerTracker> pointers = mPointerTrackers; 1147 final KeyboardActionListener listener = mKeyboardActionListener; 1148 1149 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 1150 for (int i = pointers.size(); i <= id; i++) { 1151 final PointerTracker tracker = 1152 new PointerTracker(i, this, mHandler, mKeyDetector, this); 1153 if (mKeyboard != null) 1154 tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance); 1155 if (listener != null) 1156 tracker.setOnKeyboardActionListener(listener); 1157 pointers.add(tracker); 1158 } 1159 1160 return pointers.get(id); 1161 } 1162 1163 public boolean isInSlidingKeyInput() { 1164 if (mPopupMiniKeyboardPanel != null) { 1165 return mPopupMiniKeyboardPanel.isInSlidingKeyInput(); 1166 } else { 1167 return mPointerQueue.isInSlidingKeyInput(); 1168 } 1169 } 1170 1171 public int getPointerCount() { 1172 return mOldPointerCount; 1173 } 1174 1175 @Override 1176 public boolean onTouchEvent(MotionEvent me) { 1177 final int action = me.getActionMasked(); 1178 final int pointerCount = me.getPointerCount(); 1179 final int oldPointerCount = mOldPointerCount; 1180 mOldPointerCount = pointerCount; 1181 1182 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1183 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 1184 // events except a transition from/to single-touch. 1185 if ((!mHasDistinctMultitouch || mIsAccessibilityEnabled) 1186 && pointerCount > 1 && oldPointerCount > 1) { 1187 return true; 1188 } 1189 1190 // Track the last few movements to look for spurious swipes. 1191 mSwipeTracker.addMovement(me); 1192 1193 // Gesture detector must be enabled only when mini-keyboard is not on the screen and 1194 // accessibility is not enabled. 1195 // TODO: Reconcile gesture detection and accessibility features. 1196 if (mPopupMiniKeyboardPanel == null && !mIsAccessibilityEnabled 1197 && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { 1198 dismissAllKeyPreviews(); 1199 mHandler.cancelKeyTimers(); 1200 return true; 1201 } 1202 1203 final long eventTime = me.getEventTime(); 1204 final int index = me.getActionIndex(); 1205 final int id = me.getPointerId(index); 1206 final int x = (int)me.getX(index); 1207 final int y = (int)me.getY(index); 1208 1209 // Needs to be called after the gesture detector gets a turn, as it may have displayed the 1210 // mini keyboard 1211 if (mPopupMiniKeyboardPanel != null) { 1212 return mPopupMiniKeyboardPanel.onTouchEvent(me); 1213 } 1214 1215 if (mHandler.isInKeyRepeat()) { 1216 final PointerTracker tracker = getPointerTracker(id); 1217 // Key repeating timer will be canceled if 2 or more keys are in action, and current 1218 // event (UP or DOWN) is non-modifier key. 1219 if (pointerCount > 1 && !tracker.isModifier()) { 1220 mHandler.cancelKeyRepeatTimer(); 1221 } 1222 // Up event will pass through. 1223 } 1224 1225 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1226 // Translate mutli-touch event to single-touch events on the device that has no distinct 1227 // multi-touch panel. 1228 if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) { 1229 // Use only main (id=0) pointer tracker. 1230 PointerTracker tracker = getPointerTracker(0); 1231 if (pointerCount == 1 && oldPointerCount == 2) { 1232 // Multi-touch to single touch transition. 1233 // Send a down event for the latest pointer if the key is different from the 1234 // previous key. 1235 final int newKeyIndex = tracker.getKeyIndexOn(x, y); 1236 if (mOldKeyIndex != newKeyIndex) { 1237 tracker.onDownEvent(x, y, eventTime, null); 1238 if (action == MotionEvent.ACTION_UP) 1239 tracker.onUpEvent(x, y, eventTime, null); 1240 } 1241 } else if (pointerCount == 2 && oldPointerCount == 1) { 1242 // Single-touch to multi-touch transition. 1243 // Send an up event for the last pointer. 1244 final int lastX = tracker.getLastX(); 1245 final int lastY = tracker.getLastY(); 1246 mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY); 1247 tracker.onUpEvent(lastX, lastY, eventTime, null); 1248 } else if (pointerCount == 1 && oldPointerCount == 1) { 1249 tracker.onTouchEvent(action, x, y, eventTime, null); 1250 } else { 1251 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 1252 + " (old " + oldPointerCount + ")"); 1253 } 1254 return true; 1255 } 1256 1257 final PointerTrackerQueue queue = mPointerQueue; 1258 if (action == MotionEvent.ACTION_MOVE) { 1259 for (int i = 0; i < pointerCount; i++) { 1260 final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); 1261 tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue); 1262 } 1263 } else { 1264 final PointerTracker tracker = getPointerTracker(id); 1265 switch (action) { 1266 case MotionEvent.ACTION_DOWN: 1267 case MotionEvent.ACTION_POINTER_DOWN: 1268 tracker.onDownEvent(x, y, eventTime, queue); 1269 break; 1270 case MotionEvent.ACTION_UP: 1271 case MotionEvent.ACTION_POINTER_UP: 1272 tracker.onUpEvent(x, y, eventTime, queue); 1273 break; 1274 case MotionEvent.ACTION_CANCEL: 1275 tracker.onCancelEvent(x, y, eventTime, queue); 1276 break; 1277 } 1278 } 1279 1280 return true; 1281 } 1282 1283 protected void onSwipeDown() { 1284 mKeyboardActionListener.onSwipeDown(); 1285 } 1286 1287 public void closing() { 1288 mPreviewText.setVisibility(View.GONE); 1289 mHandler.cancelAllMessages(); 1290 1291 dismissMiniKeyboard(); 1292 mDirtyRect.union(0, 0, getWidth(), getHeight()); 1293 mPopupPanelCache.clear(); 1294 requestLayout(); 1295 } 1296 1297 public void purgeKeyboardAndClosing() { 1298 mKeyboard = null; 1299 closing(); 1300 } 1301 1302 @Override 1303 public void onDetachedFromWindow() { 1304 super.onDetachedFromWindow(); 1305 closing(); 1306 } 1307 1308 private boolean dismissMiniKeyboard() { 1309 if (mPopupWindow != null && mPopupWindow.isShowing()) { 1310 mPopupWindow.dismiss(); 1311 mPopupMiniKeyboardPanel = null; 1312 invalidateAllKeys(); 1313 return true; 1314 } 1315 return false; 1316 } 1317 1318 public boolean handleBack() { 1319 return dismissMiniKeyboard(); 1320 } 1321} 1322