1 /* 2 * Copyright (C) 2011 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 17 package com.android.inputmethod.keyboard; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.content.res.Resources; 22 import android.graphics.Canvas; 23 import android.os.Message; 24 import android.os.SystemClock; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.GestureDetector; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewConfiguration; 32 import android.view.ViewGroup; 33 import android.view.accessibility.AccessibilityEvent; 34 import android.widget.PopupWindow; 35 36 import com.android.inputmethod.accessibility.AccessibilityUtils; 37 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 38 import com.android.inputmethod.deprecated.VoiceProxy; 39 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; 40 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 41 import com.android.inputmethod.latin.LatinIME; 42 import com.android.inputmethod.latin.R; 43 import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 44 import com.android.inputmethod.latin.Utils; 45 46 import java.util.WeakHashMap; 47 48 /** 49 * A view that is responsible for detecting key presses and touch movements. 50 * 51 * @attr ref R.styleable#KeyboardView_keyHysteresisDistance 52 * @attr ref R.styleable#KeyboardView_verticalCorrection 53 * @attr ref R.styleable#KeyboardView_popupLayout 54 */ 55 public class LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 56 SuddenJumpingTouchEventHandler.ProcessMotionEvent { 57 private static final String TAG = LatinKeyboardView.class.getSimpleName(); 58 59 private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; 60 61 private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; 62 63 // Timing constants 64 private final int mKeyRepeatInterval; 65 66 // Mini keyboard 67 private PopupWindow mMoreKeysWindow; 68 private MoreKeysPanel mMoreKeysPanel; 69 private int mMoreKeysPanelPointerTrackerId; 70 private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache = 71 new WeakHashMap<Key, MoreKeysPanel>(); 72 73 /** Listener for {@link KeyboardActionListener}. */ 74 private KeyboardActionListener mKeyboardActionListener; 75 76 private final boolean mHasDistinctMultitouch; 77 private int mOldPointerCount = 1; 78 private int mOldKeyIndex; 79 80 private final boolean mConfigShowMiniKeyboardAtTouchedPoint; 81 protected KeyDetector mKeyDetector; 82 83 // To detect double tap. 84 protected GestureDetector mGestureDetector; 85 86 private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this); 87 88 private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView> 89 implements TimerProxy { 90 private static final int MSG_REPEAT_KEY = 1; 91 private static final int MSG_LONGPRESS_KEY = 2; 92 private static final int MSG_IGNORE_DOUBLE_TAP = 3; 93 94 private boolean mInKeyRepeat; 95 96 public KeyTimerHandler(LatinKeyboardView outerInstance) { 97 super(outerInstance); 98 } 99 100 @Override 101 public void handleMessage(Message msg) { 102 final LatinKeyboardView keyboardView = getOuterInstance(); 103 final PointerTracker tracker = (PointerTracker) msg.obj; 104 switch (msg.what) { 105 case MSG_REPEAT_KEY: 106 tracker.onRepeatKey(msg.arg1); 107 startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker); 108 break; 109 case MSG_LONGPRESS_KEY: 110 keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker); 111 break; 112 } 113 } 114 115 @Override 116 public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { 117 mInKeyRepeat = true; 118 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); 119 } 120 121 public void cancelKeyRepeatTimer() { 122 mInKeyRepeat = false; 123 removeMessages(MSG_REPEAT_KEY); 124 } 125 126 public boolean isInKeyRepeat() { 127 return mInKeyRepeat; 128 } 129 130 @Override 131 public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { 132 cancelLongPressTimer(); 133 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); 134 } 135 136 @Override 137 public void cancelLongPressTimer() { 138 removeMessages(MSG_LONGPRESS_KEY); 139 } 140 141 @Override 142 public void cancelKeyTimers() { 143 cancelKeyRepeatTimer(); 144 cancelLongPressTimer(); 145 removeMessages(MSG_IGNORE_DOUBLE_TAP); 146 } 147 148 public void startIgnoringDoubleTap() { 149 sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP), 150 ViewConfiguration.getDoubleTapTimeout()); 151 } 152 153 public boolean isIgnoringDoubleTap() { 154 return hasMessages(MSG_IGNORE_DOUBLE_TAP); 155 } 156 157 public void cancelAllMessages() { 158 cancelKeyTimers(); 159 } 160 } 161 162 private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { 163 private boolean mProcessingShiftDoubleTapEvent = false; 164 165 @Override 166 public boolean onDoubleTap(MotionEvent firstDown) { 167 final Keyboard keyboard = getKeyboard(); 168 if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard 169 && ((LatinKeyboard) keyboard).isAlphaKeyboard()) { 170 final int pointerIndex = firstDown.getActionIndex(); 171 final int id = firstDown.getPointerId(pointerIndex); 172 final PointerTracker tracker = getPointerTracker(id); 173 // If the first down event is on shift key. 174 if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) { 175 mProcessingShiftDoubleTapEvent = true; 176 return true; 177 } 178 } 179 mProcessingShiftDoubleTapEvent = false; 180 return false; 181 } 182 183 @Override 184 public boolean onDoubleTapEvent(MotionEvent secondTap) { 185 if (mProcessingShiftDoubleTapEvent 186 && secondTap.getAction() == MotionEvent.ACTION_DOWN) { 187 final MotionEvent secondDown = secondTap; 188 final int pointerIndex = secondDown.getActionIndex(); 189 final int id = secondDown.getPointerId(pointerIndex); 190 final PointerTracker tracker = getPointerTracker(id); 191 // If the second down event is also on shift key. 192 if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) { 193 // Detected a double tap on shift key. If we are in the ignoring double tap 194 // mode, it means we have already turned off caps lock in 195 // {@link KeyboardSwitcher#onReleaseShift} . 196 onDoubleTapShiftKey(tracker, mKeyTimerHandler.isIgnoringDoubleTap()); 197 return true; 198 } 199 // Otherwise these events should not be handled as double tap. 200 mProcessingShiftDoubleTapEvent = false; 201 } 202 return mProcessingShiftDoubleTapEvent; 203 } 204 } 205 206 public LatinKeyboardView(Context context, AttributeSet attrs) { 207 this(context, attrs, R.attr.keyboardViewStyle); 208 } 209 210 public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { 211 super(context, attrs, defStyle); 212 213 mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); 214 215 final Resources res = getResources(); 216 mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean( 217 R.bool.config_show_mini_keyboard_at_touched_point); 218 final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance); 219 mKeyDetector = new KeyDetector(keyHysteresisDistance); 220 221 final boolean ignoreMultitouch = true; 222 mGestureDetector = new GestureDetector( 223 getContext(), new DoubleTapListener(), null, ignoreMultitouch); 224 mGestureDetector.setIsLongpressEnabled(false); 225 226 mHasDistinctMultitouch = context.getPackageManager() 227 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 228 mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); 229 230 PointerTracker.init(mHasDistinctMultitouch, getContext()); 231 } 232 233 public void startIgnoringDoubleTap() { 234 if (ENABLE_CAPSLOCK_BY_DOUBLETAP) 235 mKeyTimerHandler.startIgnoringDoubleTap(); 236 } 237 238 public void setKeyboardActionListener(KeyboardActionListener listener) { 239 mKeyboardActionListener = listener; 240 PointerTracker.setKeyboardActionListener(listener); 241 } 242 243 /** 244 * Returns the {@link KeyboardActionListener} object. 245 * @return the listener attached to this keyboard 246 */ 247 @Override 248 public KeyboardActionListener getKeyboardActionListener() { 249 return mKeyboardActionListener; 250 } 251 252 @Override 253 public KeyDetector getKeyDetector() { 254 return mKeyDetector; 255 } 256 257 @Override 258 public DrawingProxy getDrawingProxy() { 259 return this; 260 } 261 262 @Override 263 public TimerProxy getTimerProxy() { 264 return mKeyTimerHandler; 265 } 266 267 @Override 268 public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { 269 final Keyboard keyboard = getKeyboard(); 270 if (keyboard instanceof LatinKeyboard) { 271 final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard; 272 if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) { 273 // Phone and number keyboard never shows popup preview. 274 super.setKeyPreviewPopupEnabled(false, delay); 275 return; 276 } 277 } 278 super.setKeyPreviewPopupEnabled(previewEnabled, delay); 279 } 280 281 /** 282 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 283 * view will re-layout itself to accommodate the keyboard. 284 * @see Keyboard 285 * @see #getKeyboard() 286 * @param keyboard the keyboard to display in this view 287 */ 288 @Override 289 public void setKeyboard(Keyboard keyboard) { 290 // Remove any pending messages, except dismissing preview 291 mKeyTimerHandler.cancelKeyTimers(); 292 super.setKeyboard(keyboard); 293 mKeyDetector.setKeyboard( 294 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); 295 mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth); 296 PointerTracker.setKeyDetector(mKeyDetector); 297 mTouchScreenRegulator.setKeyboard(keyboard); 298 mMoreKeysPanelCache.clear(); 299 } 300 301 /** 302 * Returns whether the device has distinct multi-touch panel. 303 * @return true if the device has distinct multi-touch panel. 304 */ 305 public boolean hasDistinctMultitouch() { 306 return mHasDistinctMultitouch; 307 } 308 309 /** 310 * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key 311 * codes for adjacent keys. When disabled, only the primary key code will be 312 * reported. 313 * @param enabled whether or not the proximity correction is enabled 314 */ 315 public void setProximityCorrectionEnabled(boolean enabled) { 316 mKeyDetector.setProximityCorrectionEnabled(enabled); 317 } 318 319 /** 320 * Returns true if proximity correction is enabled. 321 */ 322 public boolean isProximityCorrectionEnabled() { 323 return mKeyDetector.isProximityCorrectionEnabled(); 324 } 325 326 @Override 327 public void cancelAllMessages() { 328 mKeyTimerHandler.cancelAllMessages(); 329 super.cancelAllMessages(); 330 } 331 332 private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) { 333 // Check if we have a popup layout specified first. 334 if (mMoreKeysLayout == 0) { 335 return false; 336 } 337 338 // Check if we are already displaying popup panel. 339 if (mMoreKeysPanel != null) 340 return false; 341 final Key parentKey = tracker.getKey(keyIndex); 342 if (parentKey == null) 343 return false; 344 return onLongPress(parentKey, tracker); 345 } 346 347 private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker, 348 final boolean ignore) { 349 // When shift key is double tapped, the first tap is correctly processed as usual tap. And 350 // the second tap is treated as this double tap event, so that we need not mark tracker 351 // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue. 352 final int primaryCode = ignore ? Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY 353 : Keyboard.CODE_CAPSLOCK; 354 mKeyboardActionListener.onCodeInput(primaryCode, null, 0, 0); 355 } 356 357 // This default implementation returns a more keys panel. 358 protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) { 359 if (parentKey.mMoreKeys == null) 360 return null; 361 362 final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); 363 if (container == null) 364 throw new NullPointerException(); 365 366 final MiniKeyboardView miniKeyboardView = 367 (MiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); 368 final Keyboard parentKeyboard = getKeyboard(); 369 final Keyboard miniKeyboard = new MiniKeyboard.Builder( 370 this, parentKeyboard.mMoreKeysTemplate, parentKey, parentKeyboard).build(); 371 miniKeyboardView.setKeyboard(miniKeyboard); 372 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 373 374 return miniKeyboardView; 375 } 376 377 public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) { 378 final Keyboard keyboard = getKeyboard(); 379 // We should not set text fade factor to the keyboard which does not display the language on 380 // its spacebar. 381 if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) { 382 ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this); 383 } 384 } 385 386 /** 387 * Called when a key is long pressed. By default this will open mini keyboard associated 388 * with this key. 389 * @param parentKey the key that was long pressed 390 * @param tracker the pointer tracker which pressed the parent key 391 * @return true if the long press is handled, false otherwise. Subclasses should call the 392 * method on the base class if the subclass doesn't wish to handle the call. 393 */ 394 protected boolean onLongPress(Key parentKey, PointerTracker tracker) { 395 final int primaryCode = parentKey.mCode; 396 final Keyboard keyboard = getKeyboard(); 397 if (keyboard instanceof LatinKeyboard) { 398 final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard; 399 if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) { 400 tracker.onLongPressed(); 401 // Long pressing on 0 in phone number keypad gives you a '+'. 402 return invokeOnKey(Keyboard.CODE_PLUS); 403 } 404 if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) { 405 tracker.onLongPressed(); 406 return invokeOnKey(Keyboard.CODE_CAPSLOCK); 407 } 408 } 409 if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) { 410 // Both long pressing settings key and space key invoke IME switcher dialog. 411 if (getKeyboardActionListener().onCustomRequest( 412 LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { 413 tracker.onLongPressed(); 414 return true; 415 } else { 416 return openMoreKeysPanel(parentKey, tracker); 417 } 418 } else { 419 return openMoreKeysPanel(parentKey, tracker); 420 } 421 } 422 423 private boolean invokeOnKey(int primaryCode) { 424 getKeyboardActionListener().onCodeInput(primaryCode, null, 425 KeyboardActionListener.NOT_A_TOUCH_COORDINATE, 426 KeyboardActionListener.NOT_A_TOUCH_COORDINATE); 427 return true; 428 } 429 430 private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) { 431 MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); 432 if (moreKeysPanel == null) { 433 moreKeysPanel = onCreateMoreKeysPanel(parentKey); 434 if (moreKeysPanel == null) 435 return false; 436 mMoreKeysPanelCache.put(parentKey, moreKeysPanel); 437 } 438 if (mMoreKeysWindow == null) { 439 mMoreKeysWindow = new PopupWindow(getContext()); 440 mMoreKeysWindow.setBackgroundDrawable(null); 441 mMoreKeysWindow.setAnimationStyle(R.style.MiniKeyboardAnimation); 442 } 443 mMoreKeysPanel = moreKeysPanel; 444 mMoreKeysPanelPointerTrackerId = tracker.mPointerId; 445 446 final Keyboard keyboard = getKeyboard(); 447 moreKeysPanel.setShifted(keyboard.isShiftedOrShiftLocked()); 448 final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX() 449 : parentKey.mX + parentKey.mWidth / 2; 450 final int pointY = parentKey.mY - keyboard.mVerticalGap; 451 moreKeysPanel.showMoreKeysPanel( 452 this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener()); 453 final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); 454 final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); 455 tracker.onShowMoreKeysPanel( 456 translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel); 457 dimEntireKeyboard(true); 458 return true; 459 } 460 461 private PointerTracker getPointerTracker(final int id) { 462 return PointerTracker.getPointerTracker(id, this); 463 } 464 465 public boolean isInSlidingKeyInput() { 466 if (mMoreKeysPanel != null) { 467 return true; 468 } else { 469 return PointerTracker.isAnyInSlidingKeyInput(); 470 } 471 } 472 473 public int getPointerCount() { 474 return mOldPointerCount; 475 } 476 477 @Override 478 public boolean onTouchEvent(MotionEvent me) { 479 if (getKeyboard() == null) { 480 return false; 481 } 482 return mTouchScreenRegulator.onTouchEvent(me); 483 } 484 485 @Override 486 public boolean processMotionEvent(MotionEvent me) { 487 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 488 final int action = me.getActionMasked(); 489 final int pointerCount = me.getPointerCount(); 490 final int oldPointerCount = mOldPointerCount; 491 mOldPointerCount = pointerCount; 492 493 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 494 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 495 // events except a transition from/to single-touch. 496 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 497 return true; 498 } 499 500 // Gesture detector must be enabled only when mini-keyboard is not on the screen. 501 if (mMoreKeysPanel == null && mGestureDetector != null 502 && mGestureDetector.onTouchEvent(me)) { 503 PointerTracker.dismissAllKeyPreviews(); 504 mKeyTimerHandler.cancelKeyTimers(); 505 return true; 506 } 507 508 final long eventTime = me.getEventTime(); 509 final int index = me.getActionIndex(); 510 final int id = me.getPointerId(index); 511 final int x, y; 512 if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) { 513 x = mMoreKeysPanel.translateX((int)me.getX(index)); 514 y = mMoreKeysPanel.translateY((int)me.getY(index)); 515 } else { 516 x = (int)me.getX(index); 517 y = (int)me.getY(index); 518 } 519 520 if (mKeyTimerHandler.isInKeyRepeat()) { 521 final PointerTracker tracker = getPointerTracker(id); 522 // Key repeating timer will be canceled if 2 or more keys are in action, and current 523 // event (UP or DOWN) is non-modifier key. 524 if (pointerCount > 1 && !tracker.isModifier()) { 525 mKeyTimerHandler.cancelKeyRepeatTimer(); 526 } 527 // Up event will pass through. 528 } 529 530 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 531 // Translate mutli-touch event to single-touch events on the device that has no distinct 532 // multi-touch panel. 533 if (nonDistinctMultitouch) { 534 // Use only main (id=0) pointer tracker. 535 PointerTracker tracker = getPointerTracker(0); 536 if (pointerCount == 1 && oldPointerCount == 2) { 537 // Multi-touch to single touch transition. 538 // Send a down event for the latest pointer if the key is different from the 539 // previous key. 540 final int newKeyIndex = tracker.getKeyIndexOn(x, y); 541 if (mOldKeyIndex != newKeyIndex) { 542 tracker.onDownEvent(x, y, eventTime, this); 543 if (action == MotionEvent.ACTION_UP) 544 tracker.onUpEvent(x, y, eventTime); 545 } 546 } else if (pointerCount == 2 && oldPointerCount == 1) { 547 // Single-touch to multi-touch transition. 548 // Send an up event for the last pointer. 549 final int lastX = tracker.getLastX(); 550 final int lastY = tracker.getLastY(); 551 mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY); 552 tracker.onUpEvent(lastX, lastY, eventTime); 553 } else if (pointerCount == 1 && oldPointerCount == 1) { 554 tracker.processMotionEvent(action, x, y, eventTime, this); 555 } else { 556 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 557 + " (old " + oldPointerCount + ")"); 558 } 559 return true; 560 } 561 562 if (action == MotionEvent.ACTION_MOVE) { 563 for (int i = 0; i < pointerCount; i++) { 564 final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); 565 final int px, py; 566 if (mMoreKeysPanel != null 567 && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { 568 px = mMoreKeysPanel.translateX((int)me.getX(i)); 569 py = mMoreKeysPanel.translateY((int)me.getY(i)); 570 } else { 571 px = (int)me.getX(i); 572 py = (int)me.getY(i); 573 } 574 tracker.onMoveEvent(px, py, eventTime); 575 } 576 } else { 577 getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this); 578 } 579 580 return true; 581 } 582 583 @Override 584 public void closing() { 585 super.closing(); 586 dismissMoreKeysPanel(); 587 mMoreKeysPanelCache.clear(); 588 } 589 590 @Override 591 public boolean dismissMoreKeysPanel() { 592 if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { 593 mMoreKeysWindow.dismiss(); 594 mMoreKeysPanel = null; 595 mMoreKeysPanelPointerTrackerId = -1; 596 dimEntireKeyboard(false); 597 return true; 598 } 599 return false; 600 } 601 602 public boolean handleBack() { 603 return dismissMoreKeysPanel(); 604 } 605 606 @Override 607 public void draw(Canvas c) { 608 Utils.GCUtils.getInstance().reset(); 609 boolean tryGC = true; 610 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 611 try { 612 super.draw(c); 613 tryGC = false; 614 } catch (OutOfMemoryError e) { 615 tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e); 616 } 617 } 618 } 619 620 @Override 621 protected void onAttachedToWindow() { 622 // Token is available from here. 623 VoiceProxy.getInstance().onAttachedToWindow(); 624 } 625 626 @Override 627 public boolean dispatchTouchEvent(MotionEvent event) { 628 // Drop non-hover touch events when touch exploration is enabled. 629 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 630 return false; 631 } 632 633 return super.dispatchTouchEvent(event); 634 } 635 636 @Override 637 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 638 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 639 final PointerTracker tracker = getPointerTracker(0); 640 return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent( 641 event, tracker) || super.dispatchPopulateAccessibilityEvent(event); 642 } 643 644 return super.dispatchPopulateAccessibilityEvent(event); 645 } 646 647 /** 648 * Receives hover events from the input framework. This method overrides 649 * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On 650 * lower SDK versions, this method is never called. 651 * 652 * @param event The motion event to be dispatched. 653 * @return {@code true} if the event was handled by the view, {@code false} 654 * otherwise 655 */ 656 public boolean dispatchHoverEvent(MotionEvent event) { 657 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 658 final PointerTracker tracker = getPointerTracker(0); 659 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 660 } 661 662 // Reflection doesn't support calling superclass methods. 663 return false; 664 } 665 } 666