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 
MainKeyboardView(final Context context, final AttributeSet attrs)174     public MainKeyboardView(final Context context, final AttributeSet attrs) {
175         this(context, attrs, R.attr.mainKeyboardViewStyle);
176     }
177 
MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)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
setHardwareAcceleratedDrawingEnabled(final boolean enabled)279     public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
280         super.setHardwareAcceleratedDrawingEnabled(enabled);
281         mDrawingPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
282     }
283 
loadObjectAnimator(final int resId, final Object target)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 
cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)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
startWhileTypingAnimation(final int fadeInOrOut)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
getLanguageOnSpacebarAnimAlpha()334     public int getLanguageOnSpacebarAnimAlpha() {
335         return mLanguageOnSpacebarAnimAlpha;
336     }
337 
338     @ExternallyReferenced
setLanguageOnSpacebarAnimAlpha(final int alpha)339     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
340         mLanguageOnSpacebarAnimAlpha = alpha;
341         invalidateKey(mSpaceKey);
342     }
343 
344     @ExternallyReferenced
getAltCodeKeyWhileTypingAnimAlpha()345     public int getAltCodeKeyWhileTypingAnimAlpha() {
346         return mAltCodeKeyWhileTypingAnimAlpha;
347     }
348 
349     @ExternallyReferenced
setAltCodeKeyWhileTypingAnimAlpha(final int alpha)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 
setKeyboardActionListener(final KeyboardActionListener listener)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.
getKeyX(final int x)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.
getKeyY(final int y)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
setKeyboard(final Keyboard keyboard)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      */
setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay)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      */
setKeyPreviewAnimationParams(final boolean hasCustomAnimationParams, final float showUpStartXScale, final float showUpStartYScale, final int showUpDuration, final float dismissEndXScale, final float dismissEndYScale, final int dismissDuration)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 
locatePreviewPlacerView()444     private void locatePreviewPlacerView() {
445         getLocationInWindow(mOriginCoords);
446         mDrawingPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, getWidth(), getHeight());
447     }
448 
installPreviewPlacerView()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
onKeyPressed(@onnull final Key key, final boolean withPreview)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 
showKeyPreview(@onnull final Key key)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 
dismissKeyPreviewWithoutDelay(@onnull final Key key)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
onKeyReleased(@onnull final Key key, final boolean withAnimation)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 
dismissKeyPreview(@onnull final Key key)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 
setSlidingKeyInputPreviewEnabled(final boolean enabled)519     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
520         mSlidingKeyInputDrawingPreview.setPreviewEnabled(enabled);
521     }
522 
523     @Override
showSlidingKeyInputPreview(@ullable final PointerTracker tracker)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 
setGesturePreviewMode(final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)533     private void setGesturePreviewMode(final boolean isGestureTrailEnabled,
534             final boolean isGestureFloatingPreviewTextEnabled) {
535         mGestureFloatingTextDrawingPreview.setPreviewEnabled(isGestureFloatingPreviewTextEnabled);
536         mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled);
537     }
538 
showGestureFloatingPreviewText(@onnull final SuggestedWords suggestedWords, final boolean dismissDelayed)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
dismissGestureFloatingPreviewTextWithoutDelay()553     public void dismissGestureFloatingPreviewTextWithoutDelay() {
554         mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText();
555     }
556 
557     @Override
showGestureTrail(@onnull final PointerTracker tracker, final boolean showsFloatingPreviewText)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")
setMainDictionaryAvailability(final boolean mainDictionaryAvailable)569     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
570         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
571     }
572 
setGestureHandlingEnabledByUser(final boolean isGestureHandlingEnabledByUser, final boolean isGestureTrailEnabled, final boolean isGestureFloatingPreviewTextEnabled)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
onAttachedToWindow()582     protected void onAttachedToWindow() {
583         super.onAttachedToWindow();
584         installPreviewPlacerView();
585     }
586 
587     @Override
onDetachedFromWindow()588     protected void onDetachedFromWindow() {
589         super.onDetachedFromWindow();
590         mDrawingPreviewPlacerView.removeAllViews();
591     }
592 
593     // Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}.
594     @Override
595     @Nullable
showMoreKeysKeyboard(@onnull final Key key, @Nonnull final PointerTracker tracker)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 
isInDraggingFinger()647     public boolean isInDraggingFinger() {
648         if (isShowingMoreKeysPanel()) {
649             return true;
650         }
651         return PointerTracker.isAnyInDraggingFinger();
652     }
653 
654     @Override
onShowMoreKeysPanel(final MoreKeysPanel panel)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 
isShowingMoreKeysPanel()667     public boolean isShowingMoreKeysPanel() {
668         return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
669     }
670 
671     @Override
onCancelMoreKeysPanel()672     public void onCancelMoreKeysPanel() {
673         PointerTracker.dismissAllMoreKeysPanels();
674     }
675 
676     @Override
onDismissMoreKeysPanel()677     public void onDismissMoreKeysPanel() {
678         if (isShowingMoreKeysPanel()) {
679             mMoreKeysPanel.removeFromParent();
680             mMoreKeysPanel = null;
681         }
682     }
683 
startDoubleTapShiftKeyTimer()684     public void startDoubleTapShiftKeyTimer() {
685         mTimerHandler.startDoubleTapShiftKeyTimer();
686     }
687 
cancelDoubleTapShiftKeyTimer()688     public void cancelDoubleTapShiftKeyTimer() {
689         mTimerHandler.cancelDoubleTapShiftKeyTimer();
690     }
691 
isInDoubleTapShiftKeyTimeout()692     public boolean isInDoubleTapShiftKeyTimeout() {
693         return mTimerHandler.isInDoubleTapShiftKeyTimeout();
694     }
695 
696     @Override
onTouchEvent(final MotionEvent event)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 
processMotionEvent(final MotionEvent event)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 
cancelAllOngoingEvents()727     public void cancelAllOngoingEvents() {
728         mTimerHandler.cancelAllMessages();
729         PointerTracker.setReleasedKeyGraphicsToAllKeys();
730         mGestureFloatingTextDrawingPreview.dismissGestureFloatingPreviewText();
731         mSlidingKeyInputDrawingPreview.dismissSlidingKeyInputPreview();
732         PointerTracker.dismissAllMoreKeysPanels();
733         PointerTracker.cancelAllPointerTrackers();
734     }
735 
closing()736     public void closing() {
737         cancelAllOngoingEvents();
738         mMoreKeysKeyboardCache.clear();
739     }
740 
onHideWindow()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
onHoverEvent(final MotionEvent event)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 
updateShortcutKey(final boolean available)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 
startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final int languageOnSpacebarFormatType, final boolean hasMultipleEnabledIMEsOrSubtypes)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
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)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 
fitsTextIntoWidth(final int width, final String text, final Paint paint)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.
layoutLanguageOnSpacebar(final Paint paint, final RichInputMethodSubtype subtype, final int width)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 
drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint)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
deallocateMemory()891     public void deallocateMemory() {
892         super.deallocateMemory();
893         mDrawingPreviewPlacerView.deallocateMemory();
894     }
895 }
896