MainKeyboardView.java revision 30977a151e15e30f5385a349e92fb770b987435f
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.SharedPreferences; 23import android.content.pm.PackageManager; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.Color; 27import android.graphics.Paint; 28import android.graphics.Paint.Align; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.os.Message; 32import android.os.SystemClock; 33import android.preference.PreferenceManager; 34import android.util.AttributeSet; 35import android.util.DisplayMetrics; 36import android.util.Log; 37import android.util.SparseArray; 38import android.util.TypedValue; 39import android.view.LayoutInflater; 40import android.view.MotionEvent; 41import android.view.View; 42import android.view.ViewConfiguration; 43import android.view.ViewGroup; 44import android.view.inputmethod.InputMethodSubtype; 45import android.widget.TextView; 46 47import com.android.inputmethod.accessibility.AccessibilityUtils; 48import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 49import com.android.inputmethod.annotations.ExternallyReferenced; 50import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; 51import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 52import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText; 53import com.android.inputmethod.keyboard.internal.GestureTrailsPreview; 54import com.android.inputmethod.keyboard.internal.KeyDrawParams; 55import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; 56import com.android.inputmethod.keyboard.internal.PreviewPlacerView; 57import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; 58import com.android.inputmethod.latin.Constants; 59import com.android.inputmethod.latin.LatinImeLogger; 60import com.android.inputmethod.latin.R; 61import com.android.inputmethod.latin.SuggestedWords; 62import com.android.inputmethod.latin.define.ProductionFlag; 63import com.android.inputmethod.latin.settings.DebugSettings; 64import com.android.inputmethod.latin.utils.CollectionUtils; 65import com.android.inputmethod.latin.utils.CoordinateUtils; 66import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; 67import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 68import com.android.inputmethod.latin.utils.TypefaceUtils; 69import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils; 70import com.android.inputmethod.latin.utils.ViewLayoutUtils; 71import com.android.inputmethod.research.ResearchLogger; 72 73import java.util.WeakHashMap; 74 75/** 76 * A view that is responsible for detecting key presses and touch movements. 77 * 78 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled 79 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon 80 * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio 81 * @attr ref R.styleable#MainKeyboardView_spacebarTextColor 82 * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor 83 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha 84 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator 85 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator 86 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator 87 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance 88 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime 89 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance 90 * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable 91 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout 92 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval 93 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout 94 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout 95 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout 96 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout 97 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset 98 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight 99 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout 100 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout 101 * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha 102 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint 103 * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout 104 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping 105 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold 106 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration 107 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom 108 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo 109 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom 110 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo 111 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance 112 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime 113 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold 114 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration 115 */ 116public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 117 PointerTracker.DrawingProxy, MoreKeysPanel.Controller { 118 private static final String TAG = MainKeyboardView.class.getSimpleName(); 119 120 /** Listener for {@link KeyboardActionListener}. */ 121 private KeyboardActionListener mKeyboardActionListener; 122 123 /* Space key and its icons */ 124 private Key mSpaceKey; 125 private Drawable mSpaceIcon; 126 // Stuff to draw language name on spacebar. 127 private final int mLanguageOnSpacebarFinalAlpha; 128 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 129 private boolean mNeedsToDisplayLanguage; 130 private boolean mHasMultipleEnabledIMEsOrSubtypes; 131 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 132 private final float mSpacebarTextRatio; 133 private float mSpacebarTextSize; 134 private final int mSpacebarTextColor; 135 private final int mSpacebarTextShadowColor; 136 // The minimum x-scale to fit the language name on spacebar. 137 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 138 // Stuff to draw auto correction LED on spacebar. 139 private boolean mAutoCorrectionSpacebarLedOn; 140 private final boolean mAutoCorrectionSpacebarLedEnabled; 141 private final Drawable mAutoCorrectionSpacebarLedIcon; 142 private static final int SPACE_LED_LENGTH_PERCENT = 80; 143 144 // Stuff to draw altCodeWhileTyping keys. 145 private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 146 private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 147 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 148 149 // Preview placer view 150 private final PreviewPlacerView mPreviewPlacerView; 151 private final int[] mOriginCoords = CoordinateUtils.newInstance(); 152 private final GestureFloatingPreviewText mGestureFloatingPreviewText; 153 private final GestureTrailsPreview mGestureTrailsPreview; 154 private final SlidingKeyInputPreview mSlidingKeyInputPreview; 155 156 // Key preview 157 private static final int PREVIEW_ALPHA = 240; 158 private final int mKeyPreviewLayoutId; 159 private final int mKeyPreviewOffset; 160 private final int mKeyPreviewHeight; 161 private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray(); 162 private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams(); 163 private boolean mShowKeyPreviewPopup = true; 164 private int mKeyPreviewLingerTimeout; 165 166 // More keys keyboard 167 private final Paint mBackgroundDimAlphaPaint = new Paint(); 168 private boolean mNeedsToDimEntireKeyboard; 169 private final View mMoreKeysKeyboardContainer; 170 private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = 171 CollectionUtils.newWeakHashMap(); 172 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 173 // More keys panel (used by both more keys keyboard and more suggestions view) 174 // TODO: Consider extending to support multiple more keys panels 175 private MoreKeysPanel mMoreKeysPanel; 176 177 // Gesture floating preview text 178 // TODO: Make this parameter customizable by user via settings. 179 private int mGestureFloatingPreviewTextLingerTimeout; 180 181 private KeyDetector mKeyDetector; 182 private final boolean mHasDistinctMultitouch; 183 private int mOldPointerCount = 1; 184 private Key mOldKey; 185 186 private final KeyTimerHandler mKeyTimerHandler; 187 188 private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView> 189 implements TimerProxy { 190 private static final int MSG_TYPING_STATE_EXPIRED = 0; 191 private static final int MSG_REPEAT_KEY = 1; 192 private static final int MSG_LONGPRESS_KEY = 2; 193 private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3; 194 private static final int MSG_UPDATE_BATCH_INPUT = 4; 195 196 private final int mKeyRepeatStartTimeout; 197 private final int mKeyRepeatInterval; 198 private final int mIgnoreAltCodeKeyTimeout; 199 private final int mGestureRecognitionUpdateTime; 200 201 public KeyTimerHandler(final MainKeyboardView outerInstance, 202 final TypedArray mainKeyboardViewAttr) { 203 super(outerInstance); 204 205 mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( 206 R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); 207 mKeyRepeatInterval = mainKeyboardViewAttr.getInt( 208 R.styleable.MainKeyboardView_keyRepeatInterval, 0); 209 mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( 210 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); 211 mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( 212 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); 213 } 214 215 @Override 216 public void handleMessage(final Message msg) { 217 final MainKeyboardView keyboardView = getOuterInstance(); 218 if (keyboardView == null) { 219 return; 220 } 221 final PointerTracker tracker = (PointerTracker) msg.obj; 222 switch (msg.what) { 223 case MSG_TYPING_STATE_EXPIRED: 224 startWhileTypingFadeinAnimation(keyboardView); 225 break; 226 case MSG_REPEAT_KEY: 227 final Key currentKey = tracker.getKey(); 228 final int code = msg.arg1; 229 if (currentKey != null && currentKey.mCode == code) { 230 startKeyRepeatTimer(tracker, mKeyRepeatInterval); 231 startTypingStateTimer(currentKey); 232 final KeyboardActionListener listener = 233 keyboardView.getKeyboardActionListener(); 234 listener.onPressKey(code, true /* isRepeatKey */, true /* isSinglePointer */); 235 listener.onCodeInput(code, 236 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 237 } 238 break; 239 case MSG_LONGPRESS_KEY: 240 keyboardView.onLongPress(tracker); 241 break; 242 case MSG_UPDATE_BATCH_INPUT: 243 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis()); 244 startUpdateBatchInputTimer(tracker); 245 break; 246 } 247 } 248 249 private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { 250 final Key key = tracker.getKey(); 251 if (key == null) { 252 return; 253 } 254 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); 255 } 256 257 @Override 258 public void startKeyRepeatTimer(final PointerTracker tracker) { 259 startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout); 260 } 261 262 public void cancelKeyRepeatTimer() { 263 removeMessages(MSG_REPEAT_KEY); 264 } 265 266 // TODO: Suppress layout changes in key repeat mode 267 public boolean isInKeyRepeat() { 268 return hasMessages(MSG_REPEAT_KEY); 269 } 270 271 @Override 272 public void startLongPressTimer(final PointerTracker tracker, final int delay) { 273 cancelLongPressTimer(); 274 if (delay <= 0) return; 275 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); 276 } 277 278 @Override 279 public void cancelLongPressTimer() { 280 removeMessages(MSG_LONGPRESS_KEY); 281 } 282 283 private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, 284 final ObjectAnimator animatorToStart) { 285 if (animatorToCancel == null || animatorToStart == null) { 286 // TODO: Stop using null as a no-operation animator. 287 return; 288 } 289 float startFraction = 0.0f; 290 if (animatorToCancel.isStarted()) { 291 animatorToCancel.cancel(); 292 startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); 293 } 294 final long startTime = (long)(animatorToStart.getDuration() * startFraction); 295 animatorToStart.start(); 296 animatorToStart.setCurrentPlayTime(startTime); 297 } 298 299 private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) { 300 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, 301 keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); 302 } 303 304 private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) { 305 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, 306 keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); 307 } 308 309 @Override 310 public void startTypingStateTimer(final Key typedKey) { 311 if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { 312 return; 313 } 314 315 final boolean isTyping = isTypingState(); 316 removeMessages(MSG_TYPING_STATE_EXPIRED); 317 final MainKeyboardView keyboardView = getOuterInstance(); 318 319 // When user hits the space or the enter key, just cancel the while-typing timer. 320 final int typedCode = typedKey.mCode; 321 if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { 322 if (isTyping) { 323 startWhileTypingFadeinAnimation(keyboardView); 324 } 325 return; 326 } 327 328 sendMessageDelayed( 329 obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout); 330 if (isTyping) { 331 return; 332 } 333 startWhileTypingFadeoutAnimation(keyboardView); 334 } 335 336 @Override 337 public boolean isTypingState() { 338 return hasMessages(MSG_TYPING_STATE_EXPIRED); 339 } 340 341 @Override 342 public void startDoubleTapShiftKeyTimer() { 343 sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY), 344 ViewConfiguration.getDoubleTapTimeout()); 345 } 346 347 @Override 348 public void cancelDoubleTapShiftKeyTimer() { 349 removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY); 350 } 351 352 @Override 353 public boolean isInDoubleTapShiftKeyTimeout() { 354 return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY); 355 } 356 357 @Override 358 public void cancelKeyTimers() { 359 cancelKeyRepeatTimer(); 360 cancelLongPressTimer(); 361 } 362 363 @Override 364 public void startUpdateBatchInputTimer(final PointerTracker tracker) { 365 if (mGestureRecognitionUpdateTime <= 0) { 366 return; 367 } 368 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 369 sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker), 370 mGestureRecognitionUpdateTime); 371 } 372 373 @Override 374 public void cancelUpdateBatchInputTimer(final PointerTracker tracker) { 375 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 376 } 377 378 @Override 379 public void cancelAllUpdateBatchInputTimers() { 380 removeMessages(MSG_UPDATE_BATCH_INPUT); 381 } 382 383 public void cancelAllMessages() { 384 cancelKeyTimers(); 385 cancelAllUpdateBatchInputTimers(); 386 } 387 } 388 389 private final DrawingHandler mDrawingHandler = new DrawingHandler(this); 390 391 public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> { 392 private static final int MSG_DISMISS_KEY_PREVIEW = 0; 393 private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; 394 395 public DrawingHandler(final MainKeyboardView outerInstance) { 396 super(outerInstance); 397 } 398 399 @Override 400 public void handleMessage(final Message msg) { 401 final MainKeyboardView mainKeyboardView = getOuterInstance(); 402 if (mainKeyboardView == null) return; 403 final PointerTracker tracker = (PointerTracker) msg.obj; 404 switch (msg.what) { 405 case MSG_DISMISS_KEY_PREVIEW: 406 final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get( 407 tracker.mPointerId); 408 if (previewText != null) { 409 previewText.setVisibility(INVISIBLE); 410 } 411 break; 412 case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: 413 mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY); 414 break; 415 } 416 } 417 418 public void dismissKeyPreview(final long delay, final PointerTracker tracker) { 419 sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay); 420 } 421 422 public void cancelDismissKeyPreview(final PointerTracker tracker) { 423 removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); 424 } 425 426 private void cancelAllDismissKeyPreviews() { 427 removeMessages(MSG_DISMISS_KEY_PREVIEW); 428 } 429 430 public void dismissGestureFloatingPreviewText(final long delay) { 431 sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay); 432 } 433 434 public void cancelAllMessages() { 435 cancelAllDismissKeyPreviews(); 436 } 437 } 438 439 public MainKeyboardView(final Context context, final AttributeSet attrs) { 440 this(context, attrs, R.attr.mainKeyboardViewStyle); 441 } 442 443 public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 444 super(context, attrs, defStyle); 445 446 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 447 final boolean forceNonDistinctMultitouch = prefs.getBoolean( 448 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); 449 final boolean hasDistinctMultitouch = context.getPackageManager() 450 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 451 mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; 452 PointerTracker.init(getResources()); 453 mPreviewPlacerView = new PreviewPlacerView(context, attrs); 454 455 final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( 456 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); 457 final int backgroundDimAlpha = mainKeyboardViewAttr.getInt( 458 R.styleable.MainKeyboardView_backgroundDimAlpha, 0); 459 mBackgroundDimAlphaPaint.setColor(Color.BLACK); 460 mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha); 461 mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean( 462 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); 463 mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable( 464 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); 465 mSpacebarTextRatio = mainKeyboardViewAttr.getFraction( 466 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f); 467 mSpacebarTextColor = mainKeyboardViewAttr.getColor( 468 R.styleable.MainKeyboardView_spacebarTextColor, 0); 469 mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor( 470 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); 471 mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt( 472 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, 473 Constants.Color.ALPHA_OPAQUE); 474 final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 475 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); 476 final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 477 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); 478 final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId( 479 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); 480 481 final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension( 482 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f); 483 final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension( 484 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f); 485 mKeyDetector = new KeyDetector( 486 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); 487 mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr); 488 mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset( 489 R.styleable.MainKeyboardView_keyPreviewOffset, 0); 490 mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize( 491 R.styleable.MainKeyboardView_keyPreviewHeight, 0); 492 mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt( 493 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0); 494 mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId( 495 R.styleable.MainKeyboardView_keyPreviewLayout, 0); 496 if (mKeyPreviewLayoutId == 0) { 497 mShowKeyPreviewPopup = false; 498 } 499 final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId( 500 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0); 501 mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean( 502 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); 503 504 mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt( 505 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0); 506 PointerTracker.setParameters(mainKeyboardViewAttr); 507 508 mGestureFloatingPreviewText = new GestureFloatingPreviewText( 509 mPreviewPlacerView, mainKeyboardViewAttr); 510 mPreviewPlacerView.addPreview(mGestureFloatingPreviewText); 511 512 mGestureTrailsPreview = new GestureTrailsPreview( 513 mPreviewPlacerView, mainKeyboardViewAttr); 514 mPreviewPlacerView.addPreview(mGestureTrailsPreview); 515 516 mSlidingKeyInputPreview = new SlidingKeyInputPreview( 517 mPreviewPlacerView, mainKeyboardViewAttr); 518 mPreviewPlacerView.addPreview(mSlidingKeyInputPreview); 519 mainKeyboardViewAttr.recycle(); 520 521 mMoreKeysKeyboardContainer = LayoutInflater.from(getContext()) 522 .inflate(moreKeysKeyboardLayoutId, null); 523 mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( 524 languageOnSpacebarFadeoutAnimatorResId, this); 525 mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( 526 altCodeKeyWhileTypingFadeoutAnimatorResId, this); 527 mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( 528 altCodeKeyWhileTypingFadeinAnimatorResId, this); 529 530 mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; 531 } 532 533 private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { 534 if (resId == 0) { 535 // TODO: Stop returning null. 536 return null; 537 } 538 final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( 539 getContext(), resId); 540 if (animator != null) { 541 animator.setTarget(target); 542 } 543 return animator; 544 } 545 546 @ExternallyReferenced 547 public int getLanguageOnSpacebarAnimAlpha() { 548 return mLanguageOnSpacebarAnimAlpha; 549 } 550 551 @ExternallyReferenced 552 public void setLanguageOnSpacebarAnimAlpha(final int alpha) { 553 mLanguageOnSpacebarAnimAlpha = alpha; 554 invalidateKey(mSpaceKey); 555 } 556 557 @ExternallyReferenced 558 public int getAltCodeKeyWhileTypingAnimAlpha() { 559 return mAltCodeKeyWhileTypingAnimAlpha; 560 } 561 562 @ExternallyReferenced 563 public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { 564 if (mAltCodeKeyWhileTypingAnimAlpha == alpha) { 565 return; 566 } 567 // Update the visual of alt-code-key-while-typing. 568 mAltCodeKeyWhileTypingAnimAlpha = alpha; 569 final Keyboard keyboard = getKeyboard(); 570 if (keyboard == null) { 571 return; 572 } 573 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 574 invalidateKey(key); 575 } 576 } 577 578 public void setKeyboardActionListener(final KeyboardActionListener listener) { 579 mKeyboardActionListener = listener; 580 PointerTracker.setKeyboardActionListener(listener); 581 } 582 583 /** 584 * Returns the {@link KeyboardActionListener} object. 585 * @return the listener attached to this keyboard 586 */ 587 @Override 588 public KeyboardActionListener getKeyboardActionListener() { 589 return mKeyboardActionListener; 590 } 591 592 @Override 593 public KeyDetector getKeyDetector() { 594 return mKeyDetector; 595 } 596 597 @Override 598 public DrawingProxy getDrawingProxy() { 599 return this; 600 } 601 602 @Override 603 public TimerProxy getTimerProxy() { 604 return mKeyTimerHandler; 605 } 606 607 /** 608 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 609 * view will re-layout itself to accommodate the keyboard. 610 * @see Keyboard 611 * @see #getKeyboard() 612 * @param keyboard the keyboard to display in this view 613 */ 614 @Override 615 public void setKeyboard(final Keyboard keyboard) { 616 // Remove any pending messages, except dismissing preview and key repeat. 617 mKeyTimerHandler.cancelLongPressTimer(); 618 super.setKeyboard(keyboard); 619 mKeyDetector.setKeyboard( 620 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); 621 PointerTracker.setKeyDetector(mKeyDetector); 622 mMoreKeysKeyboardCache.clear(); 623 624 mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); 625 mSpaceIcon = (mSpaceKey != null) 626 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; 627 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 628 mSpacebarTextSize = keyHeight * mSpacebarTextRatio; 629 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 630 ResearchLogger.mainKeyboardView_setKeyboard(keyboard); 631 } 632 633 // This always needs to be set since the accessibility state can 634 // potentially change without the keyboard being set again. 635 AccessibleKeyboardViewProxy.getInstance().setKeyboard(); 636 } 637 638 /** 639 * Enables or disables the key feedback popup. This is a popup that shows a magnified 640 * version of the depressed key. By default the preview is enabled. 641 * @param previewEnabled whether or not to enable the key feedback preview 642 * @param delay the delay after which the preview is dismissed 643 * @see #isKeyPreviewPopupEnabled() 644 */ 645 public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { 646 mShowKeyPreviewPopup = previewEnabled; 647 mKeyPreviewLingerTimeout = delay; 648 } 649 650 651 private void locatePreviewPlacerView() { 652 if (mPreviewPlacerView.getParent() != null) { 653 return; 654 } 655 final int width = getWidth(); 656 final int height = getHeight(); 657 if (width == 0 || height == 0) { 658 // In transient state. 659 return; 660 } 661 getLocationInWindow(mOriginCoords); 662 final DisplayMetrics dm = getResources().getDisplayMetrics(); 663 if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) { 664 // In transient state. 665 return; 666 } 667 final View rootView = getRootView(); 668 if (rootView == null) { 669 Log.w(TAG, "Cannot find root view"); 670 return; 671 } 672 final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); 673 // Note: It'd be very weird if we get null by android.R.id.content. 674 if (windowContentView == null) { 675 Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView"); 676 } else { 677 windowContentView.addView(mPreviewPlacerView); 678 mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height); 679 } 680 } 681 682 /** 683 * Returns the enabled state of the key feedback preview 684 * @return whether or not the key feedback preview is enabled 685 * @see #setKeyPreviewPopupEnabled(boolean, int) 686 */ 687 public boolean isKeyPreviewPopupEnabled() { 688 return mShowKeyPreviewPopup; 689 } 690 691 private void addKeyPreview(final TextView keyPreview) { 692 locatePreviewPlacerView(); 693 mPreviewPlacerView.addView( 694 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0)); 695 } 696 697 private TextView getKeyPreviewText(final int pointerId) { 698 TextView previewText = mKeyPreviewTexts.get(pointerId); 699 if (previewText != null) { 700 return previewText; 701 } 702 final Context context = getContext(); 703 if (mKeyPreviewLayoutId != 0) { 704 previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null); 705 } else { 706 previewText = new TextView(context); 707 } 708 mKeyPreviewTexts.put(pointerId, previewText); 709 return previewText; 710 } 711 712 private void dismissAllKeyPreviews() { 713 final int pointerCount = mKeyPreviewTexts.size(); 714 for (int id = 0; id < pointerCount; id++) { 715 final TextView previewText = mKeyPreviewTexts.get(id); 716 if (previewText != null) { 717 previewText.setVisibility(INVISIBLE); 718 } 719 } 720 PointerTracker.setReleasedKeyGraphicsToAllKeys(); 721 } 722 723 // Background state set 724 private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = { 725 { // STATE_MIDDLE 726 EMPTY_STATE_SET, 727 { R.attr.state_has_morekeys } 728 }, 729 { // STATE_LEFT 730 { R.attr.state_left_edge }, 731 { R.attr.state_left_edge, R.attr.state_has_morekeys } 732 }, 733 { // STATE_RIGHT 734 { R.attr.state_right_edge }, 735 { R.attr.state_right_edge, R.attr.state_has_morekeys } 736 } 737 }; 738 private static final int STATE_MIDDLE = 0; 739 private static final int STATE_LEFT = 1; 740 private static final int STATE_RIGHT = 2; 741 private static final int STATE_NORMAL = 0; 742 private static final int STATE_HAS_MOREKEYS = 1; 743 744 @Override 745 public void showKeyPreview(final PointerTracker tracker) { 746 final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; 747 final Keyboard keyboard = getKeyboard(); 748 if (!mShowKeyPreviewPopup) { 749 previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap; 750 return; 751 } 752 753 final TextView previewText = getKeyPreviewText(tracker.mPointerId); 754 // If the key preview has no parent view yet, add it to the ViewGroup which can place 755 // key preview absolutely in SoftInputWindow. 756 if (previewText.getParent() == null) { 757 addKeyPreview(previewText); 758 } 759 760 mDrawingHandler.cancelDismissKeyPreview(tracker); 761 final Key key = tracker.getKey(); 762 // If key is invalid or IME is already closed, we must not show key preview. 763 // Trying to show key preview while root window is closed causes 764 // WindowManager.BadTokenException. 765 if (key == null) { 766 return; 767 } 768 769 final KeyDrawParams drawParams = mKeyDrawParams; 770 previewText.setTextColor(drawParams.mPreviewTextColor); 771 final Drawable background = previewText.getBackground(); 772 final String label = key.getPreviewLabel(); 773 // What we show as preview should match what we show on a key top in onDraw(). 774 if (label != null) { 775 // TODO Should take care of temporaryShiftLabel here. 776 previewText.setCompoundDrawables(null, null, null, null); 777 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 778 key.selectPreviewTextSize(drawParams)); 779 previewText.setTypeface(key.selectPreviewTypeface(drawParams)); 780 previewText.setText(label); 781 } else { 782 previewText.setCompoundDrawables(null, null, null, 783 key.getPreviewIcon(keyboard.mIconsSet)); 784 previewText.setText(null); 785 } 786 787 previewText.measure( 788 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 789 final int keyDrawWidth = key.getDrawWidth(); 790 final int previewWidth = previewText.getMeasuredWidth(); 791 final int previewHeight = mKeyPreviewHeight; 792 // The width and height of visible part of the key preview background. The content marker 793 // of the background 9-patch have to cover the visible part of the background. 794 previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft() 795 - previewText.getPaddingRight(); 796 previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop() 797 - previewText.getPaddingBottom(); 798 // The distance between the top edge of the parent key and the bottom of the visible part 799 // of the key preview background. 800 previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom(); 801 getLocationInWindow(mOriginCoords); 802 // The key preview is horizontally aligned with the center of the visible part of the 803 // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and 804 // the left/right background is used if such background is specified. 805 final int statePosition; 806 int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 807 + CoordinateUtils.x(mOriginCoords); 808 if (previewX < 0) { 809 previewX = 0; 810 statePosition = STATE_LEFT; 811 } else if (previewX > getWidth() - previewWidth) { 812 previewX = getWidth() - previewWidth; 813 statePosition = STATE_RIGHT; 814 } else { 815 statePosition = STATE_MIDDLE; 816 } 817 // The key preview is placed vertically above the top edge of the parent key with an 818 // arbitrary offset. 819 final int previewY = key.mY - previewHeight + mKeyPreviewOffset 820 + CoordinateUtils.y(mOriginCoords); 821 822 if (background != null) { 823 final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL; 824 background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]); 825 background.setAlpha(PREVIEW_ALPHA); 826 } 827 ViewLayoutUtils.placeViewAt( 828 previewText, previewX, previewY, previewWidth, previewHeight); 829 previewText.setVisibility(VISIBLE); 830 } 831 832 @Override 833 public void dismissKeyPreview(final PointerTracker tracker) { 834 mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker); 835 } 836 837 public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { 838 mSlidingKeyInputPreview.setPreviewEnabled(enabled); 839 } 840 841 @Override 842 public void showSlidingKeyInputPreview(final PointerTracker tracker) { 843 locatePreviewPlacerView(); 844 mSlidingKeyInputPreview.setPreviewPosition(tracker); 845 } 846 847 @Override 848 public void dismissSlidingKeyInputPreview() { 849 mSlidingKeyInputPreview.dismissSlidingKeyInputPreview(); 850 } 851 852 public void setGesturePreviewMode(final boolean drawsGestureTrail, 853 final boolean drawsGestureFloatingPreviewText) { 854 mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText); 855 mGestureTrailsPreview.setPreviewEnabled(drawsGestureTrail); 856 } 857 858 public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) { 859 locatePreviewPlacerView(); 860 mGestureFloatingPreviewText.setSuggetedWords(suggestedWords); 861 } 862 863 public void dismissGestureFloatingPreviewText() { 864 locatePreviewPlacerView(); 865 mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout); 866 } 867 868 @Override 869 public void showGestureTrail(final PointerTracker tracker, 870 final boolean showsFloatingPreviewText) { 871 locatePreviewPlacerView(); 872 if (showsFloatingPreviewText) { 873 mGestureFloatingPreviewText.setPreviewPosition(tracker); 874 } 875 mGestureTrailsPreview.setPreviewPosition(tracker); 876 } 877 878 // Note that this method is called from a non-UI thread. 879 public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 880 PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); 881 } 882 883 public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 884 PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); 885 } 886 887 @Override 888 protected void onAttachedToWindow() { 889 super.onAttachedToWindow(); 890 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 891 // been attached. This is needed to properly show the splash screen, which requires that 892 // the window token of the KeyboardView be non-null. 893 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 894 ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); 895 } 896 } 897 898 @Override 899 protected void onDetachedFromWindow() { 900 super.onDetachedFromWindow(); 901 mPreviewPlacerView.removeAllViews(); 902 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 903 // been detached. This is needed to invalidate the reference of {@link MainKeyboardView} 904 // to null. 905 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 906 ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); 907 } 908 } 909 910 private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { 911 if (key.mMoreKeys == null) { 912 return null; 913 } 914 Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); 915 if (moreKeysKeyboard == null) { 916 moreKeysKeyboard = new MoreKeysKeyboard.Builder( 917 context, key, this, mKeyPreviewDrawParams).build(); 918 mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); 919 } 920 921 final View container = mMoreKeysKeyboardContainer; 922 final MoreKeysKeyboardView moreKeysKeyboardView = 923 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 924 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 925 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 926 return moreKeysKeyboardView; 927 } 928 929 /** 930 * Called when a key is long pressed. 931 * @param tracker the pointer tracker which pressed the parent key 932 */ 933 private void onLongPress(final PointerTracker tracker) { 934 if (isShowingMoreKeysPanel()) { 935 return; 936 } 937 final Key key = tracker.getKey(); 938 if (key == null) { 939 return; 940 } 941 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 942 ResearchLogger.mainKeyboardView_onLongPress(); 943 } 944 final KeyboardActionListener listener = mKeyboardActionListener; 945 if (key.hasNoPanelAutoMoreKey()) { 946 final int moreKeyCode = key.mMoreKeys[0].mCode; 947 tracker.onLongPressed(); 948 listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */); 949 listener.onCodeInput(moreKeyCode, 950 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 951 listener.onReleaseKey(moreKeyCode, false /* withSliding */); 952 return; 953 } 954 final int code = key.mCode; 955 if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { 956 // Long pressing the space key invokes IME switcher dialog. 957 if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { 958 tracker.onLongPressed(); 959 listener.onReleaseKey(code, false /* withSliding */); 960 return; 961 } 962 } 963 openMoreKeysPanel(key, tracker); 964 } 965 966 private void openMoreKeysPanel(final Key key, final PointerTracker tracker) { 967 final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); 968 if (moreKeysPanel == null) { 969 return; 970 } 971 972 final int[] lastCoords = CoordinateUtils.newInstance(); 973 tracker.getLastCoordinates(lastCoords); 974 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview(); 975 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 976 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 977 // keys keyboard is placed at the touch point of the parent key. 978 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 979 ? CoordinateUtils.x(lastCoords) 980 : key.mX + key.mWidth / 2; 981 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 982 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 983 // aligned with the bottom edge of the visible part of the key preview. 984 // {@code mPreviewVisibleOffset} has been set appropriately in 985 // {@link KeyboardView#showKeyPreview(PointerTracker)}. 986 final int pointY = key.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; 987 moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); 988 final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); 989 final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); 990 tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); 991 } 992 993 public boolean isInSlidingKeyInput() { 994 if (isShowingMoreKeysPanel()) { 995 return true; 996 } 997 return PointerTracker.isAnyInSlidingKeyInput(); 998 } 999 1000 @Override 1001 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 1002 locatePreviewPlacerView(); 1003 // TODO: Remove this check 1004 if (panel.isShowingInParent()) { 1005 panel.dismissMoreKeysPanel(); 1006 } 1007 mPreviewPlacerView.addView(panel.getContainerView()); 1008 mMoreKeysPanel = panel; 1009 dimEntireKeyboard(true /* dimmed */); 1010 } 1011 1012 public boolean isShowingMoreKeysPanel() { 1013 return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); 1014 } 1015 1016 @Override 1017 public void onCancelMoreKeysPanel(final MoreKeysPanel panel) { 1018 PointerTracker.dismissAllMoreKeysPanels(); 1019 } 1020 1021 @Override 1022 public void onDismissMoreKeysPanel(final MoreKeysPanel panel) { 1023 dimEntireKeyboard(false /* dimmed */); 1024 if (isShowingMoreKeysPanel()) { 1025 mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView()); 1026 mMoreKeysPanel = null; 1027 } 1028 } 1029 1030 @Override 1031 public boolean dispatchTouchEvent(MotionEvent event) { 1032 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1033 return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event); 1034 } 1035 return super.dispatchTouchEvent(event); 1036 } 1037 1038 @Override 1039 public boolean onTouchEvent(final MotionEvent me) { 1040 if (getKeyboard() == null) { 1041 return false; 1042 } 1043 // TODO: Add multi-touch to single-touch event converter for non-distinct multi-touch 1044 // device. 1045 return processMotionEvent(me); 1046 } 1047 1048 public boolean processMotionEvent(final MotionEvent me) { 1049 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 1050 final int action = me.getActionMasked(); 1051 final int pointerCount = me.getPointerCount(); 1052 final int oldPointerCount = mOldPointerCount; 1053 mOldPointerCount = pointerCount; 1054 1055 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1056 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 1057 // events except a transition from/to single-touch. 1058 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 1059 return true; 1060 } 1061 1062 final long eventTime = me.getEventTime(); 1063 final int index = me.getActionIndex(); 1064 final int id = me.getPointerId(index); 1065 final int x = (int)me.getX(index); 1066 final int y = (int)me.getY(index); 1067 1068 // TODO: This might be moved to the tracker.processMotionEvent() call below. 1069 if (LatinImeLogger.sUsabilityStudy) { 1070 UsabilityStudyLogUtils.writeMotionEvent(me); 1071 } 1072 // TODO: This should be moved to the tracker.processMotionEvent() call below. 1073 // Currently the same "move" event is being logged twice. 1074 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 1075 ResearchLogger.mainKeyboardView_processMotionEvent( 1076 me, action, eventTime, index, id, x, y); 1077 } 1078 1079 if (mKeyTimerHandler.isInKeyRepeat()) { 1080 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1081 // Key repeating timer will be canceled if 2 or more keys are in action, and current 1082 // event (UP or DOWN) is non-modifier key. 1083 if (pointerCount > 1 && !tracker.isModifier()) { 1084 mKeyTimerHandler.cancelKeyRepeatTimer(); 1085 } 1086 // Up event will pass through. 1087 } 1088 1089 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1090 // Translate mutli-touch event to single-touch events on the device that has no distinct 1091 // multi-touch panel. 1092 if (nonDistinctMultitouch) { 1093 // Use only main (id=0) pointer tracker. 1094 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1095 if (pointerCount == 1 && oldPointerCount == 2) { 1096 // Multi-touch to single touch transition. 1097 // Send a down event for the latest pointer if the key is different from the 1098 // previous key. 1099 final Key newKey = tracker.getKeyOn(x, y); 1100 if (mOldKey != newKey) { 1101 tracker.onDownEvent(x, y, eventTime, this); 1102 if (action == MotionEvent.ACTION_UP) { 1103 tracker.onUpEvent(x, y, eventTime); 1104 } 1105 } 1106 } else if (pointerCount == 2 && oldPointerCount == 1) { 1107 // Single-touch to multi-touch transition. 1108 // Send an up event for the last pointer. 1109 final int[] lastCoords = CoordinateUtils.newInstance(); 1110 mOldKey = tracker.getKeyOn( 1111 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); 1112 tracker.onUpEvent( 1113 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); 1114 } else if (pointerCount == 1 && oldPointerCount == 1) { 1115 tracker.processMotionEvent(action, x, y, eventTime, this); 1116 } else { 1117 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 1118 + " (old " + oldPointerCount + ")"); 1119 } 1120 return true; 1121 } 1122 1123 if (action == MotionEvent.ACTION_MOVE) { 1124 for (int i = 0; i < pointerCount; i++) { 1125 final int pointerId = me.getPointerId(i); 1126 final PointerTracker tracker = PointerTracker.getPointerTracker( 1127 pointerId, this); 1128 final int px = (int)me.getX(i); 1129 final int py = (int)me.getY(i); 1130 tracker.onMoveEvent(px, py, eventTime, me); 1131 } 1132 } else { 1133 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1134 tracker.processMotionEvent(action, x, y, eventTime, this); 1135 } 1136 1137 return true; 1138 } 1139 1140 public void cancelAllOngoingEvents() { 1141 mKeyTimerHandler.cancelAllMessages(); 1142 mDrawingHandler.cancelAllMessages(); 1143 dismissAllKeyPreviews(); 1144 dismissGestureFloatingPreviewText(); 1145 dismissSlidingKeyInputPreview(); 1146 PointerTracker.dismissAllMoreKeysPanels(); 1147 PointerTracker.cancelAllPointerTrackers(); 1148 } 1149 1150 public void closing() { 1151 cancelAllOngoingEvents(); 1152 mMoreKeysKeyboardCache.clear(); 1153 } 1154 1155 /** 1156 * Receives hover events from the input framework. 1157 * 1158 * @param event The motion event to be dispatched. 1159 * @return {@code true} if the event was handled by the view, {@code false} 1160 * otherwise 1161 */ 1162 @Override 1163 public boolean dispatchHoverEvent(final MotionEvent event) { 1164 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1165 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1166 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 1167 } 1168 1169 // Reflection doesn't support calling superclass methods. 1170 return false; 1171 } 1172 1173 public void updateShortcutKey(final boolean available) { 1174 final Keyboard keyboard = getKeyboard(); 1175 if (keyboard == null) { 1176 return; 1177 } 1178 final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); 1179 if (shortcutKey == null) { 1180 return; 1181 } 1182 shortcutKey.setEnabled(available); 1183 invalidateKey(shortcutKey); 1184 } 1185 1186 public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, 1187 final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) { 1188 mNeedsToDisplayLanguage = needsToDisplayLanguage; 1189 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 1190 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 1191 if (animator == null) { 1192 mNeedsToDisplayLanguage = false; 1193 } else { 1194 if (subtypeChanged && needsToDisplayLanguage) { 1195 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 1196 if (animator.isStarted()) { 1197 animator.cancel(); 1198 } 1199 animator.start(); 1200 } else { 1201 if (!animator.isStarted()) { 1202 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 1203 } 1204 } 1205 } 1206 invalidateKey(mSpaceKey); 1207 } 1208 1209 public void updateAutoCorrectionState(final boolean isAutoCorrection) { 1210 if (!mAutoCorrectionSpacebarLedEnabled) { 1211 return; 1212 } 1213 mAutoCorrectionSpacebarLedOn = isAutoCorrection; 1214 invalidateKey(mSpaceKey); 1215 } 1216 1217 private void dimEntireKeyboard(final boolean dimmed) { 1218 final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; 1219 mNeedsToDimEntireKeyboard = dimmed; 1220 if (needsRedrawing) { 1221 invalidateAllKeys(); 1222 } 1223 } 1224 1225 @Override 1226 protected void onDraw(final Canvas canvas) { 1227 super.onDraw(canvas); 1228 1229 // Overlay a dark rectangle to dim. 1230 if (mNeedsToDimEntireKeyboard) { 1231 canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint); 1232 } 1233 } 1234 1235 @Override 1236 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 1237 final KeyDrawParams params) { 1238 if (key.altCodeWhileTyping() && key.isEnabled()) { 1239 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 1240 } 1241 if (key.mCode == Constants.CODE_SPACE) { 1242 drawSpacebar(key, canvas, paint); 1243 // Whether space key needs to show the "..." popup hint for special purposes 1244 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 1245 drawKeyPopupHint(key, canvas, paint, params); 1246 } 1247 } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { 1248 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1249 drawKeyPopupHint(key, canvas, paint, params); 1250 } else { 1251 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1252 } 1253 } 1254 1255 private static boolean fitsTextIntoWidth(final int width, final String text, 1256 final Paint paint) { 1257 paint.setTextScaleX(1.0f); 1258 final float textWidth = TypefaceUtils.getLabelWidth(text, paint); 1259 if (textWidth < width) { 1260 return true; 1261 } 1262 1263 final float scaleX = width / textWidth; 1264 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { 1265 return false; 1266 } 1267 1268 paint.setTextScaleX(scaleX); 1269 return TypefaceUtils.getLabelWidth(text, paint) < width; 1270 } 1271 1272 // Layout language name on spacebar. 1273 private static String layoutLanguageOnSpacebar(final Paint paint, 1274 final InputMethodSubtype subtype, final int width) { 1275 // Choose appropriate language name to fit into the width. 1276 final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype); 1277 if (fitsTextIntoWidth(width, fullText, paint)) { 1278 return fullText; 1279 } 1280 1281 final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype); 1282 if (fitsTextIntoWidth(width, middleText, paint)) { 1283 return middleText; 1284 } 1285 1286 final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype); 1287 if (fitsTextIntoWidth(width, shortText, paint)) { 1288 return shortText; 1289 } 1290 1291 return ""; 1292 } 1293 1294 private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) { 1295 final int width = key.mWidth; 1296 final int height = key.mHeight; 1297 1298 // If input language are explicitly selected. 1299 if (mNeedsToDisplayLanguage) { 1300 paint.setTextAlign(Align.CENTER); 1301 paint.setTypeface(Typeface.DEFAULT); 1302 paint.setTextSize(mSpacebarTextSize); 1303 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 1304 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 1305 // Draw language text with shadow 1306 final float descent = paint.descent(); 1307 final float textHeight = -paint.ascent() + descent; 1308 final float baseline = height / 2 + textHeight / 2; 1309 paint.setColor(mSpacebarTextShadowColor); 1310 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1311 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 1312 paint.setColor(mSpacebarTextColor); 1313 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1314 canvas.drawText(language, width / 2, baseline - descent, paint); 1315 } 1316 1317 // Draw the spacebar icon at the bottom 1318 if (mAutoCorrectionSpacebarLedOn) { 1319 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 1320 final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); 1321 int x = (width - iconWidth) / 2; 1322 int y = height - iconHeight; 1323 drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); 1324 } else if (mSpaceIcon != null) { 1325 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 1326 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 1327 int x = (width - iconWidth) / 2; 1328 int y = height - iconHeight; 1329 drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); 1330 } 1331 } 1332} 1333