• 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.Resources;
25 import android.content.res.TypedArray;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.graphics.Paint.Align;
30 import android.graphics.Typeface;
31 import android.graphics.drawable.Drawable;
32 import android.os.Message;
33 import android.os.SystemClock;
34 import android.preference.PreferenceManager;
35 import android.util.AttributeSet;
36 import android.util.DisplayMetrics;
37 import android.util.Log;
38 import android.util.SparseArray;
39 import android.util.TypedValue;
40 import android.view.LayoutInflater;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewConfiguration;
44 import android.view.ViewGroup;
45 import android.view.inputmethod.InputMethodSubtype;
46 import android.widget.TextView;
47 
48 import com.android.inputmethod.accessibility.AccessibilityUtils;
49 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
50 import com.android.inputmethod.annotations.ExternallyReferenced;
51 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
52 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
53 import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
54 import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
55 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
56 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
57 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
58 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
59 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
60 import com.android.inputmethod.latin.CollectionUtils;
61 import com.android.inputmethod.latin.Constants;
62 import com.android.inputmethod.latin.CoordinateUtils;
63 import com.android.inputmethod.latin.DebugSettings;
64 import com.android.inputmethod.latin.LatinIME;
65 import com.android.inputmethod.latin.LatinImeLogger;
66 import com.android.inputmethod.latin.R;
67 import com.android.inputmethod.latin.ResourceUtils;
68 import com.android.inputmethod.latin.Settings;
69 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
70 import com.android.inputmethod.latin.StringUtils;
71 import com.android.inputmethod.latin.SubtypeLocale;
72 import com.android.inputmethod.latin.SuggestedWords;
73 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
74 import com.android.inputmethod.latin.define.ProductionFlag;
75 import com.android.inputmethod.research.ResearchLogger;
76 
77 import java.util.Locale;
78 import java.util.WeakHashMap;
79 
80 /**
81  * A view that is responsible for detecting key presses and touch movements.
82  *
83  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
84  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
85  * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
86  * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
87  * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
88  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
89  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
90  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
91  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
92  * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
93  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
94  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
95  * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
96  * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
97  * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
98  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
99  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
100  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
101  * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
102  * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
103  * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
104  * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
105  * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
106  * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
107  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
108  * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
109  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
110  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
111  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
112  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
113  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
114  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
115  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
116  * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
117  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
118  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
119  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
120  */
121 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
122         PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
123         TouchScreenRegulator.ProcessMotionEvent {
124     private static final String TAG = MainKeyboardView.class.getSimpleName();
125 
126     // TODO: Kill process when the usability study mode was changed.
127     private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
128 
129     /** Listener for {@link KeyboardActionListener}. */
130     private KeyboardActionListener mKeyboardActionListener;
131 
132     /* Space key and its icons */
133     private Key mSpaceKey;
134     private Drawable mSpaceIcon;
135     // Stuff to draw language name on spacebar.
136     private final int mLanguageOnSpacebarFinalAlpha;
137     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
138     private boolean mNeedsToDisplayLanguage;
139     private boolean mHasMultipleEnabledIMEsOrSubtypes;
140     private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
141     private final float mSpacebarTextRatio;
142     private float mSpacebarTextSize;
143     private final int mSpacebarTextColor;
144     private final int mSpacebarTextShadowColor;
145     // The minimum x-scale to fit the language name on spacebar.
146     private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
147     // Stuff to draw auto correction LED on spacebar.
148     private boolean mAutoCorrectionSpacebarLedOn;
149     private final boolean mAutoCorrectionSpacebarLedEnabled;
150     private final Drawable mAutoCorrectionSpacebarLedIcon;
151     private static final int SPACE_LED_LENGTH_PERCENT = 80;
152 
153     // Stuff to draw altCodeWhileTyping keys.
154     private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
155     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
156     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
157 
158     // Preview placer view
159     private final PreviewPlacerView mPreviewPlacerView;
160     private final int[] mOriginCoords = CoordinateUtils.newInstance();
161     private final GestureFloatingPreviewText mGestureFloatingPreviewText;
162     private final GestureTrailsPreview mGestureTrailsPreview;
163     private final SlidingKeyInputPreview mSlidingKeyInputPreview;
164 
165     // Key preview
166     private static final int PREVIEW_ALPHA = 240;
167     private final int mKeyPreviewLayoutId;
168     private final int mKeyPreviewOffset;
169     private final int mKeyPreviewHeight;
170     private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
171     private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
172     private boolean mShowKeyPreviewPopup = true;
173     private int mKeyPreviewLingerTimeout;
174 
175     // More keys keyboard
176     private final Paint mBackgroundDimAlphaPaint = new Paint();
177     private boolean mNeedsToDimEntireKeyboard;
178     private final View mMoreKeysKeyboardContainer;
179     private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache =
180             CollectionUtils.newWeakHashMap();
181     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
182     // More keys panel (used by both more keys keyboard and more suggestions view)
183     // TODO: Consider extending to support multiple more keys panels
184     private MoreKeysPanel mMoreKeysPanel;
185 
186     // Gesture floating preview text
187     // TODO: Make this parameter customizable by user via settings.
188     private int mGestureFloatingPreviewTextLingerTimeout;
189 
190     private final TouchScreenRegulator mTouchScreenRegulator;
191 
192     private KeyDetector mKeyDetector;
193     private final boolean mHasDistinctMultitouch;
194     private int mOldPointerCount = 1;
195     private Key mOldKey;
196 
197     private final KeyTimerHandler mKeyTimerHandler;
198 
199     private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
200             implements TimerProxy {
201         private static final int MSG_TYPING_STATE_EXPIRED = 0;
202         private static final int MSG_REPEAT_KEY = 1;
203         private static final int MSG_LONGPRESS_KEY = 2;
204         private static final int MSG_DOUBLE_TAP = 3;
205         private static final int MSG_UPDATE_BATCH_INPUT = 4;
206 
207         private final int mKeyRepeatStartTimeout;
208         private final int mKeyRepeatInterval;
209         private final int mLongPressShiftLockTimeout;
210         private final int mIgnoreAltCodeKeyTimeout;
211         private final int mGestureRecognitionUpdateTime;
212 
KeyTimerHandler(final MainKeyboardView outerInstance, final TypedArray mainKeyboardViewAttr)213         public KeyTimerHandler(final MainKeyboardView outerInstance,
214                 final TypedArray mainKeyboardViewAttr) {
215             super(outerInstance);
216 
217             mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
218                     R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
219             mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
220                     R.styleable.MainKeyboardView_keyRepeatInterval, 0);
221             mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
222                     R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
223             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
224                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
225             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
226                     R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
227         }
228 
229         @Override
handleMessage(final Message msg)230         public void handleMessage(final Message msg) {
231             final MainKeyboardView keyboardView = getOuterInstance();
232             if (keyboardView == null) {
233                 return;
234             }
235             final PointerTracker tracker = (PointerTracker) msg.obj;
236             switch (msg.what) {
237             case MSG_TYPING_STATE_EXPIRED:
238                 startWhileTypingFadeinAnimation(keyboardView);
239                 break;
240             case MSG_REPEAT_KEY:
241                 final Key currentKey = tracker.getKey();
242                 if (currentKey != null && currentKey.mCode == msg.arg1) {
243                     tracker.onRegisterKey(currentKey);
244                     startKeyRepeatTimer(tracker, mKeyRepeatInterval);
245                 }
246                 break;
247             case MSG_LONGPRESS_KEY:
248                 if (tracker != null) {
249                     keyboardView.onLongPress(tracker);
250                 } else {
251                     KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1);
252                 }
253                 break;
254             case MSG_UPDATE_BATCH_INPUT:
255                 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
256                 startUpdateBatchInputTimer(tracker);
257                 break;
258             }
259         }
260 
startKeyRepeatTimer(final PointerTracker tracker, final long delay)261         private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
262             final Key key = tracker.getKey();
263             if (key == null) {
264                 return;
265             }
266             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
267         }
268 
269         @Override
startKeyRepeatTimer(final PointerTracker tracker)270         public void startKeyRepeatTimer(final PointerTracker tracker) {
271             startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
272         }
273 
cancelKeyRepeatTimer()274         public void cancelKeyRepeatTimer() {
275             removeMessages(MSG_REPEAT_KEY);
276         }
277 
278         // TODO: Suppress layout changes in key repeat mode
isInKeyRepeat()279         public boolean isInKeyRepeat() {
280             return hasMessages(MSG_REPEAT_KEY);
281         }
282 
283         @Override
startLongPressTimer(final int code)284         public void startLongPressTimer(final int code) {
285             cancelLongPressTimer();
286             final int delay;
287             switch (code) {
288             case Constants.CODE_SHIFT:
289                 delay = mLongPressShiftLockTimeout;
290                 break;
291             default:
292                 delay = 0;
293                 break;
294             }
295             if (delay > 0) {
296                 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay);
297             }
298         }
299 
300         @Override
startLongPressTimer(final PointerTracker tracker)301         public void startLongPressTimer(final PointerTracker tracker) {
302             cancelLongPressTimer();
303             if (tracker == null) {
304                 return;
305             }
306             final Key key = tracker.getKey();
307             final int delay;
308             switch (key.mCode) {
309             case Constants.CODE_SHIFT:
310                 delay = mLongPressShiftLockTimeout;
311                 break;
312             default:
313                 final int longpressTimeout =
314                         Settings.getInstance().getCurrent().mKeyLongpressTimeout;
315                 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
316                     // We use longer timeout for sliding finger input started from the symbols
317                     // mode key.
318                     delay = longpressTimeout * 3;
319                 } else {
320                     delay = longpressTimeout;
321                 }
322                 break;
323             }
324             if (delay > 0) {
325                 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
326             }
327         }
328 
329         @Override
cancelLongPressTimer()330         public void cancelLongPressTimer() {
331             removeMessages(MSG_LONGPRESS_KEY);
332         }
333 
cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)334         private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
335                 final ObjectAnimator animatorToStart) {
336             if (animatorToCancel == null || animatorToStart == null) {
337                 // TODO: Stop using null as a no-operation animator.
338                 return;
339             }
340             float startFraction = 0.0f;
341             if (animatorToCancel.isStarted()) {
342                 animatorToCancel.cancel();
343                 startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
344             }
345             final long startTime = (long)(animatorToStart.getDuration() * startFraction);
346             animatorToStart.start();
347             animatorToStart.setCurrentPlayTime(startTime);
348         }
349 
startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView)350         private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
351             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
352                     keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
353         }
354 
startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView)355         private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
356             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
357                     keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
358         }
359 
360         @Override
startTypingStateTimer(final Key typedKey)361         public void startTypingStateTimer(final Key typedKey) {
362             if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
363                 return;
364             }
365 
366             final boolean isTyping = isTypingState();
367             removeMessages(MSG_TYPING_STATE_EXPIRED);
368             final MainKeyboardView keyboardView = getOuterInstance();
369 
370             // When user hits the space or the enter key, just cancel the while-typing timer.
371             final int typedCode = typedKey.mCode;
372             if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
373                 if (isTyping) {
374                     startWhileTypingFadeinAnimation(keyboardView);
375                 }
376                 return;
377             }
378 
379             sendMessageDelayed(
380                     obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
381             if (isTyping) {
382                 return;
383             }
384             startWhileTypingFadeoutAnimation(keyboardView);
385         }
386 
387         @Override
isTypingState()388         public boolean isTypingState() {
389             return hasMessages(MSG_TYPING_STATE_EXPIRED);
390         }
391 
392         @Override
startDoubleTapTimer()393         public void startDoubleTapTimer() {
394             sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
395                     ViewConfiguration.getDoubleTapTimeout());
396         }
397 
398         @Override
cancelDoubleTapTimer()399         public void cancelDoubleTapTimer() {
400             removeMessages(MSG_DOUBLE_TAP);
401         }
402 
403         @Override
isInDoubleTapTimeout()404         public boolean isInDoubleTapTimeout() {
405             return hasMessages(MSG_DOUBLE_TAP);
406         }
407 
408         @Override
cancelKeyTimers()409         public void cancelKeyTimers() {
410             cancelKeyRepeatTimer();
411             cancelLongPressTimer();
412         }
413 
414         @Override
startUpdateBatchInputTimer(final PointerTracker tracker)415         public void startUpdateBatchInputTimer(final PointerTracker tracker) {
416             if (mGestureRecognitionUpdateTime <= 0) {
417                 return;
418             }
419             removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
420             sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
421                     mGestureRecognitionUpdateTime);
422         }
423 
424         @Override
cancelUpdateBatchInputTimer(final PointerTracker tracker)425         public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
426             removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
427         }
428 
429         @Override
cancelAllUpdateBatchInputTimers()430         public void cancelAllUpdateBatchInputTimers() {
431             removeMessages(MSG_UPDATE_BATCH_INPUT);
432         }
433 
cancelAllMessages()434         public void cancelAllMessages() {
435             cancelKeyTimers();
436             cancelAllUpdateBatchInputTimers();
437         }
438     }
439 
440     private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
441 
442     public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
443         private static final int MSG_DISMISS_KEY_PREVIEW = 0;
444         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
445 
DrawingHandler(final MainKeyboardView outerInstance)446         public DrawingHandler(final MainKeyboardView outerInstance) {
447             super(outerInstance);
448         }
449 
450         @Override
handleMessage(final Message msg)451         public void handleMessage(final Message msg) {
452             final MainKeyboardView mainKeyboardView = getOuterInstance();
453             if (mainKeyboardView == null) return;
454             final PointerTracker tracker = (PointerTracker) msg.obj;
455             switch (msg.what) {
456             case MSG_DISMISS_KEY_PREVIEW:
457                 final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
458                         tracker.mPointerId);
459                 if (previewText != null) {
460                     previewText.setVisibility(INVISIBLE);
461                 }
462                 break;
463             case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
464                 mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
465                 break;
466             }
467         }
468 
dismissKeyPreview(final long delay, final PointerTracker tracker)469         public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
470             sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
471         }
472 
cancelDismissKeyPreview(final PointerTracker tracker)473         public void cancelDismissKeyPreview(final PointerTracker tracker) {
474             removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
475         }
476 
cancelAllDismissKeyPreviews()477         private void cancelAllDismissKeyPreviews() {
478             removeMessages(MSG_DISMISS_KEY_PREVIEW);
479         }
480 
dismissGestureFloatingPreviewText(final long delay)481         public void dismissGestureFloatingPreviewText(final long delay) {
482             sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
483         }
484 
cancelAllMessages()485         public void cancelAllMessages() {
486             cancelAllDismissKeyPreviews();
487         }
488     }
489 
MainKeyboardView(final Context context, final AttributeSet attrs)490     public MainKeyboardView(final Context context, final AttributeSet attrs) {
491         this(context, attrs, R.attr.mainKeyboardViewStyle);
492     }
493 
MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)494     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
495         super(context, attrs, defStyle);
496 
497         mTouchScreenRegulator = new TouchScreenRegulator(context, this);
498 
499         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
500         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
501                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
502         final boolean hasDistinctMultitouch = context.getPackageManager()
503                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
504         mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch;
505         final Resources res = getResources();
506         final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
507                 ResourceUtils.getDeviceOverrideValue(
508                         res, R.array.phantom_sudden_move_event_device_list));
509         PointerTracker.init(needsPhantomSuddenMoveEventHack);
510         mPreviewPlacerView = new PreviewPlacerView(context, attrs);
511 
512         final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
513                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
514         final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
515                 R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
516         mBackgroundDimAlphaPaint.setColor(Color.BLACK);
517         mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
518         mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
519                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
520         mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
521                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
522         mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
523                 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
524         mSpacebarTextColor = mainKeyboardViewAttr.getColor(
525                 R.styleable.MainKeyboardView_spacebarTextColor, 0);
526         mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
527                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
528         mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
529                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
530                 Constants.Color.ALPHA_OPAQUE);
531         final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
532                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
533         final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
534                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
535         final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
536                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
537 
538         final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
539                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
540         final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
541                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
542         mKeyDetector = new KeyDetector(
543                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
544         mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
545         mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
546                 R.styleable.MainKeyboardView_keyPreviewOffset, 0);
547         mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
548                 R.styleable.MainKeyboardView_keyPreviewHeight, 0);
549         mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
550                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
551         mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
552                 R.styleable.MainKeyboardView_keyPreviewLayout, 0);
553         if (mKeyPreviewLayoutId == 0) {
554             mShowKeyPreviewPopup = false;
555         }
556         final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
557                 R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
558         mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
559                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
560 
561         mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
562                 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
563         PointerTracker.setParameters(mainKeyboardViewAttr);
564 
565         mGestureFloatingPreviewText = new GestureFloatingPreviewText(
566                 mPreviewPlacerView, mainKeyboardViewAttr);
567         mPreviewPlacerView.addPreview(mGestureFloatingPreviewText);
568 
569         mGestureTrailsPreview = new GestureTrailsPreview(
570                 mPreviewPlacerView, mainKeyboardViewAttr);
571         mPreviewPlacerView.addPreview(mGestureTrailsPreview);
572 
573         mSlidingKeyInputPreview = new SlidingKeyInputPreview(
574                 mPreviewPlacerView, mainKeyboardViewAttr);
575         mPreviewPlacerView.addPreview(mSlidingKeyInputPreview);
576         mainKeyboardViewAttr.recycle();
577 
578         mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
579                 .inflate(moreKeysKeyboardLayoutId, null);
580         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
581                 languageOnSpacebarFadeoutAnimatorResId, this);
582         mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
583                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
584         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
585                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
586     }
587 
loadObjectAnimator(final int resId, final Object target)588     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
589         if (resId == 0) {
590             // TODO: Stop returning null.
591             return null;
592         }
593         final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
594                 getContext(), resId);
595         if (animator != null) {
596             animator.setTarget(target);
597         }
598         return animator;
599     }
600 
601     @ExternallyReferenced
getLanguageOnSpacebarAnimAlpha()602     public int getLanguageOnSpacebarAnimAlpha() {
603         return mLanguageOnSpacebarAnimAlpha;
604     }
605 
606     @ExternallyReferenced
setLanguageOnSpacebarAnimAlpha(final int alpha)607     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
608         mLanguageOnSpacebarAnimAlpha = alpha;
609         invalidateKey(mSpaceKey);
610     }
611 
612     @ExternallyReferenced
getAltCodeKeyWhileTypingAnimAlpha()613     public int getAltCodeKeyWhileTypingAnimAlpha() {
614         return mAltCodeKeyWhileTypingAnimAlpha;
615     }
616 
617     @ExternallyReferenced
setAltCodeKeyWhileTypingAnimAlpha(final int alpha)618     public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
619         if (mAltCodeKeyWhileTypingAnimAlpha == alpha) {
620             return;
621         }
622         // Update the visual of alt-code-key-while-typing.
623         mAltCodeKeyWhileTypingAnimAlpha = alpha;
624         final Keyboard keyboard = getKeyboard();
625         if (keyboard == null) {
626             return;
627         }
628         for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
629             invalidateKey(key);
630         }
631     }
632 
setKeyboardActionListener(final KeyboardActionListener listener)633     public void setKeyboardActionListener(final KeyboardActionListener listener) {
634         mKeyboardActionListener = listener;
635         PointerTracker.setKeyboardActionListener(listener);
636     }
637 
638     /**
639      * Returns the {@link KeyboardActionListener} object.
640      * @return the listener attached to this keyboard
641      */
642     @Override
getKeyboardActionListener()643     public KeyboardActionListener getKeyboardActionListener() {
644         return mKeyboardActionListener;
645     }
646 
647     @Override
getKeyDetector()648     public KeyDetector getKeyDetector() {
649         return mKeyDetector;
650     }
651 
652     @Override
getDrawingProxy()653     public DrawingProxy getDrawingProxy() {
654         return this;
655     }
656 
657     @Override
getTimerProxy()658     public TimerProxy getTimerProxy() {
659         return mKeyTimerHandler;
660     }
661 
662     /**
663      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
664      * view will re-layout itself to accommodate the keyboard.
665      * @see Keyboard
666      * @see #getKeyboard()
667      * @param keyboard the keyboard to display in this view
668      */
669     @Override
setKeyboard(final Keyboard keyboard)670     public void setKeyboard(final Keyboard keyboard) {
671         // Remove any pending messages, except dismissing preview and key repeat.
672         mKeyTimerHandler.cancelLongPressTimer();
673         super.setKeyboard(keyboard);
674         mKeyDetector.setKeyboard(
675                 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
676         PointerTracker.setKeyDetector(mKeyDetector);
677         mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
678         mMoreKeysKeyboardCache.clear();
679 
680         mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
681         mSpaceIcon = (mSpaceKey != null)
682                 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
683         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
684         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
685         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
686             ResearchLogger.mainKeyboardView_setKeyboard(keyboard);
687         }
688 
689         // This always needs to be set since the accessibility state can
690         // potentially change without the keyboard being set again.
691         AccessibleKeyboardViewProxy.getInstance().setKeyboard();
692     }
693 
694     /**
695      * Enables or disables the key feedback popup. This is a popup that shows a magnified
696      * version of the depressed key. By default the preview is enabled.
697      * @param previewEnabled whether or not to enable the key feedback preview
698      * @param delay the delay after which the preview is dismissed
699      * @see #isKeyPreviewPopupEnabled()
700      */
setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay)701     public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
702         mShowKeyPreviewPopup = previewEnabled;
703         mKeyPreviewLingerTimeout = delay;
704     }
705 
706 
locatePreviewPlacerView()707     private void locatePreviewPlacerView() {
708         if (mPreviewPlacerView.getParent() != null) {
709             return;
710         }
711         final int width = getWidth();
712         final int height = getHeight();
713         if (width == 0 || height == 0) {
714             // In transient state.
715             return;
716         }
717         getLocationInWindow(mOriginCoords);
718         final DisplayMetrics dm = getResources().getDisplayMetrics();
719         if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
720             // In transient state.
721             return;
722         }
723         final View rootView = getRootView();
724         if (rootView == null) {
725             Log.w(TAG, "Cannot find root view");
726             return;
727         }
728         final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
729         // Note: It'd be very weird if we get null by android.R.id.content.
730         if (windowContentView == null) {
731             Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
732         } else {
733             windowContentView.addView(mPreviewPlacerView);
734             mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
735         }
736     }
737 
738     /**
739      * Returns the enabled state of the key feedback preview
740      * @return whether or not the key feedback preview is enabled
741      * @see #setKeyPreviewPopupEnabled(boolean, int)
742      */
isKeyPreviewPopupEnabled()743     public boolean isKeyPreviewPopupEnabled() {
744         return mShowKeyPreviewPopup;
745     }
746 
addKeyPreview(final TextView keyPreview)747     private void addKeyPreview(final TextView keyPreview) {
748         locatePreviewPlacerView();
749         mPreviewPlacerView.addView(
750                 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
751     }
752 
getKeyPreviewText(final int pointerId)753     private TextView getKeyPreviewText(final int pointerId) {
754         TextView previewText = mKeyPreviewTexts.get(pointerId);
755         if (previewText != null) {
756             return previewText;
757         }
758         final Context context = getContext();
759         if (mKeyPreviewLayoutId != 0) {
760             previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
761         } else {
762             previewText = new TextView(context);
763         }
764         mKeyPreviewTexts.put(pointerId, previewText);
765         return previewText;
766     }
767 
dismissAllKeyPreviews()768     private void dismissAllKeyPreviews() {
769         final int pointerCount = mKeyPreviewTexts.size();
770         for (int id = 0; id < pointerCount; id++) {
771             final TextView previewText = mKeyPreviewTexts.get(id);
772             if (previewText != null) {
773                 previewText.setVisibility(INVISIBLE);
774             }
775         }
776         PointerTracker.setReleasedKeyGraphicsToAllKeys();
777     }
778 
779     // Background state set
780     private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
781         { // STATE_MIDDLE
782             EMPTY_STATE_SET,
783             { R.attr.state_has_morekeys }
784         },
785         { // STATE_LEFT
786             { R.attr.state_left_edge },
787             { R.attr.state_left_edge, R.attr.state_has_morekeys }
788         },
789         { // STATE_RIGHT
790             { R.attr.state_right_edge },
791             { R.attr.state_right_edge, R.attr.state_has_morekeys }
792         }
793     };
794     private static final int STATE_MIDDLE = 0;
795     private static final int STATE_LEFT = 1;
796     private static final int STATE_RIGHT = 2;
797     private static final int STATE_NORMAL = 0;
798     private static final int STATE_HAS_MOREKEYS = 1;
799     private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
800             KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
801 
802     @Override
showKeyPreview(final PointerTracker tracker)803     public void showKeyPreview(final PointerTracker tracker) {
804         final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
805         final Keyboard keyboard = getKeyboard();
806         if (!mShowKeyPreviewPopup) {
807             previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
808             return;
809         }
810 
811         final TextView previewText = getKeyPreviewText(tracker.mPointerId);
812         // If the key preview has no parent view yet, add it to the ViewGroup which can place
813         // key preview absolutely in SoftInputWindow.
814         if (previewText.getParent() == null) {
815             addKeyPreview(previewText);
816         }
817 
818         mDrawingHandler.cancelDismissKeyPreview(tracker);
819         final Key key = tracker.getKey();
820         // If key is invalid or IME is already closed, we must not show key preview.
821         // Trying to show key preview while root window is closed causes
822         // WindowManager.BadTokenException.
823         if (key == null) {
824             return;
825         }
826 
827         final KeyDrawParams drawParams = mKeyDrawParams;
828         previewText.setTextColor(drawParams.mPreviewTextColor);
829         final Drawable background = previewText.getBackground();
830         if (background != null) {
831             background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
832             background.setAlpha(PREVIEW_ALPHA);
833         }
834         final String label = key.getPreviewLabel();
835         // What we show as preview should match what we show on a key top in onDraw().
836         if (label != null) {
837             // TODO Should take care of temporaryShiftLabel here.
838             previewText.setCompoundDrawables(null, null, null, null);
839             previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
840                     key.selectPreviewTextSize(drawParams));
841             previewText.setTypeface(key.selectPreviewTypeface(drawParams));
842             previewText.setText(label);
843         } else {
844             previewText.setCompoundDrawables(null, null, null,
845                     key.getPreviewIcon(keyboard.mIconsSet));
846             previewText.setText(null);
847         }
848 
849         previewText.measure(
850                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
851         final int keyDrawWidth = key.getDrawWidth();
852         final int previewWidth = previewText.getMeasuredWidth();
853         final int previewHeight = mKeyPreviewHeight;
854         // The width and height of visible part of the key preview background. The content marker
855         // of the background 9-patch have to cover the visible part of the background.
856         previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
857                 - previewText.getPaddingRight();
858         previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
859                 - previewText.getPaddingBottom();
860         // The distance between the top edge of the parent key and the bottom of the visible part
861         // of the key preview background.
862         previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
863         getLocationInWindow(mOriginCoords);
864         // The key preview is horizontally aligned with the center of the visible part of the
865         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
866         // the left/right background is used if such background is specified.
867         final int statePosition;
868         int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
869                 + CoordinateUtils.x(mOriginCoords);
870         if (previewX < 0) {
871             previewX = 0;
872             statePosition = STATE_LEFT;
873         } else if (previewX > getWidth() - previewWidth) {
874             previewX = getWidth() - previewWidth;
875             statePosition = STATE_RIGHT;
876         } else {
877             statePosition = STATE_MIDDLE;
878         }
879         // The key preview is placed vertically above the top edge of the parent key with an
880         // arbitrary offset.
881         final int previewY = key.mY - previewHeight + mKeyPreviewOffset
882                 + CoordinateUtils.y(mOriginCoords);
883 
884         if (background != null) {
885             final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
886             background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
887         }
888         ViewLayoutUtils.placeViewAt(
889                 previewText, previewX, previewY, previewWidth, previewHeight);
890         previewText.setVisibility(VISIBLE);
891     }
892 
893     @Override
dismissKeyPreview(final PointerTracker tracker)894     public void dismissKeyPreview(final PointerTracker tracker) {
895         mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
896     }
897 
setSlidingKeyInputPreviewEnabled(final boolean enabled)898     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
899         mSlidingKeyInputPreview.setPreviewEnabled(enabled);
900     }
901 
902     @Override
showSlidingKeyInputPreview(final PointerTracker tracker)903     public void showSlidingKeyInputPreview(final PointerTracker tracker) {
904         locatePreviewPlacerView();
905         mSlidingKeyInputPreview.setPreviewPosition(tracker);
906     }
907 
908     @Override
dismissSlidingKeyInputPreview()909     public void dismissSlidingKeyInputPreview() {
910         mSlidingKeyInputPreview.dismissSlidingKeyInputPreview();
911     }
912 
setGesturePreviewMode(final boolean drawsGestureTrail, final boolean drawsGestureFloatingPreviewText)913     public void setGesturePreviewMode(final boolean drawsGestureTrail,
914             final boolean drawsGestureFloatingPreviewText) {
915         mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText);
916         mGestureTrailsPreview.setPreviewEnabled(drawsGestureTrail);
917     }
918 
showGestureFloatingPreviewText(final SuggestedWords suggestedWords)919     public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
920         locatePreviewPlacerView();
921         mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
922     }
923 
dismissGestureFloatingPreviewText()924     public void dismissGestureFloatingPreviewText() {
925         locatePreviewPlacerView();
926         mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
927     }
928 
929     @Override
showGestureTrail(final PointerTracker tracker)930     public void showGestureTrail(final PointerTracker tracker) {
931         locatePreviewPlacerView();
932         mGestureFloatingPreviewText.setPreviewPosition(tracker);
933         mGestureTrailsPreview.setPreviewPosition(tracker);
934     }
935 
936     // Note that this method is called from a non-UI thread.
setMainDictionaryAvailability(final boolean mainDictionaryAvailable)937     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
938         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
939     }
940 
setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser)941     public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
942         PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
943     }
944 
945     @Override
onAttachedToWindow()946     protected void onAttachedToWindow() {
947         super.onAttachedToWindow();
948         // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
949         // been attached.  This is needed to properly show the splash screen, which requires that
950         // the window token of the KeyboardView be non-null.
951         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
952             ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
953         }
954     }
955 
956     @Override
onDetachedFromWindow()957     protected void onDetachedFromWindow() {
958         super.onDetachedFromWindow();
959         mPreviewPlacerView.removeAllViews();
960         // Notify the ResearchLogger (development only diagnostics) that the keyboard view has
961         // been detached.  This is needed to invalidate the reference of {@link MainKeyboardView}
962         // to null.
963         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
964             ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
965         }
966     }
967 
onCreateMoreKeysPanel(final Key key, final Context context)968     private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
969         if (key.mMoreKeys == null) {
970             return null;
971         }
972         Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
973         if (moreKeysKeyboard == null) {
974             moreKeysKeyboard = new MoreKeysKeyboard.Builder(
975                     context, key, this, mKeyPreviewDrawParams).build();
976             mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
977         }
978 
979         final View container = mMoreKeysKeyboardContainer;
980         final MoreKeysKeyboardView moreKeysKeyboardView =
981                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
982         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
983         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
984         return moreKeysKeyboardView;
985     }
986 
987     /**
988      * Called when a key is long pressed.
989      * @param tracker the pointer tracker which pressed the parent key
990      * @return true if the long press is handled, false otherwise. Subclasses should call the
991      * method on the base class if the subclass doesn't wish to handle the call.
992      */
onLongPress(final PointerTracker tracker)993     private boolean onLongPress(final PointerTracker tracker) {
994         if (isShowingMoreKeysPanel()) {
995             return false;
996         }
997         final Key key = tracker.getKey();
998         if (key == null) {
999             return false;
1000         }
1001         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
1002             ResearchLogger.mainKeyboardView_onLongPress();
1003         }
1004         final int code = key.mCode;
1005         if (key.hasEmbeddedMoreKey()) {
1006             final int embeddedCode = key.mMoreKeys[0].mCode;
1007             tracker.onLongPressed();
1008             invokeCodeInput(embeddedCode);
1009             invokeReleaseKey(code);
1010             KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code);
1011             return true;
1012         }
1013         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
1014             // Long pressing the space key invokes IME switcher dialog.
1015             if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
1016                 tracker.onLongPressed();
1017                 invokeReleaseKey(code);
1018                 return true;
1019             }
1020         }
1021         return openMoreKeysPanel(key, tracker);
1022     }
1023 
invokeCustomRequest(final int requestCode)1024     private boolean invokeCustomRequest(final int requestCode) {
1025         return mKeyboardActionListener.onCustomRequest(requestCode);
1026     }
1027 
invokeCodeInput(final int code)1028     private void invokeCodeInput(final int code) {
1029         mKeyboardActionListener.onCodeInput(
1030                 code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
1031     }
1032 
invokeReleaseKey(final int code)1033     private void invokeReleaseKey(final int code) {
1034         mKeyboardActionListener.onReleaseKey(code, false);
1035     }
1036 
openMoreKeysPanel(final Key key, final PointerTracker tracker)1037     private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) {
1038         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
1039         if (moreKeysPanel == null) {
1040             return false;
1041         }
1042 
1043         final int[] lastCoords = CoordinateUtils.newInstance();
1044         tracker.getLastCoordinates(lastCoords);
1045         final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview();
1046         // The more keys keyboard is usually horizontally aligned with the center of the parent key.
1047         // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
1048         // keys keyboard is placed at the touch point of the parent key.
1049         final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
1050                 ? CoordinateUtils.x(lastCoords)
1051                 : key.mX + key.mWidth / 2;
1052         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
1053         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
1054         // aligned with the bottom edge of the visible part of the key preview.
1055         // {@code mPreviewVisibleOffset} has been set appropriately in
1056         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
1057         final int pointY = key.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
1058         moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
1059         final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords));
1060         final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords));
1061         tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
1062         return true;
1063     }
1064 
isInSlidingKeyInput()1065     public boolean isInSlidingKeyInput() {
1066         if (isShowingMoreKeysPanel()) {
1067             return true;
1068         }
1069         return PointerTracker.isAnyInSlidingKeyInput();
1070     }
1071 
1072     @Override
onShowMoreKeysPanel(final MoreKeysPanel panel)1073     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
1074         locatePreviewPlacerView();
1075         if (isShowingMoreKeysPanel()) {
1076             onDismissMoreKeysPanel();
1077         }
1078         mPreviewPlacerView.addView(panel.getContainerView());
1079         mMoreKeysPanel = panel;
1080         dimEntireKeyboard(true /* dimmed */);
1081     }
1082 
isShowingMoreKeysPanel()1083     public boolean isShowingMoreKeysPanel() {
1084         return mMoreKeysPanel != null && mMoreKeysPanel.isShowingInParent();
1085     }
1086 
1087     @Override
onCancelMoreKeysPanel()1088     public void onCancelMoreKeysPanel() {
1089         PointerTracker.dismissAllMoreKeysPanels();
1090     }
1091 
1092     @Override
onDismissMoreKeysPanel()1093     public boolean onDismissMoreKeysPanel() {
1094         dimEntireKeyboard(false /* dimmed */);
1095         if (isShowingMoreKeysPanel()) {
1096             mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
1097             mMoreKeysPanel = null;
1098             return true;
1099         }
1100         return false;
1101     }
1102 
1103     @Override
dispatchTouchEvent(MotionEvent event)1104     public boolean dispatchTouchEvent(MotionEvent event) {
1105         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
1106             return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event);
1107         }
1108         return super.dispatchTouchEvent(event);
1109     }
1110 
1111     @Override
onTouchEvent(final MotionEvent me)1112     public boolean onTouchEvent(final MotionEvent me) {
1113         if (getKeyboard() == null) {
1114             return false;
1115         }
1116         return mTouchScreenRegulator.onTouchEvent(me);
1117     }
1118 
1119     @Override
processMotionEvent(final MotionEvent me)1120     public boolean processMotionEvent(final MotionEvent me) {
1121         final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
1122         final int action = me.getActionMasked();
1123         final int pointerCount = me.getPointerCount();
1124         final int oldPointerCount = mOldPointerCount;
1125         mOldPointerCount = pointerCount;
1126 
1127         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
1128         // If the device does not have distinct multi-touch support panel, ignore all multi-touch
1129         // events except a transition from/to single-touch.
1130         if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
1131             return true;
1132         }
1133 
1134         final long eventTime = me.getEventTime();
1135         final int index = me.getActionIndex();
1136         final int id = me.getPointerId(index);
1137         final int x = (int)me.getX(index);
1138         final int y = (int)me.getY(index);
1139 
1140         // TODO: This might be moved to the tracker.processMotionEvent() call below.
1141         if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) {
1142             writeUsabilityStudyLog(me, action, eventTime, index, id, x, y);
1143         }
1144         // TODO: This should be moved to the tracker.processMotionEvent() call below.
1145         // Currently the same "move" event is being logged twice.
1146         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
1147             ResearchLogger.mainKeyboardView_processMotionEvent(
1148                     me, action, eventTime, index, id, x, y);
1149         }
1150 
1151         if (mKeyTimerHandler.isInKeyRepeat()) {
1152             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
1153             // Key repeating timer will be canceled if 2 or more keys are in action, and current
1154             // event (UP or DOWN) is non-modifier key.
1155             if (pointerCount > 1 && !tracker.isModifier()) {
1156                 mKeyTimerHandler.cancelKeyRepeatTimer();
1157             }
1158             // Up event will pass through.
1159         }
1160 
1161         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
1162         // Translate mutli-touch event to single-touch events on the device that has no distinct
1163         // multi-touch panel.
1164         if (nonDistinctMultitouch) {
1165             // Use only main (id=0) pointer tracker.
1166             final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
1167             if (pointerCount == 1 && oldPointerCount == 2) {
1168                 // Multi-touch to single touch transition.
1169                 // Send a down event for the latest pointer if the key is different from the
1170                 // previous key.
1171                 final Key newKey = tracker.getKeyOn(x, y);
1172                 if (mOldKey != newKey) {
1173                     tracker.onDownEvent(x, y, eventTime, this);
1174                     if (action == MotionEvent.ACTION_UP) {
1175                         tracker.onUpEvent(x, y, eventTime);
1176                     }
1177                 }
1178             } else if (pointerCount == 2 && oldPointerCount == 1) {
1179                 // Single-touch to multi-touch transition.
1180                 // Send an up event for the last pointer.
1181                 final int[] lastCoords = CoordinateUtils.newInstance();
1182                 mOldKey = tracker.getKeyOn(
1183                         CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords));
1184                 tracker.onUpEvent(
1185                         CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime);
1186             } else if (pointerCount == 1 && oldPointerCount == 1) {
1187                 tracker.processMotionEvent(action, x, y, eventTime, this);
1188             } else {
1189                 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
1190                         + " (old " + oldPointerCount + ")");
1191             }
1192             return true;
1193         }
1194 
1195         if (action == MotionEvent.ACTION_MOVE) {
1196             for (int i = 0; i < pointerCount; i++) {
1197                 final int pointerId = me.getPointerId(i);
1198                 final PointerTracker tracker = PointerTracker.getPointerTracker(
1199                         pointerId, this);
1200                 final int px = (int)me.getX(i);
1201                 final int py = (int)me.getY(i);
1202                 tracker.onMoveEvent(px, py, eventTime, me);
1203                 if (ENABLE_USABILITY_STUDY_LOG) {
1204                     writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py);
1205                 }
1206                 // TODO: This seems to be no longer necessary, and confusing because it leads to
1207                 // duplicate MotionEvents being recorded.
1208                 // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
1209                 //     ResearchLogger.mainKeyboardView_processMotionEvent(
1210                 //             me, action, eventTime, i, pointerId, px, py);
1211                 // }
1212             }
1213         } else {
1214             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
1215             tracker.processMotionEvent(action, x, y, eventTime, this);
1216         }
1217 
1218         return true;
1219     }
1220 
writeUsabilityStudyLog(final MotionEvent me, final int action, final long eventTime, final int index, final int id, final int x, final int y)1221     private static void writeUsabilityStudyLog(final MotionEvent me, final int action,
1222             final long eventTime, final int index, final int id, final int x, final int y) {
1223         final String eventTag;
1224         switch (action) {
1225         case MotionEvent.ACTION_UP:
1226             eventTag = "[Up]";
1227             break;
1228         case MotionEvent.ACTION_DOWN:
1229             eventTag = "[Down]";
1230             break;
1231         case MotionEvent.ACTION_POINTER_UP:
1232             eventTag = "[PointerUp]";
1233             break;
1234         case MotionEvent.ACTION_POINTER_DOWN:
1235             eventTag = "[PointerDown]";
1236             break;
1237         case MotionEvent.ACTION_MOVE:
1238             eventTag = "[Move]";
1239             break;
1240         default:
1241             eventTag = "[Action" + action + "]";
1242             break;
1243         }
1244         final float size = me.getSize(index);
1245         final float pressure = me.getPressure(index);
1246         UsabilityStudyLogUtils.getInstance().write(
1247                 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
1248     }
1249 
cancelAllMessages()1250     public void cancelAllMessages() {
1251         mKeyTimerHandler.cancelAllMessages();
1252         mDrawingHandler.cancelAllMessages();
1253     }
1254 
closing()1255     public void closing() {
1256         dismissAllKeyPreviews();
1257         cancelAllMessages();
1258         onDismissMoreKeysPanel();
1259         mMoreKeysKeyboardCache.clear();
1260     }
1261 
1262     /**
1263      * Receives hover events from the input framework.
1264      *
1265      * @param event The motion event to be dispatched.
1266      * @return {@code true} if the event was handled by the view, {@code false}
1267      *         otherwise
1268      */
1269     @Override
dispatchHoverEvent(final MotionEvent event)1270     public boolean dispatchHoverEvent(final MotionEvent event) {
1271         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
1272             final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
1273             return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
1274         }
1275 
1276         // Reflection doesn't support calling superclass methods.
1277         return false;
1278     }
1279 
updateShortcutKey(final boolean available)1280     public void updateShortcutKey(final boolean available) {
1281         final Keyboard keyboard = getKeyboard();
1282         if (keyboard == null) {
1283             return;
1284         }
1285         final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT);
1286         if (shortcutKey == null) {
1287             return;
1288         }
1289         shortcutKey.setEnabled(available);
1290         invalidateKey(shortcutKey);
1291     }
1292 
startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes)1293     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
1294             final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
1295         mNeedsToDisplayLanguage = needsToDisplayLanguage;
1296         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
1297         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
1298         if (animator == null) {
1299             mNeedsToDisplayLanguage = false;
1300         } else {
1301             if (subtypeChanged && needsToDisplayLanguage) {
1302                 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
1303                 if (animator.isStarted()) {
1304                     animator.cancel();
1305                 }
1306                 animator.start();
1307             } else {
1308                 if (!animator.isStarted()) {
1309                     mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
1310                 }
1311             }
1312         }
1313         invalidateKey(mSpaceKey);
1314     }
1315 
updateAutoCorrectionState(final boolean isAutoCorrection)1316     public void updateAutoCorrectionState(final boolean isAutoCorrection) {
1317         if (!mAutoCorrectionSpacebarLedEnabled) {
1318             return;
1319         }
1320         mAutoCorrectionSpacebarLedOn = isAutoCorrection;
1321         invalidateKey(mSpaceKey);
1322     }
1323 
dimEntireKeyboard(final boolean dimmed)1324     private void dimEntireKeyboard(final boolean dimmed) {
1325         final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
1326         mNeedsToDimEntireKeyboard = dimmed;
1327         if (needsRedrawing) {
1328             invalidateAllKeys();
1329         }
1330     }
1331 
1332     @Override
onDraw(final Canvas canvas)1333     protected void onDraw(final Canvas canvas) {
1334         super.onDraw(canvas);
1335 
1336         // Overlay a dark rectangle to dim.
1337         if (mNeedsToDimEntireKeyboard) {
1338             canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
1339         }
1340     }
1341 
1342     @Override
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)1343     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
1344             final KeyDrawParams params) {
1345         if (key.altCodeWhileTyping() && key.isEnabled()) {
1346             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
1347         }
1348         if (key.mCode == Constants.CODE_SPACE) {
1349             drawSpacebar(key, canvas, paint);
1350             // Whether space key needs to show the "..." popup hint for special purposes
1351             if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
1352                 drawKeyPopupHint(key, canvas, paint, params);
1353             }
1354         } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) {
1355             super.onDrawKeyTopVisuals(key, canvas, paint, params);
1356             drawKeyPopupHint(key, canvas, paint, params);
1357         } else {
1358             super.onDrawKeyTopVisuals(key, canvas, paint, params);
1359         }
1360     }
1361 
fitsTextIntoWidth(final int width, final String text, final Paint paint)1362     private static boolean fitsTextIntoWidth(final int width, final String text,
1363             final Paint paint) {
1364         paint.setTextScaleX(1.0f);
1365         final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
1366         if (textWidth < width) {
1367             return true;
1368         }
1369 
1370         final float scaleX = width / textWidth;
1371         if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
1372             return false;
1373         }
1374 
1375         paint.setTextScaleX(scaleX);
1376         return TypefaceUtils.getLabelWidth(text, paint) < width;
1377     }
1378 
1379     // Layout language name on spacebar.
layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width)1380     private static String layoutLanguageOnSpacebar(final Paint paint,
1381             final InputMethodSubtype subtype, final int width) {
1382         // Choose appropriate language name to fit into the width.
1383         final String fullText = getFullDisplayName(subtype);
1384         if (fitsTextIntoWidth(width, fullText, paint)) {
1385             return fullText;
1386         }
1387 
1388         final String middleText = getMiddleDisplayName(subtype);
1389         if (fitsTextIntoWidth(width, middleText, paint)) {
1390             return middleText;
1391         }
1392 
1393         final String shortText = getShortDisplayName(subtype);
1394         if (fitsTextIntoWidth(width, shortText, paint)) {
1395             return shortText;
1396         }
1397 
1398         return "";
1399     }
1400 
drawSpacebar(final Key key, final Canvas canvas, final Paint paint)1401     private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
1402         final int width = key.mWidth;
1403         final int height = key.mHeight;
1404 
1405         // If input language are explicitly selected.
1406         if (mNeedsToDisplayLanguage) {
1407             paint.setTextAlign(Align.CENTER);
1408             paint.setTypeface(Typeface.DEFAULT);
1409             paint.setTextSize(mSpacebarTextSize);
1410             final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
1411             final String language = layoutLanguageOnSpacebar(paint, subtype, width);
1412             // Draw language text with shadow
1413             final float descent = paint.descent();
1414             final float textHeight = -paint.ascent() + descent;
1415             final float baseline = height / 2 + textHeight / 2;
1416             paint.setColor(mSpacebarTextShadowColor);
1417             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
1418             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
1419             paint.setColor(mSpacebarTextColor);
1420             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
1421             canvas.drawText(language, width / 2, baseline - descent, paint);
1422         }
1423 
1424         // Draw the spacebar icon at the bottom
1425         if (mAutoCorrectionSpacebarLedOn) {
1426             final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
1427             final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
1428             int x = (width - iconWidth) / 2;
1429             int y = height - iconHeight;
1430             drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
1431         } else if (mSpaceIcon != null) {
1432             final int iconWidth = mSpaceIcon.getIntrinsicWidth();
1433             final int iconHeight = mSpaceIcon.getIntrinsicHeight();
1434             int x = (width - iconWidth) / 2;
1435             int y = height - iconHeight;
1436             drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
1437         }
1438     }
1439 
1440     // InputMethodSubtype's display name for spacebar text in its locale.
1441     //        isAdditionalSubtype (T=true, F=false)
1442     // locale layout  | Short  Middle      Full
1443     // ------ ------- - ---- --------- ----------------------
1444     //  en_US qwerty  F  En  English   English (US)           exception
1445     //  en_GB qwerty  F  En  English   English (UK)           exception
1446     //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
1447     //  fr    azerty  F  Fr  Français  Français
1448     //  fr_CA qwerty  F  Fr  Français  Français (Canada)
1449     //  de    qwertz  F  De  Deutsch   Deutsch
1450     //  zz    qwerty  F      QWERTY    QWERTY
1451     //  fr    qwertz  T  Fr  Français  Français
1452     //  de    qwerty  T  De  Deutsch   Deutsch
1453     //  en_US azerty  T  En  English   English (US)
1454     //  zz    azerty  T      AZERTY    AZERTY
1455 
1456     // Get InputMethodSubtype's full display name in its locale.
getFullDisplayName(final InputMethodSubtype subtype)1457     static String getFullDisplayName(final InputMethodSubtype subtype) {
1458         if (SubtypeLocale.isNoLanguage(subtype)) {
1459             return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
1460         }
1461         return SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale());
1462     }
1463 
1464     // Get InputMethodSubtype's short display name in its locale.
getShortDisplayName(final InputMethodSubtype subtype)1465     static String getShortDisplayName(final InputMethodSubtype subtype) {
1466         if (SubtypeLocale.isNoLanguage(subtype)) {
1467             return "";
1468         }
1469         final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
1470         return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
1471     }
1472 
1473     // Get InputMethodSubtype's middle display name in its locale.
getMiddleDisplayName(final InputMethodSubtype subtype)1474     static String getMiddleDisplayName(final InputMethodSubtype subtype) {
1475         if (SubtypeLocale.isNoLanguage(subtype)) {
1476             return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
1477         }
1478         final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
1479         return SubtypeLocale.getSubtypeLocaleDisplayName(locale.getLanguage());
1480     }
1481 }
1482