MainKeyboardView.java revision 4b3cae9b0cbd5bf30a1c8da383ff247f9c2afc5a
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.StaticInnerHandlerWrapper; 69import com.android.inputmethod.latin.StringUtils; 70import com.android.inputmethod.latin.SubtypeLocale; 71import com.android.inputmethod.latin.SuggestedWords; 72import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; 73import com.android.inputmethod.latin.define.ProductionFlag; 74import com.android.inputmethod.research.ResearchLogger; 75 76import java.util.Locale; 77import java.util.WeakHashMap; 78 79/** 80 * A view that is responsible for detecting key presses and touch movements. 81 * 82 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled 83 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon 84 * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio 85 * @attr ref R.styleable#MainKeyboardView_spacebarTextColor 86 * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor 87 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha 88 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator 89 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator 90 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator 91 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance 92 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime 93 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance 94 * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable 95 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout 96 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval 97 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout 98 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout 99 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout 100 * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout 101 * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset 102 * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight 103 * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout 104 * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout 105 * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha 106 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint 107 * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout 108 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping 109 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold 110 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration 111 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom 112 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo 113 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom 114 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo 115 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance 116 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime 117 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold 118 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration 119 */ 120public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 121 PointerTracker.DrawingProxy, MoreKeysPanel.Controller, 122 TouchScreenRegulator.ProcessMotionEvent { 123 private static final String TAG = MainKeyboardView.class.getSimpleName(); 124 125 // TODO: Kill process when the usability study mode was changed. 126 private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; 127 128 /** Listener for {@link KeyboardActionListener}. */ 129 private KeyboardActionListener mKeyboardActionListener; 130 131 /* Space key and its icons */ 132 private Key mSpaceKey; 133 private Drawable mSpaceIcon; 134 // Stuff to draw language name on spacebar. 135 private final int mLanguageOnSpacebarFinalAlpha; 136 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 137 private boolean mNeedsToDisplayLanguage; 138 private boolean mHasMultipleEnabledIMEsOrSubtypes; 139 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 140 private final float mSpacebarTextRatio; 141 private float mSpacebarTextSize; 142 private final int mSpacebarTextColor; 143 private final int mSpacebarTextShadowColor; 144 // The minimum x-scale to fit the language name on spacebar. 145 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 146 // Stuff to draw auto correction LED on spacebar. 147 private boolean mAutoCorrectionSpacebarLedOn; 148 private final boolean mAutoCorrectionSpacebarLedEnabled; 149 private final Drawable mAutoCorrectionSpacebarLedIcon; 150 private static final int SPACE_LED_LENGTH_PERCENT = 80; 151 152 // Stuff to draw altCodeWhileTyping keys. 153 private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 154 private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 155 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 156 157 // Preview placer view 158 private final PreviewPlacerView mPreviewPlacerView; 159 private final int[] mOriginCoords = CoordinateUtils.newInstance(); 160 private final GestureFloatingPreviewText mGestureFloatingPreviewText; 161 private final GestureTrailsPreview mGestureTrailsPreview; 162 private final SlidingKeyInputPreview mSlidingKeyInputPreview; 163 164 // Key preview 165 private static final int PREVIEW_ALPHA = 240; 166 private final int mKeyPreviewLayoutId; 167 private final int mKeyPreviewOffset; 168 private final int mKeyPreviewHeight; 169 private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray(); 170 private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams(); 171 private boolean mShowKeyPreviewPopup = true; 172 private int mKeyPreviewLingerTimeout; 173 174 // More keys keyboard 175 private final Paint mBackgroundDimAlphaPaint = new Paint(); 176 private boolean mNeedsToDimEntireKeyboard; 177 private final View mMoreKeysKeyboardContainer; 178 private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = 179 CollectionUtils.newWeakHashMap(); 180 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 181 // More keys panel (used by both more keys keyboard and more suggestions view) 182 // TODO: Consider extending to support multiple more keys panels 183 private MoreKeysPanel mMoreKeysPanel; 184 185 // Gesture floating preview text 186 // TODO: Make this parameter customizable by user via settings. 187 private int mGestureFloatingPreviewTextLingerTimeout; 188 189 private final TouchScreenRegulator mTouchScreenRegulator; 190 191 private KeyDetector mKeyDetector; 192 private final boolean mHasDistinctMultitouch; 193 private int mOldPointerCount = 1; 194 private Key mOldKey; 195 196 private final KeyTimerHandler mKeyTimerHandler; 197 198 private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView> 199 implements TimerProxy { 200 private static final int MSG_TYPING_STATE_EXPIRED = 0; 201 private static final int MSG_REPEAT_KEY = 1; 202 private static final int MSG_LONGPRESS_KEY = 2; 203 private static final int MSG_DOUBLE_TAP = 3; 204 private static final int MSG_UPDATE_BATCH_INPUT = 4; 205 206 private final int mKeyRepeatStartTimeout; 207 private final int mKeyRepeatInterval; 208 private final int mLongPressKeyTimeout; 209 private final int mLongPressShiftKeyTimeout; 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 mLongPressKeyTimeout = mainKeyboardViewAttr.getInt( 222 R.styleable.MainKeyboardView_longPressKeyTimeout, 0); 223 mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt( 224 R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0); 225 mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( 226 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); 227 mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( 228 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); 229 } 230 231 @Override 232 public void handleMessage(final Message msg) { 233 final MainKeyboardView keyboardView = getOuterInstance(); 234 final PointerTracker tracker = (PointerTracker) msg.obj; 235 switch (msg.what) { 236 case MSG_TYPING_STATE_EXPIRED: 237 startWhileTypingFadeinAnimation(keyboardView); 238 break; 239 case MSG_REPEAT_KEY: 240 final Key currentKey = tracker.getKey(); 241 if (currentKey != null && currentKey.mCode == msg.arg1) { 242 tracker.onRegisterKey(currentKey); 243 startKeyRepeatTimer(tracker, mKeyRepeatInterval); 244 } 245 break; 246 case MSG_LONGPRESS_KEY: 247 if (tracker != null) { 248 keyboardView.onLongPress(tracker); 249 } else { 250 KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); 251 } 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 int code) { 284 cancelLongPressTimer(); 285 final int delay; 286 switch (code) { 287 case Constants.CODE_SHIFT: 288 delay = mLongPressShiftKeyTimeout; 289 break; 290 default: 291 delay = 0; 292 break; 293 } 294 if (delay > 0) { 295 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); 296 } 297 } 298 299 @Override 300 public void startLongPressTimer(final PointerTracker tracker) { 301 cancelLongPressTimer(); 302 if (tracker == null) { 303 return; 304 } 305 final Key key = tracker.getKey(); 306 final int delay; 307 switch (key.mCode) { 308 case Constants.CODE_SHIFT: 309 delay = mLongPressShiftKeyTimeout; 310 break; 311 default: 312 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { 313 // We use longer timeout for sliding finger input started from the symbols 314 // mode key. 315 delay = mLongPressKeyTimeout * 3; 316 } else { 317 delay = mLongPressKeyTimeout; 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.IS_EXPERIMENTAL) { 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.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel; 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 if (StringUtils.codePointCount(label) > 1) { 820 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize); 821 previewText.setTypeface(Typeface.DEFAULT_BOLD); 822 } else { 823 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize); 824 previewText.setTypeface(key.selectTypeface(drawParams)); 825 } 826 previewText.setText(label); 827 } else { 828 previewText.setCompoundDrawables(null, null, null, 829 key.getPreviewIcon(keyboard.mIconsSet)); 830 previewText.setText(null); 831 } 832 833 previewText.measure( 834 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 835 final int keyDrawWidth = key.getDrawWidth(); 836 final int previewWidth = previewText.getMeasuredWidth(); 837 final int previewHeight = mKeyPreviewHeight; 838 // The width and height of visible part of the key preview background. The content marker 839 // of the background 9-patch have to cover the visible part of the background. 840 previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft() 841 - previewText.getPaddingRight(); 842 previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop() 843 - previewText.getPaddingBottom(); 844 // The distance between the top edge of the parent key and the bottom of the visible part 845 // of the key preview background. 846 previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom(); 847 getLocationInWindow(mOriginCoords); 848 // The key preview is horizontally aligned with the center of the visible part of the 849 // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and 850 // the left/right background is used if such background is specified. 851 final int statePosition; 852 int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 853 + CoordinateUtils.x(mOriginCoords); 854 if (previewX < 0) { 855 previewX = 0; 856 statePosition = STATE_LEFT; 857 } else if (previewX > getWidth() - previewWidth) { 858 previewX = getWidth() - previewWidth; 859 statePosition = STATE_RIGHT; 860 } else { 861 statePosition = STATE_MIDDLE; 862 } 863 // The key preview is placed vertically above the top edge of the parent key with an 864 // arbitrary offset. 865 final int previewY = key.mY - previewHeight + mKeyPreviewOffset 866 + CoordinateUtils.y(mOriginCoords); 867 868 if (background != null) { 869 final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL; 870 background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]); 871 } 872 ViewLayoutUtils.placeViewAt( 873 previewText, previewX, previewY, previewWidth, previewHeight); 874 previewText.setVisibility(VISIBLE); 875 } 876 877 @Override 878 public void dismissKeyPreview(final PointerTracker tracker) { 879 mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker); 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 research logger that the keyboard view has been attached. This is needed 929 // to properly show the splash screen, which requires that the window token of the 930 // KeyboardView be non-null. 931 if (ProductionFlag.IS_EXPERIMENTAL) { 932 ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); 933 } 934 } 935 936 @Override 937 protected void onDetachedFromWindow() { 938 super.onDetachedFromWindow(); 939 mPreviewPlacerView.removeAllViews(); 940 // Notify the research logger that the keyboard view has been detached. This is needed 941 // to invalidate the reference of {@link MainKeyboardView} to null. 942 if (ProductionFlag.IS_EXPERIMENTAL) { 943 ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); 944 } 945 } 946 947 private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { 948 if (key.mMoreKeys == null) { 949 return null; 950 } 951 Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); 952 if (moreKeysKeyboard == null) { 953 moreKeysKeyboard = new MoreKeysKeyboard.Builder( 954 context, key, this, mKeyPreviewDrawParams).build(); 955 mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); 956 } 957 958 final View container = mMoreKeysKeyboardContainer; 959 final MoreKeysKeyboardView moreKeysKeyboardView = 960 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 961 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 962 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 963 return moreKeysKeyboardView; 964 } 965 966 /** 967 * Called when a key is long pressed. 968 * @param tracker the pointer tracker which pressed the parent key 969 * @return true if the long press is handled, false otherwise. Subclasses should call the 970 * method on the base class if the subclass doesn't wish to handle the call. 971 */ 972 private boolean onLongPress(final PointerTracker tracker) { 973 if (isShowingMoreKeysPanel()) { 974 return false; 975 } 976 final Key key = tracker.getKey(); 977 if (key == null) { 978 return false; 979 } 980 if (ProductionFlag.IS_EXPERIMENTAL) { 981 ResearchLogger.mainKeyboardView_onLongPress(); 982 } 983 final int code = key.mCode; 984 if (key.hasEmbeddedMoreKey()) { 985 final int embeddedCode = key.mMoreKeys[0].mCode; 986 tracker.onLongPressed(); 987 invokeCodeInput(embeddedCode); 988 invokeReleaseKey(code); 989 KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code); 990 return true; 991 } 992 if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { 993 // Long pressing the space key invokes IME switcher dialog. 994 if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { 995 tracker.onLongPressed(); 996 invokeReleaseKey(code); 997 return true; 998 } 999 } 1000 return openMoreKeysPanel(key, tracker); 1001 } 1002 1003 private boolean invokeCustomRequest(final int requestCode) { 1004 return mKeyboardActionListener.onCustomRequest(requestCode); 1005 } 1006 1007 private void invokeCodeInput(final int code) { 1008 mKeyboardActionListener.onCodeInput( 1009 code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 1010 } 1011 1012 private void invokeReleaseKey(final int code) { 1013 mKeyboardActionListener.onReleaseKey(code, false); 1014 } 1015 1016 private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) { 1017 final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); 1018 if (moreKeysPanel == null) { 1019 return false; 1020 } 1021 1022 final int[] lastCoords = CoordinateUtils.newInstance(); 1023 tracker.getLastCoordinates(lastCoords); 1024 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview(); 1025 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 1026 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 1027 // keys keyboard is placed at the touch point of the parent key. 1028 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 1029 ? CoordinateUtils.x(lastCoords) 1030 : key.mX + key.mWidth / 2; 1031 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 1032 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 1033 // aligned with the bottom edge of the visible part of the key preview. 1034 // {@code mPreviewVisibleOffset} has been set appropriately in 1035 // {@link KeyboardView#showKeyPreview(PointerTracker)}. 1036 final int pointY = key.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; 1037 moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); 1038 final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); 1039 final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); 1040 tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); 1041 return true; 1042 } 1043 1044 public boolean isInSlidingKeyInput() { 1045 if (isShowingMoreKeysPanel()) { 1046 return true; 1047 } 1048 return PointerTracker.isAnyInSlidingKeyInput(); 1049 } 1050 1051 @Override 1052 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 1053 if (isShowingMoreKeysPanel()) { 1054 onDismissMoreKeysPanel(); 1055 } 1056 mPreviewPlacerView.addView(panel.getContainerView()); 1057 mMoreKeysPanel = panel; 1058 dimEntireKeyboard(true /* dimmed */); 1059 } 1060 1061 public boolean isShowingMoreKeysPanel() { 1062 return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); 1063 } 1064 1065 @Override 1066 public void onCancelMoreKeysPanel() { 1067 PointerTracker.dismissAllMoreKeysPanels(); 1068 } 1069 1070 @Override 1071 public boolean onDismissMoreKeysPanel() { 1072 dimEntireKeyboard(false /* dimmed */); 1073 if (isShowingMoreKeysPanel()) { 1074 mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView()); 1075 mMoreKeysPanel = null; 1076 return true; 1077 } 1078 return false; 1079 } 1080 1081 public int getPointerCount() { 1082 return mOldPointerCount; 1083 } 1084 1085 @Override 1086 public boolean dispatchTouchEvent(MotionEvent event) { 1087 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1088 return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event); 1089 } 1090 return super.dispatchTouchEvent(event); 1091 } 1092 1093 @Override 1094 public boolean onTouchEvent(final MotionEvent me) { 1095 if (getKeyboard() == null) { 1096 return false; 1097 } 1098 return mTouchScreenRegulator.onTouchEvent(me); 1099 } 1100 1101 @Override 1102 public boolean processMotionEvent(final MotionEvent me) { 1103 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 1104 final int action = me.getActionMasked(); 1105 final int pointerCount = me.getPointerCount(); 1106 final int oldPointerCount = mOldPointerCount; 1107 mOldPointerCount = pointerCount; 1108 1109 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1110 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 1111 // events except a transition from/to single-touch. 1112 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 1113 return true; 1114 } 1115 1116 final long eventTime = me.getEventTime(); 1117 final int index = me.getActionIndex(); 1118 final int id = me.getPointerId(index); 1119 final int x = (int)me.getX(index); 1120 final int y = (int)me.getY(index); 1121 1122 // TODO: This might be moved to the tracker.processMotionEvent() call below. 1123 if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { 1124 writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); 1125 } 1126 // TODO: This should be moved to the tracker.processMotionEvent() call below. 1127 // Currently the same "move" event is being logged twice. 1128 if (ProductionFlag.IS_EXPERIMENTAL) { 1129 ResearchLogger.mainKeyboardView_processMotionEvent( 1130 me, action, eventTime, index, id, x, y); 1131 } 1132 1133 if (mKeyTimerHandler.isInKeyRepeat()) { 1134 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1135 // Key repeating timer will be canceled if 2 or more keys are in action, and current 1136 // event (UP or DOWN) is non-modifier key. 1137 if (pointerCount > 1 && !tracker.isModifier()) { 1138 mKeyTimerHandler.cancelKeyRepeatTimer(); 1139 } 1140 // Up event will pass through. 1141 } 1142 1143 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 1144 // Translate mutli-touch event to single-touch events on the device that has no distinct 1145 // multi-touch panel. 1146 if (nonDistinctMultitouch) { 1147 // Use only main (id=0) pointer tracker. 1148 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1149 if (pointerCount == 1 && oldPointerCount == 2) { 1150 // Multi-touch to single touch transition. 1151 // Send a down event for the latest pointer if the key is different from the 1152 // previous key. 1153 final Key newKey = tracker.getKeyOn(x, y); 1154 if (mOldKey != newKey) { 1155 tracker.onDownEvent(x, y, eventTime, this); 1156 if (action == MotionEvent.ACTION_UP) { 1157 tracker.onUpEvent(x, y, eventTime); 1158 } 1159 } 1160 } else if (pointerCount == 2 && oldPointerCount == 1) { 1161 // Single-touch to multi-touch transition. 1162 // Send an up event for the last pointer. 1163 final int[] lastCoords = CoordinateUtils.newInstance(); 1164 mOldKey = tracker.getKeyOn( 1165 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); 1166 tracker.onUpEvent( 1167 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); 1168 } else if (pointerCount == 1 && oldPointerCount == 1) { 1169 tracker.processMotionEvent(action, x, y, eventTime, this); 1170 } else { 1171 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 1172 + " (old " + oldPointerCount + ")"); 1173 } 1174 return true; 1175 } 1176 1177 if (action == MotionEvent.ACTION_MOVE) { 1178 for (int i = 0; i < pointerCount; i++) { 1179 final int pointerId = me.getPointerId(i); 1180 final PointerTracker tracker = PointerTracker.getPointerTracker( 1181 pointerId, this); 1182 final int px = (int)me.getX(i); 1183 final int py = (int)me.getY(i); 1184 tracker.onMoveEvent(px, py, eventTime, me); 1185 if (ENABLE_USABILITY_STUDY_LOG) { 1186 writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); 1187 } 1188 if (ProductionFlag.IS_EXPERIMENTAL) { 1189 ResearchLogger.mainKeyboardView_processMotionEvent( 1190 me, action, eventTime, i, pointerId, px, py); 1191 } 1192 } 1193 } else { 1194 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 1195 tracker.processMotionEvent(action, x, y, eventTime, this); 1196 } 1197 1198 return true; 1199 } 1200 1201 private static void writeUsabilityStudyLog(final MotionEvent me, final int action, 1202 final long eventTime, final int index, final int id, final int x, final int y) { 1203 final String eventTag; 1204 switch (action) { 1205 case MotionEvent.ACTION_UP: 1206 eventTag = "[Up]"; 1207 break; 1208 case MotionEvent.ACTION_DOWN: 1209 eventTag = "[Down]"; 1210 break; 1211 case MotionEvent.ACTION_POINTER_UP: 1212 eventTag = "[PointerUp]"; 1213 break; 1214 case MotionEvent.ACTION_POINTER_DOWN: 1215 eventTag = "[PointerDown]"; 1216 break; 1217 case MotionEvent.ACTION_MOVE: 1218 eventTag = "[Move]"; 1219 break; 1220 default: 1221 eventTag = "[Action" + action + "]"; 1222 break; 1223 } 1224 final float size = me.getSize(index); 1225 final float pressure = me.getPressure(index); 1226 UsabilityStudyLogUtils.getInstance().write( 1227 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); 1228 } 1229 1230 public void cancelAllMessages() { 1231 mKeyTimerHandler.cancelAllMessages(); 1232 mDrawingHandler.cancelAllMessages(); 1233 } 1234 1235 @Override 1236 public void closing() { 1237 dismissAllKeyPreviews(); 1238 cancelAllMessages(); 1239 onDismissMoreKeysPanel(); 1240 mMoreKeysKeyboardCache.clear(); 1241 super.closing(); 1242 } 1243 1244 /** 1245 * Receives hover events from the input framework. 1246 * 1247 * @param event The motion event to be dispatched. 1248 * @return {@code true} if the event was handled by the view, {@code false} 1249 * otherwise 1250 */ 1251 @Override 1252 public boolean dispatchHoverEvent(final MotionEvent event) { 1253 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 1254 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 1255 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 1256 } 1257 1258 // Reflection doesn't support calling superclass methods. 1259 return false; 1260 } 1261 1262 public void updateShortcutKey(final boolean available) { 1263 final Keyboard keyboard = getKeyboard(); 1264 if (keyboard == null) { 1265 return; 1266 } 1267 final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); 1268 if (shortcutKey == null) { 1269 return; 1270 } 1271 shortcutKey.setEnabled(available); 1272 invalidateKey(shortcutKey); 1273 } 1274 1275 private void updateAltCodeKeyWhileTyping() { 1276 final Keyboard keyboard = getKeyboard(); 1277 if (keyboard == null) { 1278 return; 1279 } 1280 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 1281 invalidateKey(key); 1282 } 1283 } 1284 1285 public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, 1286 final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) { 1287 mNeedsToDisplayLanguage = needsToDisplayLanguage; 1288 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 1289 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 1290 if (animator == null) { 1291 mNeedsToDisplayLanguage = false; 1292 } else { 1293 if (subtypeChanged && needsToDisplayLanguage) { 1294 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 1295 if (animator.isStarted()) { 1296 animator.cancel(); 1297 } 1298 animator.start(); 1299 } else { 1300 if (!animator.isStarted()) { 1301 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 1302 } 1303 } 1304 } 1305 invalidateKey(mSpaceKey); 1306 } 1307 1308 public void updateAutoCorrectionState(final boolean isAutoCorrection) { 1309 if (!mAutoCorrectionSpacebarLedEnabled) { 1310 return; 1311 } 1312 mAutoCorrectionSpacebarLedOn = isAutoCorrection; 1313 invalidateKey(mSpaceKey); 1314 } 1315 1316 private void dimEntireKeyboard(final boolean dimmed) { 1317 final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; 1318 mNeedsToDimEntireKeyboard = dimmed; 1319 if (needsRedrawing) { 1320 invalidateAllKeys(); 1321 } 1322 } 1323 1324 @Override 1325 protected void onDraw(final Canvas canvas) { 1326 super.onDraw(canvas); 1327 1328 // Overlay a dark rectangle to dim. 1329 if (mNeedsToDimEntireKeyboard) { 1330 canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundDimAlphaPaint); 1331 } 1332 } 1333 1334 @Override 1335 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 1336 final KeyDrawParams params) { 1337 if (key.altCodeWhileTyping() && key.isEnabled()) { 1338 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 1339 } 1340 if (key.mCode == Constants.CODE_SPACE) { 1341 drawSpacebar(key, canvas, paint); 1342 // Whether space key needs to show the "..." popup hint for special purposes 1343 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 1344 drawKeyPopupHint(key, canvas, paint, params); 1345 } 1346 } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { 1347 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1348 drawKeyPopupHint(key, canvas, paint, params); 1349 } else { 1350 super.onDrawKeyTopVisuals(key, canvas, paint, params); 1351 } 1352 } 1353 1354 private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { 1355 paint.setTextScaleX(1.0f); 1356 final float textWidth = getLabelWidth(text, paint); 1357 if (textWidth < width) { 1358 return true; 1359 } 1360 1361 final float scaleX = width / textWidth; 1362 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { 1363 return false; 1364 } 1365 1366 paint.setTextScaleX(scaleX); 1367 return getLabelWidth(text, paint) < width; 1368 } 1369 1370 // Layout language name on spacebar. 1371 private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, 1372 final int width) { 1373 // Choose appropriate language name to fit into the width. 1374 final String fullText = getFullDisplayName(subtype); 1375 if (fitsTextIntoWidth(width, fullText, paint)) { 1376 return fullText; 1377 } 1378 1379 final String middleText = getMiddleDisplayName(subtype); 1380 if (fitsTextIntoWidth(width, middleText, paint)) { 1381 return middleText; 1382 } 1383 1384 final String shortText = getShortDisplayName(subtype); 1385 if (fitsTextIntoWidth(width, shortText, paint)) { 1386 return shortText; 1387 } 1388 1389 return ""; 1390 } 1391 1392 private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) { 1393 final int width = key.mWidth; 1394 final int height = key.mHeight; 1395 1396 // If input language are explicitly selected. 1397 if (mNeedsToDisplayLanguage) { 1398 paint.setTextAlign(Align.CENTER); 1399 paint.setTypeface(Typeface.DEFAULT); 1400 paint.setTextSize(mSpacebarTextSize); 1401 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 1402 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 1403 // Draw language text with shadow 1404 final float descent = paint.descent(); 1405 final float textHeight = -paint.ascent() + descent; 1406 final float baseline = height / 2 + textHeight / 2; 1407 paint.setColor(mSpacebarTextShadowColor); 1408 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1409 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 1410 paint.setColor(mSpacebarTextColor); 1411 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1412 canvas.drawText(language, width / 2, baseline - descent, paint); 1413 } 1414 1415 // Draw the spacebar icon at the bottom 1416 if (mAutoCorrectionSpacebarLedOn) { 1417 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 1418 final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); 1419 int x = (width - iconWidth) / 2; 1420 int y = height - iconHeight; 1421 drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); 1422 } else if (mSpaceIcon != null) { 1423 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 1424 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 1425 int x = (width - iconWidth) / 2; 1426 int y = height - iconHeight; 1427 drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); 1428 } 1429 } 1430 1431 // InputMethodSubtype's display name for spacebar text in its locale. 1432 // isAdditionalSubtype (T=true, F=false) 1433 // locale layout | Short Middle Full 1434 // ------ ------- - ---- --------- ---------------------- 1435 // en_US qwerty F En English English (US) exception 1436 // en_GB qwerty F En English English (UK) exception 1437 // es_US spanish F Es Español Español (EE.UU.) exception 1438 // fr azerty F Fr Français Français 1439 // fr_CA qwerty F Fr Français Français (Canada) 1440 // de qwertz F De Deutsch Deutsch 1441 // zz qwerty F QWERTY QWERTY 1442 // fr qwertz T Fr Français Français (QWERTZ) 1443 // de qwerty T De Deutsch Deutsch (QWERTY) 1444 // en_US azerty T En English English (US) (AZERTY) 1445 // zz azerty T AZERTY AZERTY 1446 1447 // Get InputMethodSubtype's full display name in its locale. 1448 static String getFullDisplayName(final InputMethodSubtype subtype) { 1449 if (SubtypeLocale.isNoLanguage(subtype)) { 1450 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1451 } 1452 1453 return SubtypeLocale.getSubtypeDisplayName(subtype); 1454 } 1455 1456 // Get InputMethodSubtype's short display name in its locale. 1457 static String getShortDisplayName(final InputMethodSubtype subtype) { 1458 if (SubtypeLocale.isNoLanguage(subtype)) { 1459 return ""; 1460 } 1461 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1462 return StringUtils.toTitleCase(locale.getLanguage(), locale); 1463 } 1464 1465 // Get InputMethodSubtype's middle display name in its locale. 1466 static String getMiddleDisplayName(final InputMethodSubtype subtype) { 1467 if (SubtypeLocale.isNoLanguage(subtype)) { 1468 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1469 } 1470 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1471 return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); 1472 } 1473} 1474