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