MainKeyboardView.java revision 915f348b35cb66ed9696a51c9250f9b25799fb82
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.Paint; 28import android.graphics.Paint.Align; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.os.Message; 32import android.os.SystemClock; 33import android.preference.PreferenceManager; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.MotionEvent; 38import android.view.View; 39import android.view.ViewConfiguration; 40import android.view.ViewGroup; 41import android.view.inputmethod.InputMethodSubtype; 42 43import com.android.inputmethod.accessibility.AccessibilityUtils; 44import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 45import com.android.inputmethod.annotations.ExternallyReferenced; 46import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; 47import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 48import com.android.inputmethod.keyboard.internal.KeyDrawParams; 49import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; 50import com.android.inputmethod.latin.Constants; 51import com.android.inputmethod.latin.CoordinateUtils; 52import com.android.inputmethod.latin.DebugSettings; 53import com.android.inputmethod.latin.LatinIME; 54import com.android.inputmethod.latin.LatinImeLogger; 55import com.android.inputmethod.latin.R; 56import com.android.inputmethod.latin.ResourceUtils; 57import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 58import com.android.inputmethod.latin.StringUtils; 59import com.android.inputmethod.latin.SubtypeLocale; 60import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; 61import com.android.inputmethod.latin.define.ProductionFlag; 62import com.android.inputmethod.research.ResearchLogger; 63 64import java.util.Locale; 65import java.util.WeakHashMap; 66 67/** 68 * A view that is responsible for detecting key presses and touch movements. 69 * 70 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled 71 * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon 72 * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio 73 * @attr ref R.styleable#MainKeyboardView_spacebarTextColor 74 * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor 75 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha 76 * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator 77 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator 78 * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator 79 * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance 80 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime 81 * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance 82 * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable 83 * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout 84 * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval 85 * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout 86 * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout 87 * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout 88 * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint 89 * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping 90 * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold 91 * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration 92 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom 93 * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo 94 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom 95 * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo 96 * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance 97 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime 98 * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold 99 * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration 100 */ 101public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, 102 TouchScreenRegulator.ProcessMotionEvent { 103 private static final String TAG = MainKeyboardView.class.getSimpleName(); 104 105 // TODO: Kill process when the usability study mode was changed. 106 private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; 107 108 /** Listener for {@link KeyboardActionListener}. */ 109 private KeyboardActionListener mKeyboardActionListener; 110 111 /* Space key and its icons */ 112 private Key mSpaceKey; 113 private Drawable mSpaceIcon; 114 // Stuff to draw language name on spacebar. 115 private final int mLanguageOnSpacebarFinalAlpha; 116 private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; 117 private boolean mNeedsToDisplayLanguage; 118 private boolean mHasMultipleEnabledIMEsOrSubtypes; 119 private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; 120 private final float mSpacebarTextRatio; 121 private float mSpacebarTextSize; 122 private final int mSpacebarTextColor; 123 private final int mSpacebarTextShadowColor; 124 // The minimum x-scale to fit the language name on spacebar. 125 private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; 126 // Stuff to draw auto correction LED on spacebar. 127 private boolean mAutoCorrectionSpacebarLedOn; 128 private final boolean mAutoCorrectionSpacebarLedEnabled; 129 private final Drawable mAutoCorrectionSpacebarLedIcon; 130 private static final int SPACE_LED_LENGTH_PERCENT = 80; 131 132 // Stuff to draw altCodeWhileTyping keys. 133 private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; 134 private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; 135 private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; 136 137 // More keys keyboard 138 private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache = 139 new WeakHashMap<Key, MoreKeysPanel>(); 140 private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; 141 142 private final TouchScreenRegulator mTouchScreenRegulator; 143 144 protected KeyDetector mKeyDetector; 145 private final boolean mHasDistinctMultitouch; 146 private int mOldPointerCount = 1; 147 private Key mOldKey; 148 149 private final KeyTimerHandler mKeyTimerHandler; 150 151 private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView> 152 implements TimerProxy { 153 private static final int MSG_TYPING_STATE_EXPIRED = 0; 154 private static final int MSG_REPEAT_KEY = 1; 155 private static final int MSG_LONGPRESS_KEY = 2; 156 private static final int MSG_DOUBLE_TAP = 3; 157 private static final int MSG_UPDATE_BATCH_INPUT = 4; 158 159 private final int mKeyRepeatStartTimeout; 160 private final int mKeyRepeatInterval; 161 private final int mLongPressKeyTimeout; 162 private final int mLongPressShiftKeyTimeout; 163 private final int mIgnoreAltCodeKeyTimeout; 164 private final int mGestureRecognitionUpdateTime; 165 166 public KeyTimerHandler(final MainKeyboardView outerInstance, 167 final TypedArray mainKeyboardViewAttr) { 168 super(outerInstance); 169 170 mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( 171 R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); 172 mKeyRepeatInterval = mainKeyboardViewAttr.getInt( 173 R.styleable.MainKeyboardView_keyRepeatInterval, 0); 174 mLongPressKeyTimeout = mainKeyboardViewAttr.getInt( 175 R.styleable.MainKeyboardView_longPressKeyTimeout, 0); 176 mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt( 177 R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0); 178 mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( 179 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); 180 mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( 181 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); 182 } 183 184 @Override 185 public void handleMessage(final Message msg) { 186 final MainKeyboardView keyboardView = getOuterInstance(); 187 final PointerTracker tracker = (PointerTracker) msg.obj; 188 switch (msg.what) { 189 case MSG_TYPING_STATE_EXPIRED: 190 startWhileTypingFadeinAnimation(keyboardView); 191 break; 192 case MSG_REPEAT_KEY: 193 final Key currentKey = tracker.getKey(); 194 if (currentKey != null && currentKey.mCode == msg.arg1) { 195 tracker.onRegisterKey(currentKey); 196 startKeyRepeatTimer(tracker, mKeyRepeatInterval); 197 } 198 break; 199 case MSG_LONGPRESS_KEY: 200 if (tracker != null) { 201 keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker); 202 } else { 203 KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); 204 } 205 break; 206 case MSG_UPDATE_BATCH_INPUT: 207 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis()); 208 startUpdateBatchInputTimer(tracker); 209 break; 210 } 211 } 212 213 private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { 214 final Key key = tracker.getKey(); 215 if (key == null) { 216 return; 217 } 218 sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); 219 } 220 221 @Override 222 public void startKeyRepeatTimer(final PointerTracker tracker) { 223 startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout); 224 } 225 226 public void cancelKeyRepeatTimer() { 227 removeMessages(MSG_REPEAT_KEY); 228 } 229 230 // TODO: Suppress layout changes in key repeat mode 231 public boolean isInKeyRepeat() { 232 return hasMessages(MSG_REPEAT_KEY); 233 } 234 235 @Override 236 public void startLongPressTimer(final int code) { 237 cancelLongPressTimer(); 238 final int delay; 239 switch (code) { 240 case Constants.CODE_SHIFT: 241 delay = mLongPressShiftKeyTimeout; 242 break; 243 default: 244 delay = 0; 245 break; 246 } 247 if (delay > 0) { 248 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay); 249 } 250 } 251 252 @Override 253 public void startLongPressTimer(final PointerTracker tracker) { 254 cancelLongPressTimer(); 255 if (tracker == null) { 256 return; 257 } 258 final Key key = tracker.getKey(); 259 final int delay; 260 switch (key.mCode) { 261 case Constants.CODE_SHIFT: 262 delay = mLongPressShiftKeyTimeout; 263 break; 264 default: 265 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { 266 // We use longer timeout for sliding finger input started from the symbols 267 // mode key. 268 delay = mLongPressKeyTimeout * 3; 269 } else { 270 delay = mLongPressKeyTimeout; 271 } 272 break; 273 } 274 if (delay > 0) { 275 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); 276 } 277 } 278 279 @Override 280 public void cancelLongPressTimer() { 281 removeMessages(MSG_LONGPRESS_KEY); 282 } 283 284 private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, 285 final ObjectAnimator animatorToStart) { 286 float startFraction = 0.0f; 287 if (animatorToCancel.isStarted()) { 288 animatorToCancel.cancel(); 289 startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); 290 } 291 final long startTime = (long)(animatorToStart.getDuration() * startFraction); 292 animatorToStart.start(); 293 animatorToStart.setCurrentPlayTime(startTime); 294 } 295 296 private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) { 297 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator, 298 keyboardView.mAltCodeKeyWhileTypingFadeinAnimator); 299 } 300 301 private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) { 302 cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, 303 keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); 304 } 305 306 @Override 307 public void startTypingStateTimer(final Key typedKey) { 308 if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { 309 return; 310 } 311 312 final boolean isTyping = isTypingState(); 313 removeMessages(MSG_TYPING_STATE_EXPIRED); 314 final MainKeyboardView keyboardView = getOuterInstance(); 315 316 // When user hits the space or the enter key, just cancel the while-typing timer. 317 final int typedCode = typedKey.mCode; 318 if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { 319 startWhileTypingFadeinAnimation(keyboardView); 320 return; 321 } 322 323 sendMessageDelayed( 324 obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout); 325 if (isTyping) { 326 return; 327 } 328 startWhileTypingFadeoutAnimation(keyboardView); 329 } 330 331 @Override 332 public boolean isTypingState() { 333 return hasMessages(MSG_TYPING_STATE_EXPIRED); 334 } 335 336 @Override 337 public void startDoubleTapTimer() { 338 sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP), 339 ViewConfiguration.getDoubleTapTimeout()); 340 } 341 342 @Override 343 public void cancelDoubleTapTimer() { 344 removeMessages(MSG_DOUBLE_TAP); 345 } 346 347 @Override 348 public boolean isInDoubleTapTimeout() { 349 return hasMessages(MSG_DOUBLE_TAP); 350 } 351 352 @Override 353 public void cancelKeyTimers() { 354 cancelKeyRepeatTimer(); 355 cancelLongPressTimer(); 356 } 357 358 @Override 359 public void startUpdateBatchInputTimer(final PointerTracker tracker) { 360 if (mGestureRecognitionUpdateTime <= 0) { 361 return; 362 } 363 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 364 sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker), 365 mGestureRecognitionUpdateTime); 366 } 367 368 @Override 369 public void cancelUpdateBatchInputTimer(final PointerTracker tracker) { 370 removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); 371 } 372 373 @Override 374 public void cancelAllUpdateBatchInputTimers() { 375 removeMessages(MSG_UPDATE_BATCH_INPUT); 376 } 377 378 public void cancelAllMessages() { 379 cancelKeyTimers(); 380 cancelAllUpdateBatchInputTimers(); 381 } 382 } 383 384 public MainKeyboardView(final Context context, final AttributeSet attrs) { 385 this(context, attrs, R.attr.mainKeyboardViewStyle); 386 } 387 388 public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 389 super(context, attrs, defStyle); 390 391 mTouchScreenRegulator = new TouchScreenRegulator(context, this); 392 393 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 394 final boolean forceNonDistinctMultitouch = prefs.getBoolean( 395 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); 396 final boolean hasDistinctMultitouch = context.getPackageManager() 397 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 398 mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; 399 final Resources res = getResources(); 400 final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( 401 ResourceUtils.getDeviceOverrideValue( 402 res, R.array.phantom_sudden_move_event_device_list)); 403 PointerTracker.init(needsPhantomSuddenMoveEventHack); 404 405 final TypedArray a = context.obtainStyledAttributes( 406 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); 407 mAutoCorrectionSpacebarLedEnabled = a.getBoolean( 408 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); 409 mAutoCorrectionSpacebarLedIcon = a.getDrawable( 410 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); 411 mSpacebarTextRatio = a.getFraction( 412 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f); 413 mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0); 414 mSpacebarTextShadowColor = a.getColor( 415 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); 416 mLanguageOnSpacebarFinalAlpha = a.getInt( 417 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, 418 Constants.Color.ALPHA_OPAQUE); 419 final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId( 420 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); 421 final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId( 422 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); 423 final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( 424 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); 425 426 final float keyHysteresisDistance = a.getDimension( 427 R.styleable.MainKeyboardView_keyHysteresisDistance, 0); 428 final float keyHysteresisDistanceForSlidingModifier = a.getDimension( 429 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0); 430 mKeyDetector = new KeyDetector( 431 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); 432 mKeyTimerHandler = new KeyTimerHandler(this, a); 433 mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( 434 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); 435 PointerTracker.setParameters(a); 436 a.recycle(); 437 438 mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( 439 languageOnSpacebarFadeoutAnimatorResId, this); 440 mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( 441 altCodeKeyWhileTypingFadeoutAnimatorResId, this); 442 mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( 443 altCodeKeyWhileTypingFadeinAnimatorResId, this); 444 } 445 446 private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { 447 if (resId == 0) { 448 return null; 449 } 450 final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( 451 getContext(), resId); 452 if (animator != null) { 453 animator.setTarget(target); 454 } 455 return animator; 456 } 457 458 @ExternallyReferenced 459 public int getLanguageOnSpacebarAnimAlpha() { 460 return mLanguageOnSpacebarAnimAlpha; 461 } 462 463 @ExternallyReferenced 464 public void setLanguageOnSpacebarAnimAlpha(final int alpha) { 465 mLanguageOnSpacebarAnimAlpha = alpha; 466 invalidateKey(mSpaceKey); 467 } 468 469 @ExternallyReferenced 470 public int getAltCodeKeyWhileTypingAnimAlpha() { 471 return mAltCodeKeyWhileTypingAnimAlpha; 472 } 473 474 @ExternallyReferenced 475 public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { 476 mAltCodeKeyWhileTypingAnimAlpha = alpha; 477 updateAltCodeKeyWhileTyping(); 478 } 479 480 public void setKeyboardActionListener(final KeyboardActionListener listener) { 481 mKeyboardActionListener = listener; 482 PointerTracker.setKeyboardActionListener(listener); 483 } 484 485 /** 486 * Returns the {@link KeyboardActionListener} object. 487 * @return the listener attached to this keyboard 488 */ 489 @Override 490 public KeyboardActionListener getKeyboardActionListener() { 491 return mKeyboardActionListener; 492 } 493 494 @Override 495 public KeyDetector getKeyDetector() { 496 return mKeyDetector; 497 } 498 499 @Override 500 public DrawingProxy getDrawingProxy() { 501 return this; 502 } 503 504 @Override 505 public TimerProxy getTimerProxy() { 506 return mKeyTimerHandler; 507 } 508 509 /** 510 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 511 * view will re-layout itself to accommodate the keyboard. 512 * @see Keyboard 513 * @see #getKeyboard() 514 * @param keyboard the keyboard to display in this view 515 */ 516 @Override 517 public void setKeyboard(final Keyboard keyboard) { 518 // Remove any pending messages, except dismissing preview and key repeat. 519 mKeyTimerHandler.cancelLongPressTimer(); 520 super.setKeyboard(keyboard); 521 mKeyDetector.setKeyboard( 522 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); 523 PointerTracker.setKeyDetector(mKeyDetector); 524 mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth); 525 mMoreKeysPanelCache.clear(); 526 527 mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); 528 mSpaceIcon = (mSpaceKey != null) 529 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; 530 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 531 mSpacebarTextSize = keyHeight * mSpacebarTextRatio; 532 if (ProductionFlag.IS_EXPERIMENTAL) { 533 ResearchLogger.mainKeyboardView_setKeyboard(keyboard); 534 } 535 536 // This always needs to be set since the accessibility state can 537 // potentially change without the keyboard being set again. 538 AccessibleKeyboardViewProxy.getInstance().setKeyboard(); 539 } 540 541 // Note that this method is called from a non-UI thread. 542 public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { 543 PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); 544 } 545 546 public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { 547 PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); 548 } 549 550 @Override 551 protected void onAttachedToWindow() { 552 super.onAttachedToWindow(); 553 // Notify the research logger that the keyboard view has been attached. This is needed 554 // to properly show the splash screen, which requires that the window token of the 555 // KeyboardView be non-null. 556 if (ProductionFlag.IS_EXPERIMENTAL) { 557 ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); 558 } 559 } 560 561 @Override 562 protected void onDetachedFromWindow() { 563 super.onDetachedFromWindow(); 564 // Notify the research logger that the keyboard view has been detached. This is needed 565 // to invalidate the reference of {@link MainKeyboardView} to null. 566 if (ProductionFlag.IS_EXPERIMENTAL) { 567 ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); 568 } 569 } 570 571 @Override 572 public void cancelAllMessages() { 573 mKeyTimerHandler.cancelAllMessages(); 574 super.cancelAllMessages(); 575 } 576 577 private boolean openMoreKeysKeyboardIfRequired(final Key parentKey, 578 final PointerTracker tracker) { 579 // Check if we have a popup layout specified first. 580 if (mMoreKeysLayout == 0) { 581 return false; 582 } 583 584 // Check if we are already displaying popup panel. 585 if (mMoreKeysPanel != null) { 586 return false; 587 } 588 if (parentKey == null) { 589 return false; 590 } 591 return onLongPress(parentKey, tracker); 592 } 593 594 // This default implementation returns a more keys panel. 595 protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) { 596 if (parentKey.mMoreKeys == null) { 597 return null; 598 } 599 600 final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); 601 if (container == null) { 602 throw new NullPointerException(); 603 } 604 605 final MoreKeysKeyboardView moreKeysKeyboardView = 606 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); 607 final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this) 608 .build(); 609 moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); 610 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 611 612 return moreKeysKeyboardView; 613 } 614 615 /** 616 * Called when a key is long pressed. By default this will open more keys keyboard associated 617 * with this key. 618 * @param parentKey the key that was long pressed 619 * @param tracker the pointer tracker which pressed the parent key 620 * @return true if the long press is handled, false otherwise. Subclasses should call the 621 * method on the base class if the subclass doesn't wish to handle the call. 622 */ 623 protected boolean onLongPress(final Key parentKey, final PointerTracker tracker) { 624 if (ProductionFlag.IS_EXPERIMENTAL) { 625 ResearchLogger.mainKeyboardView_onLongPress(); 626 } 627 final int primaryCode = parentKey.mCode; 628 if (parentKey.hasEmbeddedMoreKey()) { 629 final int embeddedCode = parentKey.mMoreKeys[0].mCode; 630 tracker.onLongPressed(); 631 invokeCodeInput(embeddedCode); 632 invokeReleaseKey(primaryCode); 633 KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); 634 return true; 635 } 636 if (primaryCode == Constants.CODE_SPACE || primaryCode == Constants.CODE_LANGUAGE_SWITCH) { 637 // Long pressing the space key invokes IME switcher dialog. 638 if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { 639 tracker.onLongPressed(); 640 invokeReleaseKey(primaryCode); 641 return true; 642 } 643 } 644 return openMoreKeysPanel(parentKey, tracker); 645 } 646 647 private boolean invokeCustomRequest(final int code) { 648 return mKeyboardActionListener.onCustomRequest(code); 649 } 650 651 private void invokeCodeInput(final int primaryCode) { 652 mKeyboardActionListener.onCodeInput( 653 primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 654 } 655 656 private void invokeReleaseKey(final int primaryCode) { 657 mKeyboardActionListener.onReleaseKey(primaryCode, false); 658 } 659 660 private boolean openMoreKeysPanel(final Key parentKey, final PointerTracker tracker) { 661 MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); 662 if (moreKeysPanel == null) { 663 moreKeysPanel = onCreateMoreKeysPanel(parentKey); 664 if (moreKeysPanel == null) { 665 return false; 666 } 667 mMoreKeysPanelCache.put(parentKey, moreKeysPanel); 668 } 669 670 final int[] lastCoords = CoordinateUtils.newInstance(); 671 tracker.getLastCoordinates(lastCoords); 672 final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview(); 673 // The more keys keyboard is usually horizontally aligned with the center of the parent key. 674 // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more 675 // keys keyboard is placed at the touch point of the parent key. 676 final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) 677 ? CoordinateUtils.x(lastCoords) 678 : parentKey.mX + parentKey.mWidth / 2; 679 // The more keys keyboard is usually vertically aligned with the top edge of the parent key 680 // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically 681 // aligned with the bottom edge of the visible part of the key preview. 682 // {@code mPreviewVisibleOffset} has been set appropriately in 683 // {@link KeyboardView#showKeyPreview(PointerTracker)}. 684 final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; 685 moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); 686 final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); 687 final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); 688 tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); 689 dimEntireKeyboard(true /* dimmed */); 690 return true; 691 } 692 693 public boolean isInSlidingKeyInput() { 694 if (mMoreKeysPanel != null) { 695 return true; 696 } 697 return PointerTracker.isAnyInSlidingKeyInput(); 698 } 699 700 public int getPointerCount() { 701 return mOldPointerCount; 702 } 703 704 @Override 705 public boolean dispatchTouchEvent(MotionEvent event) { 706 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 707 return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event); 708 } 709 return super.dispatchTouchEvent(event); 710 } 711 712 @Override 713 public boolean onTouchEvent(final MotionEvent me) { 714 if (getKeyboard() == null) { 715 return false; 716 } 717 return mTouchScreenRegulator.onTouchEvent(me); 718 } 719 720 @Override 721 public boolean processMotionEvent(final MotionEvent me) { 722 final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; 723 final int action = me.getActionMasked(); 724 final int pointerCount = me.getPointerCount(); 725 final int oldPointerCount = mOldPointerCount; 726 mOldPointerCount = pointerCount; 727 728 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 729 // If the device does not have distinct multi-touch support panel, ignore all multi-touch 730 // events except a transition from/to single-touch. 731 if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { 732 return true; 733 } 734 735 final long eventTime = me.getEventTime(); 736 final int index = me.getActionIndex(); 737 final int id = me.getPointerId(index); 738 final int x = (int)me.getX(index); 739 final int y = (int)me.getY(index); 740 741 // TODO: This might be moved to the tracker.processMotionEvent() call below. 742 if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { 743 writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); 744 } 745 // TODO: This should be moved to the tracker.processMotionEvent() call below. 746 // Currently the same "move" event is being logged twice. 747 if (ProductionFlag.IS_EXPERIMENTAL) { 748 ResearchLogger.mainKeyboardView_processMotionEvent( 749 me, action, eventTime, index, id, x, y); 750 } 751 752 if (mKeyTimerHandler.isInKeyRepeat()) { 753 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 754 // Key repeating timer will be canceled if 2 or more keys are in action, and current 755 // event (UP or DOWN) is non-modifier key. 756 if (pointerCount > 1 && !tracker.isModifier()) { 757 mKeyTimerHandler.cancelKeyRepeatTimer(); 758 } 759 // Up event will pass through. 760 } 761 762 // TODO: cleanup this code into a multi-touch to single-touch event converter class? 763 // Translate mutli-touch event to single-touch events on the device that has no distinct 764 // multi-touch panel. 765 if (nonDistinctMultitouch) { 766 // Use only main (id=0) pointer tracker. 767 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 768 if (pointerCount == 1 && oldPointerCount == 2) { 769 // Multi-touch to single touch transition. 770 // Send a down event for the latest pointer if the key is different from the 771 // previous key. 772 final Key newKey = tracker.getKeyOn(x, y); 773 if (mOldKey != newKey) { 774 tracker.onDownEvent(x, y, eventTime, this); 775 if (action == MotionEvent.ACTION_UP) { 776 tracker.onUpEvent(x, y, eventTime); 777 } 778 } 779 } else if (pointerCount == 2 && oldPointerCount == 1) { 780 // Single-touch to multi-touch transition. 781 // Send an up event for the last pointer. 782 final int[] lastCoords = CoordinateUtils.newInstance(); 783 mOldKey = tracker.getKeyOn( 784 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); 785 tracker.onUpEvent( 786 CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); 787 } else if (pointerCount == 1 && oldPointerCount == 1) { 788 tracker.processMotionEvent(action, x, y, eventTime, this); 789 } else { 790 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount 791 + " (old " + oldPointerCount + ")"); 792 } 793 return true; 794 } 795 796 if (action == MotionEvent.ACTION_MOVE) { 797 for (int i = 0; i < pointerCount; i++) { 798 final int pointerId = me.getPointerId(i); 799 final PointerTracker tracker = PointerTracker.getPointerTracker( 800 pointerId, this); 801 final int px = (int)me.getX(i); 802 final int py = (int)me.getY(i); 803 tracker.onMoveEvent(px, py, eventTime, me); 804 if (ENABLE_USABILITY_STUDY_LOG) { 805 writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); 806 } 807 if (ProductionFlag.IS_EXPERIMENTAL) { 808 ResearchLogger.mainKeyboardView_processMotionEvent( 809 me, action, eventTime, i, pointerId, px, py); 810 } 811 } 812 } else { 813 final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); 814 tracker.processMotionEvent(action, x, y, eventTime, this); 815 } 816 817 return true; 818 } 819 820 private static void writeUsabilityStudyLog(final MotionEvent me, final int action, 821 final long eventTime, final int index, final int id, final int x, final int y) { 822 final String eventTag; 823 switch (action) { 824 case MotionEvent.ACTION_UP: 825 eventTag = "[Up]"; 826 break; 827 case MotionEvent.ACTION_DOWN: 828 eventTag = "[Down]"; 829 break; 830 case MotionEvent.ACTION_POINTER_UP: 831 eventTag = "[PointerUp]"; 832 break; 833 case MotionEvent.ACTION_POINTER_DOWN: 834 eventTag = "[PointerDown]"; 835 break; 836 case MotionEvent.ACTION_MOVE: 837 eventTag = "[Move]"; 838 break; 839 default: 840 eventTag = "[Action" + action + "]"; 841 break; 842 } 843 final float size = me.getSize(index); 844 final float pressure = me.getPressure(index); 845 UsabilityStudyLogUtils.getInstance().write( 846 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); 847 } 848 849 @Override 850 public void closing() { 851 super.closing(); 852 onCancelMoreKeysPanel(); 853 mMoreKeysPanelCache.clear(); 854 } 855 856 @Override 857 public void onCancelMoreKeysPanel() { 858 super.onCancelMoreKeysPanel(); 859 PointerTracker.dismissAllMoreKeysPanels(); 860 } 861 862 @Override 863 public boolean onDismissMoreKeysPanel() { 864 dimEntireKeyboard(false /* dimmed */); 865 return super.onDismissMoreKeysPanel(); 866 } 867 868 /** 869 * Receives hover events from the input framework. 870 * 871 * @param event The motion event to be dispatched. 872 * @return {@code true} if the event was handled by the view, {@code false} 873 * otherwise 874 */ 875 @Override 876 public boolean dispatchHoverEvent(final MotionEvent event) { 877 if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { 878 final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); 879 return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); 880 } 881 882 // Reflection doesn't support calling superclass methods. 883 return false; 884 } 885 886 public void updateShortcutKey(final boolean available) { 887 final Keyboard keyboard = getKeyboard(); 888 if (keyboard == null) { 889 return; 890 } 891 final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); 892 if (shortcutKey == null) { 893 return; 894 } 895 shortcutKey.setEnabled(available); 896 invalidateKey(shortcutKey); 897 } 898 899 private void updateAltCodeKeyWhileTyping() { 900 final Keyboard keyboard = getKeyboard(); 901 if (keyboard == null) { 902 return; 903 } 904 for (final Key key : keyboard.mAltCodeKeysWhileTyping) { 905 invalidateKey(key); 906 } 907 } 908 909 public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, 910 final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) { 911 mNeedsToDisplayLanguage = needsToDisplayLanguage; 912 mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; 913 final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; 914 if (animator == null) { 915 mNeedsToDisplayLanguage = false; 916 } else { 917 if (subtypeChanged && needsToDisplayLanguage) { 918 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); 919 if (animator.isStarted()) { 920 animator.cancel(); 921 } 922 animator.start(); 923 } else { 924 if (!animator.isStarted()) { 925 mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; 926 } 927 } 928 } 929 invalidateKey(mSpaceKey); 930 } 931 932 public void updateAutoCorrectionState(final boolean isAutoCorrection) { 933 if (!mAutoCorrectionSpacebarLedEnabled) { 934 return; 935 } 936 mAutoCorrectionSpacebarLedOn = isAutoCorrection; 937 invalidateKey(mSpaceKey); 938 } 939 940 @Override 941 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 942 final KeyDrawParams params) { 943 if (key.altCodeWhileTyping() && key.isEnabled()) { 944 params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; 945 } 946 if (key.mCode == Constants.CODE_SPACE) { 947 drawSpacebar(key, canvas, paint); 948 // Whether space key needs to show the "..." popup hint for special purposes 949 if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { 950 drawKeyPopupHint(key, canvas, paint, params); 951 } 952 } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { 953 super.onDrawKeyTopVisuals(key, canvas, paint, params); 954 drawKeyPopupHint(key, canvas, paint, params); 955 } else { 956 super.onDrawKeyTopVisuals(key, canvas, paint, params); 957 } 958 } 959 960 private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { 961 paint.setTextScaleX(1.0f); 962 final float textWidth = getLabelWidth(text, paint); 963 if (textWidth < width) { 964 return true; 965 } 966 967 final float scaleX = width / textWidth; 968 if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { 969 return false; 970 } 971 972 paint.setTextScaleX(scaleX); 973 return getLabelWidth(text, paint) < width; 974 } 975 976 // Layout language name on spacebar. 977 private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, 978 final int width) { 979 // Choose appropriate language name to fit into the width. 980 final String fullText = getFullDisplayName(subtype, getResources()); 981 if (fitsTextIntoWidth(width, fullText, paint)) { 982 return fullText; 983 } 984 985 final String middleText = getMiddleDisplayName(subtype); 986 if (fitsTextIntoWidth(width, middleText, paint)) { 987 return middleText; 988 } 989 990 final String shortText = getShortDisplayName(subtype); 991 if (fitsTextIntoWidth(width, shortText, paint)) { 992 return shortText; 993 } 994 995 return ""; 996 } 997 998 private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) { 999 final int width = key.mWidth; 1000 final int height = key.mHeight; 1001 1002 // If input language are explicitly selected. 1003 if (mNeedsToDisplayLanguage) { 1004 paint.setTextAlign(Align.CENTER); 1005 paint.setTypeface(Typeface.DEFAULT); 1006 paint.setTextSize(mSpacebarTextSize); 1007 final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; 1008 final String language = layoutLanguageOnSpacebar(paint, subtype, width); 1009 // Draw language text with shadow 1010 final float descent = paint.descent(); 1011 final float textHeight = -paint.ascent() + descent; 1012 final float baseline = height / 2 + textHeight / 2; 1013 paint.setColor(mSpacebarTextShadowColor); 1014 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1015 canvas.drawText(language, width / 2, baseline - descent - 1, paint); 1016 paint.setColor(mSpacebarTextColor); 1017 paint.setAlpha(mLanguageOnSpacebarAnimAlpha); 1018 canvas.drawText(language, width / 2, baseline - descent, paint); 1019 } 1020 1021 // Draw the spacebar icon at the bottom 1022 if (mAutoCorrectionSpacebarLedOn) { 1023 final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; 1024 final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); 1025 int x = (width - iconWidth) / 2; 1026 int y = height - iconHeight; 1027 drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); 1028 } else if (mSpaceIcon != null) { 1029 final int iconWidth = mSpaceIcon.getIntrinsicWidth(); 1030 final int iconHeight = mSpaceIcon.getIntrinsicHeight(); 1031 int x = (width - iconWidth) / 2; 1032 int y = height - iconHeight; 1033 drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); 1034 } 1035 } 1036 1037 // InputMethodSubtype's display name for spacebar text in its locale. 1038 // isAdditionalSubtype (T=true, F=false) 1039 // locale layout | Short Middle Full 1040 // ------ ------- - ---- --------- ---------------------- 1041 // en_US qwerty F En English English (US) exception 1042 // en_GB qwerty F En English English (UK) exception 1043 // es_US spanish F Es Español Español (EE.UU.) exception 1044 // fr azerty F Fr Français Français 1045 // fr_CA qwerty F Fr Français Français (Canada) 1046 // de qwertz F De Deutsch Deutsch 1047 // zz qwerty F QWERTY QWERTY 1048 // fr qwertz T Fr Français Français (QWERTZ) 1049 // de qwerty T De Deutsch Deutsch (QWERTY) 1050 // en_US azerty T En English English (US) (AZERTY) 1051 // zz azerty T AZERTY AZERTY 1052 1053 // Get InputMethodSubtype's full display name in its locale. 1054 static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) { 1055 if (SubtypeLocale.isNoLanguage(subtype)) { 1056 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1057 } 1058 1059 return SubtypeLocale.getSubtypeDisplayName(subtype, res); 1060 } 1061 1062 // Get InputMethodSubtype's short display name in its locale. 1063 static String getShortDisplayName(final InputMethodSubtype subtype) { 1064 if (SubtypeLocale.isNoLanguage(subtype)) { 1065 return ""; 1066 } 1067 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1068 return StringUtils.toTitleCase(locale.getLanguage(), locale); 1069 } 1070 1071 // Get InputMethodSubtype's middle display name in its locale. 1072 static String getMiddleDisplayName(final InputMethodSubtype subtype) { 1073 if (SubtypeLocale.isNoLanguage(subtype)) { 1074 return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype); 1075 } 1076 final Locale locale = SubtypeLocale.getSubtypeLocale(subtype); 1077 return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); 1078 } 1079} 1080