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