Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.animation.AnimatorInflater;
     20 import android.animation.ObjectAnimator;
     21 import android.content.Context;
     22 import android.content.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 
    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
    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 
    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
    211         public void startKeyRepeatTimer(final PointerTracker tracker) {
    212             startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
    213         }
    214 
    215         public void cancelKeyRepeatTimer() {
    216             removeMessages(MSG_REPEAT_KEY);
    217         }
    218 
    219         // TODO: Suppress layout changes in key repeat mode
    220         public boolean isInKeyRepeat() {
    221             return hasMessages(MSG_REPEAT_KEY);
    222         }
    223 
    224         @Override
    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
    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
    269         public void cancelLongPressTimer() {
    270             removeMessages(MSG_LONGPRESS_KEY);
    271         }
    272 
    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 
    285         private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
    286             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
    287                     keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
    288         }
    289 
    290         private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
    291             cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
    292                     keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
    293         }
    294 
    295         @Override
    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
    321         public boolean isTypingState() {
    322             return hasMessages(MSG_TYPING_STATE_EXPIRED);
    323         }
    324 
    325         @Override
    326         public void startDoubleTapTimer() {
    327             sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
    328                     ViewConfiguration.getDoubleTapTimeout());
    329         }
    330 
    331         @Override
    332         public void cancelDoubleTapTimer() {
    333             removeMessages(MSG_DOUBLE_TAP);
    334         }
    335 
    336         @Override
    337         public boolean isInDoubleTapTimeout() {
    338             return hasMessages(MSG_DOUBLE_TAP);
    339         }
    340 
    341         @Override
    342         public void cancelKeyTimers() {
    343             cancelKeyRepeatTimer();
    344             cancelLongPressTimer();
    345         }
    346 
    347         public void cancelAllMessages() {
    348             cancelKeyTimers();
    349         }
    350     }
    351 
    352     public MainKeyboardView(final Context context, final AttributeSet attrs) {
    353         this(context, attrs, R.attr.mainKeyboardViewStyle);
    354     }
    355 
    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 
    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}.
    421     public int getLanguageOnSpacebarAnimAlpha() {
    422         return mLanguageOnSpacebarAnimAlpha;
    423     }
    424 
    425     public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
    426         mLanguageOnSpacebarAnimAlpha = alpha;
    427         invalidateKey(mSpaceKey);
    428     }
    429 
    430     public int getAltCodeKeyWhileTypingAnimAlpha() {
    431         return mAltCodeKeyWhileTypingAnimAlpha;
    432     }
    433 
    434     public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
    435         mAltCodeKeyWhileTypingAnimAlpha = alpha;
    436         updateAltCodeKeyWhileTyping();
    437     }
    438 
    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
    449     public KeyboardActionListener getKeyboardActionListener() {
    450         return mKeyboardActionListener;
    451     }
    452 
    453     @Override
    454     public KeyDetector getKeyDetector() {
    455         return mKeyDetector;
    456     }
    457 
    458     @Override
    459     public DrawingProxy getDrawingProxy() {
    460         return this;
    461     }
    462 
    463     @Override
    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
    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.
    501     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
    502         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
    503     }
    504 
    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      */
    513     public boolean hasDistinctMultitouch() {
    514         return mHasDistinctMultitouch;
    515     }
    516 
    517     public void setDistinctMultitouch(final boolean hasDistinctMultitouch) {
    518         mHasDistinctMultitouch = hasDistinctMultitouch;
    519     }
    520 
    521     @Override
    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
    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
    543     public void cancelAllMessages() {
    544         mKeyTimerHandler.cancelAllMessages();
    545         super.cancelAllMessages();
    546     }
    547 
    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.
    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      */
    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 
    614     private boolean invokeCustomRequest(final int code) {
    615         return mKeyboardActionListener.onCustomRequest(code);
    616     }
    617 
    618     private void invokeCodeInput(final int primaryCode) {
    619         mKeyboardActionListener.onCodeInput(
    620                 primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
    621     }
    622 
    623     private void invokeReleaseKey(final int primaryCode) {
    624         mKeyboardActionListener.onReleaseKey(primaryCode, false);
    625     }
    626 
    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 
    665     public boolean isInSlidingKeyInput() {
    666         if (mMoreKeysPanel != null) {
    667             return true;
    668         } else {
    669             return PointerTracker.isAnyInSlidingKeyInput();
    670         }
    671     }
    672 
    673     public int getPointerCount() {
    674         return mOldPointerCount;
    675     }
    676 
    677     @Override
    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
    686     public boolean onTouchEvent(final MotionEvent me) {
    687         if (getKeyboard() == null) {
    688             return false;
    689         }
    690         return mTouchScreenRegulator.onTouchEvent(me);
    691     }
    692 
    693     @Override
    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
    835     public void closing() {
    836         super.closing();
    837         dismissMoreKeysPanel();
    838         mMoreKeysPanelCache.clear();
    839     }
    840 
    841     @Override
    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
    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 
    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 
    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 
    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 
    911     public void updateAutoCorrectionState(final boolean isAutoCorrection) {
    912         if (!mAutoCorrectionSpacebarLedEnabled) return;
    913         mAutoCorrectionSpacebarLedOn = isAutoCorrection;
    914         invalidateKey(mSpaceKey);
    915     }
    916 
    917     @Override
    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 
    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.
    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 
    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  Franais  Franais
   1017     //  fr_CA qwerty F  Fr  Franais  Franais (Canada)
   1018     //  de    qwertz F  De  Deutsch   Deutsch
   1019     //  zz    qwerty F      QWERTY    QWERTY
   1020     //  fr    qwertz T  Fr  Franais  Franais (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.
   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.
   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.
   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