From 11d9ee742f8ff3fb31b0e3beb32ee4870c63d8e3 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 4 Apr 2012 14:30:42 +0900 Subject: Use keyboardSet extra value of subtype to specify layout type Change-Id: Ice1f345a08a8d760e3b847c885c4072e3e142c97 --- .../com/android/inputmethod/keyboard/Keyboard.java | 15 +- .../inputmethod/keyboard/KeyboardLayoutSet.java | 447 +++++++++++++++++++++ .../android/inputmethod/keyboard/KeyboardSet.java | 407 ------------------- .../inputmethod/keyboard/KeyboardSwitcher.java | 27 +- .../keyboard/internal/KeySpecParser.java | 2 +- .../com/android/inputmethod/latin/LatinIME.java | 11 +- .../com/android/inputmethod/latin/StringUtils.java | 17 + .../android/inputmethod/latin/SubtypeLocale.java | 36 +- .../android/inputmethod/latin/SubtypeSwitcher.java | 27 +- .../android/inputmethod/latin/SubtypeUtils.java | 16 + .../spellcheck/AndroidSpellCheckerService.java | 3 +- 11 files changed, 530 insertions(+), 478 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java delete mode 100644 java/src/com/android/inputmethod/keyboard/KeyboardSet.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 67e4e4a96..6ada09067 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -239,7 +239,7 @@ public class Keyboard { public final ArrayList mAltCodeKeysWhileTyping = new ArrayList(); public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); - public KeyboardSet.KeysCache mKeysCache; + public KeyboardLayoutSet.KeysCache mKeysCache; public int mMostCommonKeyHeight = 0; public int mMostCommonKeyWidth = 0; @@ -637,7 +637,7 @@ public class Keyboard { params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); } - public void setAutoGenerate(KeyboardSet.KeysCache keysCache) { + public void setAutoGenerate(KeyboardLayoutSet.KeysCache keysCache) { mParams.mKeysCache = keysCache; } @@ -1061,8 +1061,8 @@ public class Keyboard { final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Case); try { - final boolean keyboardSetElementMatched = matchTypedValue(a, - R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId, + final boolean keyboardLayoutSetElementMatched = matchTypedValue(a, + R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId, KeyboardId.elementIdToName(id.mElementId)); final boolean modeMatched = matchTypedValue(a, R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); @@ -1091,7 +1091,7 @@ public class Keyboard { R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); final boolean countryCodeMatched = matchString(a, R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); - final boolean selected = keyboardSetElementMatched && modeMatched + final boolean selected = keyboardLayoutSetElementMatched && modeMatched && navigateNextMatched && navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched && shortcutKeyEnabledMatched && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched @@ -1100,8 +1100,9 @@ public class Keyboard { if (DEBUG) { startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, - textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement), - "keyboardSetElement"), + textAttr(a.getString( + R.styleable.Keyboard_Case_keyboardLayoutSetElement), + "keyboardLayoutSetElement"), textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), "imeAction"), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java new file mode 100644 index 000000000..51cd90549 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.text.InputType; +import android.util.Log; +import android.util.Xml; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams; +import com.android.inputmethod.keyboard.internal.KeySpecParser; +import com.android.inputmethod.latin.InputTypeUtils; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.LocaleUtils; +import com.android.inputmethod.latin.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.SubtypeLocale; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.XmlParseUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Locale; + +/** + * This class represents a set of keyboard layouts. Each of them represents a different keyboard + * specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same + * {@link KeyboardLayoutSet} are related to each other. + * A {@link KeyboardLayoutSet} needs to be created for each + * {@link android.view.inputmethod.EditorInfo}. + */ +public class KeyboardLayoutSet { + private static final String TAG = KeyboardLayoutSet.class.getSimpleName(); + private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG; + + private static final String TAG_KEYBOARD_SET = "KeyboardLayoutSet"; + private static final String TAG_ELEMENT = "Element"; + + private static final String DEFAULT_KEYBOARD_LAYOUT_SET = "qwerty"; + private static final char KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER = ':'; + private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "xml/keyboard_layout_set_"; + private static final int DEFAULT_KEYBOARD_LAYOUT_SET_RESOURCE_ID = + R.xml.keyboard_layout_set_qwerty; + + private final Context mContext; + private final Params mParams; + + private static final HashMap> sKeyboardCache = + new HashMap>(); + private static final KeysCache sKeysCache = new KeysCache(); + + public static class KeyboardLayoutSetException extends RuntimeException { + public final KeyboardId mKeyboardId; + + public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) { + super(cause); + mKeyboardId = keyboardId; + } + } + + public static class KeysCache { + private final HashMap mMap; + + public KeysCache() { + mMap = new HashMap(); + } + + public void clear() { + mMap.clear(); + } + + public Key get(Key key) { + final Key existingKey = mMap.get(key); + if (existingKey != null) { + // Reuse the existing element that equals to "key" without adding "key" to the map. + return existingKey; + } + mMap.put(key, key); + return key; + } + } + + static class Params { + String mKeyboardLayoutSetName; + int mMode; + EditorInfo mEditorInfo; + boolean mTouchPositionCorrectionEnabled; + boolean mVoiceKeyEnabled; + boolean mVoiceKeyOnMain; + boolean mNoSettingsKey; + boolean mLanguageSwitchKeyEnabled; + Locale mLocale; + int mOrientation; + int mWidth; + // KeyboardLayoutSet element id to element's parameters map. + final HashMap mKeyboardLayoutSetElementIdToParamsMap = + new HashMap(); + + static class ElementParams { + int mKeyboardXmlId; + boolean mProximityCharsCorrectionEnabled; + } + } + + public static void clearKeyboardCache() { + sKeyboardCache.clear(); + sKeysCache.clear(); + } + + private KeyboardLayoutSet(Context context, Params params) { + mContext = context; + mParams = params; + } + + public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) { + final int keyboardLayoutSetElementId; + switch (mParams.mMode) { + case KeyboardId.MODE_PHONE: + if (baseKeyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS) { + keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS; + } else { + keyboardLayoutSetElementId = KeyboardId.ELEMENT_PHONE; + } + break; + case KeyboardId.MODE_NUMBER: + case KeyboardId.MODE_DATE: + case KeyboardId.MODE_TIME: + case KeyboardId.MODE_DATETIME: + keyboardLayoutSetElementId = KeyboardId.ELEMENT_NUMBER; + break; + default: + keyboardLayoutSetElementId = baseKeyboardLayoutSetElementId; + break; + } + + ElementParams elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get( + keyboardLayoutSetElementId); + if (elementParams == null) { + elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get( + KeyboardId.ELEMENT_ALPHABET); + } + final KeyboardId id = getKeyboardId(keyboardLayoutSetElementId); + try { + return getKeyboard(mContext, elementParams, id); + } catch (RuntimeException e) { + throw new KeyboardLayoutSetException(e, id); + } + } + + private Keyboard getKeyboard(Context context, ElementParams elementParams, + final KeyboardId id) { + final SoftReference ref = sKeyboardCache.get(id); + Keyboard keyboard = (ref == null) ? null : ref.get(); + if (keyboard == null) { + final Keyboard.Builder builder = + new Keyboard.Builder(mContext, new Keyboard.Params()); + if (id.isAlphabetKeyboard()) { + builder.setAutoGenerate(sKeysCache); + } + final int keyboardXmlId = elementParams.mKeyboardXmlId; + final RunInLocale job = new RunInLocale() { + @Override + protected Void job(Resources res) { + builder.load(keyboardXmlId, id); + return null; + } + }; + job.runInLocale(context.getResources(), id.mLocale); + builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled); + builder.setProximityCharsCorrectionEnabled( + elementParams.mProximityCharsCorrectionEnabled); + keyboard = builder.build(); + sKeyboardCache.put(id, new SoftReference(keyboard)); + + if (DEBUG_CACHE) { + Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": " + + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); + } + } else if (DEBUG_CACHE) { + Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT id=" + id); + } + + return keyboard; + } + + // Note: The keyboard for each locale, shift state, and mode are represented as + // KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies + // which XML layout should be used for each keyboard. The KeyboardId is an internal key for + // Keyboard object. + private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) { + final Params params = mParams; + final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS + || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); + final boolean noLanguage = params.mLocale.getLanguage().equals(SubtypeLocale.NO_LANGUAGE); + final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !noLanguage; + final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain); + return new KeyboardId(keyboardLayoutSetElementId, params.mLocale, params.mOrientation, + params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey, + voiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled); + } + + private static String getKeyboardLayoutSetName(InputMethodSubtype subtype) { + final String keyboardLayoutSet = subtype.getExtraValueOf( + LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET); + // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is + // fixed. + if (keyboardLayoutSet == null) return DEFAULT_KEYBOARD_LAYOUT_SET; + final int pos = keyboardLayoutSet.indexOf(KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER); + return (pos > 0) ? keyboardLayoutSet.substring(0, pos) : keyboardLayoutSet; + } + + public static String getKeyboardLayoutSetLocaleString(InputMethodSubtype subtype) { + final String keyboardLayoutSet = subtype.getExtraValueOf( + LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET); + // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is + // fixed. + if (keyboardLayoutSet == null) return subtype.getLocale(); + final int pos = keyboardLayoutSet.indexOf(KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER); + return (pos > 0) ? keyboardLayoutSet.substring(pos + 1) : subtype.getLocale(); + } + + public static Locale getKeyboardLayoutSetLocale(InputMethodSubtype subtype) { + return LocaleUtils.constructLocaleFromString(getKeyboardLayoutSetLocaleString(subtype)); + } + + public static class Builder { + private final Context mContext; + private final String mPackageName; + private final Resources mResources; + private final EditorInfo mEditorInfo; + + private final Params mParams = new Params(); + + private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); + + public Builder(Context context, EditorInfo editorInfo) { + mContext = context; + mPackageName = context.getPackageName(); + mResources = context.getResources(); + mEditorInfo = editorInfo; + final Params params = mParams; + + params.mMode = getKeyboardMode(editorInfo); + params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO; + params.mNoSettingsKey = StringUtils.inPrivateImeOptions( + mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo); + } + + public Builder setScreenGeometry(int orientation, int widthPixels) { + mParams.mOrientation = orientation; + mParams.mWidth = widthPixels; + return this; + } + + public Builder setSubtype(InputMethodSubtype subtype) { + final boolean asciiCapable = subtype.containsExtraValueKey( + LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE); + final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions( + mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo); + final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii( + mParams.mEditorInfo.imeOptions) + || deprecatedForceAscii; + final InputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable) + ? SubtypeSwitcher.getInstance().getNoLanguageSubtype() + : subtype; + mParams.mLocale = getKeyboardLayoutSetLocale(keyboardSubtype); + mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX + + getKeyboardLayoutSetName(keyboardSubtype); + return this; + } + + public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain, + boolean languageSwitchKeyEnabled) { + @SuppressWarnings("deprecation") + final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions( + null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo); + final boolean noMicrophone = StringUtils.inPrivateImeOptions( + mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo) + || deprecatedNoMicrophone; + mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone; + mParams.mVoiceKeyOnMain = voiceKeyOnMain; + mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled; + return this; + } + + public void setTouchPositionCorrectionEnabled(boolean enabled) { + mParams.mTouchPositionCorrectionEnabled = enabled; + } + + public KeyboardLayoutSet build() { + if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED) + throw new RuntimeException("Screen geometry is not specified"); + if (mParams.mLocale == null) + throw new RuntimeException("KeyboardLayoutSet subtype is not specified"); + final String keyboardLayoutSetName = mParams.mKeyboardLayoutSetName; + final int xmlId = KeySpecParser.getResourceId( + mResources, keyboardLayoutSetName, DEFAULT_KEYBOARD_LAYOUT_SET_RESOURCE_ID); + final RunInLocale job = new RunInLocale() { + @Override + protected Void job(Resources res) { + try { + parseKeyboardLayoutSet(res, xmlId); + } catch (Exception e) { + throw new RuntimeException(e.getMessage() + " in " + keyboardLayoutSetName); + } + return null; + } + }; + job.runInLocale(mResources, mParams.mLocale); + return new KeyboardLayoutSet(mContext, mParams); + } + + private void parseKeyboardLayoutSet(Resources res, int resId) + throws XmlPullParserException, IOException { + final XmlResourceParser parser = res.getXml(resId); + try { + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_KEYBOARD_SET.equals(tag)) { + parseKeyboardLayoutSetContent(parser); + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET); + } + } + } + } finally { + parser.close(); + } + } + + private void parseKeyboardLayoutSetContent(XmlPullParser parser) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_ELEMENT.equals(tag)) { + parseKeyboardLayoutSetElement(parser); + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET); + } + } else if (event == XmlPullParser.END_TAG) { + final String tag = parser.getName(); + if (TAG_KEYBOARD_SET.equals(tag)) { + break; + } else { + throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEYBOARD_SET); + } + } + } + } + + private void parseKeyboardLayoutSetElement(XmlPullParser parser) + throws XmlPullParserException, IOException { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.KeyboardLayoutSet_Element); + try { + XmlParseUtils.checkAttributeExists(a, + R.styleable.KeyboardLayoutSet_Element_elementName, "elementName", + TAG_ELEMENT, parser); + XmlParseUtils.checkAttributeExists(a, + R.styleable.KeyboardLayoutSet_Element_elementKeyboard, "elementKeyboard", + TAG_ELEMENT, parser); + XmlParseUtils.checkEndTag(TAG_ELEMENT, parser); + + final ElementParams elementParams = new ElementParams(); + final int elementName = a.getInt( + R.styleable.KeyboardLayoutSet_Element_elementName, 0); + elementParams.mKeyboardXmlId = a.getResourceId( + R.styleable.KeyboardLayoutSet_Element_elementKeyboard, 0); + elementParams.mProximityCharsCorrectionEnabled = a.getBoolean( + R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection, + false); + mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams); + } finally { + a.recycle(); + } + } + + private static int getKeyboardMode(EditorInfo editorInfo) { + if (editorInfo == null) + return KeyboardId.MODE_TEXT; + + final int inputType = editorInfo.inputType; + final int variation = inputType & InputType.TYPE_MASK_VARIATION; + + switch (inputType & InputType.TYPE_MASK_CLASS) { + case InputType.TYPE_CLASS_NUMBER: + return KeyboardId.MODE_NUMBER; + case InputType.TYPE_CLASS_DATETIME: + switch (variation) { + case InputType.TYPE_DATETIME_VARIATION_DATE: + return KeyboardId.MODE_DATE; + case InputType.TYPE_DATETIME_VARIATION_TIME: + return KeyboardId.MODE_TIME; + default: // InputType.TYPE_DATETIME_VARIATION_NORMAL + return KeyboardId.MODE_DATETIME; + } + case InputType.TYPE_CLASS_PHONE: + return KeyboardId.MODE_PHONE; + case InputType.TYPE_CLASS_TEXT: + if (InputTypeUtils.isEmailVariation(variation)) { + return KeyboardId.MODE_EMAIL; + } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { + return KeyboardId.MODE_URL; + } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { + return KeyboardId.MODE_IM; + } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return KeyboardId.MODE_TEXT; + } else { + return KeyboardId.MODE_TEXT; + } + default: + return KeyboardId.MODE_TEXT; + } + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java deleted file mode 100644 index f9b6b72c7..000000000 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.keyboard; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.text.InputType; -import android.util.Log; -import android.util.Xml; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.keyboard.KeyboardSet.Params.ElementParams; -import com.android.inputmethod.latin.InputTypeUtils; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.LocaleUtils.RunInLocale; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StringUtils; -import com.android.inputmethod.latin.SubtypeLocale; -import com.android.inputmethod.latin.XmlParseUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Locale; - -/** - * This class represents a set of keyboards. Each of them represents a different keyboard - * specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same - * {@link KeyboardSet} are related to each other. - * A {@link KeyboardSet} needs to be created for each {@link android.view.inputmethod.EditorInfo}. - */ -public class KeyboardSet { - private static final String TAG = KeyboardSet.class.getSimpleName(); - private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG; - - private static final String TAG_KEYBOARD_SET = "KeyboardSet"; - private static final String TAG_ELEMENT = "Element"; - - private final Context mContext; - private final Params mParams; - - private static final HashMap> sKeyboardCache = - new HashMap>(); - private static final KeysCache sKeysCache = new KeysCache(); - - public static class KeyboardSetException extends RuntimeException { - public final KeyboardId mKeyboardId; - - public KeyboardSetException(Throwable cause, KeyboardId keyboardId) { - super(cause); - mKeyboardId = keyboardId; - } - } - - public static class KeysCache { - private final HashMap mMap; - - public KeysCache() { - mMap = new HashMap(); - } - - public void clear() { - mMap.clear(); - } - - public Key get(Key key) { - final Key existingKey = mMap.get(key); - if (existingKey != null) { - // Reuse the existing element that equals to "key" without adding "key" to the map. - return existingKey; - } - mMap.put(key, key); - return key; - } - } - - static class Params { - int mMode; - EditorInfo mEditorInfo; - boolean mTouchPositionCorrectionEnabled; - boolean mVoiceKeyEnabled; - boolean mVoiceKeyOnMain; - boolean mNoSettingsKey; - boolean mLanguageSwitchKeyEnabled; - Locale mLocale; - int mOrientation; - int mWidth; - // KeyboardSet element id to element's parameters map. - final HashMap mKeyboardSetElementIdToParamsMap = - new HashMap(); - - static class ElementParams { - int mKeyboardXmlId; - boolean mProximityCharsCorrectionEnabled; - } - } - - public static void clearKeyboardCache() { - sKeyboardCache.clear(); - sKeysCache.clear(); - } - - private KeyboardSet(Context context, Params params) { - mContext = context; - mParams = params; - } - - public Keyboard getKeyboard(int baseKeyboardSetElementId) { - final int keyboardSetElementId; - switch (mParams.mMode) { - case KeyboardId.MODE_PHONE: - if (baseKeyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS) { - keyboardSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS; - } else { - keyboardSetElementId = KeyboardId.ELEMENT_PHONE; - } - break; - case KeyboardId.MODE_NUMBER: - case KeyboardId.MODE_DATE: - case KeyboardId.MODE_TIME: - case KeyboardId.MODE_DATETIME: - keyboardSetElementId = KeyboardId.ELEMENT_NUMBER; - break; - default: - keyboardSetElementId = baseKeyboardSetElementId; - break; - } - - ElementParams elementParams = mParams.mKeyboardSetElementIdToParamsMap.get( - keyboardSetElementId); - if (elementParams == null) { - elementParams = mParams.mKeyboardSetElementIdToParamsMap.get( - KeyboardId.ELEMENT_ALPHABET); - } - final KeyboardId id = getKeyboardId(keyboardSetElementId); - try { - return getKeyboard(mContext, elementParams, id); - } catch (RuntimeException e) { - throw new KeyboardSetException(e, id); - } - } - - private Keyboard getKeyboard(Context context, ElementParams elementParams, - final KeyboardId id) { - final SoftReference ref = sKeyboardCache.get(id); - Keyboard keyboard = (ref == null) ? null : ref.get(); - if (keyboard == null) { - final Keyboard.Builder builder = - new Keyboard.Builder(mContext, new Keyboard.Params()); - if (id.isAlphabetKeyboard()) { - builder.setAutoGenerate(sKeysCache); - } - final int keyboardXmlId = elementParams.mKeyboardXmlId; - final RunInLocale job = new RunInLocale() { - @Override - protected Void job(Resources res) { - builder.load(keyboardXmlId, id); - return null; - } - }; - job.runInLocale(context.getResources(), id.mLocale); - builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled); - builder.setProximityCharsCorrectionEnabled( - elementParams.mProximityCharsCorrectionEnabled); - keyboard = builder.build(); - sKeyboardCache.put(id, new SoftReference(keyboard)); - - if (DEBUG_CACHE) { - Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": " - + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); - } - } else if (DEBUG_CACHE) { - Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT id=" + id); - } - - return keyboard; - } - - // Note: The keyboard for each locale, shift state, and mode are represented as KeyboardSet - // element id that is a key in keyboard_set.xml. Also that file specifies which XML layout - // should be used for each keyboard. The KeyboardId is an internal key for Keyboard object. - private KeyboardId getKeyboardId(int keyboardSetElementId) { - final Params params = mParams; - final boolean isSymbols = (keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS - || keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); - final boolean noLanguage = params.mLocale.getLanguage().equals(SubtypeLocale.NO_LANGUAGE); - final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !noLanguage; - final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain); - return new KeyboardId(keyboardSetElementId, params.mLocale, params.mOrientation, - params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey, - voiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled); - } - - public static class Builder { - private final Context mContext; - private final String mPackageName; - private final Resources mResources; - private final EditorInfo mEditorInfo; - - private final Params mParams = new Params(); - - private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); - - public Builder(Context context, EditorInfo editorInfo) { - mContext = context; - mPackageName = context.getPackageName(); - mResources = context.getResources(); - mEditorInfo = editorInfo; - final Params params = mParams; - - params.mMode = getKeyboardMode(editorInfo); - params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO; - params.mNoSettingsKey = StringUtils.inPrivateImeOptions( - mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo); - } - - public Builder setScreenGeometry(int orientation, int widthPixels) { - mParams.mOrientation = orientation; - mParams.mWidth = widthPixels; - return this; - } - - public Builder setSubtype(InputMethodSubtype subtype) { - final Locale inputLocale = SubtypeLocale.getSubtypeLocale(subtype); - final boolean asciiCapable = subtype.containsExtraValueKey( - LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE); - final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions( - mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo); - final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii( - mParams.mEditorInfo.imeOptions) - || deprecatedForceAscii; - mParams.mLocale = (forceAscii && !asciiCapable) - ? SubtypeLocale.LOCALE_NO_LANGUAGE_QWERTY : inputLocale; - return this; - } - - public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain, - boolean languageSwitchKeyEnabled) { - @SuppressWarnings("deprecation") - final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions( - null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo); - final boolean noMicrophone = StringUtils.inPrivateImeOptions( - mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo) - || deprecatedNoMicrophone; - mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone; - mParams.mVoiceKeyOnMain = voiceKeyOnMain; - mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled; - return this; - } - - public void setTouchPositionCorrectionEnabled(boolean enabled) { - mParams.mTouchPositionCorrectionEnabled = enabled; - } - - public KeyboardSet build() { - if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED) - throw new RuntimeException("Screen geometry is not specified"); - if (mParams.mLocale == null) - throw new RuntimeException("KeyboardSet subtype is not specified"); - - final RunInLocale job = new RunInLocale() { - @Override - protected Void job(Resources res) { - try { - parseKeyboardSet(res, R.xml.keyboard_set); - } catch (Exception e) { - throw new RuntimeException(e.getMessage() + " in " - + res.getResourceName(R.xml.keyboard_set) - + " of locale " + mParams.mLocale); - } - return null; - } - }; - job.runInLocale(mResources, mParams.mLocale); - return new KeyboardSet(mContext, mParams); - } - - private void parseKeyboardSet(Resources res, int resId) throws XmlPullParserException, - IOException { - final XmlResourceParser parser = res.getXml(resId); - try { - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD_SET.equals(tag)) { - parseKeyboardSetContent(parser); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET); - } - } - } - } finally { - parser.close(); - } - } - - private void parseKeyboardSetContent(XmlPullParser parser) throws XmlPullParserException, - IOException { - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_ELEMENT.equals(tag)) { - parseKeyboardSetElement(parser); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD_SET.equals(tag)) { - break; - } else { - throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEYBOARD_SET); - } - } - } - } - - private void parseKeyboardSetElement(XmlPullParser parser) throws XmlPullParserException, - IOException { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.KeyboardSet_Element); - try { - XmlParseUtils.checkAttributeExists(a, - R.styleable.KeyboardSet_Element_elementName, "elementName", - TAG_ELEMENT, parser); - XmlParseUtils.checkAttributeExists(a, - R.styleable.KeyboardSet_Element_elementKeyboard, "elementKeyboard", - TAG_ELEMENT, parser); - XmlParseUtils.checkEndTag(TAG_ELEMENT, parser); - - final ElementParams elementParams = new ElementParams(); - final int elementName = a.getInt( - R.styleable.KeyboardSet_Element_elementName, 0); - elementParams.mKeyboardXmlId = a.getResourceId( - R.styleable.KeyboardSet_Element_elementKeyboard, 0); - elementParams.mProximityCharsCorrectionEnabled = a.getBoolean( - R.styleable.KeyboardSet_Element_enableProximityCharsCorrection, false); - mParams.mKeyboardSetElementIdToParamsMap.put(elementName, elementParams); - } finally { - a.recycle(); - } - } - - private static int getKeyboardMode(EditorInfo editorInfo) { - if (editorInfo == null) - return KeyboardId.MODE_TEXT; - - final int inputType = editorInfo.inputType; - final int variation = inputType & InputType.TYPE_MASK_VARIATION; - - switch (inputType & InputType.TYPE_MASK_CLASS) { - case InputType.TYPE_CLASS_NUMBER: - return KeyboardId.MODE_NUMBER; - case InputType.TYPE_CLASS_DATETIME: - switch (variation) { - case InputType.TYPE_DATETIME_VARIATION_DATE: - return KeyboardId.MODE_DATE; - case InputType.TYPE_DATETIME_VARIATION_TIME: - return KeyboardId.MODE_TIME; - default: // InputType.TYPE_DATETIME_VARIATION_NORMAL - return KeyboardId.MODE_DATETIME; - } - case InputType.TYPE_CLASS_PHONE: - return KeyboardId.MODE_PHONE; - case InputType.TYPE_CLASS_TEXT: - if (InputTypeUtils.isEmailVariation(variation)) { - return KeyboardId.MODE_EMAIL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { - return KeyboardId.MODE_URL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - return KeyboardId.MODE_IM; - } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - return KeyboardId.MODE_TEXT; - } else { - return KeyboardId.MODE_TEXT; - } - default: - return KeyboardId.MODE_TEXT; - } - } - } -} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 3bc63f3dd..e50d922ea 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -27,7 +27,7 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; -import com.android.inputmethod.keyboard.KeyboardSet.KeyboardSetException; +import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.latin.DebugSettings; @@ -76,7 +76,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { private KeyboardState mState; - private KeyboardSet mKeyboardSet; + private KeyboardLayoutSet mKeyboardLayoutSet; /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of * what user actually typed. */ @@ -129,12 +129,13 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { mKeyboardTheme = keyboardTheme; mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); - KeyboardSet.clearKeyboardCache(); + KeyboardLayoutSet.clearKeyboardCache(); } } public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) { - final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo); + final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( + mThemeContext, editorInfo); builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation, mThemeContext.getResources().getDisplayMetrics().widthPixels); builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype()); @@ -142,10 +143,10 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { settingsValues.isVoiceKeyEnabled(editorInfo), settingsValues.isVoiceKeyOnMain(), settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); - mKeyboardSet = builder.build(); + mKeyboardLayoutSet = builder.build(); try { mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); - } catch (KeyboardSetException e) { + } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); return; @@ -214,43 +215,43 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetManualShiftedKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetAutomaticShiftedKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetShiftLockedKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetShiftLockShiftedKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { - setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); + setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 0aba813b2..4abd887f0 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -318,7 +318,7 @@ public class KeySpecParser { } } - private static int getResourceId(Resources res, String name, int packageNameResId) { + public static int getResourceId(Resources res, String name, int packageNameResId) { String packageName = res.getResourcePackageName(packageNameResId); int resId = res.getIdentifier(name, null, packageName); if (resId == 0) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 8a26d2b9e..f5c09974e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -115,16 +115,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public static final String IME_OPTION_FORCE_ASCII = "forceAscii"; /** - * The subtype extra value used to indicate that the subtype keyboard layout is capable for - * typing ASCII characters. + * The subtype extra value used to indicate that the subtype keyboard layout set name. */ - public static final String SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable"; + public static final String SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET = "KeyboardLayoutSet"; /** - * The subtype extra value used to indicate that the subtype keyboard layout should be loaded - * from the specified locale. + * The subtype extra value used to indicate that the subtype keyboard layout is capable for + * typing ASCII characters. */ - public static final String SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE = "KeyboardLocale"; + public static final String SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable"; private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index 7000e4633..42fce53d0 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -22,6 +22,7 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Keyboard; import java.util.ArrayList; +import java.util.Locale; public class StringUtils { private StringUtils() { @@ -149,4 +150,20 @@ public class StringUtils { i++; } } + + public static String toTitleCase(String s, Locale locale) { + if (s.length() <= 1) { + // TODO: is this really correct? Shouldn't this be s.toUpperCase()? + return s; + } + // TODO: fix the bugs below + // - This does not work for Greek, because it returns upper case instead of title case. + // - It does not work for Serbian, because it fails to account for the "lj" character, + // which should be "Lj" in title case and "LJ" in upper case. + // - It does not work for Dutch, because it fails to account for the "ij" digraph, which + // are two different characters but both should be capitalized as "IJ" as if they were + // a single letter. + // - It also does not work with unicode surrogate code points. + return s.toUpperCase(locale).charAt(0) + s.substring(1); + } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java index fac74f0b5..2bc22a6f9 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.res.Resources; -import android.view.inputmethod.InputMethodSubtype; + import java.util.Locale; @@ -69,10 +69,10 @@ public class SubtypeLocale { } final String value = lookupExceptionalLocale(key); if (value == null) { - return toTitleCase(locale.getDisplayName(locale), locale); + return StringUtils.toTitleCase(locale.getDisplayName(locale), locale); } if (value.indexOf("%s") >= 0) { - final String languageName = toTitleCase(locale.getDisplayLanguage(locale), locale); + final String languageName = StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); return String.format(value, languageName); } return value; @@ -88,7 +88,7 @@ public class SubtypeLocale { if (NO_LANGUAGE.equals(locale.getLanguage())) { return lookupExceptionalLocale(locale.getCountry()); } else { - return toTitleCase(locale.getDisplayLanguage(locale), locale); + return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale); } } @@ -102,33 +102,7 @@ public class SubtypeLocale { if (NO_LANGUAGE.equals(locale.getLanguage())) { return locale.getCountry(); } else { - return toTitleCase(locale.getLanguage(), locale); - } - } - - public static String toTitleCase(String s, Locale locale) { - if (s.length() <= 1) { - // TODO: is this really correct? Shouldn't this be s.toUpperCase()? - return s; + return StringUtils.toTitleCase(locale.getLanguage(), locale); } - // TODO: fix the bugs below - // - This does not work for Greek, because it returns upper case instead of title case. - // - It does not work for Serbian, because it fails to account for the "lj" character, - // which should be "Lj" in title case and "LJ" in upper case. - // - It does not work for Dutch, because it fails to account for the "ij" digraph, which - // are two different characters but both should be capitalized as "IJ" as if they were - // a single letter. - // - It also does not work with unicode surrogate code points. - return s.toUpperCase(locale).charAt(0) + s.substring(1); - } - - public static String getSubtypeLocaleString(InputMethodSubtype subtype) { - final String keyboardLocale = subtype.getExtraValueOf( - LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE); - return keyboardLocale != null ? keyboardLocale : subtype.getLocale(); - } - - public static Locale getSubtypeLocale(InputMethodSubtype subtype) { - return LocaleUtils.constructLocaleFromString(getSubtypeLocaleString(subtype)); } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 3ed7f8700..dfb01a24c 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -33,6 +33,7 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.keyboard.KeyboardSwitcher; import java.util.ArrayList; @@ -68,6 +69,7 @@ public class SubtypeSwitcher { private InputMethodInfo mShortcutInputMethodInfo; private InputMethodSubtype mShortcutSubtype; private List mAllEnabledSubtypesOfCurrentInputMethod; + private InputMethodSubtype mNoLanguageSubtype; // Note: This variable is always non-null after {@link #initialize(LatinIME)}. private InputMethodSubtype mCurrentSubtype; private Locale mSystemLocale; @@ -104,6 +106,8 @@ public class SubtypeSwitcher { mInputLocaleStr = null; mCurrentSubtype = mImm.getCurrentInputMethodSubtype(); mAllEnabledSubtypesOfCurrentInputMethod = null; + mNoLanguageSubtype = SubtypeUtils.findSubtypeByKeyboardLayoutSetLocale( + service, SubtypeLocale.LOCALE_NO_LANGUAGE_QWERTY); final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); mIsNetworkConnected = (info != null && info.isConnected()); @@ -133,7 +137,7 @@ public class SubtypeSwitcher { mEnabledLanguagesOfCurrentInputMethod.clear(); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); for (InputMethodSubtype ims : mAllEnabledSubtypesOfCurrentInputMethod) { - final String locale = getSubtypeLocale(ims); + final String locale = KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(ims); final String mode = ims.getMode(); mLocaleSplitter.setString(locale); if (mLocaleSplitter.hasNext()) { @@ -162,7 +166,8 @@ public class SubtypeSwitcher { Log.d(TAG, "Update shortcut IME from : " + (mShortcutInputMethodInfo == null ? "" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "" : (getSubtypeLocale(mShortcutSubtype) + + (mShortcutSubtype == null ? "" : ( + KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(mShortcutSubtype) + ", " + mShortcutSubtype.getMode()))); } // TODO: Update an icon for shortcut IME @@ -184,20 +189,15 @@ public class SubtypeSwitcher { Log.d(TAG, "Update shortcut IME to : " + (mShortcutInputMethodInfo == null ? "" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "" : (getSubtypeLocale(mShortcutSubtype) + + (mShortcutSubtype == null ? "" : ( + KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(mShortcutSubtype) + ", " + mShortcutSubtype.getMode()))); } } - private static String getSubtypeLocale(InputMethodSubtype subtype) { - final String keyboardLocale = subtype.getExtraValueOf( - LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE); - return keyboardLocale != null ? keyboardLocale : subtype.getLocale(); - } - // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. public void updateSubtype(InputMethodSubtype newSubtype) { - final String newLocale = getSubtypeLocale(newSubtype); + final String newLocale = KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(newSubtype); final String newMode = newSubtype.getMode(); final String oldMode = getCurrentSubtypeMode(); if (DBG) { @@ -301,7 +301,8 @@ public class SubtypeSwitcher { final String imiPackageName = imi.getPackageName(); if (DBG) { Log.d(TAG, "Update icons of IME: " + imiPackageName + "," - + getSubtypeLocale(subtype) + "," + subtype.getMode()); + + KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(subtype) + "," + + subtype.getMode()); } if (subtype != null) { return pm.getDrawable(imiPackageName, subtype.getIconResId(), @@ -438,4 +439,8 @@ public class SubtypeSwitcher { public InputMethodSubtype getCurrentSubtype() { return mCurrentSubtype; } + + public InputMethodSubtype getNoLanguageSubtype() { + return mNoLanguageSubtype; + } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeUtils.java b/java/src/com/android/inputmethod/latin/SubtypeUtils.java index 2c5d58200..a747c9ad7 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeUtils.java +++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java @@ -21,9 +21,11 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; import java.util.Collections; import java.util.List; +import java.util.Locale; public class SubtypeUtils { private SubtypeUtils() { @@ -129,4 +131,18 @@ public class SubtypeUtils { } throw new RuntimeException("Can not find input method id for " + packageName); } + + public static InputMethodSubtype findSubtypeByKeyboardLayoutSetLocale( + Context context, Locale locale) { + final String localeString = locale.toString(); + final InputMethodInfo imi = SubtypeUtils.getInputMethodInfo(context.getPackageName()); + final int count = imi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype subtype = imi.getSubtypeAt(i); + if (localeString.equals(KeyboardLayoutSet.getKeyboardLayoutSetLocaleString(subtype))) { + return subtype; + } + } + throw new RuntimeException("Can not find subtype of locale " + localeString); + } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index cd01bb146..1fc945f3c 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -37,7 +37,6 @@ import com.android.inputmethod.latin.Flag; import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StringUtils; -import com.android.inputmethod.latin.SubtypeLocale; import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary; import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary; import com.android.inputmethod.latin.WhitelistDictionary; @@ -326,7 +325,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService } else if (CAPITALIZE_FIRST == capitalizeType) { for (int i = 0; i < mSuggestions.size(); ++i) { // Likewise - mSuggestions.set(i, SubtypeLocale.toTitleCase( + mSuggestions.set(i, StringUtils.toTitleCase( mSuggestions.get(i).toString(), locale)); } } -- cgit v1.2.3-83-g751a