• 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.pm.PackageManager;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Paint;
27 import android.graphics.Paint.Align;
28 import android.graphics.Typeface;
29 import android.graphics.drawable.Drawable;
30 import android.os.Message;
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.ViewConfiguration;
38 import android.view.ViewGroup;
39 import android.view.inputmethod.InputMethodSubtype;
40 import android.widget.PopupWindow;
41 
42 import com.android.inputmethod.accessibility.AccessibilityUtils;
43 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
44 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
45 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
46 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
47 import com.android.inputmethod.keyboard.internal.SuddenJumpingTouchEventHandler;
48 import com.android.inputmethod.latin.Constants;
49 import com.android.inputmethod.latin.LatinIME;
50 import com.android.inputmethod.latin.LatinImeLogger;
51 import com.android.inputmethod.latin.R;
52 import com.android.inputmethod.latin.ResourceUtils;
53 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
54 import com.android.inputmethod.latin.StringUtils;
55 import com.android.inputmethod.latin.SubtypeLocale;
56 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
57 import com.android.inputmethod.latin.define.ProductionFlag;
58 import com.android.inputmethod.research.ResearchLogger;
59 
60 import java.util.Locale;
61 import java.util.WeakHashMap;
62 
63 /**
64  * A view that is responsible for detecting key presses and touch movements.
65  *
66  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
67  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
68  * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
69  * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
70  * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
71  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
72  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
73  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
74  * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
75  * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
76  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
77  * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
78  * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
79  * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
80  * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
81  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
82  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
83  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
84  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
85  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
86  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
87  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
88  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
89  * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
90  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
91  * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
92  * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
93  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
94  * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
95  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
96  */
97 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
98         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
99     private static final String TAG = MainKeyboardView.class.getSimpleName();
100 
101     // TODO: Kill process when the usability study mode was changed.
102     private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
103 
104     /** Listener for {@link KeyboardActionListener}. */
105     private KeyboardActionListener mKeyboardActionListener;
106 
107     /* Space key and its icons */
108     private Key mSpaceKey;
109     private Drawable mSpaceIcon;
110     // Stuff to draw language name on spacebar.
111     private final int mLanguageOnSpacebarFinalAlpha;
112     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
113     private boolean mNeedsToDisplayLanguage;
114     private boolean mHasMultipleEnabledIMEsOrSubtypes;
115     private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
116     private final float mSpacebarTextRatio;
117     private float mSpacebarTextSize;
118     private final int mSpacebarTextColor;
119     private final int mSpacebarTextShadowColor;
120     // The minimum x-scale to fit the language name on spacebar.
121     private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
122     // Stuff to draw auto correction LED on spacebar.
123     private boolean mAutoCorrectionSpacebarLedOn;
124     private final boolean mAutoCorrectionSpacebarLedEnabled;
125     private final Drawable mAutoCorrectionSpacebarLedIcon;
126     private static final int SPACE_LED_LENGTH_PERCENT = 80;
127 
128     // Stuff to draw altCodeWhileTyping keys.
129     private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
130     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
131     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
132 
133     // More keys keyboard
134     private PopupWindow mMoreKeysWindow;
135     private MoreKeysPanel mMoreKeysPanel;
136     private int mMoreKeysPanelPointerTrackerId;
137     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
138             new WeakHashMap<Key, MoreKeysPanel>();
139     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
140 
141     private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
142 
143     protected KeyDetector mKeyDetector;
144     private boolean mHasDistinctMultitouch;
145     private int mOldPointerCount = 1;
146     private Key mOldKey;
147 
148     private final KeyTimerHandler mKeyTimerHandler;
149 
150     private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
151             implements TimerProxy {
152         private static final int MSG_TYPING_STATE_EXPIRED = 0;
153         private static final int MSG_REPEAT_KEY = 1;
154         private static final int MSG_LONGPRESS_KEY = 2;
155         private static final int MSG_DOUBLE_TAP = 3;
156 
157         private final int mKeyRepeatStartTimeout;
158         private final int mKeyRepeatInterval;
159         private final int mLongPressKeyTimeout;
160         private final int mLongPressShiftKeyTimeout;
161         private final int mIgnoreAltCodeKeyTimeout;
162 
KeyTimerHandler(final MainKeyboardView outerInstance, final TypedArray mainKeyboardViewAttr)163         public KeyTimerHandler(final MainKeyboardView outerInstance,
164                 final TypedArray mainKeyboardViewAttr) {
165             super(outerInstance);
166 
167             mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
168                     R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
169             mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
170                     R.styleable.MainKeyboardView_keyRepeatInterval, 0);
171             mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
172                     R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
173             mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
174                     R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
175             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
176                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
177         }
178 
179         @Override
handleMessage(final Message msg)180         public void handleMessage(final Message msg) {
181             final MainKeyboardView keyboardView = getOuterInstance();
182             final PointerTracker tracker = (PointerTracker) msg.obj;
183             switch (msg.what) {
184             case MSG_TYPING_STATE_EXPIRED:
185                 startWhileTypingFadeinAnimation(keyboardView);
186                 break;
187             case MSG_REPEAT_KEY:
188                 final Key currentKey = tracker.getKey();
189                 if (currentKey != null && currentKey.mCode == msg.arg1) {
190                     tracker.onRegisterKey(currentKey);
191                     startKeyRepeatTimer(tracker, mKeyRepeatInterval);
192                 }
193                 break;
194             case MSG_LONGPRESS_KEY:
195                 if (tracker != null) {
196                     keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker);
197                 } else {
198                     KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1);
199                 }
200                 break;
201             }
202         }
203 
startKeyRepeatTimer(final PointerTracker tracker, final long delay)204         private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
205             final Key key = tracker.getKey();
206             if (key == null) return;
207             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
208         }
209 
210         @Override
startKeyRepeatTimer(final PointerTracker tracker)211         public void startKeyRepeatTimer(final PointerTracker tracker) {
212             startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
213         }
214 
cancelKeyRepeatTimer()215         public void cancelKeyRepeatTimer() {
216             removeMessages(MSG_REPEAT_KEY);
217         }
218 
219         // TODO: Suppress layout changes in key repeat mode
isInKeyRepeat()220         public boolean isInKeyRepeat() {
221             return hasMessages(MSG_REPEAT_KEY);
222         }
223 
224         @Override
startLongPressTimer(final int code)225         public void startLongPressTimer(final int code) {
226             cancelLongPressTimer();
227             final int delay;
228             switch (code) {
229             case Keyboard.CODE_SHIFT:
230                 delay = mLongPressShiftKeyTimeout;
231                 break;
232             default:
233                 delay = 0;
234                 break;
235             }
236             if (delay > 0) {
237                 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay);
238             }
239         }
240 
241         @Override
startLongPressTimer(final PointerTracker tracker)242         public void startLongPressTimer(final PointerTracker tracker) {
243             cancelLongPressTimer();
244             if (tracker == null) {
245                 return;
246             }
247             final Key key = tracker.getKey();
248             final int delay;
249             switch (key.mCode) {
250             case Keyboard.CODE_SHIFT:
251                 delay = mLongPressShiftKeyTimeout;
252                 break;
253             default:
254                 if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
255                     // We use longer timeout for sliding finger input started from the symbols
256                     // mode key.
257                     delay = mLongPressKeyTimeout * 3;
258                 } else {
259                     delay = mLongPressKeyTimeout;
260                 }
261                 break;
262             }
263             if (delay > 0) {
264                 sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
265             }
266         }
267 
268         @Override
cancelLongPressTimer()269         public void cancelLongPressTimer() {
270             removeMessages(MSG_LONGPRESS_KEY);
271         }
272 
cancelAndStartAnimators(final ObjectAnimator animatorToCancel, final ObjectAnimator animatorToStart)273         private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
274                 final ObjectAnimator animatorToStart) {
275             float startFraction = 0.0f;
276             if (animatorToCancel.isStarted()) {
277                 animatorToCancel.cancel();
278                 startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
279             }
280             final long startTime = (long)(animatorToStart.getDuration() * startFraction);
281             animatorToStart.start();
282             animatorToStart.setCurrentPlayTime(startTime);
283         }
284 
startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView)285         private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
286             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
287                     keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
288         }
289 
startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView)290         private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
291             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
292                     keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
293         }
294 
295         @Override
startTypingStateTimer(final Key typedKey)296         public void startTypingStateTimer(final Key typedKey) {
297             if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
298                 return;
299             }
300 
301             final boolean isTyping = isTypingState();
302             removeMessages(MSG_TYPING_STATE_EXPIRED);
303             final MainKeyboardView keyboardView = getOuterInstance();
304 
305             // When user hits the space or the enter key, just cancel the while-typing timer.
306             final int typedCode = typedKey.mCode;
307             if (typedCode == Keyboard.CODE_SPACE || typedCode == Keyboard.CODE_ENTER) {
308                 startWhileTypingFadeinAnimation(keyboardView);
309                 return;
310             }
311 
312             sendMessageDelayed(
313                     obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
314             if (isTyping) {
315                 return;
316             }
317             startWhileTypingFadeoutAnimation(keyboardView);
318         }
319 
320         @Override
isTypingState()321         public boolean isTypingState() {
322             return hasMessages(MSG_TYPING_STATE_EXPIRED);
323         }
324 
325         @Override
startDoubleTapTimer()326         public void startDoubleTapTimer() {
327             sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
328                     ViewConfiguration.getDoubleTapTimeout());
329         }
330 
331         @Override
cancelDoubleTapTimer()332         public void cancelDoubleTapTimer() {
333             removeMessages(MSG_DOUBLE_TAP);
334         }
335 
336         @Override
isInDoubleTapTimeout()337         public boolean isInDoubleTapTimeout() {
338             return hasMessages(MSG_DOUBLE_TAP);
339         }
340 
341         @Override
cancelKeyTimers()342         public void cancelKeyTimers() {
343             cancelKeyRepeatTimer();
344             cancelLongPressTimer();
345         }
346 
cancelAllMessages()347         public void cancelAllMessages() {
348             cancelKeyTimers();
349         }
350     }
351 
MainKeyboardView(final Context context, final AttributeSet attrs)352     public MainKeyboardView(final Context context, final AttributeSet attrs) {
353         this(context, attrs, R.attr.mainKeyboardViewStyle);
354     }
355 
MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle)356     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
357         super(context, attrs, defStyle);
358 
359         mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
360 
361         mHasDistinctMultitouch = context.getPackageManager()
362                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
363         final Resources res = getResources();
364         final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
365                 ResourceUtils.getDeviceOverrideValue(res,
366                         R.array.phantom_sudden_move_event_device_list, "false"));
367         PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
368 
369         final TypedArray a = context.obtainStyledAttributes(
370                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
371         mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
372                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
373         mAutoCorrectionSpacebarLedIcon = a.getDrawable(
374                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
375         mSpacebarTextRatio = a.getFraction(
376                 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
377         mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
378         mSpacebarTextShadowColor = a.getColor(
379                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
380         mLanguageOnSpacebarFinalAlpha = a.getInt(
381                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
382                 Constants.Color.ALPHA_OPAQUE);
383         final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
384                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
385         final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
386                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
387         final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
388                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
389 
390         final float keyHysteresisDistance = a.getDimension(
391                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
392         final float keyHysteresisDistanceForSlidingModifier = a.getDimension(
393                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
394         mKeyDetector = new KeyDetector(
395                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
396         mKeyTimerHandler = new KeyTimerHandler(this, a);
397         mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
398                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
399         PointerTracker.setParameters(a);
400         a.recycle();
401 
402         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
403                 languageOnSpacebarFadeoutAnimatorResId, this);
404         mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
405                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
406         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
407                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
408     }
409 
loadObjectAnimator(final int resId, final Object target)410     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
411         if (resId == 0) return null;
412         final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
413                 getContext(), resId);
414         if (animator != null) {
415             animator.setTarget(target);
416         }
417         return animator;
418     }
419 
420     // Getter/setter methods for {@link ObjectAnimator}.
getLanguageOnSpacebarAnimAlpha()421     public int getLanguageOnSpacebarAnimAlpha() {
422         return mLanguageOnSpacebarAnimAlpha;
423     }
424 
setLanguageOnSpacebarAnimAlpha(final int alpha)425     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
426         mLanguageOnSpacebarAnimAlpha = alpha;
427         invalidateKey(mSpaceKey);
428     }
429 
getAltCodeKeyWhileTypingAnimAlpha()430     public int getAltCodeKeyWhileTypingAnimAlpha() {
431         return mAltCodeKeyWhileTypingAnimAlpha;
432     }
433 
setAltCodeKeyWhileTypingAnimAlpha(final int alpha)434     public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
435         mAltCodeKeyWhileTypingAnimAlpha = alpha;
436         updateAltCodeKeyWhileTyping();
437     }
438 
setKeyboardActionListener(final KeyboardActionListener listener)439     public void setKeyboardActionListener(final KeyboardActionListener listener) {
440         mKeyboardActionListener = listener;
441         PointerTracker.setKeyboardActionListener(listener);
442     }
443 
444     /**
445      * Returns the {@link KeyboardActionListener} object.
446      * @return the listener attached to this keyboard
447      */
448     @Override
getKeyboardActionListener()449     public KeyboardActionListener getKeyboardActionListener() {
450         return mKeyboardActionListener;
451     }
452 
453     @Override
getKeyDetector()454     public KeyDetector getKeyDetector() {
455         return mKeyDetector;
456     }
457 
458     @Override
getDrawingProxy()459     public DrawingProxy getDrawingProxy() {
460         return this;
461     }
462 
463     @Override
getTimerProxy()464     public TimerProxy getTimerProxy() {
465         return mKeyTimerHandler;
466     }
467 
468     /**
469      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
470      * view will re-layout itself to accommodate the keyboard.
471      * @see Keyboard
472      * @see #getKeyboard()
473      * @param keyboard the keyboard to display in this view
474      */
475     @Override
setKeyboard(final Keyboard keyboard)476     public void setKeyboard(final Keyboard keyboard) {
477         // Remove any pending messages, except dismissing preview and key repeat.
478         mKeyTimerHandler.cancelLongPressTimer();
479         super.setKeyboard(keyboard);
480         mKeyDetector.setKeyboard(
481                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
482         PointerTracker.setKeyDetector(mKeyDetector);
483         mTouchScreenRegulator.setKeyboard(keyboard);
484         mMoreKeysPanelCache.clear();
485 
486         mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE);
487         mSpaceIcon = (mSpaceKey != null)
488                 ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
489         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
490         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
491         if (ProductionFlag.IS_EXPERIMENTAL) {
492             ResearchLogger.mainKeyboardView_setKeyboard(keyboard);
493         }
494 
495         // This always needs to be set since the accessibility state can
496         // potentially change without the keyboard being set again.
497         AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
498     }
499 
500     // Note that this method is called from a non-UI thread.
setMainDictionaryAvailability(final boolean mainDictionaryAvailable)501     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
502         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
503     }
504 
setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser)505     public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
506         PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
507     }
508 
509     /**
510      * Returns whether the device has distinct multi-touch panel.
511      * @return true if the device has distinct multi-touch panel.
512      */
hasDistinctMultitouch()513     public boolean hasDistinctMultitouch() {
514         return mHasDistinctMultitouch;
515     }
516 
setDistinctMultitouch(final boolean hasDistinctMultitouch)517     public void setDistinctMultitouch(final boolean hasDistinctMultitouch) {
518         mHasDistinctMultitouch = hasDistinctMultitouch;
519     }
520 
521     @Override
onAttachedToWindow()522     protected void onAttachedToWindow() {
523         super.onAttachedToWindow();
524         // Notify the research logger that the keyboard view has been attached.  This is needed
525         // to properly show the splash screen, which requires that the window token of the
526         // KeyboardView be non-null.
527         if (ProductionFlag.IS_EXPERIMENTAL) {
528             ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
529         }
530     }
531 
532     @Override
onDetachedFromWindow()533     protected void onDetachedFromWindow() {
534         super.onDetachedFromWindow();
535         // Notify the research logger that the keyboard view has been detached.  This is needed
536         // to invalidate the reference of {@link MainKeyboardView} to null.
537         if (ProductionFlag.IS_EXPERIMENTAL) {
538             ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
539         }
540     }
541 
542     @Override
cancelAllMessages()543     public void cancelAllMessages() {
544         mKeyTimerHandler.cancelAllMessages();
545         super.cancelAllMessages();
546     }
547 
openMoreKeysKeyboardIfRequired(final Key parentKey, final PointerTracker tracker)548     private boolean openMoreKeysKeyboardIfRequired(final Key parentKey,
549             final PointerTracker tracker) {
550         // Check if we have a popup layout specified first.
551         if (mMoreKeysLayout == 0) {
552             return false;
553         }
554 
555         // Check if we are already displaying popup panel.
556         if (mMoreKeysPanel != null)
557             return false;
558         if (parentKey == null)
559             return false;
560         return onLongPress(parentKey, tracker);
561     }
562 
563     // This default implementation returns a more keys panel.
onCreateMoreKeysPanel(final Key parentKey)564     protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
565         if (parentKey.mMoreKeys == null)
566             return null;
567 
568         final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null);
569         if (container == null)
570             throw new NullPointerException();
571 
572         final MoreKeysKeyboardView moreKeysKeyboardView =
573                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
574         final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this)
575                 .build();
576         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
577         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
578 
579         return moreKeysKeyboardView;
580     }
581 
582     /**
583      * Called when a key is long pressed. By default this will open more keys keyboard associated
584      * with this key.
585      * @param parentKey the key that was long pressed
586      * @param tracker the pointer tracker which pressed the parent key
587      * @return true if the long press is handled, false otherwise. Subclasses should call the
588      * method on the base class if the subclass doesn't wish to handle the call.
589      */
onLongPress(final Key parentKey, final PointerTracker tracker)590     protected boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
591         if (ProductionFlag.IS_EXPERIMENTAL) {
592             ResearchLogger.mainKeyboardView_onLongPress();
593         }
594         final int primaryCode = parentKey.mCode;
595         if (parentKey.hasEmbeddedMoreKey()) {
596             final int embeddedCode = parentKey.mMoreKeys[0].mCode;
597             tracker.onLongPressed();
598             invokeCodeInput(embeddedCode);
599             invokeReleaseKey(primaryCode);
600             KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode);
601             return true;
602         }
603         if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) {
604             // Long pressing the space key invokes IME switcher dialog.
605             if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
606                 tracker.onLongPressed();
607                 invokeReleaseKey(primaryCode);
608                 return true;
609             }
610         }
611         return openMoreKeysPanel(parentKey, tracker);
612     }
613 
invokeCustomRequest(final int code)614     private boolean invokeCustomRequest(final int code) {
615         return mKeyboardActionListener.onCustomRequest(code);
616     }
617 
invokeCodeInput(final int primaryCode)618     private void invokeCodeInput(final int primaryCode) {
619         mKeyboardActionListener.onCodeInput(
620                 primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
621     }
622 
invokeReleaseKey(final int primaryCode)623     private void invokeReleaseKey(final int primaryCode) {
624         mKeyboardActionListener.onReleaseKey(primaryCode, false);
625     }
626 
openMoreKeysPanel(final Key parentKey, final PointerTracker tracker)627     private boolean openMoreKeysPanel(final Key parentKey, final PointerTracker tracker) {
628         MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey);
629         if (moreKeysPanel == null) {
630             moreKeysPanel = onCreateMoreKeysPanel(parentKey);
631             if (moreKeysPanel == null)
632                 return false;
633             mMoreKeysPanelCache.put(parentKey, moreKeysPanel);
634         }
635         if (mMoreKeysWindow == null) {
636             mMoreKeysWindow = new PopupWindow(getContext());
637             mMoreKeysWindow.setBackgroundDrawable(null);
638             mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation);
639         }
640         mMoreKeysPanel = moreKeysPanel;
641         mMoreKeysPanelPointerTrackerId = tracker.mPointerId;
642 
643         final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview();
644         // The more keys keyboard is usually horizontally aligned with the center of the parent key.
645         // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
646         // keys keyboard is placed at the touch point of the parent key.
647         final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
648                 ? tracker.getLastX()
649                 : parentKey.mX + parentKey.mWidth / 2;
650         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
651         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
652         // aligned with the bottom edge of the visible part of the key preview.
653         // {@code mPreviewVisibleOffset} has been set appropriately in
654         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
655         final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
656         moreKeysPanel.showMoreKeysPanel(
657                 this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
658         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
659         final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
660         tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
661         dimEntireKeyboard(true);
662         return true;
663     }
664 
isInSlidingKeyInput()665     public boolean isInSlidingKeyInput() {
666         if (mMoreKeysPanel != null) {
667             return true;
668         } else {
669             return PointerTracker.isAnyInSlidingKeyInput();
670         }
671     }
672 
getPointerCount()673     public int getPointerCount() {
674         return mOldPointerCount;
675     }
676 
677     @Override
dispatchTouchEvent(MotionEvent event)678     public boolean dispatchTouchEvent(MotionEvent event) {
679         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
680             return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event);
681         }
682         return super.dispatchTouchEvent(event);
683     }
684 
685     @Override
onTouchEvent(final MotionEvent me)686     public boolean onTouchEvent(final MotionEvent me) {
687         if (getKeyboard() == null) {
688             return false;
689         }
690         return mTouchScreenRegulator.onTouchEvent(me);
691     }
692 
693     @Override
processMotionEvent(final MotionEvent me)694     public boolean processMotionEvent(final MotionEvent me) {
695         final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
696         final int action = me.getActionMasked();
697         final int pointerCount = me.getPointerCount();
698         final int oldPointerCount = mOldPointerCount;
699         mOldPointerCount = pointerCount;
700 
701         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
702         // If the device does not have distinct multi-touch support panel, ignore all multi-touch
703         // events except a transition from/to single-touch.
704         if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
705             return true;
706         }
707 
708         final long eventTime = me.getEventTime();
709         final int index = me.getActionIndex();
710         final int id = me.getPointerId(index);
711         final int x, y;
712         if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) {
713             x = mMoreKeysPanel.translateX((int)me.getX(index));
714             y = mMoreKeysPanel.translateY((int)me.getY(index));
715         } else {
716             x = (int)me.getX(index);
717             y = (int)me.getY(index);
718         }
719         if (ENABLE_USABILITY_STUDY_LOG) {
720             final String eventTag;
721             switch (action) {
722                 case MotionEvent.ACTION_UP:
723                     eventTag = "[Up]";
724                     break;
725                 case MotionEvent.ACTION_DOWN:
726                     eventTag = "[Down]";
727                     break;
728                 case MotionEvent.ACTION_POINTER_UP:
729                     eventTag = "[PointerUp]";
730                     break;
731                 case MotionEvent.ACTION_POINTER_DOWN:
732                     eventTag = "[PointerDown]";
733                     break;
734                 case MotionEvent.ACTION_MOVE: // Skip this as being logged below
735                     eventTag = "";
736                     break;
737                 default:
738                     eventTag = "[Action" + action + "]";
739                     break;
740             }
741             if (!TextUtils.isEmpty(eventTag)) {
742                 final float size = me.getSize(index);
743                 final float pressure = me.getPressure(index);
744                 UsabilityStudyLogUtils.getInstance().write(
745                         eventTag + eventTime + "," + id + "," + x + "," + y + ","
746                         + size + "," + pressure);
747             }
748         }
749         if (ProductionFlag.IS_EXPERIMENTAL) {
750             ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, index, id,
751                     x, y);
752         }
753 
754         if (mKeyTimerHandler.isInKeyRepeat()) {
755             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
756             // Key repeating timer will be canceled if 2 or more keys are in action, and current
757             // event (UP or DOWN) is non-modifier key.
758             if (pointerCount > 1 && !tracker.isModifier()) {
759                 mKeyTimerHandler.cancelKeyRepeatTimer();
760             }
761             // Up event will pass through.
762         }
763 
764         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
765         // Translate mutli-touch event to single-touch events on the device that has no distinct
766         // multi-touch panel.
767         if (nonDistinctMultitouch) {
768             // Use only main (id=0) pointer tracker.
769             final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
770             if (pointerCount == 1 && oldPointerCount == 2) {
771                 // Multi-touch to single touch transition.
772                 // Send a down event for the latest pointer if the key is different from the
773                 // previous key.
774                 final Key newKey = tracker.getKeyOn(x, y);
775                 if (mOldKey != newKey) {
776                     tracker.onDownEvent(x, y, eventTime, this);
777                     if (action == MotionEvent.ACTION_UP)
778                         tracker.onUpEvent(x, y, eventTime);
779                 }
780             } else if (pointerCount == 2 && oldPointerCount == 1) {
781                 // Single-touch to multi-touch transition.
782                 // Send an up event for the last pointer.
783                 final int lastX = tracker.getLastX();
784                 final int lastY = tracker.getLastY();
785                 mOldKey = tracker.getKeyOn(lastX, lastY);
786                 tracker.onUpEvent(lastX, lastY, eventTime);
787             } else if (pointerCount == 1 && oldPointerCount == 1) {
788                 tracker.processMotionEvent(action, x, y, eventTime, this);
789             } else {
790                 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
791                         + " (old " + oldPointerCount + ")");
792             }
793             return true;
794         }
795 
796         if (action == MotionEvent.ACTION_MOVE) {
797             for (int i = 0; i < pointerCount; i++) {
798                 final int pointerId = me.getPointerId(i);
799                 final PointerTracker tracker = PointerTracker.getPointerTracker(
800                         pointerId, this);
801                 final int px, py;
802                 final MotionEvent motionEvent;
803                 if (mMoreKeysPanel != null
804                         && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
805                     px = mMoreKeysPanel.translateX((int)me.getX(i));
806                     py = mMoreKeysPanel.translateY((int)me.getY(i));
807                     motionEvent = null;
808                 } else {
809                     px = (int)me.getX(i);
810                     py = (int)me.getY(i);
811                     motionEvent = me;
812                 }
813                 tracker.onMoveEvent(px, py, eventTime, motionEvent);
814                 if (ENABLE_USABILITY_STUDY_LOG) {
815                     final float pointerSize = me.getSize(i);
816                     final float pointerPressure = me.getPressure(i);
817                     UsabilityStudyLogUtils.getInstance().write("[Move]"  + eventTime + ","
818                             + pointerId + "," + px + "," + py + ","
819                             + pointerSize + "," + pointerPressure);
820                 }
821                 if (ProductionFlag.IS_EXPERIMENTAL) {
822                     ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime,
823                             i, pointerId, px, py);
824                 }
825             }
826         } else {
827             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
828             tracker.processMotionEvent(action, x, y, eventTime, this);
829         }
830 
831         return true;
832     }
833 
834     @Override
closing()835     public void closing() {
836         super.closing();
837         dismissMoreKeysPanel();
838         mMoreKeysPanelCache.clear();
839     }
840 
841     @Override
dismissMoreKeysPanel()842     public boolean dismissMoreKeysPanel() {
843         if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) {
844             mMoreKeysWindow.dismiss();
845             mMoreKeysPanel = null;
846             mMoreKeysPanelPointerTrackerId = -1;
847             dimEntireKeyboard(false);
848             return true;
849         }
850         return false;
851     }
852 
853     /**
854      * Receives hover events from the input framework.
855      *
856      * @param event The motion event to be dispatched.
857      * @return {@code true} if the event was handled by the view, {@code false}
858      *         otherwise
859      */
860     @Override
dispatchHoverEvent(final MotionEvent event)861     public boolean dispatchHoverEvent(final MotionEvent event) {
862         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
863             final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
864             return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
865         }
866 
867         // Reflection doesn't support calling superclass methods.
868         return false;
869     }
870 
updateShortcutKey(final boolean available)871     public void updateShortcutKey(final boolean available) {
872         final Keyboard keyboard = getKeyboard();
873         if (keyboard == null) return;
874         final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT);
875         if (shortcutKey == null) return;
876         shortcutKey.setEnabled(available);
877         invalidateKey(shortcutKey);
878     }
879 
updateAltCodeKeyWhileTyping()880     private void updateAltCodeKeyWhileTyping() {
881         final Keyboard keyboard = getKeyboard();
882         if (keyboard == null) return;
883         for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
884             invalidateKey(key);
885         }
886     }
887 
startDisplayLanguageOnSpacebar(final boolean subtypeChanged, final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes)888     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
889             final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
890         mNeedsToDisplayLanguage = needsToDisplayLanguage;
891         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
892         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
893         if (animator == null) {
894             mNeedsToDisplayLanguage = false;
895         } else {
896             if (subtypeChanged && needsToDisplayLanguage) {
897                 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
898                 if (animator.isStarted()) {
899                     animator.cancel();
900                 }
901                 animator.start();
902             } else {
903                 if (!animator.isStarted()) {
904                     mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
905                 }
906             }
907         }
908         invalidateKey(mSpaceKey);
909     }
910 
updateAutoCorrectionState(final boolean isAutoCorrection)911     public void updateAutoCorrectionState(final boolean isAutoCorrection) {
912         if (!mAutoCorrectionSpacebarLedEnabled) return;
913         mAutoCorrectionSpacebarLedOn = isAutoCorrection;
914         invalidateKey(mSpaceKey);
915     }
916 
917     @Override
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)918     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
919             final KeyDrawParams params) {
920         if (key.altCodeWhileTyping() && key.isEnabled()) {
921             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
922         }
923         if (key.mCode == Keyboard.CODE_SPACE) {
924             drawSpacebar(key, canvas, paint);
925             // Whether space key needs to show the "..." popup hint for special purposes
926             if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
927                 drawKeyPopupHint(key, canvas, paint, params);
928             }
929         } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) {
930             super.onDrawKeyTopVisuals(key, canvas, paint, params);
931             drawKeyPopupHint(key, canvas, paint, params);
932         } else {
933             super.onDrawKeyTopVisuals(key, canvas, paint, params);
934         }
935     }
936 
fitsTextIntoWidth(final int width, final String text, final Paint paint)937     private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
938         paint.setTextScaleX(1.0f);
939         final float textWidth = getLabelWidth(text, paint);
940         if (textWidth < width) return true;
941 
942         final float scaleX = width / textWidth;
943         if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false;
944 
945         paint.setTextScaleX(scaleX);
946         return getLabelWidth(text, paint) < width;
947     }
948 
949     // Layout language name on spacebar.
layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width)950     private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype,
951             final int width) {
952         // Choose appropriate language name to fit into the width.
953         String text = getFullDisplayName(subtype, getResources());
954         if (fitsTextIntoWidth(width, text, paint)) {
955             return text;
956         }
957 
958         text = getMiddleDisplayName(subtype);
959         if (fitsTextIntoWidth(width, text, paint)) {
960             return text;
961         }
962 
963         text = getShortDisplayName(subtype);
964         if (fitsTextIntoWidth(width, text, paint)) {
965             return text;
966         }
967 
968         return "";
969     }
970 
drawSpacebar(final Key key, final Canvas canvas, final Paint paint)971     private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
972         final int width = key.mWidth;
973         final int height = key.mHeight;
974 
975         // If input language are explicitly selected.
976         if (mNeedsToDisplayLanguage) {
977             paint.setTextAlign(Align.CENTER);
978             paint.setTypeface(Typeface.DEFAULT);
979             paint.setTextSize(mSpacebarTextSize);
980             final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
981             final String language = layoutLanguageOnSpacebar(paint, subtype, width);
982             // Draw language text with shadow
983             final float descent = paint.descent();
984             final float textHeight = -paint.ascent() + descent;
985             final float baseline = height / 2 + textHeight / 2;
986             paint.setColor(mSpacebarTextShadowColor);
987             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
988             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
989             paint.setColor(mSpacebarTextColor);
990             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
991             canvas.drawText(language, width / 2, baseline - descent, paint);
992         }
993 
994         // Draw the spacebar icon at the bottom
995         if (mAutoCorrectionSpacebarLedOn) {
996             final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
997             final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
998             int x = (width - iconWidth) / 2;
999             int y = height - iconHeight;
1000             drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
1001         } else if (mSpaceIcon != null) {
1002             final int iconWidth = mSpaceIcon.getIntrinsicWidth();
1003             final int iconHeight = mSpaceIcon.getIntrinsicHeight();
1004             int x = (width - iconWidth) / 2;
1005             int y = height - iconHeight;
1006             drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
1007         }
1008     }
1009 
1010     // InputMethodSubtype's display name for spacebar text in its locale.
1011     //        isAdditionalSubtype (T=true, F=false)
1012     // locale layout | Short  Middle      Full
1013     // ------ ------ - ---- --------- ----------------------
1014     //  en_US qwerty F  En  English   English (US)           exception
1015     //  en_GB qwerty F  En  English   English (UK)           exception
1016     //  fr    azerty F  Fr  Français  Français
1017     //  fr_CA qwerty F  Fr  Français  Français (Canada)
1018     //  de    qwertz F  De  Deutsch   Deutsch
1019     //  zz    qwerty F      QWERTY    QWERTY
1020     //  fr    qwertz T  Fr  Français  Français (QWERTZ)
1021     //  de    qwerty T  De  Deutsch   Deutsch (QWERTY)
1022     //  en_US azerty T  En  English   English (US) (AZERTY)
1023     //  zz    azerty T      AZERTY    AZERTY
1024 
1025     // Get InputMethodSubtype's full display name in its locale.
getFullDisplayName(final InputMethodSubtype subtype, final Resources res)1026     static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) {
1027         if (SubtypeLocale.isNoLanguage(subtype)) {
1028             return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
1029         }
1030 
1031         return SubtypeLocale.getSubtypeDisplayName(subtype, res);
1032     }
1033 
1034     // Get InputMethodSubtype's short display name in its locale.
getShortDisplayName(final InputMethodSubtype subtype)1035     static String getShortDisplayName(final InputMethodSubtype subtype) {
1036         if (SubtypeLocale.isNoLanguage(subtype)) {
1037             return "";
1038         }
1039         final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
1040         return StringUtils.toTitleCase(locale.getLanguage(), locale);
1041     }
1042 
1043     // Get InputMethodSubtype's middle display name in its locale.
getMiddleDisplayName(final InputMethodSubtype subtype)1044     static String getMiddleDisplayName(final InputMethodSubtype subtype) {
1045         if (SubtypeLocale.isNoLanguage(subtype)) {
1046             return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
1047         }
1048         final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
1049         return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
1050     }
1051 }
1052