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