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