Home | History | Annotate | Download | only in keyboard
      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 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.animation.AnimatorInflater;
     20 import android.animation.ObjectAnimator;
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.content.pm.PackageManager;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Canvas;
     26 import android.graphics.Color;
     27 import android.graphics.Paint;
     28 import android.graphics.Paint.Align;
     29 import android.graphics.Typeface;
     30 import android.preference.PreferenceManager;
     31 import android.util.AttributeSet;
     32 import android.util.Log;
     33 import android.view.LayoutInflater;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.inputmethod.InputMethodSubtype;
     38 
     39 import com.android.inputmethod.accessibility.AccessibilityUtils;
     40 import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
     41 import com.android.inputmethod.annotations.ExternallyReferenced;
     42 import com.android.inputmethod.keyboard.internal.DrawingHandler;
     43 import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
     44 import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview;
     45 import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview;
     46 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
     47 import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
     48 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
     49 import com.android.inputmethod.keyboard.internal.KeyPreviewView;
     50 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
     51 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
     52 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
     53 import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
     54 import com.android.inputmethod.keyboard.internal.TimerHandler;
     55 import com.android.inputmethod.latin.Constants;
     56 import com.android.inputmethod.latin.R;
     57 import com.android.inputmethod.latin.SuggestedWords;
     58 import com.android.inputmethod.latin.settings.DebugSettings;
     59 import com.android.inputmethod.latin.utils.CoordinateUtils;
     60 import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
     61 import com.android.inputmethod.latin.utils.TypefaceUtils;
     62 
     63 import java.util.WeakHashMap;
     64 
     65 /**
     66  * A view that is responsible for detecting key presses and touch movements.
     67  *
     68  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
     69  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
     70  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius
     71  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
     72  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
     73  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
     74  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
     75  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
     76  * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
     77  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
     78  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
     79  * @attr ref R.styleable#MainKeyboardView_keySelectionByDraggingFinger
     80  * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
     81  * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
     82  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
     83  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
     84  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
     85  * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
     86  * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
     87  * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
     88  * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
     89  * @attr ref R.styleable#MainKeyboardView_keyPreviewShowUpAnimator
     90  * @attr ref R.styleable#MainKeyboardView_keyPreviewDismissAnimator
     91  * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
     92  * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardForActionLayout
     93  * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
     94  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
     95  * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
     96  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
     97  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
     98  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
     99  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
    100  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
    101  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
    102  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
    103  * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
    104  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
    105  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
    106  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
    107  */
    108 public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy,
    109         MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks {
    110     private static final String TAG = MainKeyboardView.class.getSimpleName();
    111 
    112     /** Listener for {@link KeyboardActionListener}. */
    113     private KeyboardActionListener mKeyboardActionListener;
    114 
    115     /* Space key and its icon and background. */
    116     private Key mSpaceKey;
    117     // Stuff to draw language name on spacebar.
    118     private final int mLanguageOnSpacebarFinalAlpha;
    119     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
    120     private int mLanguageOnSpacebarFormatType;
    121     private boolean mHasMultipleEnabledIMEsOrSubtypes;
    122     private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
    123     private final float mLanguageOnSpacebarTextRatio;
    124     private float mLanguageOnSpacebarTextSize;
    125     private final int mLanguageOnSpacebarTextColor;
    126     private final float mLanguageOnSpacebarTextShadowRadius;
    127     private final int mLanguageOnSpacebarTextShadowColor;
    128     private static final float LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
    129     // The minimum x-scale to fit the language name on spacebar.
    130     private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
    131 
    132     // Stuff to draw altCodeWhileTyping keys.
    133     private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
    134     private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
    135     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
    136 
    137     // Drawing preview placer view
    138     private final DrawingPreviewPlacerView mDrawingPreviewPlacerView;
    139     private final int[] mOriginCoords = CoordinateUtils.newInstance();
    140     private final GestureFloatingTextDrawingPreview mGestureFloatingTextDrawingPreview;
    141     private final GestureTrailsDrawingPreview mGestureTrailsDrawingPreview;
    142     private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview;
    143 
    144     // Key preview
    145     private final KeyPreviewDrawParams mKeyPreviewDrawParams;
    146     private final KeyPreviewChoreographer mKeyPreviewChoreographer;
    147 
    148     // More keys keyboard
    149     private final Paint mBackgroundDimAlphaPaint = new Paint();
    150     private boolean mNeedsToDimEntireKeyboard;
    151     private final View mMoreKeysKeyboardContainer;
    152     private final View mMoreKeysKeyboardForActionContainer;
    153     private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>();
    154     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
    155     // More keys panel (used by both more keys keyboard and more suggestions view)
    156     // TODO: Consider extending to support multiple more keys panels
    157     private MoreKeysPanel mMoreKeysPanel;
    158 
    159     // Gesture floating preview text
    160     // TODO: Make this parameter customizable by user via settings.
    161     private int mGestureFloatingPreviewTextLingerTimeout;
    162 
    163     private final KeyDetector mKeyDetector;
    164     private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
    165 
    166     private final TimerHandler mKeyTimerHandler;
    167     private final int mLanguageOnSpacebarHorizontalMargin;
    168 
    169     private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
    170 
    171     private MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
    172 
    173     public MainKeyboardView(final Context context, final AttributeSet attrs) {
    174         this(context, attrs, R.attr.mainKeyboardViewStyle);
    175     }
    176 
    177     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
    178         super(context, attrs, defStyle);
    179 
    180         mDrawingPreviewPlacerView = new DrawingPreviewPlacerView(context, attrs);
    181 
    182         final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
    183                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
    184         final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
    185                 R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
    186         final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
    187                 R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
    188         mKeyTimerHandler = new TimerHandler(
    189                 this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime);
    190 
    191         final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
    192                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
    193         final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
    194                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
    195         mKeyDetector = new KeyDetector(
    196                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
    197 
    198         PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */);
    199 
    200         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    201         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
    202                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
    203         final boolean hasDistinctMultitouch = context.getPackageManager()
    204                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
    205                 && !forceNonDistinctMultitouch;
    206         mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
    207                 : new NonDistinctMultitouchHelper();
    208 
    209         final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
    210                 R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
    211         mBackgroundDimAlphaPaint.setColor(Color.BLACK);
    212         mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
    213         mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
    214                 R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
    215         mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
    216                 R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
    217         mLanguageOnSpacebarTextShadowRadius = mainKeyboardViewAttr.getFloat(
    218                 R.styleable.MainKeyboardView_languageOnSpacebarTextShadowRadius,
    219                 LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED);
    220         mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
    221                 R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
    222         mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
    223                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
    224                 Constants.Color.ALPHA_OPAQUE);
    225         final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
    226                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
    227         final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
    228                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
    229         final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
    230                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
    231 
    232         mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr);
    233         mKeyPreviewChoreographer = new KeyPreviewChoreographer(mKeyPreviewDrawParams);
    234 
    235         final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
    236                 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
    237         final int moreKeysKeyboardForActionLayoutId = mainKeyboardViewAttr.getResourceId(
    238                 R.styleable.MainKeyboardView_moreKeysKeyboardForActionLayout,
    239                 moreKeysKeyboardLayoutId);
    240         mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
    241                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
    242 
    243         mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
    244                 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
    245 
    246         mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
    247                 mainKeyboardViewAttr);
    248         mGestureFloatingTextDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
    249 
    250         mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(mainKeyboardViewAttr);
    251         mGestureTrailsDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
    252 
    253         mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(mainKeyboardViewAttr);
    254         mSlidingKeyInputDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
    255         mainKeyboardViewAttr.recycle();
    256 
    257         final LayoutInflater inflater = LayoutInflater.from(getContext());
    258         mMoreKeysKeyboardContainer = inflater.inflate(moreKeysKeyboardLayoutId, null);
    259         mMoreKeysKeyboardForActionContainer = inflater.inflate(
    260                 moreKeysKeyboardForActionLayoutId, null);
    261         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
    262                 languageOnSpacebarFadeoutAnimatorResId, this);
    263         mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
    264                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
    265         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
    266                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
    267 
    268         mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
    269 
    270         mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
    271                 R.dimen.config_language_on_spacebar_horizontal_margin);
    272     }
    273 
    274     @Override
    275     public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
    276         super.setHardwareAcceleratedDrawingEnabled(enabled);
    277         mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
    278     }
    279 
    280     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
    281         if (resId == 0) {
    282             // TODO: Stop returning null.
    283             return null;
    284         }
    285         final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
    286                 getContext(), resId);
    287         if (animator != null) {
    288             animator.setTarget(target);
    289         }
    290         return animator;
    291     }
    292 
    293     private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
    294             final ObjectAnimator animatorToStart) {
    295         if (animatorToCancel == null || animatorToStart == null) {
    296             // TODO: Stop using null as a no-operation animator.
    297             return;
    298         }
    299         float startFraction = 0.0f;
    300         if (animatorToCancel.isStarted()) {
    301             animatorToCancel.cancel();
    302             startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
    303         }
    304         final long startTime = (long)(animatorToStart.getDuration() * startFraction);
    305         animatorToStart.start();
    306         animatorToStart.setCurrentPlayTime(startTime);
    307     }
    308 
    309     // Implements {@link TimerHander.Callbacks} method.
    310     @Override
    311     public void startWhileTypingFadeinAnimation() {
    312         cancelAndStartAnimators(
    313                 mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
    314     }
    315 
    316     @Override
    317     public void startWhileTypingFadeoutAnimation() {
    318         cancelAndStartAnimators(
    319                 mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
    320     }
    321 
    322     @ExternallyReferenced
    323     public int getLanguageOnSpacebarAnimAlpha() {
    324         return mLanguageOnSpacebarAnimAlpha;
    325     }
    326 
    327     @ExternallyReferenced
    328     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
    329         mLanguageOnSpacebarAnimAlpha = alpha;
    330         invalidateKey(mSpaceKey);
    331     }
    332 
    333     @ExternallyReferenced
    334     public int getAltCodeKeyWhileTypingAnimAlpha() {
    335         return mAltCodeKeyWhileTypingAnimAlpha;
    336     }
    337 
    338     @ExternallyReferenced
    339     public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
    340         if (mAltCodeKeyWhileTypingAnimAlpha == alpha) {
    341             return;
    342         }
    343         // Update the visual of alt-code-key-while-typing.
    344         mAltCodeKeyWhileTypingAnimAlpha = alpha;
    345         final Keyboard keyboard = getKeyboard();
    346         if (keyboard == null) {
    347             return;
    348         }
    349         for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
    350             invalidateKey(key);
    351         }
    352     }
    353 
    354     public void setKeyboardActionListener(final KeyboardActionListener listener) {
    355         mKeyboardActionListener = listener;
    356         PointerTracker.setKeyboardActionListener(listener);
    357     }
    358 
    359     // TODO: We should reconsider which coordinate system should be used to represent keyboard
    360     // event.
    361     public int getKeyX(final int x) {
    362         return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x;
    363     }
    364 
    365     // TODO: We should reconsider which coordinate system should be used to represent keyboard
    366     // event.
    367     public int getKeyY(final int y) {
    368         return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y;
    369     }
    370 
    371     /**
    372      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
    373      * view will re-layout itself to accommodate the keyboard.
    374      * @see Keyboard
    375      * @see #getKeyboard()
    376      * @param keyboard the keyboard to display in this view
    377      */
    378     @Override
    379     public void setKeyboard(final Keyboard keyboard) {
    380         // Remove any pending messages, except dismissing preview and key repeat.
    381         mKeyTimerHandler.cancelLongPressTimers();
    382         super.setKeyboard(keyboard);
    383         mKeyDetector.setKeyboard(
    384                 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
    385         PointerTracker.setKeyDetector(mKeyDetector);
    386         mMoreKeysKeyboardCache.clear();
    387 
    388         mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
    389         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
    390         mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
    391 
    392         if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
    393             if (mAccessibilityDelegate == null) {
    394                 mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector);
    395             }
    396             mAccessibilityDelegate.setKeyboard(keyboard);
    397         } else {
    398             mAccessibilityDelegate = null;
    399         }
    400     }
    401 
    402     /**
    403      * Enables or disables the key preview popup. This is a popup that shows a magnified
    404      * version of the depressed key. By default the preview is enabled.
    405      * @param previewEnabled whether or not to enable the key feedback preview
    406      * @param delay the delay after which the preview is dismissed
    407      */
    408     public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
    409         mKeyPreviewDrawParams.setPopupEnabled(previewEnabled, delay);
    410     }
    411 
    412     /**
    413      * Enables or disables the key preview popup animations and set animations' parameters.
    414      *
    415      * @param hasCustomAnimationParams false to use the default key preview popup animations
    416      *   specified by keyPreviewShowUpAnimator and keyPreviewDismissAnimator attributes.
    417      *   true to override the default animations with the specified parameters.
    418      * @param showUpStartXScale from this x-scale the show up animation will start.
    419      * @param showUpStartYScale from this y-scale the show up animation will start.
    420      * @param showUpDuration the duration of the show up animation in milliseconds.
    421      * @param dismissEndXScale to this x-scale the dismiss animation will end.
    422      * @param dismissEndYScale to this y-scale the dismiss animation will end.
    423      * @param dismissDuration the duration of the dismiss animation in milliseconds.
    424      */
    425     public void setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams,
    426             final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration,
    427             final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration) {
    428         mKeyPreviewDrawParams.setAnimationParams(hasCustomAnimationParams,
    429                 showUpStartXScale, showUpStartYScale, showUpDuration,
    430                 dismissEndXScale, dismissEndYScale, dismissDuration);
    431     }
    432 
    433     private void locatePreviewPlacerView() {
    434         getLocationInWindow(mOriginCoords);
    435         mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight());
    436     }
    437 
    438     private void installPreviewPlacerView() {
    439         final View rootView = getRootView();
    440         if (rootView == null) {
    441             Log.w(TAG, "Cannot find root view");
    442             return;
    443         }
    444         final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
    445         // Note: It'd be very weird if we get null by android.R.id.content.
    446         if (windowContentView == null) {
    447             Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
    448             return;
    449         }
    450         windowContentView.addView(mDrawingPreviewPlacerView);
    451     }
    452 
    453     // Implements {@link DrawingHandler.Callbacks} method.
    454     @Override
    455     public void dismissAllKeyPreviews() {
    456         mKeyPreviewChoreographer.dismissAllKeyPreviews();
    457         PointerTracker.setReleasedKeyGraphicsToAllKeys();
    458     }
    459 
    460     @Override
    461     public void showKeyPreview(final Key key) {
    462         // If the key is invalid or has no key preview, we must not show key preview.
    463         if (key == null || key.noKeyPreview()) {
    464             return;
    465         }
    466         final Keyboard keyboard = getKeyboard();
    467         if (keyboard == null) {
    468             return;
    469         }
    470         final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
    471         if (!previewParams.isPopupEnabled()) {
    472             previewParams.setVisibleOffset(-keyboard.mVerticalGap);
    473             return;
    474         }
    475 
    476         locatePreviewPlacerView();
    477         getLocationInWindow(mOriginCoords);
    478         mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, mKeyDrawParams,
    479                 getWidth(), mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated());
    480     }
    481 
    482     // Implements {@link TimerHandler.Callbacks} method.
    483     @Override
    484     public void dismissKeyPreviewWithoutDelay(final Key key) {
    485         mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */);
    486         // To redraw key top letter.
    487         invalidateKey(key);
    488     }
    489 
    490     @Override
    491     public void dismissKeyPreview(final Key key) {
    492         if (!isHardwareAccelerated()) {
    493             // TODO: Implement preference option to control key preview method and duration.
    494             mDrawingHandler.dismissKeyPreview(mKeyPreviewDrawParams.getLingerTimeout(), key);
    495             return;
    496         }
    497         mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */);
    498     }
    499 
    500     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
    501         mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
    502     }
    503 
    504     @Override
    505     public void showSlidingKeyInputPreview(final PointerTracker tracker) {
    506         locatePreviewPlacerView();
    507         mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
    508     }
    509 
    510     @Override
    511     public void dismissSlidingKeyInputPreview() {
    512         mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
    513     }
    514 
    515     private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
    516             final boolean isGestureFloatingPreviewTextEnabled) {
    517         mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
    518         mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
    519     }
    520 
    521     // Implements {@link DrawingHandler.Callbacks} method.
    522     @Override
    523     public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
    524         locatePreviewPlacerView();
    525         mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
    526     }
    527 
    528     public void dismissGestureFloatingPreviewText() {
    529         locatePreviewPlacerView();
    530         mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
    531     }
    532 
    533     @Override
    534     public void showGestureTrail(final PointerTracker tracker,
    535             final boolean showsFloatingPreviewText) {
    536         locatePreviewPlacerView();
    537         if (showsFloatingPreviewText) {
    538             mGestureFloatingTextDrawingPreview.setPreviewPosition(tracker);
    539         }
    540         mGestureTrailsDrawingPreview.setPreviewPosition(tracker);
    541     }
    542 
    543     // Note that this method is called from a non-UI thread.
    544     @SuppressWarnings("static-method")
    545     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
    546         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
    547     }
    548 
    549     public void setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser,
    550             final boolean isGestureTrailEnabled,
    551             final boolean isGestureFloatingPreviewTextEnabled) {
    552         PointerTracker.setGestureHandlingEnabledByUser(isGestureHandlingEnabledByUser);
    553         setGesturePreviewMode(isGestureHandlingEnabledByUser && isGestureTrailEnabled,
    554                 isGestureHandlingEnabledByUser && isGestureFloatingPreviewTextEnabled);
    555     }
    556 
    557     @Override
    558     protected void onAttachedToWindow() {
    559         super.onAttachedToWindow();
    560         installPreviewPlacerView();
    561     }
    562 
    563     @Override
    564     protected void onDetachedFromWindow() {
    565         super.onDetachedFromWindow();
    566         mDrawingPreviewPlacerView.removeAllViews();
    567     }
    568 
    569     private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
    570         final MoreKeySpec[] moreKeys = key.getMoreKeys();
    571         if (moreKeys == null) {
    572             return null;
    573         }
    574         Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
    575         if (moreKeysKeyboard == null) {
    576             // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
    577             // {@link KeyPreviewChoreographer#placeKeyPreview(Key,TextView,KeyboardIconsSet,KeyDrawParams,int,int[]},
    578             // though there may be some chances that the value is zero. <code>width == 0</code>
    579             // will cause zero-division error at
    580             // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
    581             final boolean isSingleMoreKeyWithPreview = mKeyPreviewDrawParams.isPopupEnabled()
    582                     && !key.noKeyPreview() && moreKeys.length == 1
    583                     && mKeyPreviewDrawParams.getVisibleWidth() > 0;
    584             final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder(
    585                     context, key, getKeyboard(), isSingleMoreKeyWithPreview,
    586                     mKeyPreviewDrawParams.getVisibleWidth(),
    587                     mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key));
    588             moreKeysKeyboard = builder.build();
    589             mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
    590         }
    591 
    592         final View container = key.isActionKey() ? mMoreKeysKeyboardForActionContainer
    593                 : mMoreKeysKeyboardContainer;
    594         final MoreKeysKeyboardView moreKeysKeyboardView =
    595                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
    596         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
    597         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    598         return moreKeysKeyboardView;
    599     }
    600 
    601     // Implements {@link TimerHandler.Callbacks} method.
    602     /**
    603      * Called when a key is long pressed.
    604      * @param tracker the pointer tracker which pressed the parent key
    605      */
    606     @Override
    607     public void onLongPress(final PointerTracker tracker) {
    608         if (isShowingMoreKeysPanel()) {
    609             return;
    610         }
    611         final Key key = tracker.getKey();
    612         if (key == null) {
    613             return;
    614         }
    615         final KeyboardActionListener listener = mKeyboardActionListener;
    616         if (key.hasNoPanelAutoMoreKey()) {
    617             final int moreKeyCode = key.getMoreKeys()[0].mCode;
    618             tracker.onLongPressed();
    619             listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
    620             listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
    621                     Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
    622             listener.onReleaseKey(moreKeyCode, false /* withSliding */);
    623             return;
    624         }
    625         final int code = key.getCode();
    626         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
    627             // Long pressing the space key invokes IME switcher dialog.
    628             if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
    629                 tracker.onLongPressed();
    630                 listener.onReleaseKey(code, false /* withSliding */);
    631                 return;
    632             }
    633         }
    634         openMoreKeysPanel(key, tracker);
    635     }
    636 
    637     private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
    638         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
    639         if (moreKeysPanel == null) {
    640             return;
    641         }
    642 
    643         final int[] lastCoords = CoordinateUtils.newInstance();
    644         tracker.getLastCoordinates(lastCoords);
    645         final boolean keyPreviewEnabled = mKeyPreviewDrawParams.isPopupEnabled()
    646                 && !key.noKeyPreview();
    647         // The more keys keyboard is usually horizontally aligned with the center of the parent key.
    648         // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
    649         // keys keyboard is placed at the touch point of the parent key.
    650         final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
    651                 ? CoordinateUtils.x(lastCoords)
    652                 : key.getX() + key.getWidth() / 2;
    653         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
    654         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
    655         // aligned with the bottom edge of the visible part of the key preview.
    656         // {@code mPreviewVisibleOffset} has been set appropriately in
    657         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
    658         final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset();
    659         moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
    660         tracker.onShowMoreKeysPanel(moreKeysPanel);
    661         // TODO: Implement zoom in animation of more keys panel.
    662         dismissKeyPreviewWithoutDelay(key);
    663     }
    664 
    665     public boolean isInDraggingFinger() {
    666         if (isShowingMoreKeysPanel()) {
    667             return true;
    668         }
    669         return PointerTracker.isAnyInDraggingFinger();
    670     }
    671 
    672     @Override
    673     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
    674         locatePreviewPlacerView();
    675         panel.showInParent(mDrawingPreviewPlacerView);
    676         mMoreKeysPanel = panel;
    677         dimEntireKeyboard(true /* dimmed */);
    678     }
    679 
    680     public boolean isShowingMoreKeysPanel() {
    681         return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
    682     }
    683 
    684     @Override
    685     public void onCancelMoreKeysPanel() {
    686         PointerTracker.dismissAllMoreKeysPanels();
    687     }
    688 
    689     @Override
    690     public void onDismissMoreKeysPanel() {
    691         dimEntireKeyboard(false /* dimmed */);
    692         if (isShowingMoreKeysPanel()) {
    693             mMoreKeysPanel.removeFromParent();
    694             mMoreKeysPanel = null;
    695         }
    696     }
    697 
    698     public void startDoubleTapShiftKeyTimer() {
    699         mKeyTimerHandler.startDoubleTapShiftKeyTimer();
    700     }
    701 
    702     public void cancelDoubleTapShiftKeyTimer() {
    703         mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
    704     }
    705 
    706     public boolean isInDoubleTapShiftKeyTimeout() {
    707         return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
    708     }
    709 
    710     @Override
    711     public boolean onTouchEvent(final MotionEvent me) {
    712         if (getKeyboard() == null) {
    713             return false;
    714         }
    715         if (mNonDistinctMultitouchHelper != null) {
    716             if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
    717                 // Key repeating timer will be canceled if 2 or more keys are in action.
    718                 mKeyTimerHandler.cancelKeyRepeatTimers();
    719             }
    720             // Non distinct multitouch screen support
    721             mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
    722             return true;
    723         }
    724         return processMotionEvent(me);
    725     }
    726 
    727     public boolean processMotionEvent(final MotionEvent me) {
    728         final int index = me.getActionIndex();
    729         final int id = me.getPointerId(index);
    730         final PointerTracker tracker = PointerTracker.getPointerTracker(id);
    731         // When a more keys panel is showing, we should ignore other fingers' single touch events
    732         // other than the finger that is showing the more keys panel.
    733         if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
    734                 && PointerTracker.getActivePointerTrackerCount() == 1) {
    735             return true;
    736         }
    737         tracker.processMotionEvent(me, mKeyDetector);
    738         return true;
    739     }
    740 
    741     public void cancelAllOngoingEvents() {
    742         mKeyTimerHandler.cancelAllMessages();
    743         mDrawingHandler.cancelAllMessages();
    744         dismissAllKeyPreviews();
    745         dismissGestureFloatingPreviewText();
    746         dismissSlidingKeyInputPreview();
    747         PointerTracker.dismissAllMoreKeysPanels();
    748         PointerTracker.cancelAllPointerTrackers();
    749     }
    750 
    751     public void closing() {
    752         cancelAllOngoingEvents();
    753         mMoreKeysKeyboardCache.clear();
    754     }
    755 
    756     public void onHideWindow() {
    757         onDismissMoreKeysPanel();
    758         final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
    759         if (accessibilityDelegate != null
    760                 && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
    761             accessibilityDelegate.onHideWindow();
    762         }
    763     }
    764 
    765     /**
    766      * {@inheritDoc}
    767      */
    768     @Override
    769     public boolean onHoverEvent(final MotionEvent event) {
    770         final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
    771         if (accessibilityDelegate != null
    772                 && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
    773             return accessibilityDelegate.onHoverEvent(event);
    774         }
    775         return super.onHoverEvent(event);
    776     }
    777 
    778     public void updateShortcutKey(final boolean available) {
    779         final Keyboard keyboard = getKeyboard();
    780         if (keyboard == null) {
    781             return;
    782         }
    783         final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT);
    784         if (shortcutKey == null) {
    785             return;
    786         }
    787         shortcutKey.setEnabled(available);
    788         invalidateKey(shortcutKey);
    789     }
    790 
    791     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
    792             final int languageOnSpacebarFormatType,
    793             final boolean hasMultipleEnabledIMEsOrSubtypes) {
    794         if (subtypeChanged) {
    795             KeyPreviewView.clearTextCache();
    796         }
    797         mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
    798         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
    799         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
    800         if (animator == null) {
    801             mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
    802         } else {
    803             if (subtypeChanged
    804                     && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
    805                 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
    806                 if (animator.isStarted()) {
    807                     animator.cancel();
    808                 }
    809                 animator.start();
    810             } else {
    811                 if (!animator.isStarted()) {
    812                     mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
    813                 }
    814             }
    815         }
    816         invalidateKey(mSpaceKey);
    817     }
    818 
    819     private void dimEntireKeyboard(final boolean dimmed) {
    820         final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
    821         mNeedsToDimEntireKeyboard = dimmed;
    822         if (needsRedrawing) {
    823             invalidateAllKeys();
    824         }
    825     }
    826 
    827     @Override
    828     protected void onDraw(final Canvas canvas) {
    829         super.onDraw(canvas);
    830 
    831         // Overlay a dark rectangle to dim.
    832         if (mNeedsToDimEntireKeyboard) {
    833             canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
    834         }
    835     }
    836 
    837     @Override
    838     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
    839             final KeyDrawParams params) {
    840         if (key.altCodeWhileTyping() && key.isEnabled()) {
    841             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
    842         }
    843         super.onDrawKeyTopVisuals(key, canvas, paint, params);
    844         final int code = key.getCode();
    845         if (code == Constants.CODE_SPACE) {
    846             // If input language are explicitly selected.
    847             if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
    848                 drawLanguageOnSpacebar(key, canvas, paint);
    849             }
    850             // Whether space key needs to show the "..." popup hint for special purposes
    851             if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
    852                 drawKeyPopupHint(key, canvas, paint, params);
    853             }
    854         } else if (code == Constants.CODE_LANGUAGE_SWITCH) {
    855             drawKeyPopupHint(key, canvas, paint, params);
    856         }
    857     }
    858 
    859     private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
    860         final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
    861         paint.setTextScaleX(1.0f);
    862         final float textWidth = TypefaceUtils.getStringWidth(text, paint);
    863         if (textWidth < width) {
    864             return true;
    865         }
    866 
    867         final float scaleX = maxTextWidth / textWidth;
    868         if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
    869             return false;
    870         }
    871 
    872         paint.setTextScaleX(scaleX);
    873         return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
    874     }
    875 
    876     // Layout language name on spacebar.
    877     private String layoutLanguageOnSpacebar(final Paint paint,
    878             final InputMethodSubtype subtype, final int width) {
    879         // Choose appropriate language name to fit into the width.
    880         if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
    881             final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
    882             if (fitsTextIntoWidth(width, fullText, paint)) {
    883                 return fullText;
    884             }
    885         }
    886 
    887         final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
    888         if (fitsTextIntoWidth(width, middleText, paint)) {
    889             return middleText;
    890         }
    891 
    892         return "";
    893     }
    894 
    895     private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
    896         final int width = key.getWidth();
    897         final int height = key.getHeight();
    898         paint.setTextAlign(Align.CENTER);
    899         paint.setTypeface(Typeface.DEFAULT);
    900         paint.setTextSize(mLanguageOnSpacebarTextSize);
    901         final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
    902         final String language = layoutLanguageOnSpacebar(paint, subtype, width);
    903         // Draw language text with shadow
    904         final float descent = paint.descent();
    905         final float textHeight = -paint.ascent() + descent;
    906         final float baseline = height / 2 + textHeight / 2;
    907         if (mLanguageOnSpacebarTextShadowRadius > 0.0f) {
    908             paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0,
    909                     mLanguageOnSpacebarTextShadowColor);
    910         } else {
    911             paint.clearShadowLayer();
    912         }
    913         paint.setColor(mLanguageOnSpacebarTextColor);
    914         paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
    915         canvas.drawText(language, width / 2, baseline - descent, paint);
    916         paint.clearShadowLayer();
    917         paint.setTextScaleX(1.0f);
    918     }
    919 
    920     @Override
    921     public void deallocateMemory() {
    922         super.deallocateMemory();
    923         mDrawingPreviewPlacerView.deallocateMemory();
    924     }
    925 }
    926