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.text.TextUtils; 32 import android.util.AttributeSet; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.ViewGroup; 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.DrawingPreviewPlacerView; 43 import com.android.inputmethod.keyboard.internal.DrawingProxy; 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.MoreKeySpec; 51 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; 52 import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview; 53 import com.android.inputmethod.keyboard.internal.TimerHandler; 54 import com.android.inputmethod.latin.R; 55 import com.android.inputmethod.latin.RichInputMethodSubtype; 56 import com.android.inputmethod.latin.SuggestedWords; 57 import com.android.inputmethod.latin.common.Constants; 58 import com.android.inputmethod.latin.common.CoordinateUtils; 59 import com.android.inputmethod.latin.settings.DebugSettings; 60 import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; 61 import com.android.inputmethod.latin.utils.TypefaceUtils; 62 63 import java.util.Locale; 64 import java.util.WeakHashMap; 65 66 import javax.annotation.Nonnull; 67 import 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 */ 112 public 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 MainKeyboardView(final Context context, final AttributeSet attrs)174 public MainKeyboardView(final Context context, final AttributeSet attrs) { 175 this(context, attrs, R.attr.mainKeyboardViewStyle); 176 } 177 MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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 setHardwareAcceleratedDrawingEnabled(final boolean enabled)279 public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { 280 super.setHardwareAcceleratedDrawingEnabled(enabled); 281 mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled); 282 } 283 loadObjectAnimator(final int resId, final Object target)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 cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)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 startWhileTypingAnimation(final int fadeInOrOut)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 getLanguageOnSpacebarAnimAlpha()334 public int getLanguageOnSpacebarAnimAlpha() { 335 return mLanguageOnSpacebarAnimAlpha; 336 } 337 338 @ExternallyReferenced setLanguageOnSpacebarAnimAlpha(final int alpha)339 public void setLanguageOnSpacebarAnimAlpha(final int alpha) { 340 mLanguageOnSpacebarAnimAlpha = alpha; 341 invalidateKey(mSpaceKey); 342 } 343 344 @ExternallyReferenced getAltCodeKeyWhileTypingAnimAlpha()345 public int getAltCodeKeyWhileTypingAnimAlpha() { 346 return mAltCodeKeyWhileTypingAnimAlpha; 347 } 348 349 @ExternallyReferenced setAltCodeKeyWhileTypingAnimAlpha(final int alpha)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 setKeyboardActionListener(final KeyboardActionListener listener)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. getKeyX(final int x)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. getKeyY(final int y)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 setKeyboard(final Keyboard keyboard)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 */ setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay)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 */ setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams, final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration, final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration)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 locatePreviewPlacerView()444 private void locatePreviewPlacerView() { 445 getLocationInWindow(mOriginCoords); 446 mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight()); 447 } 448 installPreviewPlacerView()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 onKeyPressed(@onnull final Key key, final boolean withPreview)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 showKeyPreview(@onnull final Key key)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 dismissKeyPreviewWithoutDelay(@onnull final Key key)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 onKeyReleased(@onnull final Key key, final boolean withAnimation)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 dismissKeyPreview(@onnull final Key key)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 setSlidingKeyInputPreviewEnabled(final boolean enabled)519 public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { 520 mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled); 521 } 522 523 @Override showSlidingKeyInputPreview(@ullable final PointerTracker tracker)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 setGesturePreviewMode(final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)533 private void setGesturePreviewMode(final boolean isGestureTrailEnabled, 534 final boolean isGestureFloatingPreviewTextEnabled) { 535 mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled); 536 mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled); 537 } 538 showGestureFloatingPreviewText(@onnull final SuggestedWords suggestedWords, final boolean dismissDelayed)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 dismissGestureFloatingPreviewTextWithoutDelay()553 public void dismissGestureFloatingPreviewTextWithoutDelay() { 554 mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText(); 555 } 556 557 @Override showGestureTrail(@onnull final PointerTracker tracker, final boolean showsFloatingPreviewText)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") setMainDictionaryAvailability(final boolean mainDictionaryAvailable)569 public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 570 PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); 571 } 572 setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)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 onAttachedToWindow()582 protected void onAttachedToWindow() { 583 super.onAttachedToWindow(); 584 installPreviewPlacerView(); 585 } 586 587 @Override onDetachedFromWindow()588 protected void onDetachedFromWindow() { 589 super.onDetachedFromWindow(); 590 mDrawingPreviewPlacerView.removeAllViews(); 591 } 592 593 // Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}. 594 @Override 595 @Nullable showMoreKeysKeyboard(@onnull final Key key, @Nonnull final PointerTracker tracker)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 isInDraggingFinger()647 public boolean isInDraggingFinger() { 648 if (isShowingMoreKeysPanel()) { 649 return true; 650 } 651 return PointerTracker.isAnyInDraggingFinger(); 652 } 653 654 @Override onShowMoreKeysPanel(final MoreKeysPanel panel)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 isShowingMoreKeysPanel()667 public boolean isShowingMoreKeysPanel() { 668 return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); 669 } 670 671 @Override onCancelMoreKeysPanel()672 public void onCancelMoreKeysPanel() { 673 PointerTracker.dismissAllMoreKeysPanels(); 674 } 675 676 @Override onDismissMoreKeysPanel()677 public void onDismissMoreKeysPanel() { 678 if (isShowingMoreKeysPanel()) { 679 mMoreKeysPanel.removeFromParent(); 680 mMoreKeysPanel = null; 681 } 682 } 683 startDoubleTapShiftKeyTimer()684 public void startDoubleTapShiftKeyTimer() { 685 mTimerHandler.startDoubleTapShiftKeyTimer(); 686 } 687 cancelDoubleTapShiftKeyTimer()688 public void cancelDoubleTapShiftKeyTimer() { 689 mTimerHandler.cancelDoubleTapShiftKeyTimer(); 690 } 691 isInDoubleTapShiftKeyTimeout()692 public boolean isInDoubleTapShiftKeyTimeout() { 693 return mTimerHandler.isInDoubleTapShiftKeyTimeout(); 694 } 695 696 @Override onTouchEvent(final MotionEvent event)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 processMotionEvent(final MotionEvent event)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 cancelAllOngoingEvents()727 public void cancelAllOngoingEvents() { 728 mTimerHandler.cancelAllMessages(); 729 PointerTracker.setReleasedKeyGraphicsToAllKeys(); 730 mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText(); 731 mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview(); 732 PointerTracker.dismissAllMoreKeysPanels(); 733 PointerTracker.cancelAllPointerTrackers(); 734 } 735 closing()736 public void closing() { 737 cancelAllOngoingEvents(); 738 mMoreKeysKeyboardCache.clear(); 739 } 740 onHideWindow()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 onHoverEvent(final MotionEvent event)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 updateShortcutKey(final boolean available)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 startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final int languageOnSpacebarFormatType, final boolean hasMultipleEnabledIMEsOrSubtypes)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 onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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 fitsTextIntoWidth(final int width, final String text, final Paint paint)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. layoutLanguageOnSpacebar(final Paint paint, final RichInputMethodSubtype subtype, final int width)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 drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint)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 deallocateMemory()891 public void deallocateMemory() { 892 super.deallocateMemory(); 893 mDrawingPreviewPlacerView.deallocateMemory(); 894 } 895 } 896