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