MainKeyboardView.java revision 4daf32b6c0358f0273a99b622a259ecdf6b44fa4
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 17package com.android.inputmethod.keyboard; 18 19import android.animation.AnimatorInflater; 20import android.animation.ObjectAnimator; 21import android.content.Context; 22import android.content.pm.PackageManager; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.Paint; 27import android.graphics.Paint.Align; 28import android.graphics.Typeface; 29import android.graphics.drawable.Drawable; 30import android.os.Message; 31import android.text.TextUtils; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.LayoutInflater; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.ViewConfiguration; 38import android.view.ViewGroup; 39import android.view.inputmethod.InputMethodSubtype; 40import android.widget.PopupWindow; 41 42import com.android.inputmethod.accessibility.AccessibilityUtils; 43import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 44import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; 45import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 46import com.android.inputmethod.latin.Constants; 47import com.android.inputmethod.latin.LatinIME; 48import com.android.inputmethod.latin.LatinImeLogger; 49import com.android.inputmethod.latin.R; 50import com.android.inputmethod.latin.ResearchLogger; 51import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 52import com.android.inputmethod.latin.StringUtils; 53import com.android.inputmethod.latin.SubtypeLocale; 54import com.android.inputmethod.latin.Utils; 55import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; 56import com.android.inputmethod.latin.define.ProductionFlag; 57 58import java.util.Locale; 59import java.util.WeakHashMap; 60 61/** 62 * A view that is responsible for detecting key presses and touch movements. 63 * 64 * @attr ref R.styleable#KeyboardView_keyHysteresisDistance 65 * @attr ref R.styleable#KeyboardView_verticalCorrection 66 * @attr ref R.styleable#KeyboardView_popupLayout 67 */ 68public class LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 69 SuddenJumpingTouchEventHandler.ProcessMotionEvent { 70 private static final String TAG = LatinKeyboardView.class.getSimpleName(); 71 72 // TODO: Kill process when the usability study mode was changed. 73 private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; 74 75 /** Listener for {@link KeyboardActionListener}. */ 76 private KeyboardActionListener mKeyboardActionListener; 77 78 /* Space key and its icons */ 79 private Key mSpaceKey; 80 private Drawable mSpaceIcon; 81 // Stuff to draw language name on spacebar. 82 private final int mLanguageOnSpacebarFinalAlpha; 83 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 84 private boolean mNeedsToDisplayLanguage; 85 private boolean mHasMultipleEnabledIMEsOrSubtypes; 86 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 87 private final float mSpacebarTextRatio; 88 private float mSpacebarTextSize; 89 private final int mSpacebarTextColor; 90 private final int mSpacebarTextShadowColor; 91 // The minimum x-scale to fit the language name on spacebar. 92 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 93 // Stuff to draw auto correction LED on spacebar. 94 private boolean mAutoCorrectionSpacebarLedOn; 95 private final boolean mAutoCorrectionSpacebarLedEnabled; 96 private final Drawable mAutoCorrectionSpacebarLedIcon; 97 private static final int SPACE_LED_LENGTH_PERCENT = 80; 98 99 // Stuff to draw altCodeWhileTyping keys. 100 private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 101 private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 102 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 103 104 // More keys keyboard 105 private PopupWindow mMoreKeysWindow; 106 private MoreKeysPanel mMoreKeysPanel; 107 private int mMoreKeysPanelPointerTrackerId; 108 private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache = 109 new WeakHashMap<Key, MoreKeysPanel>(); 110 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 111 112 private final PointerTrackerParams mPointerTrackerParams; 113 private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; 114 115 protected KeyDetector mKeyDetector; 116 private boolean mHasDistinctMultitouch; 117 private int mOldPointerCount = 1; 118 private Key mOldKey; 119 120 private final KeyTimerHandler mKeyTimerHandler; 121 122 private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView> 123 implements TimerProxy { 124 private static final int MSG_REPEAT_KEY = 1; 125 private static final int MSG_LONGPRESS_KEY = 2; 126 private static final int MSG_DOUBLE_TAP = 3; 127 private static final int MSG_TYPING_STATE_EXPIRED = 4; 128 129 private final KeyTimerParams mParams; 130 131 public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) { 132 super(outerInstance); 133 mParams = params; 134 } 135 136 @Override 137 public void handleMessage(Message msg) { 138 final LatinKeyboardView keyboardView = getOuterInstance(); 139 final PointerTracker tracker = (PointerTracker) msg.obj; 140 switch (msg.what) { 141 case MSG_REPEAT_KEY: 142 final Key currentKey = tracker.getKey(); 143 if (currentKey != null && currentKey.mCode == msg.arg1) { 144 tracker.onRegisterKey(currentKey); 145 startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); 146 } 147 break; 148 case MSG_LONGPRESS_KEY: 149 if (tracker != null) { 150 keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker); 151 } else { 152 KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); 153 } 154 break; 155 case MSG_TYPING_STATE_EXPIRED: 156 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, 157 keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); 158 break; 159 } 160 } 161 162 private void startKeyRepeatTimer(PointerTracker tracker, long delay) { 163 final Key key = tracker.getKey(); 164 if (key == null) return; 165 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); 166 } 167 168 @Override 169 public void startKeyRepeatTimer(PointerTracker tracker) { 170 startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout); 171 } 172 173 public void cancelKeyRepeatTimer() { 174 removeMessages(MSG_REPEAT_KEY); 175 } 176 177 // TODO: Suppress layout changes in key repeat mode 178 public boolean isInKeyRepeat() { 179 return hasMessages(MSG_REPEAT_KEY); 180 } 181 182 @Override 183 public void startLongPressTimer(int code) { 184 cancelLongPressTimer(); 185 final int delay; 186 switch (code) { 187 case Keyboard.CODE_SHIFT: 188 delay = mParams.mLongPressShiftKeyTimeout; 189 break; 190 default: 191 delay = 0; 192 break; 193 } 194 if (delay > 0) { 195 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); 196 } 197 } 198 199 @Override 200 public void startLongPressTimer(PointerTracker tracker) { 201 cancelLongPressTimer(); 202 if (tracker == null) { 203 return; 204 } 205 final Key key = tracker.getKey(); 206 final int delay; 207 switch (key.mCode) { 208 case Keyboard.CODE_SHIFT: 209 delay = mParams.mLongPressShiftKeyTimeout; 210 break; 211 default: 212 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { 213 // We use longer timeout for sliding finger input started from the symbols 214 // mode key. 215 delay = mParams.mLongPressKeyTimeout * 3; 216 } else { 217 delay = mParams.mLongPressKeyTimeout; 218 } 219 break; 220 } 221 if (delay > 0) { 222 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); 223 } 224 } 225 226 @Override 227 public void cancelLongPressTimer() { 228 removeMessages(MSG_LONGPRESS_KEY); 229 } 230 231 public static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, 232 final ObjectAnimator animatorToStart) { 233 float startFraction = 0.0f; 234 if (animatorToCancel.isStarted()) { 235 animatorToCancel.cancel(); 236 startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); 237 } 238 final long startTime = (long)(animatorToStart.getDuration() * startFraction); 239 animatorToStart.start(); 240 animatorToStart.setCurrentPlayTime(startTime); 241 } 242 243 @Override 244 public void startTypingStateTimer() { 245 final boolean isTyping = isTypingState(); 246 removeMessages(MSG_TYPING_STATE_EXPIRED); 247 sendMessageDelayed( 248 obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout); 249 if (isTyping) { 250 return; 251 } 252 final LatinKeyboardView keyboardView = getOuterInstance(); 253 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, 254 keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); 255 } 256 257 @Override 258 public boolean isTypingState() { 259 return hasMessages(MSG_TYPING_STATE_EXPIRED); 260 } 261 262 @Override 263 public void startDoubleTapTimer() { 264 sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP), 265 ViewConfiguration.getDoubleTapTimeout()); 266 } 267 268 @Override 269 public void cancelDoubleTapTimer() { 270 removeMessages(MSG_DOUBLE_TAP); 271 } 272 273 @Override 274 public boolean isInDoubleTapTimeout() { 275 return hasMessages(MSG_DOUBLE_TAP); 276 } 277 278 @Override 279 public void cancelKeyTimers() { 280 cancelKeyRepeatTimer(); 281 cancelLongPressTimer(); 282 } 283 284 public void cancelAllMessages() { 285 cancelKeyTimers(); 286 } 287 } 288 289 public static class PointerTrackerParams { 290 public final boolean mSlidingKeyInputEnabled; 291 public final int mTouchNoiseThresholdTime; 292 public final float mTouchNoiseThresholdDistance; 293 294 public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); 295 296 private PointerTrackerParams() { 297 mSlidingKeyInputEnabled = false; 298 mTouchNoiseThresholdTime =0; 299 mTouchNoiseThresholdDistance = 0; 300 } 301 302 public PointerTrackerParams(TypedArray latinKeyboardViewAttr) { 303 mSlidingKeyInputEnabled = latinKeyboardViewAttr.getBoolean( 304 R.styleable.LatinKeyboardView_slidingKeyInputEnable, false); 305 mTouchNoiseThresholdTime = latinKeyboardViewAttr.getInt( 306 R.styleable.LatinKeyboardView_touchNoiseThresholdTime, 0); 307 mTouchNoiseThresholdDistance = latinKeyboardViewAttr.getDimension( 308 R.styleable.LatinKeyboardView_touchNoiseThresholdDistance, 0); 309 } 310 } 311 312 static class KeyTimerParams { 313 public final int mKeyRepeatStartTimeout; 314 public final int mKeyRepeatInterval; 315 public final int mLongPressKeyTimeout; 316 public final int mLongPressShiftKeyTimeout; 317 public final int mIgnoreAltCodeKeyTimeout; 318 319 public KeyTimerParams(TypedArray latinKeyboardViewAttr) { 320 mKeyRepeatStartTimeout = latinKeyboardViewAttr.getInt( 321 R.styleable.LatinKeyboardView_keyRepeatStartTimeout, 0); 322 mKeyRepeatInterval = latinKeyboardViewAttr.getInt( 323 R.styleable.LatinKeyboardView_keyRepeatInterval, 0); 324 mLongPressKeyTimeout = latinKeyboardViewAttr.getInt( 325 R.styleable.LatinKeyboardView_longPressKeyTimeout, 0); 326 mLongPressShiftKeyTimeout = latinKeyboardViewAttr.getInt( 327 R.styleable.LatinKeyboardView_longPressShiftKeyTimeout, 0); 328 mIgnoreAltCodeKeyTimeout = latinKeyboardViewAttr.getInt( 329 R.styleable.LatinKeyboardView_ignoreAltCodeKeyTimeout, 0); 330 } 331 } 332 333 public LatinKeyboardView(Context context, AttributeSet attrs) { 334 this(context, attrs, R.attr.latinKeyboardViewStyle); 335 } 336 337 public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { 338 super(context, attrs, defStyle); 339 340 mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); 341 342 mHasDistinctMultitouch = context.getPackageManager() 343 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 344 final Resources res = getResources(); 345 final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( 346 Utils.getDeviceOverrideValue(res, 347 R.array.phantom_sudden_move_event_device_list, "false")); 348 final boolean gestureInputEnabledByBuildConfig = res.getBoolean( 349 R.bool.config_gesture_input_enabled_by_build_config); 350 PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack, 351 gestureInputEnabledByBuildConfig); 352 353 final TypedArray a = context.obtainStyledAttributes( 354 attrs, R.styleable.LatinKeyboardView, defStyle, R.style.LatinKeyboardView); 355 mAutoCorrectionSpacebarLedEnabled = a.getBoolean( 356 R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedEnabled, false); 357 mAutoCorrectionSpacebarLedIcon = a.getDrawable( 358 R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedIcon); 359 mSpacebarTextRatio = a.getFraction(R.styleable.LatinKeyboardView_spacebarTextRatio, 360 1000, 1000, 1) / 1000.0f; 361 mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0); 362 mSpacebarTextShadowColor = a.getColor( 363 R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0); 364 mLanguageOnSpacebarFinalAlpha = a.getInt( 365 R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, 366 Constants.Color.ALPHA_OPAQUE); 367 final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId( 368 R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0); 369 final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId( 370 R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); 371 final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( 372 R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); 373 374 final KeyTimerParams keyTimerParams = new KeyTimerParams(a); 375 mPointerTrackerParams = new PointerTrackerParams(a); 376 377 final float keyHysteresisDistance = a.getDimension( 378 R.styleable.LatinKeyboardView_keyHysteresisDistance, 0); 379 mKeyDetector = new KeyDetector(keyHysteresisDistance); 380 mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams); 381 mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( 382 R.styleable.LatinKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); 383 a.recycle(); 384 385 PointerTracker.setParameters(mPointerTrackerParams); 386 387 mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( 388 languageOnSpacebarFadeoutAnimatorResId, this); 389 mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( 390 altCodeKeyWhileTypingFadeoutAnimatorResId, this); 391 mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( 392 altCodeKeyWhileTypingFadeinAnimatorResId, this); 393 } 394 395 private ObjectAnimator loadObjectAnimator(int resId, Object target) { 396 if (resId == 0) return null; 397 final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( 398 getContext(), resId); 399 if (animator != null) { 400 animator.setTarget(target); 401 } 402 return animator; 403 } 404 405 // Getter/setter methods for {@link ObjectAnimator}. 406 public int getLanguageOnSpacebarAnimAlpha() { 407 return mLanguageOnSpacebarAnimAlpha; 408 } 409 410 public void setLanguageOnSpacebarAnimAlpha(int alpha) { 411 mLanguageOnSpacebarAnimAlpha = alpha; 412 invalidateKey(mSpaceKey); 413 } 414 415 public int getAltCodeKeyWhileTypingAnimAlpha() { 416 return mAltCodeKeyWhileTypingAnimAlpha; 417 } 418 419 public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) { 420 mAltCodeKeyWhileTypingAnimAlpha = alpha; 421 updateAltCodeKeyWhileTyping(); 422 } 423 424 public void setKeyboardActionListener(KeyboardActionListener listener) { 425 mKeyboardActionListener = listener; 426 PointerTracker.setKeyboardActionListener(listener); 427 } 428 429 /** 430 * Returns the {@link KeyboardActionListener} object. 431 * @return the listener attached to this keyboard 432 */ 433 @Override 434 public KeyboardActionListener getKeyboardActionListener() { 435 return mKeyboardActionListener; 436 } 437 438 @Override 439 public KeyDetector getKeyDetector() { 440 return mKeyDetector; 441 } 442 443 @Override 444 public DrawingProxy getDrawingProxy() { 445 return this; 446 } 447 448 @Override 449 public TimerProxy getTimerProxy() { 450 return mKeyTimerHandler; 451 } 452 453 /** 454 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 455 * view will re-layout itself to accommodate the keyboard. 456 * @see Keyboard 457 * @see #getKeyboard() 458 * @param keyboard the keyboard to display in this view 459 */ 460 @Override 461 public void setKeyboard(Keyboard keyboard) { 462 // Remove any pending messages, except dismissing preview and key repeat. 463 mKeyTimerHandler.cancelLongPressTimer(); 464 super.setKeyboard(keyboard); 465 mKeyDetector.setKeyboard( 466 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); 467 PointerTracker.setKeyDetector(mKeyDetector); 468 mTouchScreenRegulator.setKeyboard(keyboard); 469 mMoreKeysPanelCache.clear(); 470 471 mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); 472 mSpaceIcon = (mSpaceKey != null) 473 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; 474 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 475 mSpacebarTextSize = keyHeight * mSpacebarTextRatio; 476 if (ProductionFlag.IS_EXPERIMENTAL) { 477 ResearchLogger.latinKeyboardView_setKeyboard(keyboard); 478 } 479 480 // This always needs to be set since the accessibility state can 481 // potentially change without the keyboard being set again. 482 AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard); 483 } 484 485 /** 486 * Returns whether the device has distinct multi-touch panel. 487 * @return true if the device has distinct multi-touch panel. 488 */ 489 public boolean hasDistinctMultitouch() { 490 return mHasDistinctMultitouch; 491 } 492 493 public void setDistinctMultitouch(boolean hasDistinctMultitouch) { 494 mHasDistinctMultitouch = hasDistinctMultitouch; 495 } 496 497 /** 498 * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key 499 * codes for adjacent keys. When disabled, only the primary key code will be 500 * reported. 501 * @param enabled whether or not the proximity correction is enabled 502 */ 503 public void setProximityCorrectionEnabled(boolean enabled) { 504 mKeyDetector.setProximityCorrectionEnabled(enabled); 505 } 506 507 /** 508 * Returns true if proximity correction is enabled. 509 */ 510 public boolean isProximityCorrectionEnabled() { 511 return mKeyDetector.isProximityCorrectionEnabled(); 512 } 513 514 @Override 515 public void cancelAllMessages() { 516 mKeyTimerHandler.cancelAllMessages(); 517 super.cancelAllMessages(); 518 } 519 520 private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) { 521 // Check if we have a popup layout specified first. 522 if (mMoreKeysLayout == 0) { 523 return false; 524 } 525 526 // Check if we are already displaying popup panel. 527 if (mMoreKeysPanel != null) 528 return false; 529 if (parentKey == null) 530 return false; 531 return onLongPress(parentKey, tracker); 532 } 533 534 // This default implementation returns a more keys panel. 535 protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) { 536 if (parentKey.mMoreKeys == null) 537 return null; 538 539 final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); 540 if (container == null) 541 throw new NullPointerException(); 542 543 final MoreKeysKeyboardView moreKeysKeyboardView = 544 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 545 final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this) 546 .build(); 547 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 548 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 549 550 return moreKeysKeyboardView; 551 } 552 553 /** 554 * Called when a key is long pressed. By default this will open more keys keyboard associated 555 * with this key. 556 * @param parentKey the key that was long pressed 557 * @param tracker the pointer tracker which pressed the parent key 558 * @return true if the long press is handled, false otherwise. Subclasses should call the 559 * method on the base class if the subclass doesn't wish to handle the call. 560 */ 561 protected boolean onLongPress(Key parentKey, PointerTracker tracker) { 562 if (ProductionFlag.IS_EXPERIMENTAL) { 563 ResearchLogger.latinKeyboardView_onLongPress(); 564 } 565 final int primaryCode = parentKey.mCode; 566 if (parentKey.hasEmbeddedMoreKey()) { 567 final int embeddedCode = parentKey.mMoreKeys[0].mCode; 568 tracker.onLongPressed(); 569 invokeCodeInput(embeddedCode); 570 invokeReleaseKey(primaryCode); 571 KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); 572 return true; 573 } 574 if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) { 575 // Long pressing the space key invokes IME switcher dialog. 576 if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { 577 tracker.onLongPressed(); 578 invokeReleaseKey(primaryCode); 579 return true; 580 } 581 } 582 return openMoreKeysPanel(parentKey, tracker); 583 } 584 585 private boolean invokeCustomRequest(int code) { 586 return mKeyboardActionListener.onCustomRequest(code); 587 } 588 589 private void invokeCodeInput(int primaryCode) { 590 mKeyboardActionListener.onCodeInput(primaryCode, 591 KeyboardActionListener.NOT_A_TOUCH_COORDINATE, 592 KeyboardActionListener.NOT_A_TOUCH_COORDINATE); 593 } 594 595 private void invokeReleaseKey(int primaryCode) { 596 mKeyboardActionListener.onReleaseKey(primaryCode, false); 597 } 598 599 private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) { 600 MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); 601 if (moreKeysPanel == null) { 602 moreKeysPanel = onCreateMoreKeysPanel(parentKey); 603 if (moreKeysPanel == null) 604 return false; 605 mMoreKeysPanelCache.put(parentKey, moreKeysPanel); 606 } 607 if (mMoreKeysWindow == null) { 608 mMoreKeysWindow = new PopupWindow(getContext()); 609 mMoreKeysWindow.setBackgroundDrawable(null); 610 mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation); 611 } 612 mMoreKeysPanel = moreKeysPanel; 613 mMoreKeysPanelPointerTrackerId = tracker.mPointerId; 614 615 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview(); 616 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 617 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 618 // keys keyboard is placed at the touch point of the parent key. 619 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 620 ? tracker.getLastX() 621 : parentKey.mX + parentKey.mWidth / 2; 622 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 623 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 624 // aligned with the bottom edge of the visible part of the key preview. 625 final int pointY = parentKey.mY + (keyPreviewEnabled 626 ? mKeyPreviewDrawParams.mPreviewVisibleOffset 627 : -parentKey.mVerticalGap); 628 moreKeysPanel.showMoreKeysPanel( 629 this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener); 630 final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); 631 final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); 632 tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); 633 dimEntireKeyboard(true); 634 return true; 635 } 636 637 public boolean isInSlidingKeyInput() { 638 if (mMoreKeysPanel != null) { 639 return true; 640 } else { 641 return PointerTracker.isAnyInSlidingKeyInput(); 642 } 643 } 644 645 public int getPointerCount() { 646 return mOldPointerCount; 647 } 648 649 @Override 650 public boolean onTouchEvent(MotionEvent me) { 651 if (getKeyboard() == null) { 652 return false; 653 } 654 return mTouchScreenRegulator.onTouchEvent(me); 655 } 656 657 @Override 658 public boolean processMotionEvent(MotionEvent me) { 659 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 660 final int action = me.getActionMasked(); 661 final int pointerCount = me.getPointerCount(); 662 final int oldPointerCount = mOldPointerCount; 663 mOldPointerCount = pointerCount; 664 665 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 666 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 667 // events except a transition from/to single-touch. 668 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 669 return true; 670 } 671 672 final long eventTime = me.getEventTime(); 673 final int index = me.getActionIndex(); 674 final int id = me.getPointerId(index); 675 final int x, y; 676 if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) { 677 x = mMoreKeysPanel.translateX((int)me.getX(index)); 678 y = mMoreKeysPanel.translateY((int)me.getY(index)); 679 } else { 680 x = (int)me.getX(index); 681 y = (int)me.getY(index); 682 } 683 if (ENABLE_USABILITY_STUDY_LOG) { 684 final String eventTag; 685 switch (action) { 686 case MotionEvent.ACTION_UP: 687 eventTag = "[Up]"; 688 break; 689 case MotionEvent.ACTION_DOWN: 690 eventTag = "[Down]"; 691 break; 692 case MotionEvent.ACTION_POINTER_UP: 693 eventTag = "[PointerUp]"; 694 break; 695 case MotionEvent.ACTION_POINTER_DOWN: 696 eventTag = "[PointerDown]"; 697 break; 698 case MotionEvent.ACTION_MOVE: // Skip this as being logged below 699 eventTag = ""; 700 break; 701 default: 702 eventTag = "[Action" + action + "]"; 703 break; 704 } 705 if (!TextUtils.isEmpty(eventTag)) { 706 final float size = me.getSize(index); 707 final float pressure = me.getPressure(index); 708 UsabilityStudyLogUtils.getInstance().write( 709 eventTag + eventTime + "," + id + "," + x + "," + y + "," 710 + size + "," + pressure); 711 } 712 } 713 if (ProductionFlag.IS_EXPERIMENTAL) { 714 ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, index, id, 715 x, y); 716 } 717 718 if (mKeyTimerHandler.isInKeyRepeat()) { 719 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 720 // Key repeating timer will be canceled if 2 or more keys are in action, and current 721 // event (UP or DOWN) is non-modifier key. 722 if (pointerCount > 1 && !tracker.isModifier()) { 723 mKeyTimerHandler.cancelKeyRepeatTimer(); 724 } 725 // Up event will pass through. 726 } 727 728 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 729 // Translate mutli-touch event to single-touch events on the device that has no distinct 730 // multi-touch panel. 731 if (nonDistinctMultitouch) { 732 // Use only main (id=0) pointer tracker. 733 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 734 if (pointerCount == 1 && oldPointerCount == 2) { 735 // Multi-touch to single touch transition. 736 // Send a down event for the latest pointer if the key is different from the 737 // previous key. 738 final Key newKey = tracker.getKeyOn(x, y); 739 if (mOldKey != newKey) { 740 tracker.onDownEvent(x, y, eventTime, this); 741 if (action == MotionEvent.ACTION_UP) 742 tracker.onUpEvent(x, y, eventTime); 743 } 744 } else if (pointerCount == 2 && oldPointerCount == 1) { 745 // Single-touch to multi-touch transition. 746 // Send an up event for the last pointer. 747 final int lastX = tracker.getLastX(); 748 final int lastY = tracker.getLastY(); 749 mOldKey = tracker.getKeyOn(lastX, lastY); 750 tracker.onUpEvent(lastX, lastY, eventTime); 751 } else if (pointerCount == 1 && oldPointerCount == 1) { 752 tracker.processMotionEvent(action, x, y, eventTime, this); 753 } else { 754 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 755 + " (old " + oldPointerCount + ")"); 756 } 757 return true; 758 } 759 760 if (action == MotionEvent.ACTION_MOVE) { 761 for (int i = 0; i < pointerCount; i++) { 762 final int pointerId = me.getPointerId(i); 763 final PointerTracker tracker = PointerTracker.getPointerTracker( 764 pointerId, this); 765 final int px, py; 766 final MotionEvent motionEvent; 767 if (mMoreKeysPanel != null 768 && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { 769 px = mMoreKeysPanel.translateX((int)me.getX(i)); 770 py = mMoreKeysPanel.translateY((int)me.getY(i)); 771 motionEvent = null; 772 } else { 773 px = (int)me.getX(i); 774 py = (int)me.getY(i); 775 motionEvent = me; 776 } 777 tracker.onMoveEvent(px, py, eventTime, motionEvent); 778 if (ENABLE_USABILITY_STUDY_LOG) { 779 final float pointerSize = me.getSize(i); 780 final float pointerPressure = me.getPressure(i); 781 UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + "," 782 + pointerId + "," + px + "," + py + "," 783 + pointerSize + "," + pointerPressure); 784 } 785 if (ProductionFlag.IS_EXPERIMENTAL) { 786 ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, 787 i, pointerId, px, py); 788 } 789 } 790 } else { 791 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 792 tracker.processMotionEvent(action, x, y, eventTime, this); 793 } 794 795 return true; 796 } 797 798 @Override 799 public void closing() { 800 super.closing(); 801 dismissMoreKeysPanel(); 802 mMoreKeysPanelCache.clear(); 803 } 804 805 @Override 806 public boolean dismissMoreKeysPanel() { 807 if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { 808 mMoreKeysWindow.dismiss(); 809 mMoreKeysPanel = null; 810 mMoreKeysPanelPointerTrackerId = -1; 811 dimEntireKeyboard(false); 812 return true; 813 } 814 return false; 815 } 816 817 @Override 818 public void draw(Canvas c) { 819 Utils.GCUtils.getInstance().reset(); 820 boolean tryGC = true; 821 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 822 try { 823 super.draw(c); 824 tryGC = false; 825 } catch (OutOfMemoryError e) { 826 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e); 827 } 828 } 829 } 830 831 /** 832 * Receives hover events from the input framework. 833 * 834 * @param event The motion event to be dispatched. 835 * @return {@code true} if the event was handled by the view, {@code false} 836 * otherwise 837 */ 838 @Override 839 public boolean dispatchHoverEvent(MotionEvent event) { 840 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 841 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 842 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 843 } 844 845 // Reflection doesn't support calling superclass methods. 846 return false; 847 } 848 849 public void updateShortcutKey(boolean available) { 850 final Keyboard keyboard = getKeyboard(); 851 if (keyboard == null) return; 852 final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT); 853 if (shortcutKey == null) return; 854 shortcutKey.setEnabled(available); 855 invalidateKey(shortcutKey); 856 } 857 858 private void updateAltCodeKeyWhileTyping() { 859 final Keyboard keyboard = getKeyboard(); 860 if (keyboard == null) return; 861 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 862 invalidateKey(key); 863 } 864 } 865 866 public void startDisplayLanguageOnSpacebar(boolean subtypeChanged, 867 boolean needsToDisplayLanguage, boolean hasMultipleEnabledIMEsOrSubtypes) { 868 mNeedsToDisplayLanguage = needsToDisplayLanguage; 869 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 870 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 871 if (animator == null) { 872 mNeedsToDisplayLanguage = false; 873 } else { 874 if (subtypeChanged && needsToDisplayLanguage) { 875 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 876 if (animator.isStarted()) { 877 animator.cancel(); 878 } 879 animator.start(); 880 } else { 881 if (!animator.isStarted()) { 882 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 883 } 884 } 885 } 886 invalidateKey(mSpaceKey); 887 } 888 889 public void updateAutoCorrectionState(boolean isAutoCorrection) { 890 if (!mAutoCorrectionSpacebarLedEnabled) return; 891 mAutoCorrectionSpacebarLedOn = isAutoCorrection; 892 invalidateKey(mSpaceKey); 893 } 894 895 @Override 896 protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { 897 if (key.altCodeWhileTyping() && key.isEnabled()) { 898 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 899 } 900 if (key.mCode == Keyboard.CODE_SPACE) { 901 drawSpacebar(key, canvas, paint); 902 // Whether space key needs to show the "..." popup hint for special purposes 903 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 904 drawKeyPopupHint(key, canvas, paint, params); 905 } 906 } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) { 907 super.onDrawKeyTopVisuals(key, canvas, paint, params); 908 drawKeyPopupHint(key, canvas, paint, params); 909 } else { 910 super.onDrawKeyTopVisuals(key, canvas, paint, params); 911 } 912 } 913 914 private boolean fitsTextIntoWidth(final int width, String text, Paint paint) { 915 paint.setTextScaleX(1.0f); 916 final float textWidth = getLabelWidth(text, paint); 917 if (textWidth < width) return true; 918 919 final float scaleX = width / textWidth; 920 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false; 921 922 paint.setTextScaleX(scaleX); 923 return getLabelWidth(text, paint) < width; 924 } 925 926 // Layout language name on spacebar. 927 private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype, 928 final int width) { 929 // Choose appropriate language name to fit into the width. 930 String text = getFullDisplayName(subtype, getResources()); 931 if (fitsTextIntoWidth(width, text, paint)) { 932 return text; 933 } 934 935 text = getMiddleDisplayName(subtype); 936 if (fitsTextIntoWidth(width, text, paint)) { 937 return text; 938 } 939 940 text = getShortDisplayName(subtype); 941 if (fitsTextIntoWidth(width, text, paint)) { 942 return text; 943 } 944 945 return ""; 946 } 947 948 private void drawSpacebar(Key key, Canvas canvas, Paint paint) { 949 final int width = key.mWidth; 950 final int height = key.mHeight; 951 952 // If input language are explicitly selected. 953 if (mNeedsToDisplayLanguage) { 954 paint.setTextAlign(Align.CENTER); 955 paint.setTypeface(Typeface.DEFAULT); 956 paint.setTextSize(mSpacebarTextSize); 957 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 958 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 959 // Draw language text with shadow 960 final float descent = paint.descent(); 961 final float textHeight = -paint.ascent() + descent; 962 final float baseline = height / 2 + textHeight / 2; 963 paint.setColor(mSpacebarTextShadowColor); 964 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 965 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 966 paint.setColor(mSpacebarTextColor); 967 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 968 canvas.drawText(language, width / 2, baseline - descent, paint); 969 } 970 971 // Draw the spacebar icon at the bottom 972 if (mAutoCorrectionSpacebarLedOn) { 973 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 974 final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); 975 int x = (width - iconWidth) / 2; 976 int y = height - iconHeight; 977 drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); 978 } else if (mSpaceIcon != null) { 979 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 980 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 981 int x = (width - iconWidth) / 2; 982 int y = height - iconHeight; 983 drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); 984 } 985 } 986 987 // InputMethodSubtype's display name for spacebar text in its locale. 988 // isAdditionalSubtype (T=true, F=false) 989 // locale layout | Short Middle Full 990 // ------ ------ - ---- --------- ---------------------- 991 // en_US qwerty F En English English (US) exception 992 // en_GB qwerty F En English English (UK) exception 993 // fr azerty F Fr Français Français 994 // fr_CA qwerty F Fr Français Français (Canada) 995 // de qwertz F De Deutsch Deutsch 996 // zz qwerty F QWERTY QWERTY 997 // fr qwertz T Fr Français Français (QWERTZ) 998 // de qwerty T De Deutsch Deutsch (QWERTY) 999 // en_US azerty T En English English (US) (AZERTY) 1000 // zz azerty T AZERTY AZERTY 1001 1002 // Get InputMethodSubtype's full display name in its locale. 1003 static String getFullDisplayName(InputMethodSubtype subtype, Resources res) { 1004 if (SubtypeLocale.isNoLanguage(subtype)) { 1005 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1006 } 1007 1008 return SubtypeLocale.getSubtypeDisplayName(subtype, res); 1009 } 1010 1011 // Get InputMethodSubtype's short display name in its locale. 1012 static String getShortDisplayName(InputMethodSubtype subtype) { 1013 if (SubtypeLocale.isNoLanguage(subtype)) { 1014 return ""; 1015 } 1016 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1017 return StringUtils.toTitleCase(locale.getLanguage(), locale); 1018 } 1019 1020 // Get InputMethodSubtype's middle display name in its locale. 1021 static String getMiddleDisplayName(InputMethodSubtype subtype) { 1022 if (SubtypeLocale.isNoLanguage(subtype)) { 1023 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1024 } 1025 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1026 return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); 1027 } 1028} 1029