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