KeyboardView.java revision a0537fb4c73dff8beecc328720830af9719d0277
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.os.SystemClock; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.util.TypedValue; 42import android.view.GestureDetector; 43import android.view.Gravity; 44import android.view.LayoutInflater; 45import android.view.MotionEvent; 46import android.view.View; 47import android.view.ViewGroup; 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.List; 56import java.util.WeakHashMap; 57 58/** 59 * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key 60 * presses and touch movements. 61 * 62 * @attr ref R.styleable#KeyboardView_keyBackground 63 * @attr ref R.styleable#KeyboardView_keyPreviewLayout 64 * @attr ref R.styleable#KeyboardView_keyPreviewOffset 65 * @attr ref R.styleable#KeyboardView_labelTextSize 66 * @attr ref R.styleable#KeyboardView_keyTextSize 67 * @attr ref R.styleable#KeyboardView_keyTextColor 68 * @attr ref R.styleable#KeyboardView_verticalCorrection 69 * @attr ref R.styleable#KeyboardView_popupLayout 70 */ 71public class KeyboardView extends View implements PointerTracker.UIProxy { 72 private static final String TAG = KeyboardView.class.getSimpleName(); 73 private static final boolean DEBUG_SHOW_ALIGN = false; 74 private static final boolean DEBUG_KEYBOARD_GRID = false; 75 76 private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = false; 77 private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; 78 79 public static final int COLOR_SCHEME_WHITE = 0; 80 public static final int COLOR_SCHEME_BLACK = 1; 81 82 // Timing constants 83 private final int mKeyRepeatInterval; 84 85 // Miscellaneous constants 86 private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; 87 private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1; 88 89 // XML attribute 90 private int mKeyLetterSize; 91 private int mKeyTextColor; 92 private int mKeyTextColorDisabled; 93 private Typeface mKeyLetterStyle = Typeface.DEFAULT; 94 private int mLabelTextSize; 95 private int mColorScheme = COLOR_SCHEME_WHITE; 96 private int mShadowColor; 97 private float mShadowRadius; 98 private Drawable mKeyBackground; 99 private float mBackgroundDimAmount; 100 private float mKeyHysteresisDistance; 101 private float mVerticalCorrection; 102 private int mPreviewOffset; 103 private int mPreviewHeight; 104 private int mPopupLayout; 105 106 // Main keyboard 107 private Keyboard mKeyboard; 108 109 // Key preview 110 private boolean mInForeground; 111 private TextView mPreviewText; 112 private int mPreviewTextSizeLarge; 113 private final int[] mOffsetInWindow = new int[2]; 114 private boolean mShowKeyPreview = true; 115 private int mKeyPreviewDisplayedY; 116 private final int mDelayBeforePreview; 117 private final int mDelayAfterPreview; 118 private ViewGroup mPreviewPlacer; 119 120 // Mini keyboard 121 private PopupWindow mMiniKeyboardWindow; 122 private KeyboardView mMiniKeyboardView; 123 private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>(); 124 private int mMiniKeyboardOriginX; 125 private int mMiniKeyboardOriginY; 126 private long mMiniKeyboardDisplayedTime; 127 private int[] mWindowOffset; 128 private final float mMiniKeyboardSlideAllowance; 129 private int mMiniKeyboardTrackerId; 130 private final boolean mConfigShowMiniKeyboardAtTouchedPoint; 131 132 /** Listener for {@link KeyboardActionListener}. */ 133 private KeyboardActionListener mKeyboardActionListener; 134 135 private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); 136 137 // TODO: Let the PointerTracker class manage this pointer queue 138 private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue(); 139 140 private final boolean mHasDistinctMultitouch; 141 private int mOldPointerCount = 1; 142 private int mOldKeyIndex; 143 144 // Accessibility 145 private boolean mIsAccessibilityEnabled; 146 147 protected KeyDetector mKeyDetector = new KeyDetector(); 148 149 // Swipe gesture detector 150 private GestureDetector mGestureDetector; 151 private final SwipeTracker mSwipeTracker = new SwipeTracker(); 152 private final int mSwipeThreshold; 153 private final boolean mDisambiguateSwipe; 154 155 // Drawing 156 /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ 157 private boolean mDrawPending; 158 /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ 159 private boolean mKeyboardChanged; 160 /** The dirty region in the keyboard bitmap */ 161 private final Rect mDirtyRect = new Rect(); 162 /** The key to invalidate. */ 163 private Key mInvalidatedKey; 164 /** The dirty region for single key drawing */ 165 private final Rect mInvalidatedKeyRect = new Rect(); 166 /** The keyboard bitmap for faster updates */ 167 private Bitmap mBuffer; 168 /** The canvas for the above mutable keyboard bitmap */ 169 private Canvas mCanvas; 170 private final Paint mPaint; 171 private final Rect mPadding; 172 // This map caches key label text height in pixel as value and key label text size as map key. 173 private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>(); 174 // Distance from horizontal center of the key, proportional to key label text height and width. 175 private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER = 0.45f; 176 private final float KEY_LABEL_VERTICAL_PADDING_FACTOR = 1.60f; 177 private final String KEY_LABEL_REFERENCE_CHAR = "H"; 178 private final int KEY_LABEL_OPTION_ALIGN_LEFT = 1; 179 private final int KEY_LABEL_OPTION_ALIGN_RIGHT = 2; 180 private final int KEY_LABEL_OPTION_ALIGN_BOTTOM = 8; 181 private final int KEY_LABEL_OPTION_FONT_NORMAL = 16; 182 private final int mKeyLabelHorizontalPadding; 183 184 private final UIHandler mHandler = new UIHandler(); 185 186 class UIHandler extends Handler { 187 private static final int MSG_SHOW_KEY_PREVIEW = 1; 188 private static final int MSG_DISMISS_KEY_PREVIEW = 2; 189 private static final int MSG_REPEAT_KEY = 3; 190 private static final int MSG_LONGPRESS_KEY = 4; 191 private static final int MSG_LONGPRESS_SHIFT_KEY = 5; 192 193 private boolean mInKeyRepeat; 194 195 @Override 196 public void handleMessage(Message msg) { 197 switch (msg.what) { 198 case MSG_SHOW_KEY_PREVIEW: 199 showKey(msg.arg1, (PointerTracker)msg.obj); 200 break; 201 case MSG_DISMISS_KEY_PREVIEW: 202 mPreviewText.setVisibility(View.INVISIBLE); 203 break; 204 case MSG_REPEAT_KEY: { 205 final PointerTracker tracker = (PointerTracker)msg.obj; 206 tracker.onRepeatKey(msg.arg1); 207 startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker); 208 break; 209 } 210 case MSG_LONGPRESS_KEY: { 211 final PointerTracker tracker = (PointerTracker)msg.obj; 212 openMiniKeyboardIfRequired(msg.arg1, tracker); 213 break; 214 } 215 case MSG_LONGPRESS_SHIFT_KEY: { 216 final PointerTracker tracker = (PointerTracker)msg.obj; 217 onLongPressShiftKey(tracker); 218 break; 219 } 220 } 221 } 222 223 public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) { 224 removeMessages(MSG_SHOW_KEY_PREVIEW); 225 if (mPreviewText.getVisibility() == VISIBLE || delay == 0) { 226 // Show right away, if it's already visible and finger is moving around 227 showKey(keyIndex, tracker); 228 } else { 229 sendMessageDelayed( 230 obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay); 231 } 232 } 233 234 public void cancelShowKeyPreview(PointerTracker tracker) { 235 removeMessages(MSG_SHOW_KEY_PREVIEW, tracker); 236 } 237 238 public void cancelAllShowKeyPreviews() { 239 removeMessages(MSG_SHOW_KEY_PREVIEW); 240 } 241 242 public void dismissKeyPreview(long delay, PointerTracker tracker) { 243 sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay); 244 } 245 246 public void cancelDismissKeyPreview(PointerTracker tracker) { 247 removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); 248 } 249 250 public void cancelAllDismissKeyPreviews() { 251 removeMessages(MSG_DISMISS_KEY_PREVIEW); 252 } 253 254 public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { 255 mInKeyRepeat = true; 256 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); 257 } 258 259 public void cancelKeyRepeatTimer() { 260 mInKeyRepeat = false; 261 removeMessages(MSG_REPEAT_KEY); 262 } 263 264 public boolean isInKeyRepeat() { 265 return mInKeyRepeat; 266 } 267 268 public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { 269 cancelLongPressTimers(); 270 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); 271 } 272 273 public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) { 274 cancelLongPressTimers(); 275 if (ENABLE_CAPSLOCK_BY_LONGPRESS) { 276 sendMessageDelayed( 277 obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay); 278 } 279 } 280 281 public void cancelLongPressTimers() { 282 removeMessages(MSG_LONGPRESS_KEY); 283 removeMessages(MSG_LONGPRESS_SHIFT_KEY); 284 } 285 286 public void cancelKeyTimers() { 287 cancelKeyRepeatTimer(); 288 cancelLongPressTimers(); 289 } 290 291 public void cancelAllMessages() { 292 cancelKeyTimers(); 293 cancelAllShowKeyPreviews(); 294 cancelAllDismissKeyPreviews(); 295 } 296 } 297 298 public KeyboardView(Context context, AttributeSet attrs) { 299 this(context, attrs, R.attr.keyboardViewStyle); 300 } 301 302 public KeyboardView(Context context, AttributeSet attrs, int defStyle) { 303 super(context, attrs, defStyle); 304 305 final TypedArray a = context.obtainStyledAttributes( 306 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 307 int previewLayout = 0; 308 int keyTextSize = 0; 309 310 int n = a.getIndexCount(); 311 312 for (int i = 0; i < n; i++) { 313 int attr = a.getIndex(i); 314 315 switch (attr) { 316 case R.styleable.KeyboardView_keyBackground: 317 mKeyBackground = a.getDrawable(attr); 318 break; 319 case R.styleable.KeyboardView_keyHysteresisDistance: 320 mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0); 321 break; 322 case R.styleable.KeyboardView_verticalCorrection: 323 mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); 324 break; 325 case R.styleable.KeyboardView_keyPreviewLayout: 326 previewLayout = a.getResourceId(attr, 0); 327 break; 328 case R.styleable.KeyboardView_keyPreviewOffset: 329 mPreviewOffset = a.getDimensionPixelOffset(attr, 0); 330 break; 331 case R.styleable.KeyboardView_keyPreviewHeight: 332 mPreviewHeight = a.getDimensionPixelSize(attr, 80); 333 break; 334 case R.styleable.KeyboardView_keyLetterSize: 335 mKeyLetterSize = a.getDimensionPixelSize(attr, 18); 336 break; 337 case R.styleable.KeyboardView_keyTextColor: 338 mKeyTextColor = a.getColor(attr, 0xFF000000); 339 break; 340 case R.styleable.KeyboardView_keyTextColorDisabled: 341 mKeyTextColorDisabled = a.getColor(attr, 0xFF000000); 342 break; 343 case R.styleable.KeyboardView_labelTextSize: 344 mLabelTextSize = a.getDimensionPixelSize(attr, 14); 345 break; 346 case R.styleable.KeyboardView_popupLayout: 347 mPopupLayout = a.getResourceId(attr, 0); 348 break; 349 case R.styleable.KeyboardView_shadowColor: 350 mShadowColor = a.getColor(attr, 0); 351 break; 352 case R.styleable.KeyboardView_shadowRadius: 353 mShadowRadius = a.getFloat(attr, 0f); 354 break; 355 // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) 356 case R.styleable.KeyboardView_backgroundDimAmount: 357 mBackgroundDimAmount = a.getFloat(attr, 0.5f); 358 break; 359 case R.styleable.KeyboardView_keyLetterStyle: 360 mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL)); 361 break; 362 case R.styleable.KeyboardView_colorScheme: 363 mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE); 364 break; 365 } 366 } 367 368 final Resources res = getResources(); 369 370 if (previewLayout != 0) { 371 mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null); 372 mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); 373 } else { 374 mShowKeyPreview = false; 375 } 376 mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview); 377 mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview); 378 mKeyLabelHorizontalPadding = (int)res.getDimension( 379 R.dimen.key_label_horizontal_alignment_padding); 380 381 mMiniKeyboardWindow = new PopupWindow(context); 382 mMiniKeyboardWindow.setBackgroundDrawable(null); 383 mMiniKeyboardWindow.setAnimationStyle(R.style.MiniKeyboardAnimation); 384 // Allow popup window to be drawn off the screen. 385 mMiniKeyboardWindow.setClippingEnabled(false); 386 387 mPaint = new Paint(); 388 mPaint.setAntiAlias(true); 389 mPaint.setTextSize(keyTextSize); 390 mPaint.setTextAlign(Align.CENTER); 391 mPaint.setAlpha(255); 392 393 mPadding = new Rect(0, 0, 0, 0); 394 mKeyBackground.getPadding(mPadding); 395 396 mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); 397 // TODO: Refer frameworks/base/core/res/res/values/config.xml 398 mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); 399 mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance); 400 mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean( 401 R.bool.config_show_mini_keyboard_at_touched_point); 402 403 GestureDetector.SimpleOnGestureListener listener = 404 new GestureDetector.SimpleOnGestureListener() { 405 private boolean mProcessingShiftDoubleTapEvent = false; 406 407 @Override 408 public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, 409 float velocityY) { 410 final float absX = Math.abs(velocityX); 411 final float absY = Math.abs(velocityY); 412 float deltaY = me2.getY() - me1.getY(); 413 int travelY = getHeight() / 2; // Half the keyboard height 414 mSwipeTracker.computeCurrentVelocity(1000); 415 final float endingVelocityY = mSwipeTracker.getYVelocity(); 416 if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { 417 if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { 418 onSwipeDown(); 419 return true; 420 } 421 } 422 return false; 423 } 424 425 @Override 426 public boolean onDoubleTap(MotionEvent firstDown) { 427 if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard 428 && ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) { 429 final int pointerIndex = firstDown.getActionIndex(); 430 final int id = firstDown.getPointerId(pointerIndex); 431 final PointerTracker tracker = getPointerTracker(id); 432 // If the first down event is on shift key. 433 if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) { 434 mProcessingShiftDoubleTapEvent = true; 435 return true; 436 } 437 } 438 mProcessingShiftDoubleTapEvent = false; 439 return false; 440 } 441 442 @Override 443 public boolean onDoubleTapEvent(MotionEvent secondTap) { 444 if (mProcessingShiftDoubleTapEvent 445 && secondTap.getAction() == MotionEvent.ACTION_DOWN) { 446 final MotionEvent secondDown = secondTap; 447 final int pointerIndex = secondDown.getActionIndex(); 448 final int id = secondDown.getPointerId(pointerIndex); 449 final PointerTracker tracker = getPointerTracker(id); 450 // If the second down event is also on shift key. 451 if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) { 452 onDoubleTapShiftKey(tracker); 453 return true; 454 } 455 // Otherwise these events should not be handled as double tap. 456 mProcessingShiftDoubleTapEvent = false; 457 } 458 return mProcessingShiftDoubleTapEvent; 459 } 460 }; 461 462 final boolean ignoreMultitouch = true; 463 mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); 464 mGestureDetector.setIsLongpressEnabled(false); 465 466 mHasDistinctMultitouch = context.getPackageManager() 467 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 468 mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); 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 mMiniKeyboardCache.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 (mMiniKeyboardView != 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 placer.addView(keyPreview, new FrameLayout.LayoutParams(0, 0)); 929 } else { 930 placer.addView(keyPreview, new LinearLayout.LayoutParams(0, 0)); 931 } 932 } 933 934 // TODO: Introduce minimum duration for displaying key previews 935 // TODO: Display up to two key previews when the user presses two keys at the same time 936 private void showKey(final int keyIndex, PointerTracker tracker) { 937 // If the key preview has no parent view yet, add it to the ViewGroup which can place 938 // key preview absolutely in SoftInputWindow. 939 if (mPreviewText.getParent() == null) { 940 addKeyPreview(mPreviewText); 941 } 942 943 final Key key = tracker.getKey(keyIndex); 944 // If keyIndex is invalid or IME is already closed, we must not show key preview. 945 // Trying to show key preview while root window is closed causes 946 // WindowManager.BadTokenException. 947 if (key == null || !mInForeground) 948 return; 949 final int keyDrawX = key.mX + key.mVisualInsetsLeft; 950 final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 951 // What we show as preview should match what we show on key top in onBufferDraw(). 952 if (key.mLabel != null) { 953 // TODO Should take care of temporaryShiftLabel here. 954 mPreviewText.setCompoundDrawables(null, null, null, null); 955 mPreviewText.setText(adjustCase(tracker.getPreviewText(key))); 956 if (key.mLabel.length() > 1) { 957 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize); 958 mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); 959 } else { 960 mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); 961 mPreviewText.setTypeface(mKeyLetterStyle); 962 } 963 } else { 964 final Drawable previewIcon = key.getPreviewIcon(); 965 mPreviewText.setCompoundDrawables(null, null, null, 966 previewIcon != null ? previewIcon : key.getIcon()); 967 mPreviewText.setText(null); 968 } 969 // Set the preview background state 970 mPreviewText.getBackground().setState( 971 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); 972 973 mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 974 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 975 int previewWidth = Math.max(mPreviewText.getMeasuredWidth(), keyDrawWidth 976 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); 977 final int previewHeight = mPreviewHeight; 978 final ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams(); 979 lp.width = previewWidth; 980 lp.height = previewHeight; 981 982 int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2; 983 int previewY = key.mY - previewHeight + mPreviewOffset; 984 985 mHandler.cancelAllDismissKeyPreviews(); 986 getLocationInWindow(mOffsetInWindow); 987 previewX += mOffsetInWindow[0]; 988 previewY += mOffsetInWindow[1]; 989 990 // Place the key preview. 991 // TODO: Adjust position of key previews which touch screen edges 992 if (lp instanceof ViewGroup.MarginLayoutParams) { 993 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp; 994 mlp.setMargins(previewX, previewY, 0, 0); 995 } 996 // Record key preview position to display mini-keyboard later at the same position 997 mKeyPreviewDisplayedY = previewY; 998 mPreviewText.setVisibility(VISIBLE); 999 } 1000 1001 /** 1002 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 1003 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 1004 * draws the cached buffer. 1005 * @see #invalidateKey(Key) 1006 */ 1007 public void invalidateAllKeys() { 1008 mDirtyRect.union(0, 0, getWidth(), getHeight()); 1009 mDrawPending = true; 1010 invalidate(); 1011 } 1012 1013 /** 1014 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 1015 * one key is changing it's content. Any changes that affect the position or size of the key 1016 * may not be honored. 1017 * @param key key in the attached {@link Keyboard}. 1018 * @see #invalidateAllKeys 1019 */ 1020 @Override 1021 public void invalidateKey(Key key) { 1022 if (key == null) 1023 return; 1024 mInvalidatedKey = key; 1025 mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight); 1026 mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop()); 1027 mDirtyRect.union(mInvalidatedKeyRect); 1028 onBufferDraw(); 1029 invalidate(mInvalidatedKeyRect); 1030 } 1031 1032 private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) { 1033 // Check if we have a popup layout specified first. 1034 if (mPopupLayout == 0) { 1035 return false; 1036 } 1037 1038 Key parentKey = tracker.getKey(keyIndex); 1039 if (parentKey == null) 1040 return false; 1041 boolean result = onLongPress(parentKey, tracker); 1042 if (result) { 1043 dismissAllKeyPreviews(); 1044 mMiniKeyboardTrackerId = tracker.mPointerId; 1045 tracker.onLongPressed(mPointerQueue); 1046 } 1047 return result; 1048 } 1049 1050 private void onLongPressShiftKey(PointerTracker tracker) { 1051 tracker.onLongPressed(mPointerQueue); 1052 mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); 1053 } 1054 1055 private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) { 1056 // When shift key is double tapped, the first tap is correctly processed as usual tap. And 1057 // the second tap is treated as this double tap event, so that we need not mark tracker 1058 // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue. 1059 mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); 1060 } 1061 1062 private View inflateMiniKeyboardContainer(Key parentKey) { 1063 final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null); 1064 if (container == null) 1065 throw new NullPointerException(); 1066 1067 final KeyboardView miniKeyboardView = 1068 (KeyboardView)container.findViewById(R.id.KeyboardView); 1069 miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() { 1070 @Override 1071 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { 1072 mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y); 1073 dismissMiniKeyboard(); 1074 } 1075 1076 @Override 1077 public void onTextInput(CharSequence text) { 1078 mKeyboardActionListener.onTextInput(text); 1079 dismissMiniKeyboard(); 1080 } 1081 1082 @Override 1083 public void onCancelInput() { 1084 mKeyboardActionListener.onCancelInput(); 1085 dismissMiniKeyboard(); 1086 } 1087 1088 @Override 1089 public void onSwipeDown() { 1090 // Nothing to do. 1091 } 1092 @Override 1093 public void onPress(int primaryCode, boolean withSliding) { 1094 mKeyboardActionListener.onPress(primaryCode, withSliding); 1095 } 1096 @Override 1097 public void onRelease(int primaryCode, boolean withSliding) { 1098 mKeyboardActionListener.onRelease(primaryCode, withSliding); 1099 } 1100 }); 1101 // Override default ProximityKeyDetector. 1102 miniKeyboardView.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance); 1103 // Remove gesture detector on mini-keyboard 1104 miniKeyboardView.mGestureDetector = null; 1105 1106 final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(), 1107 parentKey).build(); 1108 miniKeyboardView.setKeyboard(keyboard); 1109 1110 container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), 1111 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); 1112 1113 return container; 1114 } 1115 1116 private static boolean isOneRowKeys(List<Key> keys) { 1117 if (keys.size() == 0) return false; 1118 final int edgeFlags = keys.get(0).mEdgeFlags; 1119 // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows, 1120 // does not have both top and bottom edge flags on at the same time. On the other hand, 1121 // the first key of mini keyboard that was created with popupCharacters must have both top 1122 // and bottom edge flags on. 1123 // When you want to use one row mini-keyboard from xml file, make sure that the row has 1124 // both top and bottom edge flags set. 1125 return (edgeFlags & Keyboard.EDGE_TOP) != 0 1126 && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0; 1127 } 1128 1129 /** 1130 * Called when a key is long pressed. By default this will open any mini keyboard associated 1131 * with this key through the attributes popupLayout and popupCharacters. 1132 * @param parentKey the key that was long pressed 1133 * @return true if the long press is handled, false otherwise. Subclasses should call the 1134 * method on the base class if the subclass doesn't wish to handle the call. 1135 */ 1136 protected boolean onLongPress(Key parentKey, PointerTracker tracker) { 1137 if (parentKey.mPopupCharacters == null) 1138 return false; 1139 1140 View container = mMiniKeyboardCache.get(parentKey); 1141 if (container == null) { 1142 container = inflateMiniKeyboardContainer(parentKey); 1143 mMiniKeyboardCache.put(parentKey, container); 1144 } 1145 mMiniKeyboardView = (KeyboardView)container.findViewById(R.id.KeyboardView); 1146 final MiniKeyboard miniKeyboard = (MiniKeyboard)mMiniKeyboardView.getKeyboard(); 1147 1148 if (mWindowOffset == null) { 1149 mWindowOffset = new int[2]; 1150 getLocationInWindow(mWindowOffset); 1151 } 1152 final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX() 1153 : parentKey.mX + parentKey.mWidth / 2; 1154 final int miniKeyboardX = pointX - miniKeyboard.getDefaultCoordX() 1155 - container.getPaddingLeft() 1156 + getPaddingLeft() + mWindowOffset[0]; 1157 final int miniKeyboardY = parentKey.mY - mKeyboard.getVerticalGap() 1158 - (container.getMeasuredHeight() - container.getPaddingBottom()) 1159 + getPaddingTop() + mWindowOffset[1]; 1160 final int x = miniKeyboardX; 1161 final int y = mShowKeyPreview && isOneRowKeys(miniKeyboard.getKeys()) 1162 ? mKeyPreviewDisplayedY : miniKeyboardY; 1163 1164 mMiniKeyboardOriginX = x + container.getPaddingLeft() - mWindowOffset[0]; 1165 mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1]; 1166 if (miniKeyboard.setShifted( 1167 mKeyboard == null ? false : mKeyboard.isShiftedOrShiftLocked())) { 1168 mMiniKeyboardView.invalidateAllKeys(); 1169 } 1170 // Mini keyboard needs no pop-up key preview displayed. 1171 mMiniKeyboardView.setKeyPreviewEnabled(false); 1172 mMiniKeyboardWindow.setContentView(container); 1173 mMiniKeyboardWindow.setWidth(container.getMeasuredWidth()); 1174 mMiniKeyboardWindow.setHeight(container.getMeasuredHeight()); 1175 mMiniKeyboardWindow.showAtLocation(this, Gravity.NO_GRAVITY, x, y); 1176 1177 // Inject down event on the key to mini keyboard. 1178 final long eventTime = SystemClock.uptimeMillis(); 1179 mMiniKeyboardDisplayedTime = eventTime; 1180 final MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, 1181 pointX, parentKey.mY + parentKey.mHeight / 2, eventTime); 1182 mMiniKeyboardView.onTouchEvent(downEvent); 1183 downEvent.recycle(); 1184 1185 invalidateAllKeys(); 1186 return true; 1187 } 1188 1189 private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) { 1190 return MotionEvent.obtain(mMiniKeyboardDisplayedTime, eventTime, action, 1191 x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0); 1192 } 1193 1194 private PointerTracker getPointerTracker(final int id) { 1195 final ArrayList<PointerTracker> pointers = mPointerTrackers; 1196 final KeyboardActionListener listener = mKeyboardActionListener; 1197 1198 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 1199 for (int i = pointers.size(); i <= id; i++) { 1200 final PointerTracker tracker = 1201 new PointerTracker(i, this, mHandler, mKeyDetector, this); 1202 if (mKeyboard != null) 1203 tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance); 1204 if (listener != null) 1205 tracker.setOnKeyboardActionListener(listener); 1206 pointers.add(tracker); 1207 } 1208 1209 return pointers.get(id); 1210 } 1211 1212 public boolean isInSlidingKeyInput() { 1213 if (mMiniKeyboardView != null) { 1214 return mMiniKeyboardView.isInSlidingKeyInput(); 1215 } else { 1216 return mPointerQueue.isInSlidingKeyInput(); 1217 } 1218 } 1219 1220 public int getPointerCount() { 1221 return mOldPointerCount; 1222 } 1223 1224 @Override 1225 public boolean onTouchEvent(MotionEvent me) { 1226 final int action = me.getActionMasked(); 1227 final int pointerCount = me.getPointerCount(); 1228 final int oldPointerCount = mOldPointerCount; 1229 mOldPointerCount = pointerCount; 1230 1231 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1232 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 1233 // events except a transition from/to single-touch. 1234 if ((!mHasDistinctMultitouch || mIsAccessibilityEnabled) 1235 && pointerCount > 1 && oldPointerCount > 1) { 1236 return true; 1237 } 1238 1239 // Track the last few movements to look for spurious swipes. 1240 mSwipeTracker.addMovement(me); 1241 1242 // Gesture detector must be enabled only when mini-keyboard is not on the screen and 1243 // accessibility is not enabled. 1244 // TODO: Reconcile gesture detection and accessibility features. 1245 if (mMiniKeyboardView == null && !mIsAccessibilityEnabled 1246 && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { 1247 dismissAllKeyPreviews(); 1248 mHandler.cancelKeyTimers(); 1249 return true; 1250 } 1251 1252 final long eventTime = me.getEventTime(); 1253 final int index = me.getActionIndex(); 1254 final int id = me.getPointerId(index); 1255 final int x = (int)me.getX(index); 1256 final int y = (int)me.getY(index); 1257 1258 // Needs to be called after the gesture detector gets a turn, as it may have 1259 // displayed the mini keyboard 1260 if (mMiniKeyboardView != null) { 1261 final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId); 1262 if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) { 1263 final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex); 1264 final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex); 1265 MotionEvent translated = generateMiniKeyboardMotionEvent(action, 1266 miniKeyboardX, miniKeyboardY, eventTime); 1267 mMiniKeyboardView.onTouchEvent(translated); 1268 translated.recycle(); 1269 } 1270 return true; 1271 } 1272 1273 if (mHandler.isInKeyRepeat()) { 1274 final PointerTracker tracker = getPointerTracker(id); 1275 // Key repeating timer will be canceled if 2 or more keys are in action, and current 1276 // event (UP or DOWN) is non-modifier key. 1277 if (pointerCount > 1 && !tracker.isModifier()) { 1278 mHandler.cancelKeyRepeatTimer(); 1279 } 1280 // Up event will pass through. 1281 } 1282 1283 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1284 // Translate mutli-touch event to single-touch events on the device that has no distinct 1285 // multi-touch panel. 1286 if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) { 1287 // Use only main (id=0) pointer tracker. 1288 PointerTracker tracker = getPointerTracker(0); 1289 if (pointerCount == 1 && oldPointerCount == 2) { 1290 // Multi-touch to single touch transition. 1291 // Send a down event for the latest pointer if the key is different from the 1292 // previous key. 1293 final int newKeyIndex = tracker.getKeyIndexOn(x, y); 1294 if (mOldKeyIndex != newKeyIndex) { 1295 tracker.onDownEvent(x, y, eventTime, null); 1296 if (action == MotionEvent.ACTION_UP) 1297 tracker.onUpEvent(x, y, eventTime, null); 1298 } 1299 } else if (pointerCount == 2 && oldPointerCount == 1) { 1300 // Single-touch to multi-touch transition. 1301 // Send an up event for the last pointer. 1302 final int lastX = tracker.getLastX(); 1303 final int lastY = tracker.getLastY(); 1304 mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY); 1305 tracker.onUpEvent(lastX, lastY, eventTime, null); 1306 } else if (pointerCount == 1 && oldPointerCount == 1) { 1307 tracker.onTouchEvent(action, x, y, eventTime, null); 1308 } else { 1309 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 1310 + " (old " + oldPointerCount + ")"); 1311 } 1312 return true; 1313 } 1314 1315 final PointerTrackerQueue queue = mPointerQueue; 1316 if (action == MotionEvent.ACTION_MOVE) { 1317 for (int i = 0; i < pointerCount; i++) { 1318 final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); 1319 tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue); 1320 } 1321 } else { 1322 final PointerTracker tracker = getPointerTracker(id); 1323 switch (action) { 1324 case MotionEvent.ACTION_DOWN: 1325 case MotionEvent.ACTION_POINTER_DOWN: 1326 tracker.onDownEvent(x, y, eventTime, queue); 1327 break; 1328 case MotionEvent.ACTION_UP: 1329 case MotionEvent.ACTION_POINTER_UP: 1330 tracker.onUpEvent(x, y, eventTime, queue); 1331 break; 1332 case MotionEvent.ACTION_CANCEL: 1333 tracker.onCancelEvent(x, y, eventTime, queue); 1334 break; 1335 } 1336 } 1337 1338 return true; 1339 } 1340 1341 protected void onSwipeDown() { 1342 mKeyboardActionListener.onSwipeDown(); 1343 } 1344 1345 public void closing() { 1346 mPreviewText.setVisibility(View.GONE); 1347 mHandler.cancelAllMessages(); 1348 1349 dismissMiniKeyboard(); 1350 mDirtyRect.union(0, 0, getWidth(), getHeight()); 1351 mMiniKeyboardCache.clear(); 1352 requestLayout(); 1353 } 1354 1355 public void purgeKeyboardAndClosing() { 1356 mKeyboard = null; 1357 closing(); 1358 } 1359 1360 @Override 1361 public void onDetachedFromWindow() { 1362 super.onDetachedFromWindow(); 1363 closing(); 1364 } 1365 1366 private void dismissMiniKeyboard() { 1367 if (mMiniKeyboardWindow.isShowing()) { 1368 mMiniKeyboardWindow.dismiss(); 1369 mMiniKeyboardView = null; 1370 mMiniKeyboardOriginX = 0; 1371 mMiniKeyboardOriginY = 0; 1372 invalidateAllKeys(); 1373 } 1374 } 1375 1376 public boolean handleBack() { 1377 if (mMiniKeyboardWindow.isShowing()) { 1378 dismissMiniKeyboard(); 1379 return true; 1380 } 1381 return false; 1382 } 1383} 1384