/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.keyboard; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.InputMethodSubtype; import android.widget.TextView; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.annotations.ExternallyReferenced; import com.android.inputmethod.keyboard.internal.DrawingHandler; import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText; import com.android.inputmethod.keyboard.internal.GestureTrailsPreview; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; import com.android.inputmethod.keyboard.internal.TimerHandler; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.DebugSettings; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import com.android.inputmethod.latin.utils.TypefaceUtils; import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils; import com.android.inputmethod.latin.utils.ViewLayoutUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayDeque; import java.util.HashMap; import java.util.HashSet; import java.util.WeakHashMap; /** * A view that is responsible for detecting key presses and touch movements. * * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor * @attr ref R.styleable#MainKeyboardView_spacebarBackground * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration */ public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy, MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks { private static final String TAG = MainKeyboardView.class.getSimpleName(); /** Listener for {@link KeyboardActionListener}. */ private KeyboardActionListener mKeyboardActionListener; /* Space key and its icon and background. */ private Key mSpaceKey; private Drawable mSpacebarIcon; private final Drawable mSpacebarBackground; // Stuff to draw language name on spacebar. private final int mLanguageOnSpacebarFinalAlpha; private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; private boolean mNeedsToDisplayLanguage; private boolean mHasMultipleEnabledIMEsOrSubtypes; private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; private final float mLanguageOnSpacebarTextRatio; private float mLanguageOnSpacebarTextSize; private final int mLanguageOnSpacebarTextColor; private final int mLanguageOnSpacebarTextShadowColor; // The minimum x-scale to fit the language name on spacebar. private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f; // Stuff to draw auto correction LED on spacebar. private boolean mAutoCorrectionSpacebarLedOn; private final boolean mAutoCorrectionSpacebarLedEnabled; private final Drawable mAutoCorrectionSpacebarLedIcon; private static final int SPACE_LED_LENGTH_PERCENT = 80; // Stuff to draw altCodeWhileTyping keys. private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; // Preview placer view private final PreviewPlacerView mPreviewPlacerView; private final int[] mOriginCoords = CoordinateUtils.newInstance(); private final GestureFloatingPreviewText mGestureFloatingPreviewText; private final GestureTrailsPreview mGestureTrailsPreview; private final SlidingKeyInputPreview mSlidingKeyInputPreview; // Key preview private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false; private final int mKeyPreviewLayoutId; private final int mKeyPreviewOffset; private final int mKeyPreviewHeight; // Free {@link TextView} pool that can be used for key preview. private final ArrayDeque mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque(); // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview. private final HashMap mShowingKeyPreviewTextViews = CollectionUtils.newHashMap(); private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams(); private boolean mShowKeyPreviewPopup = true; private int mKeyPreviewLingerTimeout; private int mKeyPreviewZoomInDuration; private int mKeyPreviewZoomOutDuration; private static final float KEY_PREVIEW_START_ZOOM_IN_SCALE = 0.7f; private static final float KEY_PREVIEW_END_ZOOM_IN_SCALE = 1.0f; private static final float KEY_PREVIEW_END_ZOOM_OUT_SCALE = 0.7f; private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); // More keys keyboard private final Paint mBackgroundDimAlphaPaint = new Paint(); private boolean mNeedsToDimEntireKeyboard; private final View mMoreKeysKeyboardContainer; private final WeakHashMap mMoreKeysKeyboardCache = CollectionUtils.newWeakHashMap(); private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; // More keys panel (used by both more keys keyboard and more suggestions view) // TODO: Consider extending to support multiple more keys panels private MoreKeysPanel mMoreKeysPanel; // Gesture floating preview text // TODO: Make this parameter customizable by user via settings. private int mGestureFloatingPreviewTextLingerTimeout; private final KeyDetector mKeyDetector; private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper; private final TimerHandler mKeyTimerHandler; private final int mLanguageOnSpacebarHorizontalMargin; private final DrawingHandler mDrawingHandler = new DrawingHandler(this); public MainKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.mainKeyboardViewStyle); } public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); mPreviewPlacerView = new PreviewPlacerView(context, attrs); final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); mKeyTimerHandler = new TimerHandler( this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime); final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension( R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f); final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension( R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f); mKeyDetector = new KeyDetector( keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier); PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final boolean forceNonDistinctMultitouch = prefs.getBoolean( DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); final boolean hasDistinctMultitouch = context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT) && !forceNonDistinctMultitouch; mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null : new NonDistinctMultitouchHelper(); final int backgroundDimAlpha = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_backgroundDimAlpha, 0); mBackgroundDimAlphaPaint.setColor(Color.BLACK); mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha); mSpacebarBackground = mainKeyboardViewAttr.getDrawable( R.styleable.MainKeyboardView_spacebarBackground); mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean( R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable( R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction( R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f); mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor( R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0); mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor( R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0); mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, Constants.Color.ALPHA_OPAQUE); final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId( R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId( R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset( R.styleable.MainKeyboardView_keyPreviewOffset, 0); mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize( R.styleable.MainKeyboardView_keyPreviewHeight, 0); mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0); mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId( R.styleable.MainKeyboardView_keyPreviewLayout, 0); if (mKeyPreviewLayoutId == 0) { mShowKeyPreviewPopup = false; } mKeyPreviewZoomInDuration = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_keyPreviewZoomInDuration, 0); mKeyPreviewZoomOutDuration = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_keyPreviewZoomOutDuration, 0); final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId( R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0); mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean( R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0); mGestureFloatingPreviewText = new GestureFloatingPreviewText( mPreviewPlacerView, mainKeyboardViewAttr); mPreviewPlacerView.addPreview(mGestureFloatingPreviewText); mGestureTrailsPreview = new GestureTrailsPreview( mPreviewPlacerView, mainKeyboardViewAttr); mPreviewPlacerView.addPreview(mGestureTrailsPreview); mSlidingKeyInputPreview = new SlidingKeyInputPreview( mPreviewPlacerView, mainKeyboardViewAttr); mPreviewPlacerView.addPreview(mSlidingKeyInputPreview); mainKeyboardViewAttr.recycle(); mMoreKeysKeyboardContainer = LayoutInflater.from(getContext()) .inflate(moreKeysKeyboardLayoutId, null); mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( languageOnSpacebarFadeoutAnimatorResId, this); mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( altCodeKeyWhileTypingFadeoutAnimatorResId, this); mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator( altCodeKeyWhileTypingFadeinAnimatorResId, this); mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension( R.dimen.config_language_on_spacebar_horizontal_margin); } @Override public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { super.setHardwareAcceleratedDrawingEnabled(enabled); mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled); } private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { if (resId == 0) { // TODO: Stop returning null. return null; } final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( getContext(), resId); if (animator != null) { animator.setTarget(target); } return animator; } private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart) { if (animatorToCancel == null || animatorToStart == null) { // TODO: Stop using null as a no-operation animator. return; } float startFraction = 0.0f; if (animatorToCancel.isStarted()) { animatorToCancel.cancel(); startFraction = 1.0f - animatorToCancel.getAnimatedFraction(); } final long startTime = (long)(animatorToStart.getDuration() * startFraction); animatorToStart.start(); animatorToStart.setCurrentPlayTime(startTime); } // Implements {@link TimerHander.Callbacks} method. @Override public void startWhileTypingFadeinAnimation() { cancelAndStartAnimators( mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator); } @Override public void startWhileTypingFadeoutAnimation() { cancelAndStartAnimators( mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator); } @ExternallyReferenced public int getLanguageOnSpacebarAnimAlpha() { return mLanguageOnSpacebarAnimAlpha; } @ExternallyReferenced public void setLanguageOnSpacebarAnimAlpha(final int alpha) { mLanguageOnSpacebarAnimAlpha = alpha; invalidateKey(mSpaceKey); } @ExternallyReferenced public int getAltCodeKeyWhileTypingAnimAlpha() { return mAltCodeKeyWhileTypingAnimAlpha; } @ExternallyReferenced public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { if (mAltCodeKeyWhileTypingAnimAlpha == alpha) { return; } // Update the visual of alt-code-key-while-typing. mAltCodeKeyWhileTypingAnimAlpha = alpha; final Keyboard keyboard = getKeyboard(); if (keyboard == null) { return; } for (final Key key : keyboard.mAltCodeKeysWhileTyping) { invalidateKey(key); } } public void setKeyboardActionListener(final KeyboardActionListener listener) { mKeyboardActionListener = listener; PointerTracker.setKeyboardActionListener(listener); } // TODO: We should reconsider which coordinate system should be used to represent keyboard // event. public int getKeyX(final int x) { return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x; } // TODO: We should reconsider which coordinate system should be used to represent keyboard // event. public int getKeyY(final int y) { return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y; } /** * Attaches a keyboard to this view. The keyboard can be switched at any time and the * view will re-layout itself to accommodate the keyboard. * @see Keyboard * @see #getKeyboard() * @param keyboard the keyboard to display in this view */ @Override public void setKeyboard(final Keyboard keyboard) { // Remove any pending messages. mKeyTimerHandler.cancelAllKeyTimers(); super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); PointerTracker.setKeyDetector(mKeyDetector); mMoreKeysKeyboardCache.clear(); mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); mSpacebarIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio; if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { final int orientation = getContext().getResources().getConfiguration().orientation; ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation); } // This always needs to be set since the accessibility state can // potentially change without the keyboard being set again. AccessibleKeyboardViewProxy.getInstance().setKeyboard(); } /** * Enables or disables the key feedback popup. This is a popup that shows a magnified * version of the depressed key. By default the preview is enabled. * @param previewEnabled whether or not to enable the key feedback preview * @param delay the delay after which the preview is dismissed * @see #isKeyPreviewPopupEnabled() */ public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { mShowKeyPreviewPopup = previewEnabled; mKeyPreviewLingerTimeout = delay; } private void locatePreviewPlacerView() { if (mPreviewPlacerView.getParent() != null) { return; } final int width = getWidth(); final int height = getHeight(); if (width == 0 || height == 0) { // In transient state. return; } getLocationInWindow(mOriginCoords); final DisplayMetrics dm = getResources().getDisplayMetrics(); if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) { // In transient state. return; } final View rootView = getRootView(); if (rootView == null) { Log.w(TAG, "Cannot find root view"); return; } final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content); // Note: It'd be very weird if we get null by android.R.id.content. if (windowContentView == null) { Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView"); } else { windowContentView.addView(mPreviewPlacerView); mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height); } } /** * Returns the enabled state of the key feedback preview * @return whether or not the key feedback preview is enabled * @see #setKeyPreviewPopupEnabled(boolean, int) */ public boolean isKeyPreviewPopupEnabled() { return mShowKeyPreviewPopup; } private TextView getKeyPreviewTextView(final Key key) { TextView previewTextView = mShowingKeyPreviewTextViews.remove(key); if (previewTextView != null) { return previewTextView; } previewTextView = mFreeKeyPreviewTextViews.poll(); if (previewTextView != null) { return previewTextView; } final Context context = getContext(); if (mKeyPreviewLayoutId != 0) { previewTextView = (TextView)LayoutInflater.from(context) .inflate(mKeyPreviewLayoutId, null); } else { previewTextView = new TextView(context); } locatePreviewPlacerView(); mPreviewPlacerView.addView( previewTextView, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0)); return previewTextView; } // Implements {@link DrawingHandler.Callbacks} method. @Override public void dismissAllKeyPreviews() { for (final Key key : new HashSet(mShowingKeyPreviewTextViews.keySet())) { dismissKeyPreviewWithoutDelay(key); } PointerTracker.setReleasedKeyGraphicsToAllKeys(); } // Background state set private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = { { // STATE_MIDDLE EMPTY_STATE_SET, { R.attr.state_has_morekeys } }, { // STATE_LEFT { R.attr.state_left_edge }, { R.attr.state_left_edge, R.attr.state_has_morekeys } }, { // STATE_RIGHT { R.attr.state_right_edge }, { R.attr.state_right_edge, R.attr.state_has_morekeys } } }; private static final int STATE_MIDDLE = 0; private static final int STATE_LEFT = 1; private static final int STATE_RIGHT = 2; private static final int STATE_NORMAL = 0; private static final int STATE_HAS_MOREKEYS = 1; // TODO: Take this method out of this class. @Override public void showKeyPreview(final Key key) { // If key is invalid or IME is already closed, we must not show key preview. // Trying to show key preview while root window is closed causes // WindowManager.BadTokenException. if (key == null) { return; } final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; final Keyboard keyboard = getKeyboard(); if (!mShowKeyPreviewPopup) { previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap; return; } final TextView previewTextView = getKeyPreviewTextView(key); final KeyDrawParams drawParams = mKeyDrawParams; previewTextView.setTextColor(drawParams.mPreviewTextColor); final Drawable background = previewTextView.getBackground(); final String label = key.getPreviewLabel(); // What we show as preview should match what we show on a key top in onDraw(). if (label != null) { // TODO Should take care of temporaryShiftLabel here. previewTextView.setCompoundDrawables(null, null, null, null); previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams)); previewTextView.setTypeface(key.selectPreviewTypeface(drawParams)); previewTextView.setText(label); } else { previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(keyboard.mIconsSet)); previewTextView.setText(null); } previewTextView.measure( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); final int keyDrawWidth = key.getDrawWidth(); final int previewWidth = previewTextView.getMeasuredWidth(); final int previewHeight = mKeyPreviewHeight; // The width and height of visible part of the key preview background. The content marker // of the background 9-patch have to cover the visible part of the background. previewParams.mPreviewVisibleWidth = previewWidth - previewTextView.getPaddingLeft() - previewTextView.getPaddingRight(); previewParams.mPreviewVisibleHeight = previewHeight - previewTextView.getPaddingTop() - previewTextView.getPaddingBottom(); // The distance between the top edge of the parent key and the bottom of the visible part // of the key preview background. previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewTextView.getPaddingBottom(); getLocationInWindow(mOriginCoords); // The key preview is horizontally aligned with the center of the visible part of the // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and // the left/right background is used if such background is specified. final int statePosition; int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + CoordinateUtils.x(mOriginCoords); if (previewX < 0) { previewX = 0; statePosition = STATE_LEFT; } else if (previewX > getWidth() - previewWidth) { previewX = getWidth() - previewWidth; statePosition = STATE_RIGHT; } else { statePosition = STATE_MIDDLE; } // The key preview is placed vertically above the top edge of the parent key with an // arbitrary offset. final int previewY = key.getY() - previewHeight + mKeyPreviewOffset + CoordinateUtils.y(mOriginCoords); if (background != null) { final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL; background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]); } ViewLayoutUtils.placeViewAt( previewTextView, previewX, previewY, previewWidth, previewHeight); if (!isHardwareAccelerated()) { previewTextView.setVisibility(VISIBLE); mShowingKeyPreviewTextViews.put(key, previewTextView); return; } previewTextView.setPivotX(previewWidth / 2.0f); previewTextView.setPivotY(previewHeight); final Animator zoomIn = createZoomInAniation(key, previewTextView); final Animator zoomOut = createZoomOutAnimation(key, previewTextView); final KeyPreviewAnimations animation = new KeyPreviewAnimations(zoomIn, zoomOut); previewTextView.setTag(animation); animation.startZoomIn(); } // TODO: Move this internal class out to a separate external class. private static class KeyPreviewAnimations extends AnimatorListenerAdapter { private final Animator mZoomIn; private final Animator mZoomOut; public KeyPreviewAnimations(final Animator zoomIn, final Animator zoomOut) { mZoomIn = zoomIn; mZoomOut = zoomOut; } public void startZoomIn() { mZoomIn.start(); } public void startZoomOut() { if (mZoomIn.isRunning()) { mZoomIn.addListener(this); return; } mZoomOut.start(); } @Override public void onAnimationEnd(final Animator animation) { mZoomOut.start(); } } // TODO: Take this method out of this class. private Animator createZoomInAniation(final Key key, final TextView previewTextView) { final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat( previewTextView, SCALE_X, KEY_PREVIEW_START_ZOOM_IN_SCALE, KEY_PREVIEW_END_ZOOM_IN_SCALE); final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat( previewTextView, SCALE_Y, KEY_PREVIEW_START_ZOOM_IN_SCALE, KEY_PREVIEW_END_ZOOM_IN_SCALE); final AnimatorSet zoomInAnimation = new AnimatorSet(); zoomInAnimation.play(scaleXAnimation).with(scaleYAnimation); // TODO: Implement preference option to control key preview animation duration. zoomInAnimation.setDuration(mKeyPreviewZoomInDuration); zoomInAnimation.setInterpolator(DECELERATE_INTERPOLATOR); zoomInAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(final Animator animation) { previewTextView.setVisibility(VISIBLE); mShowingKeyPreviewTextViews.put(key, previewTextView); } }); return zoomInAnimation; } // TODO: Take this method out of this class. private Animator createZoomOutAnimation(final Key key, final TextView previewTextView) { final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat( previewTextView, SCALE_X, KEY_PREVIEW_END_ZOOM_OUT_SCALE); final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat( previewTextView, SCALE_Y, KEY_PREVIEW_END_ZOOM_OUT_SCALE); final AnimatorSet zoomOutAnimation = new AnimatorSet(); zoomOutAnimation.play(scaleXAnimation).with(scaleYAnimation); // TODO: Implement preference option to control key preview animation duration. zoomOutAnimation.setDuration(mKeyPreviewZoomOutDuration); zoomOutAnimation.setInterpolator(ACCELERATE_INTERPOLATOR); zoomOutAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(final Animator animation) { dismissKeyPreviewWithoutDelay(key); } }); return zoomOutAnimation; } // Implements {@link TimerHandler.Callbacks} method. // TODO: Take this method out of this class. @Override public void dismissKeyPreviewWithoutDelay(final Key key) { if (key == null) { return; } final TextView previewTextView = mShowingKeyPreviewTextViews.remove(key); if (previewTextView != null) { final Object tag = previewTextView.getTag(); if (tag instanceof Animator) { ((Animator)tag).cancel(); } previewTextView.setTag(null); previewTextView.setVisibility(INVISIBLE); mFreeKeyPreviewTextViews.add(previewTextView); } // To redraw key top letter. invalidateKey(key); } // TODO: Take this method out of this class. @Override public void dismissKeyPreview(final Key key) { final TextView previewTextView = mShowingKeyPreviewTextViews.get(key); if (previewTextView == null) { return; } if (!isHardwareAccelerated()) { // TODO: Implement preference option to control key preview method and duration. mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, key); return; } final Object tag = previewTextView.getTag(); if (tag instanceof KeyPreviewAnimations) { final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag; animation.startZoomOut(); } } public void setSlidingKeyInputPreviewEnabled(final boolean enabled) { mSlidingKeyInputPreview.setPreviewEnabled(enabled); } @Override public void showSlidingKeyInputPreview(final PointerTracker tracker) { locatePreviewPlacerView(); mSlidingKeyInputPreview.setPreviewPosition(tracker); } @Override public void dismissSlidingKeyInputPreview() { mSlidingKeyInputPreview.dismissSlidingKeyInputPreview(); } private void setGesturePreviewMode(final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled) { mGestureFloatingPreviewText.setPreviewEnabled(isGestureFloatingPreviewTextEnabled); mGestureTrailsPreview.setPreviewEnabled(isGestureTrailEnabled); } // Implements {@link DrawingHandler.Callbacks} method. @Override public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) { locatePreviewPlacerView(); mGestureFloatingPreviewText.setSuggetedWords(suggestedWords); } public void dismissGestureFloatingPreviewText() { locatePreviewPlacerView(); mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout); } @Override public void showGestureTrail(final PointerTracker tracker, final boolean showsFloatingPreviewText) { locatePreviewPlacerView(); if (showsFloatingPreviewText) { mGestureFloatingPreviewText.setPreviewPosition(tracker); } mGestureTrailsPreview.setPreviewPosition(tracker); } // Note that this method is called from a non-UI thread. public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable); } public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled) { PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser); setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled, isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // Notify the ResearchLogger (development only diagnostics) that the keyboard view has // been attached. This is needed to properly show the splash screen, which requires that // the window token of the KeyboardView be non-null. if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mPreviewPlacerView.removeAllViews(); // Notify the ResearchLogger (development only diagnostics) that the keyboard view has // been detached. This is needed to invalidate the reference of {@link MainKeyboardView} // to null. if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); } } private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { if (key.getMoreKeys() == null) { return null; } Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key); if (moreKeysKeyboard == null) { moreKeysKeyboard = new MoreKeysKeyboard.Builder( context, key, this, mKeyPreviewDrawParams).build(); mMoreKeysKeyboardCache.put(key, moreKeysKeyboard); } final View container = mMoreKeysKeyboardContainer; final MoreKeysKeyboardView moreKeysKeyboardView = (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); return moreKeysKeyboardView; } // Implements {@link TimerHandler.Callbacks} method. /** * Called when a key is long pressed. * @param tracker the pointer tracker which pressed the parent key */ @Override public void onLongPress(final PointerTracker tracker) { if (isShowingMoreKeysPanel()) { return; } final Key key = tracker.getKey(); if (key == null) { return; } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.mainKeyboardView_onLongPress(); } final KeyboardActionListener listener = mKeyboardActionListener; if (key.hasNoPanelAutoMoreKey()) { final int moreKeyCode = key.getMoreKeys()[0].mCode; tracker.onLongPressed(); listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */); listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); listener.onReleaseKey(moreKeyCode, false /* withSliding */); return; } final int code = key.getCode(); if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { // Long pressing the space key invokes IME switcher dialog. if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { tracker.onLongPressed(); listener.onReleaseKey(code, false /* withSliding */); return; } } openMoreKeysPanel(key, tracker); } private void openMoreKeysPanel(final Key key, final PointerTracker tracker) { final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); if (moreKeysPanel == null) { return; } final int[] lastCoords = CoordinateUtils.newInstance(); tracker.getLastCoordinates(lastCoords); final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview(); // The more keys keyboard is usually horizontally aligned with the center of the parent key. // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more // keys keyboard is placed at the touch point of the parent key. final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) ? CoordinateUtils.x(lastCoords) : key.getX() + key.getWidth() / 2; // The more keys keyboard is usually vertically aligned with the top edge of the parent key // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically // aligned with the bottom edge of the visible part of the key preview. // {@code mPreviewVisibleOffset} has been set appropriately in // {@link KeyboardView#showKeyPreview(PointerTracker)}. final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset; moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); tracker.onShowMoreKeysPanel(moreKeysPanel); // TODO: Implement zoom in animation of more keys panel. dismissKeyPreviewWithoutDelay(key); } public boolean isInDraggingFinger() { if (isShowingMoreKeysPanel()) { return true; } return PointerTracker.isAnyInDraggingFinger(); } @Override public void onShowMoreKeysPanel(final MoreKeysPanel panel) { locatePreviewPlacerView(); panel.showInParent(mPreviewPlacerView); mMoreKeysPanel = panel; dimEntireKeyboard(true /* dimmed */); } public boolean isShowingMoreKeysPanel() { return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent(); } @Override public void onCancelMoreKeysPanel(final MoreKeysPanel panel) { PointerTracker.dismissAllMoreKeysPanels(); } @Override public void onDismissMoreKeysPanel(final MoreKeysPanel panel) { dimEntireKeyboard(false /* dimmed */); if (isShowingMoreKeysPanel()) { mMoreKeysPanel.removeFromParent(); mMoreKeysPanel = null; } } public void startDoubleTapShiftKeyTimer() { mKeyTimerHandler.startDoubleTapShiftKeyTimer(); } public void cancelDoubleTapShiftKeyTimer() { mKeyTimerHandler.cancelDoubleTapShiftKeyTimer(); } public boolean isInDoubleTapShiftKeyTimeout() { return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event); } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(final MotionEvent me) { if (getKeyboard() == null) { return false; } if (mNonDistinctMultitouchHelper != null) { if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) { // Key repeating timer will be canceled if 2 or more keys are in action. mKeyTimerHandler.cancelKeyRepeatTimers(); } // Non distinct multitouch screen support mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector); return true; } return processMotionEvent(me); } public boolean processMotionEvent(final MotionEvent me) { if (LatinImeLogger.sUsabilityStudy) { UsabilityStudyLogUtils.writeMotionEvent(me); } // Currently the same "move" event is being logged twice. if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.mainKeyboardView_processMotionEvent(me); } final int index = me.getActionIndex(); final int id = me.getPointerId(index); final PointerTracker tracker = PointerTracker.getPointerTracker(id); // When a more keys panel is showing, we should ignore other fingers' single touch events // other than the finger that is showing the more keys panel. if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel() && PointerTracker.getActivePointerTrackerCount() == 1) { return true; } tracker.processMotionEvent(me, mKeyDetector); return true; } public void cancelAllOngoingEvents() { mKeyTimerHandler.cancelAllMessages(); mDrawingHandler.cancelAllMessages(); dismissAllKeyPreviews(); dismissGestureFloatingPreviewText(); dismissSlidingKeyInputPreview(); PointerTracker.dismissAllMoreKeysPanels(); PointerTracker.cancelAllPointerTrackers(); } public void closing() { cancelAllOngoingEvents(); mMoreKeysKeyboardCache.clear(); } /** * Receives hover events from the input framework. * * @param event The motion event to be dispatched. * @return {@code true} if the event was handled by the view, {@code false} * otherwise */ @Override public boolean dispatchHoverEvent(final MotionEvent event) { if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent( event, mKeyDetector); } // Reflection doesn't support calling superclass methods. return false; } public void updateShortcutKey(final boolean available) { final Keyboard keyboard = getKeyboard(); if (keyboard == null) { return; } final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); if (shortcutKey == null) { return; } shortcutKey.setEnabled(available); invalidateKey(shortcutKey); } public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) { mNeedsToDisplayLanguage = needsToDisplayLanguage; mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes; final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator; if (animator == null) { mNeedsToDisplayLanguage = false; } else { if (subtypeChanged && needsToDisplayLanguage) { setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); if (animator.isStarted()) { animator.cancel(); } animator.start(); } else { if (!animator.isStarted()) { mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha; } } } invalidateKey(mSpaceKey); } public void updateAutoCorrectionState(final boolean isAutoCorrection) { if (!mAutoCorrectionSpacebarLedEnabled) { return; } mAutoCorrectionSpacebarLedOn = isAutoCorrection; invalidateKey(mSpaceKey); } private void dimEntireKeyboard(final boolean dimmed) { final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; mNeedsToDimEntireKeyboard = dimmed; if (needsRedrawing) { invalidateAllKeys(); } } @Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); // Overlay a dark rectangle to dim. if (mNeedsToDimEntireKeyboard) { canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint); } } // Draw key background. @Override protected void onDrawKeyBackground(final Key key, final Canvas canvas, final Drawable background) { if (key.getCode() == Constants.CODE_SPACE) { super.onDrawKeyBackground(key, canvas, mSpacebarBackground); return; } super.onDrawKeyBackground(key, canvas, background); } @Override protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params) { if (key.altCodeWhileTyping() && key.isEnabled()) { params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; } // Don't draw key top letter when key preview is showing. if (FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED && mShowingKeyPreviewTextViews.containsKey(key)) { // TODO: Fade out animation for the key top letter, and fade in animation for the key // background color when the user presses the key. return; } final int code = key.getCode(); if (code == Constants.CODE_SPACE) { drawSpacebar(key, canvas, paint); // Whether space key needs to show the "..." popup hint for special purposes if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { drawKeyPopupHint(key, canvas, paint, params); } } else if (code == Constants.CODE_LANGUAGE_SWITCH) { super.onDrawKeyTopVisuals(key, canvas, paint, params); drawKeyPopupHint(key, canvas, paint, params); } else { super.onDrawKeyTopVisuals(key, canvas, paint, params); } } private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2; paint.setTextScaleX(1.0f); final float textWidth = TypefaceUtils.getStringWidth(text, paint); if (textWidth < width) { return true; } final float scaleX = maxTextWidth / textWidth; if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { return false; } paint.setTextScaleX(scaleX); return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth; } // Layout language name on spacebar. private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width) { // Choose appropriate language name to fit into the width. final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype); if (fitsTextIntoWidth(width, fullText, paint)) { return fullText; } final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype); if (fitsTextIntoWidth(width, middleText, paint)) { return middleText; } final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype); if (fitsTextIntoWidth(width, shortText, paint)) { return shortText; } return ""; } private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) { final int width = key.getWidth(); final int height = key.getHeight(); // If input language are explicitly selected. if (mNeedsToDisplayLanguage) { paint.setTextAlign(Align.CENTER); paint.setTypeface(Typeface.DEFAULT); paint.setTextSize(mLanguageOnSpacebarTextSize); final InputMethodSubtype subtype = getKeyboard().mId.mSubtype; final String language = layoutLanguageOnSpacebar(paint, subtype, width); // Draw language text with shadow final float descent = paint.descent(); final float textHeight = -paint.ascent() + descent; final float baseline = height / 2 + textHeight / 2; paint.setColor(mLanguageOnSpacebarTextShadowColor); paint.setAlpha(mLanguageOnSpacebarAnimAlpha); canvas.drawText(language, width / 2, baseline - descent - 1, paint); paint.setColor(mLanguageOnSpacebarTextColor); paint.setAlpha(mLanguageOnSpacebarAnimAlpha); canvas.drawText(language, width / 2, baseline - descent, paint); } // Draw the spacebar icon at the bottom if (mAutoCorrectionSpacebarLedOn) { final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); int x = (width - iconWidth) / 2; int y = height - iconHeight; drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); } else if (mSpacebarIcon != null) { final int iconWidth = mSpacebarIcon.getIntrinsicWidth(); final int iconHeight = mSpacebarIcon.getIntrinsicHeight(); int x = (width - iconWidth) / 2; int y = height - iconHeight; drawIcon(canvas, mSpacebarIcon, x, y, iconWidth, iconHeight); } } @Override public void deallocateMemory() { super.deallocateMemory(); mGestureTrailsPreview.deallocateMemory(); } }