MainKeyboardView.java revision 9647d7fbee4cbd72876e949e6544dc43fadbd148
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.animation.AnimatorInflater; 20import android.animation.ObjectAnimator; 21import android.content.Context; 22import android.content.SharedPreferences; 23import android.content.pm.PackageManager; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.Color; 27import android.graphics.Paint; 28import android.graphics.Paint.Align; 29import android.graphics.Typeface; 30import android.preference.PreferenceManager; 31import android.util.AttributeSet; 32import android.util.Log; 33import android.view.LayoutInflater; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.inputmethod.InputMethodSubtype; 38import android.widget.TextView; 39 40import com.android.inputmethod.accessibility.AccessibilityUtils; 41import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate; 42import com.android.inputmethod.annotations.ExternallyReferenced; 43import com.android.inputmethod.keyboard.internal.DrawingHandler; 44import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView; 45import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview; 46import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview; 47import com.android.inputmethod.keyboard.internal.KeyDrawParams; 48import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer; 49import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; 50import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper; 51import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; 52import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview; 53import com.android.inputmethod.keyboard.internal.TimerHandler; 54import com.android.inputmethod.latin.Constants; 55import com.android.inputmethod.latin.LatinImeLogger; 56import com.android.inputmethod.latin.R; 57import com.android.inputmethod.latin.SuggestedWords; 58import com.android.inputmethod.latin.define.ProductionFlag; 59import com.android.inputmethod.latin.settings.DebugSettings; 60import com.android.inputmethod.latin.utils.CoordinateUtils; 61import com.android.inputmethod.latin.utils.SpacebarLanguageUtils; 62import com.android.inputmethod.latin.utils.TypefaceUtils; 63import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils; 64import com.android.inputmethod.research.ResearchLogger; 65 66import java.util.WeakHashMap; 67 68/** 69 * A view that is responsible for detecting key presses and touch movements. 70 * 71 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio 72 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor 73 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius 74 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor 75 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha 76 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator 77 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator 78 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator 79 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance 80 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime 81 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance 82 * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger 83 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout 84 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval 85 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout 86 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout 87 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout 88 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout 89 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset 90 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight 91 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout 92 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout 93 * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha 94 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint 95 * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout 96 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping 97 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold 98 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration 99 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom 100 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo 101 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom 102 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo 103 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance 104 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime 105 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold 106 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration 107 */ 108public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy, 109 MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks { 110 private static final String TAG = MainKeyboardView.class.getSimpleName(); 111 112 /** Listener for {@link KeyboardActionListener}. */ 113 private KeyboardActionListener mKeyboardActionListener; 114 115 /* Space key and its icon and background. */ 116 private Key mSpaceKey; 117 // Stuff to draw language name on spacebar. 118 private final int mLanguageOnSpacebarFinalAlpha; 119 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 120 private int mLanguageOnSpacebarFormatType; 121 private boolean mHasMultipleEnabledIMEsOrSubtypes; 122 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 123 private final float mLanguageOnSpacebarTextRatio; 124 private float mLanguageOnSpacebarTextSize; 125 private final int mLanguageOnSpacebarTextColor; 126 private final float mLanguageOnSpacebarTextShadowRadius; 127 private final int mLanguageOnSpacebarTextShadowColor; 128 private static final float LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED = -1.0f; 129 // The minimum x-scale to fit the language name on spacebar. 130 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 131 132 // Stuff to draw altCodeWhileTyping keys. 133 private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 134 private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 135 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 136 137 // Drawing preview placer view 138 private final DrawingPreviewPlacerView mDrawingPreviewPlacerView; 139 private final int[] mOriginCoords = CoordinateUtils.newInstance(); 140 private final GestureFloatingTextDrawingPreview mGestureFloatingTextDrawingPreview; 141 private final GestureTrailsDrawingPreview mGestureTrailsDrawingPreview; 142 private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview; 143 144 // Key preview 145 private final KeyPreviewDrawParams mKeyPreviewDrawParams; 146 private final KeyPreviewChoreographer mKeyPreviewChoreographer; 147 148 // More keys keyboard 149 private final Paint mBackgroundDimAlphaPaint = new Paint(); 150 private boolean mNeedsToDimEntireKeyboard; 151 private final View mMoreKeysKeyboardContainer; 152 private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>(); 153 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 154 // More keys panel (used by both more keys keyboard and more suggestions view) 155 // TODO: Consider extending to support multiple more keys panels 156 private MoreKeysPanel mMoreKeysPanel; 157 158 // Gesture floating preview text 159 // TODO: Make this parameter customizable by user via settings. 160 private int mGestureFloatingPreviewTextLingerTimeout; 161 162 private final KeyDetector mKeyDetector; 163 private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper; 164 165 private final TimerHandler mKeyTimerHandler; 166 private final int mLanguageOnSpacebarHorizontalMargin; 167 168 private final DrawingHandler mDrawingHandler = 169 new DrawingHandler(this); 170 171 private final MainKeyboardAccessibilityDelegate mAccessibilityDelegate; 172 173 public MainKeyboardView(final Context context, final AttributeSet attrs) { 174 this(context, attrs, R.attr.mainKeyboardViewStyle); 175 } 176 177 public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 178 super(context, attrs, defStyle); 179 180 mDrawingPreviewPlacerView = new DrawingPreviewPlacerView(context, attrs); 181 182 final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( 183 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); 184 final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( 185 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); 186 final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( 187 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); 188 mKeyTimerHandler = new TimerHandler( 189 this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime); 190 191 final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension( 192 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f); 193 final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension( 194 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f); 195 mKeyDetector = new KeyDetector( 196 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); 197 198 PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */); 199 200 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 201 final boolean forceNonDistinctMultitouch = prefs.getBoolean( 202 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); 203 final boolean hasDistinctMultitouch = context.getPackageManager() 204 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT) 205 && !forceNonDistinctMultitouch; 206 mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null 207 : new NonDistinctMultitouchHelper(); 208 209 final int backgroundDimAlpha = mainKeyboardViewAttr.getInt( 210 R.styleable.MainKeyboardView_backgroundDimAlpha, 0); 211 mBackgroundDimAlphaPaint.setColor(Color.BLACK); 212 mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha); 213 mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction( 214 R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f); 215 mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor( 216 R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0); 217 mLanguageOnSpacebarTextShadowRadius = mainKeyboardViewAttr.getFloat( 218 R.styleable.MainKeyboardView_languageOnSpacebarTextShadowRadius, 219 LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED); 220 mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor( 221 R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0); 222 mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt( 223 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, 224 Constants.Color.ALPHA_OPAQUE); 225 final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 226 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); 227 final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 228 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); 229 final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId( 230 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); 231 232 mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr); 233 mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams); 234 235 final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId( 236 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0); 237 mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean( 238 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); 239 240 mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt( 241 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0); 242 243 mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview( 244 mDrawingPreviewPlacerView, mainKeyboardViewAttr); 245 mDrawingPreviewPlacerView.addPreview(mGestureFloatingTextDrawingPreview); 246 247 mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview( 248 mDrawingPreviewPlacerView, mainKeyboardViewAttr); 249 mDrawingPreviewPlacerView.addPreview(mGestureTrailsDrawingPreview); 250 251 mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview( 252 mDrawingPreviewPlacerView, mainKeyboardViewAttr); 253 mDrawingPreviewPlacerView.addPreview(mSlidingKeyInputDrawingPreview); 254 mainKeyboardViewAttr.recycle(); 255 256 mMoreKeysKeyboardContainer = LayoutInflater.from(getContext()) 257 .inflate(moreKeysKeyboardLayoutId, null); 258 mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( 259 languageOnSpacebarFadeoutAnimatorResId, this); 260 mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( 261 altCodeKeyWhileTypingFadeoutAnimatorResId, this); 262 mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( 263 altCodeKeyWhileTypingFadeinAnimatorResId, this); 264 265 mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; 266 267 mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension( 268 R.dimen.config_language_on_spacebar_horizontal_margin); 269 270 mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector); 271 } 272 273 @Override 274 public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { 275 super.setHardwareAcceleratedDrawingEnabled(enabled); 276 mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled); 277 } 278 279 private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { 280 if (resId == 0) { 281 // TODO: Stop returning null. 282 return null; 283 } 284 final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( 285 getContext(), resId); 286 if (animator != null) { 287 animator.setTarget(target); 288 } 289 return animator; 290 } 291 292 private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, 293 final ObjectAnimator animatorToStart) { 294 if (animatorToCancel == null || animatorToStart == null) { 295 // TODO: Stop using null as a no-operation animator. 296 return; 297 } 298 float startFraction = 0.0f; 299 if (animatorToCancel.isStarted()) { 300 animatorToCancel.cancel(); 301 startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); 302 } 303 final long startTime = (long)(animatorToStart.getDuration() * startFraction); 304 animatorToStart.start(); 305 animatorToStart.setCurrentPlayTime(startTime); 306 } 307 308 // Implements {@link TimerHander.Callbacks} method. 309 @Override 310 public void startWhileTypingFadeinAnimation() { 311 cancelAndStartAnimators( 312 mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator); 313 } 314 315 @Override 316 public void startWhileTypingFadeoutAnimation() { 317 cancelAndStartAnimators( 318 mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator); 319 } 320 321 @ExternallyReferenced 322 public int getLanguageOnSpacebarAnimAlpha() { 323 return mLanguageOnSpacebarAnimAlpha; 324 } 325 326 @ExternallyReferenced 327 public void setLanguageOnSpacebarAnimAlpha(final int alpha) { 328 mLanguageOnSpacebarAnimAlpha = alpha; 329 invalidateKey(mSpaceKey); 330 } 331 332 @ExternallyReferenced 333 public int getAltCodeKeyWhileTypingAnimAlpha() { 334 return mAltCodeKeyWhileTypingAnimAlpha; 335 } 336 337 @ExternallyReferenced 338 public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { 339 if (mAltCodeKeyWhileTypingAnimAlpha == alpha) { 340 return; 341 } 342 // Update the visual of alt-code-key-while-typing. 343 mAltCodeKeyWhileTypingAnimAlpha = alpha; 344 final Keyboard keyboard = getKeyboard(); 345 if (keyboard == null) { 346 return; 347 } 348 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 349 invalidateKey(key); 350 } 351 } 352 353 public void setKeyboardActionListener(final KeyboardActionListener listener) { 354 mKeyboardActionListener = listener; 355 PointerTracker.setKeyboardActionListener(listener); 356 } 357 358 // TODO: We should reconsider which coordinate system should be used to represent keyboard 359 // event. 360 public int getKeyX(final int x) { 361 return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x; 362 } 363 364 // TODO: We should reconsider which coordinate system should be used to represent keyboard 365 // event. 366 public int getKeyY(final int y) { 367 return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y; 368 } 369 370 /** 371 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 372 * view will re-layout itself to accommodate the keyboard. 373 * @see Keyboard 374 * @see #getKeyboard() 375 * @param keyboard the keyboard to display in this view 376 */ 377 @Override 378 public void setKeyboard(final Keyboard keyboard) { 379 // Remove any pending messages, except dismissing preview and key repeat. 380 mKeyTimerHandler.cancelLongPressTimers(); 381 super.setKeyboard(keyboard); 382 mKeyDetector.setKeyboard( 383 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); 384 PointerTracker.setKeyDetector(mKeyDetector); 385 mMoreKeysKeyboardCache.clear(); 386 387 mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); 388 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 389 mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio; 390 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 391 final int orientation = getContext().getResources().getConfiguration().orientation; 392 ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation); 393 } 394 395 mAccessibilityDelegate.setKeyboard(keyboard); 396 } 397 398 /** 399 * Enables or disables the key feedback popup. This is a popup that shows a magnified 400 * version of the depressed key. By default the preview is enabled. 401 * @param previewEnabled whether or not to enable the key feedback preview 402 * @param delay the delay after which the preview is dismissed 403 * @see #isKeyPreviewPopupEnabled() 404 */ 405 public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { 406 mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay); 407 } 408 409 public void setKeyPreviewAnimationParams(final float showUpStartScale, final int showUpDuration, 410 final float dismissEndScale, final int dismissDuration) { 411 mKeyPreviewDrawParams.setAnimationParams( 412 showUpStartScale, showUpDuration, dismissEndScale, dismissDuration); 413 } 414 415 private void locatePreviewPlacerView() { 416 getLocationInWindow(mOriginCoords); 417 mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight()); 418 } 419 420 private void installPreviewPlacerView() { 421 final View rootView = getRootView(); 422 if (rootView == null) { 423 Log.w(TAG, "Cannot find root view"); 424 return; 425 } 426 final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); 427 // Note: It'd be very weird if we get null by android.R.id.content. 428 if (windowContentView == null) { 429 Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView"); 430 return; 431 } 432 windowContentView.addView(mDrawingPreviewPlacerView); 433 } 434 435 /** 436 * Returns the enabled state of the key feedback preview 437 * @return whether or not the key feedback preview is enabled 438 * @see #setKeyPreviewPopupEnabled(boolean, int) 439 */ 440 public boolean isKeyPreviewPopupEnabled() { 441 return mKeyPreviewDrawParams.isPopupEnabled(); 442 } 443 444 // Implements {@link DrawingHandler.Callbacks} method. 445 @Override 446 public void dismissAllKeyPreviews() { 447 mKeyPreviewChoreographer.dismissAllKeyPreviews(); 448 PointerTracker.setReleasedKeyGraphicsToAllKeys(); 449 } 450 451 @Override 452 public void showKeyPreview(final Key key) { 453 // If key is invalid or IME is already closed, we must not show key preview. 454 // Trying to show key preview while root window is closed causes 455 // WindowManager.BadTokenException. 456 if (key == null) { 457 return; 458 } 459 460 final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; 461 final Keyboard keyboard = getKeyboard(); 462 if (!previewParams.isPopupEnabled()) { 463 previewParams.setVisibleOffset(-keyboard.mVerticalGap); 464 return; 465 } 466 467 locatePreviewPlacerView(); 468 final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView( 469 key, mDrawingPreviewPlacerView); 470 getLocationInWindow(mOriginCoords); 471 mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet, 472 mKeyDrawParams, getWidth(), mOriginCoords); 473 mKeyPreviewChoreographer.showKeyPreview(key, previewTextView, isHardwareAccelerated()); 474 } 475 476 // Implements {@link TimerHandler.Callbacks} method. 477 @Override 478 public void dismissKeyPreviewWithoutDelay(final Key key) { 479 mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */); 480 // To redraw key top letter. 481 invalidateKey(key); 482 } 483 484 @Override 485 public void dismissKeyPreview(final Key key) { 486 if (!isHardwareAccelerated()) { 487 // TODO: Implement preference option to control key preview method and duration. 488 mDrawingHandler.dismissKeyPreview(mKeyPreviewDrawParams.getLingerTimeout(), key); 489 return; 490 } 491 mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */); 492 } 493 494 public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { 495 mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled); 496 } 497 498 @Override 499 public void showSlidingKeyInputPreview(final PointerTracker tracker) { 500 locatePreviewPlacerView(); 501 mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker); 502 } 503 504 @Override 505 public void dismissSlidingKeyInputPreview() { 506 mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview(); 507 } 508 509 private void setGesturePreviewMode(final boolean isGestureTrailEnabled, 510 final boolean isGestureFloatingPreviewTextEnabled) { 511 mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled); 512 mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled); 513 } 514 515 // Implements {@link DrawingHandler.Callbacks} method. 516 @Override 517 public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) { 518 locatePreviewPlacerView(); 519 mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords); 520 } 521 522 public void dismissGestureFloatingPreviewText() { 523 locatePreviewPlacerView(); 524 mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout); 525 } 526 527 @Override 528 public void showGestureTrail(final PointerTracker tracker, 529 final boolean showsFloatingPreviewText) { 530 locatePreviewPlacerView(); 531 if (showsFloatingPreviewText) { 532 mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker); 533 } 534 mGestureTrailsDrawingPreview.setPreviewPosition(tracker); 535 } 536 537 // Note that this method is called from a non-UI thread. 538 @SuppressWarnings("static-method") 539 public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 540 PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); 541 } 542 543 public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, 544 final boolean isGestureTrailEnabled, 545 final boolean isGestureFloatingPreviewTextEnabled) { 546 PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser); 547 setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled, 548 isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled); 549 } 550 551 @Override 552 protected void onAttachedToWindow() { 553 super.onAttachedToWindow(); 554 installPreviewPlacerView(); 555 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 556 // been attached. This is needed to properly show the splash screen, which requires that 557 // the window token of the KeyboardView be non-null. 558 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 559 ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); 560 } 561 } 562 563 @Override 564 protected void onDetachedFromWindow() { 565 super.onDetachedFromWindow(); 566 mDrawingPreviewPlacerView.removeAllViews(); 567 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 568 // been detached. This is needed to invalidate the reference of {@link MainKeyboardView} 569 // to null. 570 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 571 ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); 572 } 573 } 574 575 private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { 576 if (key.getMoreKeys() == null) { 577 return null; 578 } 579 Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); 580 if (moreKeysKeyboard == null) { 581 moreKeysKeyboard = new MoreKeysKeyboard.Builder( 582 context, key, this, mKeyPreviewDrawParams).build(); 583 mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); 584 } 585 586 final View container = mMoreKeysKeyboardContainer; 587 final MoreKeysKeyboardView moreKeysKeyboardView = 588 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 589 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 590 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 591 return moreKeysKeyboardView; 592 } 593 594 // Implements {@link TimerHandler.Callbacks} method. 595 /** 596 * Called when a key is long pressed. 597 * @param tracker the pointer tracker which pressed the parent key 598 */ 599 @Override 600 public void onLongPress(final PointerTracker tracker) { 601 if (isShowingMoreKeysPanel()) { 602 return; 603 } 604 final Key key = tracker.getKey(); 605 if (key == null) { 606 return; 607 } 608 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 609 ResearchLogger.mainKeyboardView_onLongPress(); 610 } 611 final KeyboardActionListener listener = mKeyboardActionListener; 612 if (key.hasNoPanelAutoMoreKey()) { 613 final int moreKeyCode = key.getMoreKeys()[0].mCode; 614 tracker.onLongPressed(); 615 listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */); 616 listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, 617 Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); 618 listener.onReleaseKey(moreKeyCode, false /* withSliding */); 619 return; 620 } 621 final int code = key.getCode(); 622 if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { 623 // Long pressing the space key invokes IME switcher dialog. 624 if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { 625 tracker.onLongPressed(); 626 listener.onReleaseKey(code, false /* withSliding */); 627 return; 628 } 629 } 630 openMoreKeysPanel(key, tracker); 631 } 632 633 private void openMoreKeysPanel(final Key key, final PointerTracker tracker) { 634 final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); 635 if (moreKeysPanel == null) { 636 return; 637 } 638 639 final int[] lastCoords = CoordinateUtils.newInstance(); 640 tracker.getLastCoordinates(lastCoords); 641 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview(); 642 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 643 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 644 // keys keyboard is placed at the touch point of the parent key. 645 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 646 ? CoordinateUtils.x(lastCoords) 647 : key.getX() + key.getWidth() / 2; 648 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 649 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 650 // aligned with the bottom edge of the visible part of the key preview. 651 // {@code mPreviewVisibleOffset} has been set appropriately in 652 // {@link KeyboardView#showKeyPreview(PointerTracker)}. 653 final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset(); 654 moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); 655 tracker.onShowMoreKeysPanel(moreKeysPanel); 656 // TODO: Implement zoom in animation of more keys panel. 657 dismissKeyPreviewWithoutDelay(key); 658 } 659 660 public boolean isInDraggingFinger() { 661 if (isShowingMoreKeysPanel()) { 662 return true; 663 } 664 return PointerTracker.isAnyInDraggingFinger(); 665 } 666 667 @Override 668 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 669 locatePreviewPlacerView(); 670 panel.showInParent(mDrawingPreviewPlacerView); 671 mMoreKeysPanel = panel; 672 dimEntireKeyboard(true /* dimmed */); 673 } 674 675 public boolean isShowingMoreKeysPanel() { 676 return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); 677 } 678 679 @Override 680 public void onCancelMoreKeysPanel() { 681 PointerTracker.dismissAllMoreKeysPanels(); 682 } 683 684 @Override 685 public void onDismissMoreKeysPanel() { 686 dimEntireKeyboard(false /* dimmed */); 687 if (isShowingMoreKeysPanel()) { 688 mMoreKeysPanel.removeFromParent(); 689 mMoreKeysPanel = null; 690 } 691 } 692 693 public void startDoubleTapShiftKeyTimer() { 694 mKeyTimerHandler.startDoubleTapShiftKeyTimer(); 695 } 696 697 public void cancelDoubleTapShiftKeyTimer() { 698 mKeyTimerHandler.cancelDoubleTapShiftKeyTimer(); 699 } 700 701 public boolean isInDoubleTapShiftKeyTimeout() { 702 return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout(); 703 } 704 705 @Override 706 public boolean onTouchEvent(final MotionEvent me) { 707 if (getKeyboard() == null) { 708 return false; 709 } 710 if (mNonDistinctMultitouchHelper != null) { 711 if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) { 712 // Key repeating timer will be canceled if 2 or more keys are in action. 713 mKeyTimerHandler.cancelKeyRepeatTimers(); 714 } 715 // Non distinct multitouch screen support 716 mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector); 717 return true; 718 } 719 return processMotionEvent(me); 720 } 721 722 public boolean processMotionEvent(final MotionEvent me) { 723 if (LatinImeLogger.sUsabilityStudy) { 724 UsabilityStudyLogUtils.writeMotionEvent(me); 725 } 726 // Currently the same "move" event is being logged twice. 727 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 728 ResearchLogger.mainKeyboardView_processMotionEvent(me); 729 } 730 731 final int index = me.getActionIndex(); 732 final int id = me.getPointerId(index); 733 final PointerTracker tracker = PointerTracker.getPointerTracker(id); 734 // When a more keys panel is showing, we should ignore other fingers' single touch events 735 // other than the finger that is showing the more keys panel. 736 if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel() 737 && PointerTracker.getActivePointerTrackerCount() == 1) { 738 return true; 739 } 740 tracker.processMotionEvent(me, mKeyDetector); 741 return true; 742 } 743 744 public void cancelAllOngoingEvents() { 745 mKeyTimerHandler.cancelAllMessages(); 746 mDrawingHandler.cancelAllMessages(); 747 dismissAllKeyPreviews(); 748 dismissGestureFloatingPreviewText(); 749 dismissSlidingKeyInputPreview(); 750 PointerTracker.dismissAllMoreKeysPanels(); 751 PointerTracker.cancelAllPointerTrackers(); 752 } 753 754 public void closing() { 755 cancelAllOngoingEvents(); 756 mMoreKeysKeyboardCache.clear(); 757 } 758 759 public void onHideWindow() { 760 if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { 761 mAccessibilityDelegate.onHideWindow(); 762 } 763 } 764 765 /** 766 * {@inheritDoc} 767 */ 768 @Override 769 public boolean onHoverEvent(final MotionEvent event) { 770 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 771 return mAccessibilityDelegate.onHoverEvent(event); 772 } 773 return super.onHoverEvent(event); 774 } 775 776 public void updateShortcutKey(final boolean available) { 777 final Keyboard keyboard = getKeyboard(); 778 if (keyboard == null) { 779 return; 780 } 781 final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); 782 if (shortcutKey == null) { 783 return; 784 } 785 shortcutKey.setEnabled(available); 786 invalidateKey(shortcutKey); 787 } 788 789 public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, 790 final int languageOnSpacebarFormatType, 791 final boolean hasMultipleEnabledIMEsOrSubtypes) { 792 mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType; 793 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 794 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 795 if (animator == null) { 796 mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE; 797 } else { 798 if (subtypeChanged 799 && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) { 800 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 801 if (animator.isStarted()) { 802 animator.cancel(); 803 } 804 animator.start(); 805 } else { 806 if (!animator.isStarted()) { 807 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 808 } 809 } 810 } 811 invalidateKey(mSpaceKey); 812 } 813 814 private void dimEntireKeyboard(final boolean dimmed) { 815 final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; 816 mNeedsToDimEntireKeyboard = dimmed; 817 if (needsRedrawing) { 818 invalidateAllKeys(); 819 } 820 } 821 822 @Override 823 protected void onDraw(final Canvas canvas) { 824 super.onDraw(canvas); 825 826 // Overlay a dark rectangle to dim. 827 if (mNeedsToDimEntireKeyboard) { 828 canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint); 829 } 830 } 831 832 @Override 833 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 834 final KeyDrawParams params) { 835 if (key.altCodeWhileTyping() && key.isEnabled()) { 836 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 837 } 838 super.onDrawKeyTopVisuals(key, canvas, paint, params); 839 final int code = key.getCode(); 840 if (code == Constants.CODE_SPACE) { 841 // If input language are explicitly selected. 842 if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) { 843 drawLanguageOnSpacebar(key, canvas, paint); 844 } 845 // Whether space key needs to show the "..." popup hint for special purposes 846 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 847 drawKeyPopupHint(key, canvas, paint, params); 848 } 849 } else if (code == Constants.CODE_LANGUAGE_SWITCH) { 850 drawKeyPopupHint(key, canvas, paint, params); 851 } 852 } 853 854 private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { 855 final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2; 856 paint.setTextScaleX(1.0f); 857 final float textWidth = TypefaceUtils.getStringWidth(text, paint); 858 if (textWidth < width) { 859 return true; 860 } 861 862 final float scaleX = maxTextWidth / textWidth; 863 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { 864 return false; 865 } 866 867 paint.setTextScaleX(scaleX); 868 return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth; 869 } 870 871 // Layout language name on spacebar. 872 private String layoutLanguageOnSpacebar(final Paint paint, 873 final InputMethodSubtype subtype, final int width) { 874 // Choose appropriate language name to fit into the width. 875 if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) { 876 final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype); 877 if (fitsTextIntoWidth(width, fullText, paint)) { 878 return fullText; 879 } 880 } 881 882 final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype); 883 if (fitsTextIntoWidth(width, middleText, paint)) { 884 return middleText; 885 } 886 887 return ""; 888 } 889 890 private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) { 891 final int width = key.getWidth(); 892 final int height = key.getHeight(); 893 paint.setTextAlign(Align.CENTER); 894 paint.setTypeface(Typeface.DEFAULT); 895 paint.setTextSize(mLanguageOnSpacebarTextSize); 896 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 897 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 898 // Draw language text with shadow 899 final float descent = paint.descent(); 900 final float textHeight = -paint.ascent() + descent; 901 final float baseline = height / 2 + textHeight / 2; 902 if (mLanguageOnSpacebarTextShadowRadius > 0.0f) { 903 paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0, 904 mLanguageOnSpacebarTextShadowColor); 905 } else { 906 paint.clearShadowLayer(); 907 } 908 paint.setColor(mLanguageOnSpacebarTextColor); 909 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 910 canvas.drawText(language, width / 2, baseline - descent, paint); 911 paint.clearShadowLayer(); 912 paint.setTextScaleX(1.0f); 913 } 914 915 @Override 916 public void deallocateMemory() { 917 super.deallocateMemory(); 918 mDrawingPreviewPlacerView.deallocateMemory(); 919 } 920} 921