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