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