• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MainKeyboardView(final Context context, final AttributeSet attrs)173     public MainKeyboardView(final Context context, final AttributeSet attrs) {
174         this(context, attrs, R.attr.mainKeyboardViewStyle);
175     }
176 
MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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
setHardwareAcceleratedDrawingEnabled(final boolean enabled)275     public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
276         super.setHardwareAcceleratedDrawingEnabled(enabled);
277         mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
278     }
279 
loadObjectAnimator(final int resId, final Object target)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 
cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)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
startWhileTypingFadeinAnimation()311     public void startWhileTypingFadeinAnimation() {
312         cancelAndStartAnimators(
313                 mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
314     }
315 
316     @Override
startWhileTypingFadeoutAnimation()317     public void startWhileTypingFadeoutAnimation() {
318         cancelAndStartAnimators(
319                 mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
320     }
321 
322     @ExternallyReferenced
getLanguageOnSpacebarAnimAlpha()323     public int getLanguageOnSpacebarAnimAlpha() {
324         return mLanguageOnSpacebarAnimAlpha;
325     }
326 
327     @ExternallyReferenced
setLanguageOnSpacebarAnimAlpha(final int alpha)328     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
329         mLanguageOnSpacebarAnimAlpha = alpha;
330         invalidateKey(mSpaceKey);
331     }
332 
333     @ExternallyReferenced
getAltCodeKeyWhileTypingAnimAlpha()334     public int getAltCodeKeyWhileTypingAnimAlpha() {
335         return mAltCodeKeyWhileTypingAnimAlpha;
336     }
337 
338     @ExternallyReferenced
setAltCodeKeyWhileTypingAnimAlpha(final int alpha)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 
setKeyboardActionListener(final KeyboardActionListener listener)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.
getKeyX(final int x)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.
getKeyY(final int y)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
setKeyboard(final Keyboard keyboard)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      */
setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay)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      */
setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams, final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration, final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration)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 
locatePreviewPlacerView()433     private void locatePreviewPlacerView() {
434         getLocationInWindow(mOriginCoords);
435         mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight());
436     }
437 
installPreviewPlacerView()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
dismissAllKeyPreviews()455     public void dismissAllKeyPreviews() {
456         mKeyPreviewChoreographer.dismissAllKeyPreviews();
457         PointerTracker.setReleasedKeyGraphicsToAllKeys();
458     }
459 
460     @Override
showKeyPreview(final Key key)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
dismissKeyPreviewWithoutDelay(final Key key)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
dismissKeyPreview(final Key key)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 
setSlidingKeyInputPreviewEnabled(final boolean enabled)500     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
501         mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
502     }
503 
504     @Override
showSlidingKeyInputPreview(final PointerTracker tracker)505     public void showSlidingKeyInputPreview(final PointerTracker tracker) {
506         locatePreviewPlacerView();
507         mSlidingKeyInputDrawingPreview.setPreviewPosition(tracker);
508     }
509 
510     @Override
dismissSlidingKeyInputPreview()511     public void dismissSlidingKeyInputPreview() {
512         mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
513     }
514 
setGesturePreviewMode(final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)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
showGestureFloatingPreviewText(final SuggestedWords suggestedWords)523     public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
524         locatePreviewPlacerView();
525         mGestureFloatingTextDrawingPreview.setSuggetedWords(suggestedWords);
526     }
527 
dismissGestureFloatingPreviewText()528     public void dismissGestureFloatingPreviewText() {
529         locatePreviewPlacerView();
530         mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
531     }
532 
533     @Override
showGestureTrail(final PointerTracker tracker, final boolean showsFloatingPreviewText)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")
setMainDictionaryAvailability(final boolean mainDictionaryAvailable)545     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
546         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
547     }
548 
setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)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
onAttachedToWindow()558     protected void onAttachedToWindow() {
559         super.onAttachedToWindow();
560         installPreviewPlacerView();
561     }
562 
563     @Override
onDetachedFromWindow()564     protected void onDetachedFromWindow() {
565         super.onDetachedFromWindow();
566         mDrawingPreviewPlacerView.removeAllViews();
567     }
568 
onCreateMoreKeysPanel(final Key key, final Context context)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
onLongPress(final PointerTracker tracker)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 
openMoreKeysPanel(final Key key, final PointerTracker tracker)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 
isInDraggingFinger()665     public boolean isInDraggingFinger() {
666         if (isShowingMoreKeysPanel()) {
667             return true;
668         }
669         return PointerTracker.isAnyInDraggingFinger();
670     }
671 
672     @Override
onShowMoreKeysPanel(final MoreKeysPanel panel)673     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
674         locatePreviewPlacerView();
675         panel.showInParent(mDrawingPreviewPlacerView);
676         mMoreKeysPanel = panel;
677         dimEntireKeyboard(true /* dimmed */);
678     }
679 
isShowingMoreKeysPanel()680     public boolean isShowingMoreKeysPanel() {
681         return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
682     }
683 
684     @Override
onCancelMoreKeysPanel()685     public void onCancelMoreKeysPanel() {
686         PointerTracker.dismissAllMoreKeysPanels();
687     }
688 
689     @Override
onDismissMoreKeysPanel()690     public void onDismissMoreKeysPanel() {
691         dimEntireKeyboard(false /* dimmed */);
692         if (isShowingMoreKeysPanel()) {
693             mMoreKeysPanel.removeFromParent();
694             mMoreKeysPanel = null;
695         }
696     }
697 
startDoubleTapShiftKeyTimer()698     public void startDoubleTapShiftKeyTimer() {
699         mKeyTimerHandler.startDoubleTapShiftKeyTimer();
700     }
701 
cancelDoubleTapShiftKeyTimer()702     public void cancelDoubleTapShiftKeyTimer() {
703         mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
704     }
705 
isInDoubleTapShiftKeyTimeout()706     public boolean isInDoubleTapShiftKeyTimeout() {
707         return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
708     }
709 
710     @Override
onTouchEvent(final MotionEvent me)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 
processMotionEvent(final MotionEvent me)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 
cancelAllOngoingEvents()741     public void cancelAllOngoingEvents() {
742         mKeyTimerHandler.cancelAllMessages();
743         mDrawingHandler.cancelAllMessages();
744         dismissAllKeyPreviews();
745         dismissGestureFloatingPreviewText();
746         dismissSlidingKeyInputPreview();
747         PointerTracker.dismissAllMoreKeysPanels();
748         PointerTracker.cancelAllPointerTrackers();
749     }
750 
closing()751     public void closing() {
752         cancelAllOngoingEvents();
753         mMoreKeysKeyboardCache.clear();
754     }
755 
onHideWindow()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
onHoverEvent(final MotionEvent event)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 
updateShortcutKey(final boolean available)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 
startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final int languageOnSpacebarFormatType, final boolean hasMultipleEnabledIMEsOrSubtypes)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 
dimEntireKeyboard(final boolean dimmed)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
onDraw(final Canvas canvas)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
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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 
fitsTextIntoWidth(final int width, final String text, final Paint paint)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.
layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width)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 
drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint)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
deallocateMemory()921     public void deallocateMemory() {
922         super.deallocateMemory();
923         mDrawingPreviewPlacerView.deallocateMemory();
924     }
925 }
926