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