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