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.Resources; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.Paint.Align; 30 import android.graphics.Typeface; 31 import android.graphics.drawable.Drawable; 32 import android.os.Message; 33 import android.os.SystemClock; 34 import android.preference.PreferenceManager; 35 import android.util.AttributeSet; 36 import android.util.DisplayMetrics; 37 import android.util.Log; 38 import android.util.SparseArray; 39 import android.util.TypedValue; 40 import android.view.LayoutInflater; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.view.ViewConfiguration; 44 import android.view.ViewGroup; 45 import android.view.inputmethod.InputMethodSubtype; 46 import android.widget.TextView; 47 48 import com.android.inputmethod.accessibility.AccessibilityUtils; 49 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 50 import com.android.inputmethod.annotations.ExternallyReferenced; 51 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; 52 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 53 import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText; 54 import com.android.inputmethod.keyboard.internal.GestureTrailsPreview; 55 import com.android.inputmethod.keyboard.internal.KeyDrawParams; 56 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; 57 import com.android.inputmethod.keyboard.internal.PreviewPlacerView; 58 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; 59 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; 60 import com.android.inputmethod.latin.CollectionUtils; 61 import com.android.inputmethod.latin.Constants; 62 import com.android.inputmethod.latin.CoordinateUtils; 63 import com.android.inputmethod.latin.DebugSettings; 64 import com.android.inputmethod.latin.LatinIME; 65 import com.android.inputmethod.latin.LatinImeLogger; 66 import com.android.inputmethod.latin.R; 67 import com.android.inputmethod.latin.ResourceUtils; 68 import com.android.inputmethod.latin.Settings; 69 import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 70 import com.android.inputmethod.latin.StringUtils; 71 import com.android.inputmethod.latin.SubtypeLocale; 72 import com.android.inputmethod.latin.SuggestedWords; 73 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; 74 import com.android.inputmethod.latin.define.ProductionFlag; 75 import com.android.inputmethod.research.ResearchLogger; 76 77 import java.util.Locale; 78 import java.util.WeakHashMap; 79 80 /** 81 * A view that is responsible for detecting key presses and touch movements. 82 * 83 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled 84 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon 85 * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio 86 * @attr ref R.styleable#MainKeyboardView_spacebarTextColor 87 * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor 88 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha 89 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator 90 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator 91 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator 92 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance 93 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime 94 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance 95 * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable 96 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout 97 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval 98 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout 99 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout 100 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout 101 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout 102 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset 103 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight 104 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout 105 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout 106 * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha 107 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint 108 * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout 109 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping 110 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold 111 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration 112 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom 113 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo 114 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom 115 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo 116 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance 117 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime 118 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold 119 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration 120 */ 121 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 122 PointerTracker.DrawingProxy, MoreKeysPanel.Controller, 123 TouchScreenRegulator.ProcessMotionEvent { 124 private static final String TAG = MainKeyboardView.class.getSimpleName(); 125 126 // TODO: Kill process when the usability study mode was changed. 127 private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; 128 129 /** Listener for {@link KeyboardActionListener}. */ 130 private KeyboardActionListener mKeyboardActionListener; 131 132 /* Space key and its icons */ 133 private Key mSpaceKey; 134 private Drawable mSpaceIcon; 135 // Stuff to draw language name on spacebar. 136 private final int mLanguageOnSpacebarFinalAlpha; 137 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 138 private boolean mNeedsToDisplayLanguage; 139 private boolean mHasMultipleEnabledIMEsOrSubtypes; 140 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 141 private final float mSpacebarTextRatio; 142 private float mSpacebarTextSize; 143 private final int mSpacebarTextColor; 144 private final int mSpacebarTextShadowColor; 145 // The minimum x-scale to fit the language name on spacebar. 146 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 147 // Stuff to draw auto correction LED on spacebar. 148 private boolean mAutoCorrectionSpacebarLedOn; 149 private final boolean mAutoCorrectionSpacebarLedEnabled; 150 private final Drawable mAutoCorrectionSpacebarLedIcon; 151 private static final int SPACE_LED_LENGTH_PERCENT = 80; 152 153 // Stuff to draw altCodeWhileTyping keys. 154 private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 155 private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 156 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 157 158 // Preview placer view 159 private final PreviewPlacerView mPreviewPlacerView; 160 private final int[] mOriginCoords = CoordinateUtils.newInstance(); 161 private final GestureFloatingPreviewText mGestureFloatingPreviewText; 162 private final GestureTrailsPreview mGestureTrailsPreview; 163 private final SlidingKeyInputPreview mSlidingKeyInputPreview; 164 165 // Key preview 166 private static final int PREVIEW_ALPHA = 240; 167 private final int mKeyPreviewLayoutId; 168 private final int mKeyPreviewOffset; 169 private final int mKeyPreviewHeight; 170 private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray(); 171 private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams(); 172 private boolean mShowKeyPreviewPopup = true; 173 private int mKeyPreviewLingerTimeout; 174 175 // More keys keyboard 176 private final Paint mBackgroundDimAlphaPaint = new Paint(); 177 private boolean mNeedsToDimEntireKeyboard; 178 private final View mMoreKeysKeyboardContainer; 179 private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = 180 CollectionUtils.newWeakHashMap(); 181 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 182 // More keys panel (used by both more keys keyboard and more suggestions view) 183 // TODO: Consider extending to support multiple more keys panels 184 private MoreKeysPanel mMoreKeysPanel; 185 186 // Gesture floating preview text 187 // TODO: Make this parameter customizable by user via settings. 188 private int mGestureFloatingPreviewTextLingerTimeout; 189 190 private final TouchScreenRegulator mTouchScreenRegulator; 191 192 private KeyDetector mKeyDetector; 193 private final boolean mHasDistinctMultitouch; 194 private int mOldPointerCount = 1; 195 private Key mOldKey; 196 197 private final KeyTimerHandler mKeyTimerHandler; 198 199 private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView> 200 implements TimerProxy { 201 private static final int MSG_TYPING_STATE_EXPIRED = 0; 202 private static final int MSG_REPEAT_KEY = 1; 203 private static final int MSG_LONGPRESS_KEY = 2; 204 private static final int MSG_DOUBLE_TAP = 3; 205 private static final int MSG_UPDATE_BATCH_INPUT = 4; 206 207 private final int mKeyRepeatStartTimeout; 208 private final int mKeyRepeatInterval; 209 private final int mLongPressShiftLockTimeout; 210 private final int mIgnoreAltCodeKeyTimeout; 211 private final int mGestureRecognitionUpdateTime; 212 KeyTimerHandler(final MainKeyboardView outerInstance, final TypedArray mainKeyboardViewAttr)213 public KeyTimerHandler(final MainKeyboardView outerInstance, 214 final TypedArray mainKeyboardViewAttr) { 215 super(outerInstance); 216 217 mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( 218 R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); 219 mKeyRepeatInterval = mainKeyboardViewAttr.getInt( 220 R.styleable.MainKeyboardView_keyRepeatInterval, 0); 221 mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( 222 R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); 223 mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( 224 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); 225 mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( 226 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); 227 } 228 229 @Override handleMessage(final Message msg)230 public void handleMessage(final Message msg) { 231 final MainKeyboardView keyboardView = getOuterInstance(); 232 if (keyboardView == null) { 233 return; 234 } 235 final PointerTracker tracker = (PointerTracker) msg.obj; 236 switch (msg.what) { 237 case MSG_TYPING_STATE_EXPIRED: 238 startWhileTypingFadeinAnimation(keyboardView); 239 break; 240 case MSG_REPEAT_KEY: 241 final Key currentKey = tracker.getKey(); 242 if (currentKey != null && currentKey.mCode == msg.arg1) { 243 tracker.onRegisterKey(currentKey); 244 startKeyRepeatTimer(tracker, mKeyRepeatInterval); 245 } 246 break; 247 case MSG_LONGPRESS_KEY: 248 if (tracker != null) { 249 keyboardView.onLongPress(tracker); 250 } else { 251 KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); 252 } 253 break; 254 case MSG_UPDATE_BATCH_INPUT: 255 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis()); 256 startUpdateBatchInputTimer(tracker); 257 break; 258 } 259 } 260 startKeyRepeatTimer(final PointerTracker tracker, final long delay)261 private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { 262 final Key key = tracker.getKey(); 263 if (key == null) { 264 return; 265 } 266 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); 267 } 268 269 @Override startKeyRepeatTimer(final PointerTracker tracker)270 public void startKeyRepeatTimer(final PointerTracker tracker) { 271 startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout); 272 } 273 cancelKeyRepeatTimer()274 public void cancelKeyRepeatTimer() { 275 removeMessages(MSG_REPEAT_KEY); 276 } 277 278 // TODO: Suppress layout changes in key repeat mode isInKeyRepeat()279 public boolean isInKeyRepeat() { 280 return hasMessages(MSG_REPEAT_KEY); 281 } 282 283 @Override startLongPressTimer(final int code)284 public void startLongPressTimer(final int code) { 285 cancelLongPressTimer(); 286 final int delay; 287 switch (code) { 288 case Constants.CODE_SHIFT: 289 delay = mLongPressShiftLockTimeout; 290 break; 291 default: 292 delay = 0; 293 break; 294 } 295 if (delay > 0) { 296 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); 297 } 298 } 299 300 @Override startLongPressTimer(final PointerTracker tracker)301 public void startLongPressTimer(final PointerTracker tracker) { 302 cancelLongPressTimer(); 303 if (tracker == null) { 304 return; 305 } 306 final Key key = tracker.getKey(); 307 final int delay; 308 switch (key.mCode) { 309 case Constants.CODE_SHIFT: 310 delay = mLongPressShiftLockTimeout; 311 break; 312 default: 313 final int longpressTimeout = 314 Settings.getInstance().getCurrent().mKeyLongpressTimeout; 315 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { 316 // We use longer timeout for sliding finger input started from the symbols 317 // mode key. 318 delay = longpressTimeout * 3; 319 } else { 320 delay = longpressTimeout; 321 } 322 break; 323 } 324 if (delay > 0) { 325 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); 326 } 327 } 328 329 @Override cancelLongPressTimer()330 public void cancelLongPressTimer() { 331 removeMessages(MSG_LONGPRESS_KEY); 332 } 333 cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)334 private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, 335 final ObjectAnimator animatorToStart) { 336 if (animatorToCancel == null || animatorToStart == null) { 337 // TODO: Stop using null as a no-operation animator. 338 return; 339 } 340 float startFraction = 0.0f; 341 if (animatorToCancel.isStarted()) { 342 animatorToCancel.cancel(); 343 startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); 344 } 345 final long startTime = (long)(animatorToStart.getDuration() * startFraction); 346 animatorToStart.start(); 347 animatorToStart.setCurrentPlayTime(startTime); 348 } 349 startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView)350 private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) { 351 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, 352 keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); 353 } 354 startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView)355 private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) { 356 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, 357 keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); 358 } 359 360 @Override startTypingStateTimer(final Key typedKey)361 public void startTypingStateTimer(final Key typedKey) { 362 if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { 363 return; 364 } 365 366 final boolean isTyping = isTypingState(); 367 removeMessages(MSG_TYPING_STATE_EXPIRED); 368 final MainKeyboardView keyboardView = getOuterInstance(); 369 370 // When user hits the space or the enter key, just cancel the while-typing timer. 371 final int typedCode = typedKey.mCode; 372 if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { 373 if (isTyping) { 374 startWhileTypingFadeinAnimation(keyboardView); 375 } 376 return; 377 } 378 379 sendMessageDelayed( 380 obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout); 381 if (isTyping) { 382 return; 383 } 384 startWhileTypingFadeoutAnimation(keyboardView); 385 } 386 387 @Override isTypingState()388 public boolean isTypingState() { 389 return hasMessages(MSG_TYPING_STATE_EXPIRED); 390 } 391 392 @Override startDoubleTapTimer()393 public void startDoubleTapTimer() { 394 sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP), 395 ViewConfiguration.getDoubleTapTimeout()); 396 } 397 398 @Override cancelDoubleTapTimer()399 public void cancelDoubleTapTimer() { 400 removeMessages(MSG_DOUBLE_TAP); 401 } 402 403 @Override isInDoubleTapTimeout()404 public boolean isInDoubleTapTimeout() { 405 return hasMessages(MSG_DOUBLE_TAP); 406 } 407 408 @Override cancelKeyTimers()409 public void cancelKeyTimers() { 410 cancelKeyRepeatTimer(); 411 cancelLongPressTimer(); 412 } 413 414 @Override startUpdateBatchInputTimer(final PointerTracker tracker)415 public void startUpdateBatchInputTimer(final PointerTracker tracker) { 416 if (mGestureRecognitionUpdateTime <= 0) { 417 return; 418 } 419 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 420 sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker), 421 mGestureRecognitionUpdateTime); 422 } 423 424 @Override cancelUpdateBatchInputTimer(final PointerTracker tracker)425 public void cancelUpdateBatchInputTimer(final PointerTracker tracker) { 426 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 427 } 428 429 @Override cancelAllUpdateBatchInputTimers()430 public void cancelAllUpdateBatchInputTimers() { 431 removeMessages(MSG_UPDATE_BATCH_INPUT); 432 } 433 cancelAllMessages()434 public void cancelAllMessages() { 435 cancelKeyTimers(); 436 cancelAllUpdateBatchInputTimers(); 437 } 438 } 439 440 private final DrawingHandler mDrawingHandler = new DrawingHandler(this); 441 442 public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> { 443 private static final int MSG_DISMISS_KEY_PREVIEW = 0; 444 private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; 445 DrawingHandler(final MainKeyboardView outerInstance)446 public DrawingHandler(final MainKeyboardView outerInstance) { 447 super(outerInstance); 448 } 449 450 @Override handleMessage(final Message msg)451 public void handleMessage(final Message msg) { 452 final MainKeyboardView mainKeyboardView = getOuterInstance(); 453 if (mainKeyboardView == null) return; 454 final PointerTracker tracker = (PointerTracker) msg.obj; 455 switch (msg.what) { 456 case MSG_DISMISS_KEY_PREVIEW: 457 final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get( 458 tracker.mPointerId); 459 if (previewText != null) { 460 previewText.setVisibility(INVISIBLE); 461 } 462 break; 463 case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: 464 mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY); 465 break; 466 } 467 } 468 dismissKeyPreview(final long delay, final PointerTracker tracker)469 public void dismissKeyPreview(final long delay, final PointerTracker tracker) { 470 sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay); 471 } 472 cancelDismissKeyPreview(final PointerTracker tracker)473 public void cancelDismissKeyPreview(final PointerTracker tracker) { 474 removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); 475 } 476 cancelAllDismissKeyPreviews()477 private void cancelAllDismissKeyPreviews() { 478 removeMessages(MSG_DISMISS_KEY_PREVIEW); 479 } 480 dismissGestureFloatingPreviewText(final long delay)481 public void dismissGestureFloatingPreviewText(final long delay) { 482 sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay); 483 } 484 cancelAllMessages()485 public void cancelAllMessages() { 486 cancelAllDismissKeyPreviews(); 487 } 488 } 489 MainKeyboardView(final Context context, final AttributeSet attrs)490 public MainKeyboardView(final Context context, final AttributeSet attrs) { 491 this(context, attrs, R.attr.mainKeyboardViewStyle); 492 } 493 MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)494 public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 495 super(context, attrs, defStyle); 496 497 mTouchScreenRegulator = new TouchScreenRegulator(context, this); 498 499 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 500 final boolean forceNonDistinctMultitouch = prefs.getBoolean( 501 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); 502 final boolean hasDistinctMultitouch = context.getPackageManager() 503 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 504 mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; 505 final Resources res = getResources(); 506 final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( 507 ResourceUtils.getDeviceOverrideValue( 508 res, R.array.phantom_sudden_move_event_device_list)); 509 PointerTracker.init(needsPhantomSuddenMoveEventHack); 510 mPreviewPlacerView = new PreviewPlacerView(context, attrs); 511 512 final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( 513 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); 514 final int backgroundDimAlpha = mainKeyboardViewAttr.getInt( 515 R.styleable.MainKeyboardView_backgroundDimAlpha, 0); 516 mBackgroundDimAlphaPaint.setColor(Color.BLACK); 517 mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha); 518 mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean( 519 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); 520 mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable( 521 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); 522 mSpacebarTextRatio = mainKeyboardViewAttr.getFraction( 523 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f); 524 mSpacebarTextColor = mainKeyboardViewAttr.getColor( 525 R.styleable.MainKeyboardView_spacebarTextColor, 0); 526 mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor( 527 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); 528 mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt( 529 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, 530 Constants.Color.ALPHA_OPAQUE); 531 final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 532 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); 533 final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( 534 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); 535 final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId( 536 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); 537 538 final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension( 539 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f); 540 final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension( 541 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f); 542 mKeyDetector = new KeyDetector( 543 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); 544 mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr); 545 mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset( 546 R.styleable.MainKeyboardView_keyPreviewOffset, 0); 547 mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize( 548 R.styleable.MainKeyboardView_keyPreviewHeight, 0); 549 mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt( 550 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0); 551 mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId( 552 R.styleable.MainKeyboardView_keyPreviewLayout, 0); 553 if (mKeyPreviewLayoutId == 0) { 554 mShowKeyPreviewPopup = false; 555 } 556 final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId( 557 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0); 558 mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean( 559 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); 560 561 mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt( 562 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0); 563 PointerTracker.setParameters(mainKeyboardViewAttr); 564 565 mGestureFloatingPreviewText = new GestureFloatingPreviewText( 566 mPreviewPlacerView, mainKeyboardViewAttr); 567 mPreviewPlacerView.addPreview(mGestureFloatingPreviewText); 568 569 mGestureTrailsPreview = new GestureTrailsPreview( 570 mPreviewPlacerView, mainKeyboardViewAttr); 571 mPreviewPlacerView.addPreview(mGestureTrailsPreview); 572 573 mSlidingKeyInputPreview = new SlidingKeyInputPreview( 574 mPreviewPlacerView, mainKeyboardViewAttr); 575 mPreviewPlacerView.addPreview(mSlidingKeyInputPreview); 576 mainKeyboardViewAttr.recycle(); 577 578 mMoreKeysKeyboardContainer = LayoutInflater.from(getContext()) 579 .inflate(moreKeysKeyboardLayoutId, null); 580 mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( 581 languageOnSpacebarFadeoutAnimatorResId, this); 582 mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( 583 altCodeKeyWhileTypingFadeoutAnimatorResId, this); 584 mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( 585 altCodeKeyWhileTypingFadeinAnimatorResId, this); 586 } 587 loadObjectAnimator(final int resId, final Object target)588 private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { 589 if (resId == 0) { 590 // TODO: Stop returning null. 591 return null; 592 } 593 final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( 594 getContext(), resId); 595 if (animator != null) { 596 animator.setTarget(target); 597 } 598 return animator; 599 } 600 601 @ExternallyReferenced getLanguageOnSpacebarAnimAlpha()602 public int getLanguageOnSpacebarAnimAlpha() { 603 return mLanguageOnSpacebarAnimAlpha; 604 } 605 606 @ExternallyReferenced setLanguageOnSpacebarAnimAlpha(final int alpha)607 public void setLanguageOnSpacebarAnimAlpha(final int alpha) { 608 mLanguageOnSpacebarAnimAlpha = alpha; 609 invalidateKey(mSpaceKey); 610 } 611 612 @ExternallyReferenced getAltCodeKeyWhileTypingAnimAlpha()613 public int getAltCodeKeyWhileTypingAnimAlpha() { 614 return mAltCodeKeyWhileTypingAnimAlpha; 615 } 616 617 @ExternallyReferenced setAltCodeKeyWhileTypingAnimAlpha(final int alpha)618 public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { 619 if (mAltCodeKeyWhileTypingAnimAlpha == alpha) { 620 return; 621 } 622 // Update the visual of alt-code-key-while-typing. 623 mAltCodeKeyWhileTypingAnimAlpha = alpha; 624 final Keyboard keyboard = getKeyboard(); 625 if (keyboard == null) { 626 return; 627 } 628 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 629 invalidateKey(key); 630 } 631 } 632 setKeyboardActionListener(final KeyboardActionListener listener)633 public void setKeyboardActionListener(final KeyboardActionListener listener) { 634 mKeyboardActionListener = listener; 635 PointerTracker.setKeyboardActionListener(listener); 636 } 637 638 /** 639 * Returns the {@link KeyboardActionListener} object. 640 * @return the listener attached to this keyboard 641 */ 642 @Override getKeyboardActionListener()643 public KeyboardActionListener getKeyboardActionListener() { 644 return mKeyboardActionListener; 645 } 646 647 @Override getKeyDetector()648 public KeyDetector getKeyDetector() { 649 return mKeyDetector; 650 } 651 652 @Override getDrawingProxy()653 public DrawingProxy getDrawingProxy() { 654 return this; 655 } 656 657 @Override getTimerProxy()658 public TimerProxy getTimerProxy() { 659 return mKeyTimerHandler; 660 } 661 662 /** 663 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 664 * view will re-layout itself to accommodate the keyboard. 665 * @see Keyboard 666 * @see #getKeyboard() 667 * @param keyboard the keyboard to display in this view 668 */ 669 @Override setKeyboard(final Keyboard keyboard)670 public void setKeyboard(final Keyboard keyboard) { 671 // Remove any pending messages, except dismissing preview and key repeat. 672 mKeyTimerHandler.cancelLongPressTimer(); 673 super.setKeyboard(keyboard); 674 mKeyDetector.setKeyboard( 675 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); 676 PointerTracker.setKeyDetector(mKeyDetector); 677 mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth); 678 mMoreKeysKeyboardCache.clear(); 679 680 mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); 681 mSpaceIcon = (mSpaceKey != null) 682 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; 683 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 684 mSpacebarTextSize = keyHeight * mSpacebarTextRatio; 685 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 686 ResearchLogger.mainKeyboardView_setKeyboard(keyboard); 687 } 688 689 // This always needs to be set since the accessibility state can 690 // potentially change without the keyboard being set again. 691 AccessibleKeyboardViewProxy.getInstance().setKeyboard(); 692 } 693 694 /** 695 * Enables or disables the key feedback popup. This is a popup that shows a magnified 696 * version of the depressed key. By default the preview is enabled. 697 * @param previewEnabled whether or not to enable the key feedback preview 698 * @param delay the delay after which the preview is dismissed 699 * @see #isKeyPreviewPopupEnabled() 700 */ setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay)701 public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { 702 mShowKeyPreviewPopup = previewEnabled; 703 mKeyPreviewLingerTimeout = delay; 704 } 705 706 locatePreviewPlacerView()707 private void locatePreviewPlacerView() { 708 if (mPreviewPlacerView.getParent() != null) { 709 return; 710 } 711 final int width = getWidth(); 712 final int height = getHeight(); 713 if (width == 0 || height == 0) { 714 // In transient state. 715 return; 716 } 717 getLocationInWindow(mOriginCoords); 718 final DisplayMetrics dm = getResources().getDisplayMetrics(); 719 if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) { 720 // In transient state. 721 return; 722 } 723 final View rootView = getRootView(); 724 if (rootView == null) { 725 Log.w(TAG, "Cannot find root view"); 726 return; 727 } 728 final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); 729 // Note: It'd be very weird if we get null by android.R.id.content. 730 if (windowContentView == null) { 731 Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView"); 732 } else { 733 windowContentView.addView(mPreviewPlacerView); 734 mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height); 735 } 736 } 737 738 /** 739 * Returns the enabled state of the key feedback preview 740 * @return whether or not the key feedback preview is enabled 741 * @see #setKeyPreviewPopupEnabled(boolean, int) 742 */ isKeyPreviewPopupEnabled()743 public boolean isKeyPreviewPopupEnabled() { 744 return mShowKeyPreviewPopup; 745 } 746 addKeyPreview(final TextView keyPreview)747 private void addKeyPreview(final TextView keyPreview) { 748 locatePreviewPlacerView(); 749 mPreviewPlacerView.addView( 750 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0)); 751 } 752 getKeyPreviewText(final int pointerId)753 private TextView getKeyPreviewText(final int pointerId) { 754 TextView previewText = mKeyPreviewTexts.get(pointerId); 755 if (previewText != null) { 756 return previewText; 757 } 758 final Context context = getContext(); 759 if (mKeyPreviewLayoutId != 0) { 760 previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null); 761 } else { 762 previewText = new TextView(context); 763 } 764 mKeyPreviewTexts.put(pointerId, previewText); 765 return previewText; 766 } 767 dismissAllKeyPreviews()768 private void dismissAllKeyPreviews() { 769 final int pointerCount = mKeyPreviewTexts.size(); 770 for (int id = 0; id < pointerCount; id++) { 771 final TextView previewText = mKeyPreviewTexts.get(id); 772 if (previewText != null) { 773 previewText.setVisibility(INVISIBLE); 774 } 775 } 776 PointerTracker.setReleasedKeyGraphicsToAllKeys(); 777 } 778 779 // Background state set 780 private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = { 781 { // STATE_MIDDLE 782 EMPTY_STATE_SET, 783 { R.attr.state_has_morekeys } 784 }, 785 { // STATE_LEFT 786 { R.attr.state_left_edge }, 787 { R.attr.state_left_edge, R.attr.state_has_morekeys } 788 }, 789 { // STATE_RIGHT 790 { R.attr.state_right_edge }, 791 { R.attr.state_right_edge, R.attr.state_has_morekeys } 792 } 793 }; 794 private static final int STATE_MIDDLE = 0; 795 private static final int STATE_LEFT = 1; 796 private static final int STATE_RIGHT = 2; 797 private static final int STATE_NORMAL = 0; 798 private static final int STATE_HAS_MOREKEYS = 1; 799 private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE = 800 KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL]; 801 802 @Override showKeyPreview(final PointerTracker tracker)803 public void showKeyPreview(final PointerTracker tracker) { 804 final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; 805 final Keyboard keyboard = getKeyboard(); 806 if (!mShowKeyPreviewPopup) { 807 previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap; 808 return; 809 } 810 811 final TextView previewText = getKeyPreviewText(tracker.mPointerId); 812 // If the key preview has no parent view yet, add it to the ViewGroup which can place 813 // key preview absolutely in SoftInputWindow. 814 if (previewText.getParent() == null) { 815 addKeyPreview(previewText); 816 } 817 818 mDrawingHandler.cancelDismissKeyPreview(tracker); 819 final Key key = tracker.getKey(); 820 // If key is invalid or IME is already closed, we must not show key preview. 821 // Trying to show key preview while root window is closed causes 822 // WindowManager.BadTokenException. 823 if (key == null) { 824 return; 825 } 826 827 final KeyDrawParams drawParams = mKeyDrawParams; 828 previewText.setTextColor(drawParams.mPreviewTextColor); 829 final Drawable background = previewText.getBackground(); 830 if (background != null) { 831 background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE); 832 background.setAlpha(PREVIEW_ALPHA); 833 } 834 final String label = key.getPreviewLabel(); 835 // What we show as preview should match what we show on a key top in onDraw(). 836 if (label != null) { 837 // TODO Should take care of temporaryShiftLabel here. 838 previewText.setCompoundDrawables(null, null, null, null); 839 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 840 key.selectPreviewTextSize(drawParams)); 841 previewText.setTypeface(key.selectPreviewTypeface(drawParams)); 842 previewText.setText(label); 843 } else { 844 previewText.setCompoundDrawables(null, null, null, 845 key.getPreviewIcon(keyboard.mIconsSet)); 846 previewText.setText(null); 847 } 848 849 previewText.measure( 850 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 851 final int keyDrawWidth = key.getDrawWidth(); 852 final int previewWidth = previewText.getMeasuredWidth(); 853 final int previewHeight = mKeyPreviewHeight; 854 // The width and height of visible part of the key preview background. The content marker 855 // of the background 9-patch have to cover the visible part of the background. 856 previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft() 857 - previewText.getPaddingRight(); 858 previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop() 859 - previewText.getPaddingBottom(); 860 // The distance between the top edge of the parent key and the bottom of the visible part 861 // of the key preview background. 862 previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom(); 863 getLocationInWindow(mOriginCoords); 864 // The key preview is horizontally aligned with the center of the visible part of the 865 // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and 866 // the left/right background is used if such background is specified. 867 final int statePosition; 868 int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 869 + CoordinateUtils.x(mOriginCoords); 870 if (previewX < 0) { 871 previewX = 0; 872 statePosition = STATE_LEFT; 873 } else if (previewX > getWidth() - previewWidth) { 874 previewX = getWidth() - previewWidth; 875 statePosition = STATE_RIGHT; 876 } else { 877 statePosition = STATE_MIDDLE; 878 } 879 // The key preview is placed vertically above the top edge of the parent key with an 880 // arbitrary offset. 881 final int previewY = key.mY - previewHeight + mKeyPreviewOffset 882 + CoordinateUtils.y(mOriginCoords); 883 884 if (background != null) { 885 final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL; 886 background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]); 887 } 888 ViewLayoutUtils.placeViewAt( 889 previewText, previewX, previewY, previewWidth, previewHeight); 890 previewText.setVisibility(VISIBLE); 891 } 892 893 @Override dismissKeyPreview(final PointerTracker tracker)894 public void dismissKeyPreview(final PointerTracker tracker) { 895 mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker); 896 } 897 setSlidingKeyInputPreviewEnabled(final boolean enabled)898 public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { 899 mSlidingKeyInputPreview.setPreviewEnabled(enabled); 900 } 901 902 @Override showSlidingKeyInputPreview(final PointerTracker tracker)903 public void showSlidingKeyInputPreview(final PointerTracker tracker) { 904 locatePreviewPlacerView(); 905 mSlidingKeyInputPreview.setPreviewPosition(tracker); 906 } 907 908 @Override dismissSlidingKeyInputPreview()909 public void dismissSlidingKeyInputPreview() { 910 mSlidingKeyInputPreview.dismissSlidingKeyInputPreview(); 911 } 912 setGesturePreviewMode(final boolean drawsGestureTrail, final boolean drawsGestureFloatingPreviewText)913 public void setGesturePreviewMode(final boolean drawsGestureTrail, 914 final boolean drawsGestureFloatingPreviewText) { 915 mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText); 916 mGestureTrailsPreview.setPreviewEnabled(drawsGestureTrail); 917 } 918 showGestureFloatingPreviewText(final SuggestedWords suggestedWords)919 public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) { 920 locatePreviewPlacerView(); 921 mGestureFloatingPreviewText.setSuggetedWords(suggestedWords); 922 } 923 dismissGestureFloatingPreviewText()924 public void dismissGestureFloatingPreviewText() { 925 locatePreviewPlacerView(); 926 mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout); 927 } 928 929 @Override showGestureTrail(final PointerTracker tracker)930 public void showGestureTrail(final PointerTracker tracker) { 931 locatePreviewPlacerView(); 932 mGestureFloatingPreviewText.setPreviewPosition(tracker); 933 mGestureTrailsPreview.setPreviewPosition(tracker); 934 } 935 936 // Note that this method is called from a non-UI thread. setMainDictionaryAvailability(final boolean mainDictionaryAvailable)937 public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 938 PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); 939 } 940 setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser)941 public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 942 PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); 943 } 944 945 @Override onAttachedToWindow()946 protected void onAttachedToWindow() { 947 super.onAttachedToWindow(); 948 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 949 // been attached. This is needed to properly show the splash screen, which requires that 950 // the window token of the KeyboardView be non-null. 951 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 952 ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); 953 } 954 } 955 956 @Override onDetachedFromWindow()957 protected void onDetachedFromWindow() { 958 super.onDetachedFromWindow(); 959 mPreviewPlacerView.removeAllViews(); 960 // Notify the ResearchLogger (development only diagnostics) that the keyboard view has 961 // been detached. This is needed to invalidate the reference of {@link MainKeyboardView} 962 // to null. 963 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 964 ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); 965 } 966 } 967 onCreateMoreKeysPanel(final Key key, final Context context)968 private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { 969 if (key.mMoreKeys == null) { 970 return null; 971 } 972 Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); 973 if (moreKeysKeyboard == null) { 974 moreKeysKeyboard = new MoreKeysKeyboard.Builder( 975 context, key, this, mKeyPreviewDrawParams).build(); 976 mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); 977 } 978 979 final View container = mMoreKeysKeyboardContainer; 980 final MoreKeysKeyboardView moreKeysKeyboardView = 981 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 982 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 983 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 984 return moreKeysKeyboardView; 985 } 986 987 /** 988 * Called when a key is long pressed. 989 * @param tracker the pointer tracker which pressed the parent key 990 * @return true if the long press is handled, false otherwise. Subclasses should call the 991 * method on the base class if the subclass doesn't wish to handle the call. 992 */ onLongPress(final PointerTracker tracker)993 private boolean onLongPress(final PointerTracker tracker) { 994 if (isShowingMoreKeysPanel()) { 995 return false; 996 } 997 final Key key = tracker.getKey(); 998 if (key == null) { 999 return false; 1000 } 1001 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 1002 ResearchLogger.mainKeyboardView_onLongPress(); 1003 } 1004 final int code = key.mCode; 1005 if (key.hasEmbeddedMoreKey()) { 1006 final int embeddedCode = key.mMoreKeys[0].mCode; 1007 tracker.onLongPressed(); 1008 invokeCodeInput(embeddedCode); 1009 invokeReleaseKey(code); 1010 KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code); 1011 return true; 1012 } 1013 if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { 1014 // Long pressing the space key invokes IME switcher dialog. 1015 if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { 1016 tracker.onLongPressed(); 1017 invokeReleaseKey(code); 1018 return true; 1019 } 1020 } 1021 return openMoreKeysPanel(key, tracker); 1022 } 1023 invokeCustomRequest(final int requestCode)1024 private boolean invokeCustomRequest(final int requestCode) { 1025 return mKeyboardActionListener.onCustomRequest(requestCode); 1026 } 1027 invokeCodeInput(final int code)1028 private void invokeCodeInput(final int code) { 1029 mKeyboardActionListener.onCodeInput( 1030 code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 1031 } 1032 invokeReleaseKey(final int code)1033 private void invokeReleaseKey(final int code) { 1034 mKeyboardActionListener.onReleaseKey(code, false); 1035 } 1036 openMoreKeysPanel(final Key key, final PointerTracker tracker)1037 private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) { 1038 final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); 1039 if (moreKeysPanel == null) { 1040 return false; 1041 } 1042 1043 final int[] lastCoords = CoordinateUtils.newInstance(); 1044 tracker.getLastCoordinates(lastCoords); 1045 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview(); 1046 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 1047 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 1048 // keys keyboard is placed at the touch point of the parent key. 1049 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 1050 ? CoordinateUtils.x(lastCoords) 1051 : key.mX + key.mWidth / 2; 1052 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 1053 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 1054 // aligned with the bottom edge of the visible part of the key preview. 1055 // {@code mPreviewVisibleOffset} has been set appropriately in 1056 // {@link KeyboardView#showKeyPreview(PointerTracker)}. 1057 final int pointY = key.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; 1058 moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); 1059 final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); 1060 final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); 1061 tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); 1062 return true; 1063 } 1064 isInSlidingKeyInput()1065 public boolean isInSlidingKeyInput() { 1066 if (isShowingMoreKeysPanel()) { 1067 return true; 1068 } 1069 return PointerTracker.isAnyInSlidingKeyInput(); 1070 } 1071 1072 @Override onShowMoreKeysPanel(final MoreKeysPanel panel)1073 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 1074 locatePreviewPlacerView(); 1075 if (isShowingMoreKeysPanel()) { 1076 onDismissMoreKeysPanel(); 1077 } 1078 mPreviewPlacerView.addView(panel.getContainerView()); 1079 mMoreKeysPanel = panel; 1080 dimEntireKeyboard(true /* dimmed */); 1081 } 1082 isShowingMoreKeysPanel()1083 public boolean isShowingMoreKeysPanel() { 1084 return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); 1085 } 1086 1087 @Override onCancelMoreKeysPanel()1088 public void onCancelMoreKeysPanel() { 1089 PointerTracker.dismissAllMoreKeysPanels(); 1090 } 1091 1092 @Override onDismissMoreKeysPanel()1093 public boolean onDismissMoreKeysPanel() { 1094 dimEntireKeyboard(false /* dimmed */); 1095 if (isShowingMoreKeysPanel()) { 1096 mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView()); 1097 mMoreKeysPanel = null; 1098 return true; 1099 } 1100 return false; 1101 } 1102 1103 @Override dispatchTouchEvent(MotionEvent event)1104 public boolean dispatchTouchEvent(MotionEvent event) { 1105 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1106 return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event); 1107 } 1108 return super.dispatchTouchEvent(event); 1109 } 1110 1111 @Override onTouchEvent(final MotionEvent me)1112 public boolean onTouchEvent(final MotionEvent me) { 1113 if (getKeyboard() == null) { 1114 return false; 1115 } 1116 return mTouchScreenRegulator.onTouchEvent(me); 1117 } 1118 1119 @Override processMotionEvent(final MotionEvent me)1120 public boolean processMotionEvent(final MotionEvent me) { 1121 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 1122 final int action = me.getActionMasked(); 1123 final int pointerCount = me.getPointerCount(); 1124 final int oldPointerCount = mOldPointerCount; 1125 mOldPointerCount = pointerCount; 1126 1127 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1128 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 1129 // events except a transition from/to single-touch. 1130 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 1131 return true; 1132 } 1133 1134 final long eventTime = me.getEventTime(); 1135 final int index = me.getActionIndex(); 1136 final int id = me.getPointerId(index); 1137 final int x = (int)me.getX(index); 1138 final int y = (int)me.getY(index); 1139 1140 // TODO: This might be moved to the tracker.processMotionEvent() call below. 1141 if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { 1142 writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); 1143 } 1144 // TODO: This should be moved to the tracker.processMotionEvent() call below. 1145 // Currently the same "move" event is being logged twice. 1146 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 1147 ResearchLogger.mainKeyboardView_processMotionEvent( 1148 me, action, eventTime, index, id, x, y); 1149 } 1150 1151 if (mKeyTimerHandler.isInKeyRepeat()) { 1152 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1153 // Key repeating timer will be canceled if 2 or more keys are in action, and current 1154 // event (UP or DOWN) is non-modifier key. 1155 if (pointerCount > 1 && !tracker.isModifier()) { 1156 mKeyTimerHandler.cancelKeyRepeatTimer(); 1157 } 1158 // Up event will pass through. 1159 } 1160 1161 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1162 // Translate mutli-touch event to single-touch events on the device that has no distinct 1163 // multi-touch panel. 1164 if (nonDistinctMultitouch) { 1165 // Use only main (id=0) pointer tracker. 1166 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1167 if (pointerCount == 1 && oldPointerCount == 2) { 1168 // Multi-touch to single touch transition. 1169 // Send a down event for the latest pointer if the key is different from the 1170 // previous key. 1171 final Key newKey = tracker.getKeyOn(x, y); 1172 if (mOldKey != newKey) { 1173 tracker.onDownEvent(x, y, eventTime, this); 1174 if (action == MotionEvent.ACTION_UP) { 1175 tracker.onUpEvent(x, y, eventTime); 1176 } 1177 } 1178 } else if (pointerCount == 2 && oldPointerCount == 1) { 1179 // Single-touch to multi-touch transition. 1180 // Send an up event for the last pointer. 1181 final int[] lastCoords = CoordinateUtils.newInstance(); 1182 mOldKey = tracker.getKeyOn( 1183 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); 1184 tracker.onUpEvent( 1185 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); 1186 } else if (pointerCount == 1 && oldPointerCount == 1) { 1187 tracker.processMotionEvent(action, x, y, eventTime, this); 1188 } else { 1189 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 1190 + " (old " + oldPointerCount + ")"); 1191 } 1192 return true; 1193 } 1194 1195 if (action == MotionEvent.ACTION_MOVE) { 1196 for (int i = 0; i < pointerCount; i++) { 1197 final int pointerId = me.getPointerId(i); 1198 final PointerTracker tracker = PointerTracker.getPointerTracker( 1199 pointerId, this); 1200 final int px = (int)me.getX(i); 1201 final int py = (int)me.getY(i); 1202 tracker.onMoveEvent(px, py, eventTime, me); 1203 if (ENABLE_USABILITY_STUDY_LOG) { 1204 writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); 1205 } 1206 // TODO: This seems to be no longer necessary, and confusing because it leads to 1207 // duplicate MotionEvents being recorded. 1208 // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 1209 // ResearchLogger.mainKeyboardView_processMotionEvent( 1210 // me, action, eventTime, i, pointerId, px, py); 1211 // } 1212 } 1213 } else { 1214 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1215 tracker.processMotionEvent(action, x, y, eventTime, this); 1216 } 1217 1218 return true; 1219 } 1220 writeUsabilityStudyLog(final MotionEvent me, final int action, final long eventTime, final int index, final int id, final int x, final int y)1221 private static void writeUsabilityStudyLog(final MotionEvent me, final int action, 1222 final long eventTime, final int index, final int id, final int x, final int y) { 1223 final String eventTag; 1224 switch (action) { 1225 case MotionEvent.ACTION_UP: 1226 eventTag = "[Up]"; 1227 break; 1228 case MotionEvent.ACTION_DOWN: 1229 eventTag = "[Down]"; 1230 break; 1231 case MotionEvent.ACTION_POINTER_UP: 1232 eventTag = "[PointerUp]"; 1233 break; 1234 case MotionEvent.ACTION_POINTER_DOWN: 1235 eventTag = "[PointerDown]"; 1236 break; 1237 case MotionEvent.ACTION_MOVE: 1238 eventTag = "[Move]"; 1239 break; 1240 default: 1241 eventTag = "[Action" + action + "]"; 1242 break; 1243 } 1244 final float size = me.getSize(index); 1245 final float pressure = me.getPressure(index); 1246 UsabilityStudyLogUtils.getInstance().write( 1247 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); 1248 } 1249 cancelAllMessages()1250 public void cancelAllMessages() { 1251 mKeyTimerHandler.cancelAllMessages(); 1252 mDrawingHandler.cancelAllMessages(); 1253 } 1254 closing()1255 public void closing() { 1256 dismissAllKeyPreviews(); 1257 cancelAllMessages(); 1258 onDismissMoreKeysPanel(); 1259 mMoreKeysKeyboardCache.clear(); 1260 } 1261 1262 /** 1263 * Receives hover events from the input framework. 1264 * 1265 * @param event The motion event to be dispatched. 1266 * @return {@code true} if the event was handled by the view, {@code false} 1267 * otherwise 1268 */ 1269 @Override dispatchHoverEvent(final MotionEvent event)1270 public boolean dispatchHoverEvent(final MotionEvent event) { 1271 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1272 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1273 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 1274 } 1275 1276 // Reflection doesn't support calling superclass methods. 1277 return false; 1278 } 1279 updateShortcutKey(final boolean available)1280 public void updateShortcutKey(final boolean available) { 1281 final Keyboard keyboard = getKeyboard(); 1282 if (keyboard == null) { 1283 return; 1284 } 1285 final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); 1286 if (shortcutKey == null) { 1287 return; 1288 } 1289 shortcutKey.setEnabled(available); 1290 invalidateKey(shortcutKey); 1291 } 1292 startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes)1293 public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, 1294 final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) { 1295 mNeedsToDisplayLanguage = needsToDisplayLanguage; 1296 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 1297 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 1298 if (animator == null) { 1299 mNeedsToDisplayLanguage = false; 1300 } else { 1301 if (subtypeChanged && needsToDisplayLanguage) { 1302 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 1303 if (animator.isStarted()) { 1304 animator.cancel(); 1305 } 1306 animator.start(); 1307 } else { 1308 if (!animator.isStarted()) { 1309 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 1310 } 1311 } 1312 } 1313 invalidateKey(mSpaceKey); 1314 } 1315 updateAutoCorrectionState(final boolean isAutoCorrection)1316 public void updateAutoCorrectionState(final boolean isAutoCorrection) { 1317 if (!mAutoCorrectionSpacebarLedEnabled) { 1318 return; 1319 } 1320 mAutoCorrectionSpacebarLedOn = isAutoCorrection; 1321 invalidateKey(mSpaceKey); 1322 } 1323 dimEntireKeyboard(final boolean dimmed)1324 private void dimEntireKeyboard(final boolean dimmed) { 1325 final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; 1326 mNeedsToDimEntireKeyboard = dimmed; 1327 if (needsRedrawing) { 1328 invalidateAllKeys(); 1329 } 1330 } 1331 1332 @Override onDraw(final Canvas canvas)1333 protected void onDraw(final Canvas canvas) { 1334 super.onDraw(canvas); 1335 1336 // Overlay a dark rectangle to dim. 1337 if (mNeedsToDimEntireKeyboard) { 1338 canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint); 1339 } 1340 } 1341 1342 @Override onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)1343 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 1344 final KeyDrawParams params) { 1345 if (key.altCodeWhileTyping() && key.isEnabled()) { 1346 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 1347 } 1348 if (key.mCode == Constants.CODE_SPACE) { 1349 drawSpacebar(key, canvas, paint); 1350 // Whether space key needs to show the "..." popup hint for special purposes 1351 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 1352 drawKeyPopupHint(key, canvas, paint, params); 1353 } 1354 } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { 1355 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1356 drawKeyPopupHint(key, canvas, paint, params); 1357 } else { 1358 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1359 } 1360 } 1361 fitsTextIntoWidth(final int width, final String text, final Paint paint)1362 private static boolean fitsTextIntoWidth(final int width, final String text, 1363 final Paint paint) { 1364 paint.setTextScaleX(1.0f); 1365 final float textWidth = TypefaceUtils.getLabelWidth(text, paint); 1366 if (textWidth < width) { 1367 return true; 1368 } 1369 1370 final float scaleX = width / textWidth; 1371 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { 1372 return false; 1373 } 1374 1375 paint.setTextScaleX(scaleX); 1376 return TypefaceUtils.getLabelWidth(text, paint) < width; 1377 } 1378 1379 // Layout language name on spacebar. layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width)1380 private static String layoutLanguageOnSpacebar(final Paint paint, 1381 final InputMethodSubtype subtype, final int width) { 1382 // Choose appropriate language name to fit into the width. 1383 final String fullText = getFullDisplayName(subtype); 1384 if (fitsTextIntoWidth(width, fullText, paint)) { 1385 return fullText; 1386 } 1387 1388 final String middleText = getMiddleDisplayName(subtype); 1389 if (fitsTextIntoWidth(width, middleText, paint)) { 1390 return middleText; 1391 } 1392 1393 final String shortText = getShortDisplayName(subtype); 1394 if (fitsTextIntoWidth(width, shortText, paint)) { 1395 return shortText; 1396 } 1397 1398 return ""; 1399 } 1400 drawSpacebar(final Key key, final Canvas canvas, final Paint paint)1401 private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) { 1402 final int width = key.mWidth; 1403 final int height = key.mHeight; 1404 1405 // If input language are explicitly selected. 1406 if (mNeedsToDisplayLanguage) { 1407 paint.setTextAlign(Align.CENTER); 1408 paint.setTypeface(Typeface.DEFAULT); 1409 paint.setTextSize(mSpacebarTextSize); 1410 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 1411 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 1412 // Draw language text with shadow 1413 final float descent = paint.descent(); 1414 final float textHeight = -paint.ascent() + descent; 1415 final float baseline = height / 2 + textHeight / 2; 1416 paint.setColor(mSpacebarTextShadowColor); 1417 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1418 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 1419 paint.setColor(mSpacebarTextColor); 1420 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1421 canvas.drawText(language, width / 2, baseline - descent, paint); 1422 } 1423 1424 // Draw the spacebar icon at the bottom 1425 if (mAutoCorrectionSpacebarLedOn) { 1426 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 1427 final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); 1428 int x = (width - iconWidth) / 2; 1429 int y = height - iconHeight; 1430 drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); 1431 } else if (mSpaceIcon != null) { 1432 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 1433 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 1434 int x = (width - iconWidth) / 2; 1435 int y = height - iconHeight; 1436 drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); 1437 } 1438 } 1439 1440 // InputMethodSubtype's display name for spacebar text in its locale. 1441 // isAdditionalSubtype (T=true, F=false) 1442 // locale layout | Short Middle Full 1443 // ------ ------- - ---- --------- ---------------------- 1444 // en_US qwerty F En English English (US) exception 1445 // en_GB qwerty F En English English (UK) exception 1446 // es_US spanish F Es Español Español (EE.UU.) exception 1447 // fr azerty F Fr Français Français 1448 // fr_CA qwerty F Fr Français Français (Canada) 1449 // de qwertz F De Deutsch Deutsch 1450 // zz qwerty F QWERTY QWERTY 1451 // fr qwertz T Fr Français Français 1452 // de qwerty T De Deutsch Deutsch 1453 // en_US azerty T En English English (US) 1454 // zz azerty T AZERTY AZERTY 1455 1456 // Get InputMethodSubtype's full display name in its locale. getFullDisplayName(final InputMethodSubtype subtype)1457 static String getFullDisplayName(final InputMethodSubtype subtype) { 1458 if (SubtypeLocale.isNoLanguage(subtype)) { 1459 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1460 } 1461 return SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale()); 1462 } 1463 1464 // Get InputMethodSubtype's short display name in its locale. getShortDisplayName(final InputMethodSubtype subtype)1465 static String getShortDisplayName(final InputMethodSubtype subtype) { 1466 if (SubtypeLocale.isNoLanguage(subtype)) { 1467 return ""; 1468 } 1469 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1470 return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale); 1471 } 1472 1473 // Get InputMethodSubtype's middle display name in its locale. getMiddleDisplayName(final InputMethodSubtype subtype)1474 static String getMiddleDisplayName(final InputMethodSubtype subtype) { 1475 if (SubtypeLocale.isNoLanguage(subtype)) { 1476 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1477 } 1478 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1479 return SubtypeLocale.getSubtypeLocaleDisplayName(locale.getLanguage()); 1480 } 1481 } 1482