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