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 static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
20 import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_SETTINGS_KEY;
21 
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.content.res.XmlResourceParser;
26 import android.text.InputType;
27 import android.util.Log;
28 import android.util.SparseArray;
29 import android.util.Xml;
30 import android.view.inputmethod.EditorInfo;
31 import android.view.inputmethod.InputMethodSubtype;
32 
33 import com.android.inputmethod.compat.EditorInfoCompatUtils;
34 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
35 import com.android.inputmethod.compat.UserManagerCompatUtils;
36 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
37 import com.android.inputmethod.keyboard.internal.KeyboardParams;
38 import com.android.inputmethod.keyboard.internal.UniqueKeysCache;
39 import com.android.inputmethod.latin.InputAttributes;
40 import com.android.inputmethod.latin.R;
41 import com.android.inputmethod.latin.RichInputMethodSubtype;
42 import com.android.inputmethod.latin.define.DebugFlags;
43 import com.android.inputmethod.latin.utils.InputTypeUtils;
44 import com.android.inputmethod.latin.utils.ScriptUtils;
45 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
46 import com.android.inputmethod.latin.utils.XmlParseUtils;
47 
48 import org.xmlpull.v1.XmlPullParser;
49 import org.xmlpull.v1.XmlPullParserException;
50 
51 import java.io.IOException;
52 import java.lang.ref.SoftReference;
53 import java.util.HashMap;
54 
55 import javax.annotation.Nonnull;
56 import javax.annotation.Nullable;
57 
58 /**
59  * This class represents a set of keyboard layouts. Each of them represents a different keyboard
60  * specific to a keyboard state, such as alphabet, symbols, and so on.  Layouts in the same
61  * {@link KeyboardLayoutSet} are related to each other.
62  * A {@link KeyboardLayoutSet} needs to be created for each
63  * {@link android.view.inputmethod.EditorInfo}.
64  */
65 public final class KeyboardLayoutSet {
66     private static final String TAG = KeyboardLayoutSet.class.getSimpleName();
67     private static final boolean DEBUG_CACHE = false;
68 
69     private static final String TAG_KEYBOARD_SET = "KeyboardLayoutSet";
70     private static final String TAG_ELEMENT = "Element";
71     private static final String TAG_FEATURE = "Feature";
72 
73     private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_";
74 
75     private final Context mContext;
76     @Nonnull
77     private final Params mParams;
78 
79     // How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and
80     // ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of
81     // soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts.
82     private static final int FORCIBLE_CACHE_SIZE = 4;
83     // By construction of soft references, anything that is also referenced somewhere else
84     // will stay in the cache. So we forcibly keep some references in an array to prevent
85     // them from disappearing from sKeyboardCache.
86     private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE];
87     private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
88             new HashMap<>();
89     @Nonnull
90     private static final UniqueKeysCache sUniqueKeysCache = UniqueKeysCache.newInstance();
91     private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes =
92             new HashMap<>();
93 
94     @SuppressWarnings("serial")
95     public static final class KeyboardLayoutSetException extends RuntimeException {
96         public final KeyboardId mKeyboardId;
97 
KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId)98         public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
99             super(cause);
100             mKeyboardId = keyboardId;
101         }
102     }
103 
104     private static final class ElementParams {
105         int mKeyboardXmlId;
106         boolean mProximityCharsCorrectionEnabled;
107         boolean mSupportsSplitLayout;
108         boolean mAllowRedundantMoreKeys;
ElementParams()109         public ElementParams() {}
110     }
111 
112     public static final class Params {
113         String mKeyboardLayoutSetName;
114         int mMode;
115         boolean mDisableTouchPositionCorrectionDataForTest;
116         // TODO: Use {@link InputAttributes} instead of these variables.
117         EditorInfo mEditorInfo;
118         boolean mIsPasswordField;
119         boolean mVoiceInputKeyEnabled;
120         boolean mNoSettingsKey;
121         boolean mLanguageSwitchKeyEnabled;
122         RichInputMethodSubtype mSubtype;
123         boolean mIsSpellChecker;
124         int mKeyboardWidth;
125         int mKeyboardHeight;
126         int mScriptId = ScriptUtils.SCRIPT_LATIN;
127         // Indicates if the user has enabled the split-layout preference
128         // and the required ProductionFlags are enabled.
129         boolean mIsSplitLayoutEnabledByUser;
130         // Indicates if split layout is actually enabled, taking into account
131         // whether the user has enabled it, and the keyboard layout supports it.
132         boolean mIsSplitLayoutEnabled;
133         // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
134         final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
135                 new SparseArray<>();
136     }
137 
onSystemLocaleChanged()138     public static void onSystemLocaleChanged() {
139         clearKeyboardCache();
140     }
141 
onKeyboardThemeChanged()142     public static void onKeyboardThemeChanged() {
143         clearKeyboardCache();
144     }
145 
clearKeyboardCache()146     private static void clearKeyboardCache() {
147         sKeyboardCache.clear();
148         sUniqueKeysCache.clear();
149     }
150 
getScriptId(final Resources resources, @Nonnull final InputMethodSubtype subtype)151     public static int getScriptId(final Resources resources,
152             @Nonnull final InputMethodSubtype subtype) {
153         final Integer value = sScriptIdsForSubtypes.get(subtype);
154         if (null == value) {
155             final int scriptId = Builder.readScriptId(resources, subtype);
156             sScriptIdsForSubtypes.put(subtype, scriptId);
157             return scriptId;
158         }
159         return value;
160     }
161 
KeyboardLayoutSet(final Context context, @Nonnull final Params params)162     KeyboardLayoutSet(final Context context, @Nonnull final Params params) {
163         mContext = context;
164         mParams = params;
165     }
166 
167     @Nonnull
getKeyboard(final int baseKeyboardLayoutSetElementId)168     public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
169         final int keyboardLayoutSetElementId;
170         switch (mParams.mMode) {
171         case KeyboardId.MODE_PHONE:
172             if (baseKeyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS) {
173                 keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS;
174             } else {
175                 keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE;
176             }
177             break;
178         case KeyboardId.MODE_NUMBER:
179         case KeyboardId.MODE_DATE:
180         case KeyboardId.MODE_TIME:
181         case KeyboardId.MODE_DATETIME:
182             keyboardLayoutSetElementId = KeyboardId.ELEMENT_NUMBER;
183             break;
184         default:
185             keyboardLayoutSetElementId = baseKeyboardLayoutSetElementId;
186             break;
187         }
188 
189         ElementParams elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get(
190                 keyboardLayoutSetElementId);
191         if (elementParams == null) {
192             elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get(
193                     KeyboardId.ELEMENT_ALPHABET);
194         }
195         // Note: The keyboard for each shift state, and mode are represented as an elementName
196         // attribute in a keyboard_layout_set XML file.  Also each keyboard layout XML resource is
197         // specified as an elementKeyboard attribute in the file.
198         // The KeyboardId is an internal key for a Keyboard object.
199 
200         mParams.mIsSplitLayoutEnabled = mParams.mIsSplitLayoutEnabledByUser
201                 && elementParams.mSupportsSplitLayout;
202         final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
203         try {
204             return getKeyboard(elementParams, id);
205         } catch (final RuntimeException e) {
206             Log.e(TAG, "Can't create keyboard: " + id, e);
207             throw new KeyboardLayoutSetException(e, id);
208         }
209     }
210 
211     @Nonnull
getKeyboard(final ElementParams elementParams, final KeyboardId id)212     private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
213         final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
214         final Keyboard cachedKeyboard = (ref == null) ? null : ref.get();
215         if (cachedKeyboard != null) {
216             if (DEBUG_CACHE) {
217                 Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT  id=" + id);
218             }
219             return cachedKeyboard;
220         }
221 
222         final KeyboardBuilder<KeyboardParams> builder =
223                 new KeyboardBuilder<>(mContext, new KeyboardParams(sUniqueKeysCache));
224         sUniqueKeysCache.setEnabled(id.isAlphabetKeyboard());
225         builder.setAllowRedundantMoreKes(elementParams.mAllowRedundantMoreKeys);
226         final int keyboardXmlId = elementParams.mKeyboardXmlId;
227         builder.load(keyboardXmlId, id);
228         if (mParams.mDisableTouchPositionCorrectionDataForTest) {
229             builder.disableTouchPositionCorrectionDataForTest();
230         }
231         builder.setProximityCharsCorrectionEnabled(elementParams.mProximityCharsCorrectionEnabled);
232         final Keyboard keyboard = builder.build();
233         sKeyboardCache.put(id, new SoftReference<>(keyboard));
234         if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET
235                 || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)
236                 && !mParams.mIsSpellChecker) {
237             // We only forcibly cache the primary, "ALPHABET", layouts.
238             for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) {
239                 sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1];
240             }
241             sForcibleKeyboardCache[0] = keyboard;
242             if (DEBUG_CACHE) {
243                 Log.d(TAG, "forcing caching of keyboard with id=" + id);
244             }
245         }
246         if (DEBUG_CACHE) {
247             Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": "
248                     + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
249         }
250         return keyboard;
251     }
252 
getScriptId()253     public int getScriptId() {
254         return mParams.mScriptId;
255     }
256 
257     public static final class Builder {
258         private final Context mContext;
259         private final String mPackageName;
260         private final Resources mResources;
261 
262         private final Params mParams = new Params();
263 
264         private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
265 
Builder(final Context context, @Nullable final EditorInfo ei)266         public Builder(final Context context, @Nullable final EditorInfo ei) {
267             mContext = context;
268             mPackageName = context.getPackageName();
269             mResources = context.getResources();
270             final Params params = mParams;
271 
272             final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
273             params.mMode = getKeyboardMode(editorInfo);
274             // TODO: Consolidate those with {@link InputAttributes}.
275             params.mEditorInfo = editorInfo;
276             params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
277             params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
278                     mPackageName, NO_SETTINGS_KEY, editorInfo);
279 
280             // When the device is still unlocked, features like showing the IME setting app need to
281             // be locked down.
282             // TODO: Switch to {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library
283             // when it becomes publicly available.
284             @UserManagerCompatUtils.LockState
285             final int lockState = UserManagerCompatUtils.getUserLockState(context);
286             if (lockState == UserManagerCompatUtils.LOCK_STATE_LOCKED) {
287                 params.mNoSettingsKey = true;
288             }
289         }
290 
setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight)291         public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
292             mParams.mKeyboardWidth = keyboardWidth;
293             mParams.mKeyboardHeight = keyboardHeight;
294             return this;
295         }
296 
setSubtype(@onnull final RichInputMethodSubtype subtype)297         public Builder setSubtype(@Nonnull final RichInputMethodSubtype subtype) {
298             final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
299             // TODO: Consolidate with {@link InputAttributes}.
300             @SuppressWarnings("deprecation")
301             final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
302                     mPackageName, FORCE_ASCII, mParams.mEditorInfo);
303             final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
304                     mParams.mEditorInfo.imeOptions)
305                     || deprecatedForceAscii;
306             final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
307                     ? RichInputMethodSubtype.getNoLanguageSubtype()
308                     : subtype;
309             mParams.mSubtype = keyboardSubtype;
310             mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
311                     + keyboardSubtype.getKeyboardLayoutSetName();
312             return this;
313         }
314 
setIsSpellChecker(final boolean isSpellChecker)315         public Builder setIsSpellChecker(final boolean isSpellChecker) {
316             mParams.mIsSpellChecker = isSpellChecker;
317             return this;
318         }
319 
setVoiceInputKeyEnabled(final boolean enabled)320         public Builder setVoiceInputKeyEnabled(final boolean enabled) {
321             mParams.mVoiceInputKeyEnabled = enabled;
322             return this;
323         }
324 
setLanguageSwitchKeyEnabled(final boolean enabled)325         public Builder setLanguageSwitchKeyEnabled(final boolean enabled) {
326             mParams.mLanguageSwitchKeyEnabled = enabled;
327             return this;
328         }
329 
disableTouchPositionCorrectionData()330         public Builder disableTouchPositionCorrectionData() {
331             mParams.mDisableTouchPositionCorrectionDataForTest = true;
332             return this;
333         }
334 
setSplitLayoutEnabledByUser(final boolean enabled)335         public Builder setSplitLayoutEnabledByUser(final boolean enabled) {
336             mParams.mIsSplitLayoutEnabledByUser = enabled;
337             return this;
338         }
339 
340         // Super redux version of reading the script ID for some subtype from Xml.
readScriptId(final Resources resources, final InputMethodSubtype subtype)341         static int readScriptId(final Resources resources, final InputMethodSubtype subtype) {
342             final String layoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
343                     + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
344             final int xmlId = getXmlId(resources, layoutSetName);
345             final XmlResourceParser parser = resources.getXml(xmlId);
346             try {
347                 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
348                     // Bovinate through the XML stupidly searching for TAG_FEATURE, and read
349                     // the script Id from it.
350                     parser.next();
351                     final String tag = parser.getName();
352                     if (TAG_FEATURE.equals(tag)) {
353                         return readScriptIdFromTagFeature(resources, parser);
354                     }
355                 }
356             } catch (final IOException | XmlPullParserException e) {
357                 throw new RuntimeException(e.getMessage() + " in " + layoutSetName, e);
358             } finally {
359                 parser.close();
360             }
361             // If the tag is not found, then the default script is Latin.
362             return ScriptUtils.SCRIPT_LATIN;
363         }
364 
readScriptIdFromTagFeature(final Resources resources, final XmlPullParser parser)365         private static int readScriptIdFromTagFeature(final Resources resources,
366                 final XmlPullParser parser) throws IOException, XmlPullParserException {
367             final TypedArray featureAttr = resources.obtainAttributes(Xml.asAttributeSet(parser),
368                     R.styleable.KeyboardLayoutSet_Feature);
369             try {
370                 final int scriptId =
371                         featureAttr.getInt(R.styleable.KeyboardLayoutSet_Feature_supportedScript,
372                                 ScriptUtils.SCRIPT_UNKNOWN);
373                 XmlParseUtils.checkEndTag(TAG_FEATURE, parser);
374                 return scriptId;
375             } finally {
376                 featureAttr.recycle();
377             }
378         }
379 
build()380         public KeyboardLayoutSet build() {
381             if (mParams.mSubtype == null)
382                 throw new RuntimeException("KeyboardLayoutSet subtype is not specified");
383             final int xmlId = getXmlId(mResources, mParams.mKeyboardLayoutSetName);
384             try {
385                 parseKeyboardLayoutSet(mResources, xmlId);
386             } catch (final IOException | XmlPullParserException e) {
387                 throw new RuntimeException(e.getMessage() + " in " + mParams.mKeyboardLayoutSetName,
388                         e);
389             }
390             return new KeyboardLayoutSet(mContext, mParams);
391         }
392 
getXmlId(final Resources resources, final String keyboardLayoutSetName)393         private static int getXmlId(final Resources resources, final String keyboardLayoutSetName) {
394             final String packageName = resources.getResourcePackageName(
395                     R.xml.keyboard_layout_set_qwerty);
396             return resources.getIdentifier(keyboardLayoutSetName, "xml", packageName);
397         }
398 
parseKeyboardLayoutSet(final Resources res, final int resId)399         private void parseKeyboardLayoutSet(final Resources res, final int resId)
400                 throws XmlPullParserException, IOException {
401             final XmlResourceParser parser = res.getXml(resId);
402             try {
403                 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
404                     final int event = parser.next();
405                     if (event == XmlPullParser.START_TAG) {
406                         final String tag = parser.getName();
407                         if (TAG_KEYBOARD_SET.equals(tag)) {
408                             parseKeyboardLayoutSetContent(parser);
409                         } else {
410                             throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET);
411                         }
412                     }
413                 }
414             } finally {
415                 parser.close();
416             }
417         }
418 
parseKeyboardLayoutSetContent(final XmlPullParser parser)419         private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
420                 throws XmlPullParserException, IOException {
421             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
422                 final int event = parser.next();
423                 if (event == XmlPullParser.START_TAG) {
424                     final String tag = parser.getName();
425                     if (TAG_ELEMENT.equals(tag)) {
426                         parseKeyboardLayoutSetElement(parser);
427                     } else if (TAG_FEATURE.equals(tag)) {
428                         mParams.mScriptId = readScriptIdFromTagFeature(mResources, parser);
429                     } else {
430                         throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET);
431                     }
432                 } else if (event == XmlPullParser.END_TAG) {
433                     final String tag = parser.getName();
434                     if (TAG_KEYBOARD_SET.equals(tag)) {
435                         break;
436                     }
437                     throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_KEYBOARD_SET);
438                 }
439             }
440         }
441 
parseKeyboardLayoutSetElement(final XmlPullParser parser)442         private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
443                 throws XmlPullParserException, IOException {
444             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
445                     R.styleable.KeyboardLayoutSet_Element);
446             try {
447                 XmlParseUtils.checkAttributeExists(a,
448                         R.styleable.KeyboardLayoutSet_Element_elementName, "elementName",
449                         TAG_ELEMENT, parser);
450                 XmlParseUtils.checkAttributeExists(a,
451                         R.styleable.KeyboardLayoutSet_Element_elementKeyboard, "elementKeyboard",
452                         TAG_ELEMENT, parser);
453                 XmlParseUtils.checkEndTag(TAG_ELEMENT, parser);
454 
455                 final ElementParams elementParams = new ElementParams();
456                 final int elementName = a.getInt(
457                         R.styleable.KeyboardLayoutSet_Element_elementName, 0);
458                 elementParams.mKeyboardXmlId = a.getResourceId(
459                         R.styleable.KeyboardLayoutSet_Element_elementKeyboard, 0);
460                 elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
461                         R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection,
462                         false);
463                 elementParams.mSupportsSplitLayout = a.getBoolean(
464                         R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false);
465                 elementParams.mAllowRedundantMoreKeys = a.getBoolean(
466                         R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true);
467                 mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);
468             } finally {
469                 a.recycle();
470             }
471         }
472 
getKeyboardMode(final EditorInfo editorInfo)473         private static int getKeyboardMode(final EditorInfo editorInfo) {
474             final int inputType = editorInfo.inputType;
475             final int variation = inputType & InputType.TYPE_MASK_VARIATION;
476 
477             switch (inputType & InputType.TYPE_MASK_CLASS) {
478             case InputType.TYPE_CLASS_NUMBER:
479                 return KeyboardId.MODE_NUMBER;
480             case InputType.TYPE_CLASS_DATETIME:
481                 switch (variation) {
482                 case InputType.TYPE_DATETIME_VARIATION_DATE:
483                     return KeyboardId.MODE_DATE;
484                 case InputType.TYPE_DATETIME_VARIATION_TIME:
485                     return KeyboardId.MODE_TIME;
486                 default: // InputType.TYPE_DATETIME_VARIATION_NORMAL
487                     return KeyboardId.MODE_DATETIME;
488                 }
489             case InputType.TYPE_CLASS_PHONE:
490                 return KeyboardId.MODE_PHONE;
491             case InputType.TYPE_CLASS_TEXT:
492                 if (InputTypeUtils.isEmailVariation(variation)) {
493                     return KeyboardId.MODE_EMAIL;
494                 } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
495                     return KeyboardId.MODE_URL;
496                 } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
497                     return KeyboardId.MODE_IM;
498                 } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
499                     return KeyboardId.MODE_TEXT;
500                 } else {
501                     return KeyboardId.MODE_TEXT;
502                 }
503             default:
504                 return KeyboardId.MODE_TEXT;
505             }
506         }
507     }
508 }
509