2011-12-12 07:11:37 +00:00
|
|
|
/*
|
|
|
|
* 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.Resources;
|
2011-12-13 08:30:51 +00:00
|
|
|
import android.content.res.TypedArray;
|
|
|
|
import android.content.res.XmlResourceParser;
|
2011-12-15 05:45:14 +00:00
|
|
|
import android.util.Log;
|
2011-12-13 08:30:51 +00:00
|
|
|
import android.util.Xml;
|
2011-12-12 07:11:37 +00:00
|
|
|
import android.view.inputmethod.EditorInfo;
|
|
|
|
|
|
|
|
import com.android.inputmethod.latin.LatinIME;
|
2011-12-15 05:45:14 +00:00
|
|
|
import com.android.inputmethod.latin.LatinImeLogger;
|
2011-12-13 08:30:51 +00:00
|
|
|
import com.android.inputmethod.latin.LocaleUtils;
|
2011-12-12 07:11:37 +00:00
|
|
|
import com.android.inputmethod.latin.R;
|
|
|
|
import com.android.inputmethod.latin.SettingsValues;
|
|
|
|
import com.android.inputmethod.latin.SubtypeSwitcher;
|
|
|
|
import com.android.inputmethod.latin.Utils;
|
2011-12-18 10:54:08 +00:00
|
|
|
import com.android.inputmethod.latin.XmlParseUtils;
|
2011-12-12 07:11:37 +00:00
|
|
|
|
2011-12-13 08:30:51 +00:00
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2011-12-15 05:45:14 +00:00
|
|
|
import java.lang.ref.SoftReference;
|
2011-12-13 08:30:51 +00:00
|
|
|
import java.util.HashMap;
|
2011-12-12 07:11:37 +00:00
|
|
|
import java.util.Locale;
|
|
|
|
|
|
|
|
/**
|
2011-12-15 05:45:14 +00:00
|
|
|
* 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}.
|
2011-12-12 07:11:37 +00:00
|
|
|
*/
|
|
|
|
public class KeyboardSet {
|
2011-12-15 05:45:14 +00:00
|
|
|
private static final String TAG = KeyboardSet.class.getSimpleName();
|
|
|
|
private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
|
2011-12-13 08:30:51 +00:00
|
|
|
|
2011-12-15 05:45:14 +00:00
|
|
|
private static final String TAG_KEYBOARD_SET = TAG;
|
|
|
|
private static final String TAG_ELEMENT = "Element";
|
2011-12-12 07:11:37 +00:00
|
|
|
|
2011-12-15 05:45:14 +00:00
|
|
|
private final Context mContext;
|
|
|
|
private final Params mParams;
|
2011-12-16 07:22:20 +00:00
|
|
|
|
|
|
|
private static class Params {
|
|
|
|
int mMode;
|
2011-12-15 05:45:14 +00:00
|
|
|
int mInputType;
|
2011-12-16 07:22:20 +00:00
|
|
|
int mImeOptions;
|
|
|
|
boolean mSettingsKeyEnabled;
|
|
|
|
boolean mVoiceKeyEnabled;
|
|
|
|
boolean mVoiceKeyOnMain;
|
|
|
|
boolean mNoSettingsKey;
|
|
|
|
Locale mLocale;
|
|
|
|
int mOrientation;
|
|
|
|
int mWidth;
|
2011-12-15 05:45:14 +00:00
|
|
|
final HashMap<Integer, Integer> mElementKeyboards = new HashMap<Integer, Integer>();
|
2011-12-16 07:22:20 +00:00
|
|
|
Params() {}
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
|
|
|
|
new HashMap<KeyboardId, SoftReference<Keyboard>>();
|
2011-12-15 05:45:14 +00:00
|
|
|
|
|
|
|
public static void clearKeyboardCache() {
|
|
|
|
sKeyboardCache.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
private KeyboardSet(Context context, Params params) {
|
|
|
|
mContext = context;
|
|
|
|
mParams = params;
|
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
public Keyboard getMainKeyboard() {
|
2011-12-15 05:45:14 +00:00
|
|
|
return getKeyboard(false, false);
|
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
public Keyboard getSymbolsKeyboard() {
|
2011-12-15 05:45:14 +00:00
|
|
|
return getKeyboard(true, false);
|
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
public Keyboard getSymbolsShiftedKeyboard() {
|
|
|
|
final Keyboard keyboard = getKeyboard(true, true);
|
2011-12-15 05:45:14 +00:00
|
|
|
// TODO: Remove this logic once we introduce initial keyboard shift state attribute.
|
|
|
|
// Symbol shift keyboard may have a shift key that has a caps lock style indicator (a.k.a.
|
|
|
|
// sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
|
|
|
|
// that takes care of the current keyboard having such shift key or not.
|
|
|
|
keyboard.setShiftLocked(keyboard.hasShiftLockKey());
|
|
|
|
return keyboard;
|
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
private Keyboard getKeyboard(boolean isSymbols, boolean isShift) {
|
2011-12-15 05:45:14 +00:00
|
|
|
final int elementState = Builder.getElementState(mParams.mMode, isSymbols, isShift);
|
|
|
|
final int xmlId = mParams.mElementKeyboards.get(elementState);
|
|
|
|
final KeyboardId id = Builder.getKeyboardId(elementState, isSymbols, mParams);
|
2011-12-17 23:36:16 +00:00
|
|
|
final Keyboard keyboard = getKeyboard(mContext, xmlId, id);
|
2011-12-15 05:45:14 +00:00
|
|
|
return keyboard;
|
|
|
|
}
|
|
|
|
|
|
|
|
public KeyboardId getMainKeyboardId() {
|
|
|
|
final int elementState = Builder.getElementState(mParams.mMode, false, false);
|
|
|
|
return Builder.getKeyboardId(elementState, false, mParams);
|
|
|
|
}
|
|
|
|
|
2011-12-17 23:36:16 +00:00
|
|
|
private static Keyboard getKeyboard(Context context, int xmlId, KeyboardId id) {
|
2011-12-15 05:45:14 +00:00
|
|
|
final Resources res = context.getResources();
|
|
|
|
final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
|
2011-12-17 23:36:16 +00:00
|
|
|
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
|
|
|
|
Keyboard keyboard = (ref == null) ? null : ref.get();
|
2011-12-15 05:45:14 +00:00
|
|
|
if (keyboard == null) {
|
|
|
|
final Locale savedLocale = LocaleUtils.setSystemLocale(res, id.mLocale);
|
|
|
|
try {
|
2011-12-18 10:54:08 +00:00
|
|
|
final Keyboard.Builder<Keyboard.Params> builder =
|
|
|
|
new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params());
|
2011-12-15 05:45:14 +00:00
|
|
|
builder.load(xmlId, id);
|
|
|
|
builder.setTouchPositionCorrectionEnabled(
|
|
|
|
subtypeSwitcher.currentSubtypeContainsExtraValueKey(
|
|
|
|
LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
|
|
|
|
keyboard = builder.build();
|
|
|
|
} finally {
|
|
|
|
LocaleUtils.setSystemLocale(res, savedLocale);
|
|
|
|
}
|
2011-12-17 23:36:16 +00:00
|
|
|
sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
|
2011-12-15 05:45:14 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Remove setShiftLocked and setShift calls.
|
|
|
|
keyboard.setShiftLocked(false);
|
|
|
|
keyboard.setShifted(false);
|
|
|
|
return keyboard;
|
|
|
|
}
|
|
|
|
|
2011-12-12 07:11:37 +00:00
|
|
|
public static class Builder {
|
2011-12-15 05:45:14 +00:00
|
|
|
private final Context mContext;
|
2011-12-12 07:11:37 +00:00
|
|
|
private final Resources mResources;
|
|
|
|
|
2011-12-16 07:22:20 +00:00
|
|
|
private final Params mParams = new Params();
|
2011-12-12 07:11:37 +00:00
|
|
|
|
|
|
|
public Builder(Context context, EditorInfo editorInfo, SettingsValues settingsValues) {
|
2011-12-15 05:45:14 +00:00
|
|
|
mContext = context;
|
2011-12-12 07:11:37 +00:00
|
|
|
mResources = context.getResources();
|
|
|
|
final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
|
|
|
|
final String packageName = context.getPackageName();
|
2011-12-16 07:22:20 +00:00
|
|
|
final Params params = mParams;
|
2011-12-12 07:11:37 +00:00
|
|
|
|
2011-12-16 07:22:20 +00:00
|
|
|
params.mMode = Utils.getKeyboardMode(editorInfo);
|
|
|
|
if (editorInfo != null) {
|
2011-12-15 05:45:14 +00:00
|
|
|
params.mInputType = editorInfo.inputType;
|
2011-12-16 07:22:20 +00:00
|
|
|
params.mImeOptions = editorInfo.imeOptions;
|
|
|
|
}
|
|
|
|
params.mSettingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
|
2011-12-12 07:11:37 +00:00
|
|
|
@SuppressWarnings("deprecation")
|
|
|
|
final boolean noMicrophone = Utils.inPrivateImeOptions(
|
|
|
|
packageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
|
|
|
|
|| Utils.inPrivateImeOptions(
|
|
|
|
null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
|
2011-12-16 07:22:20 +00:00
|
|
|
params.mVoiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo) && !noMicrophone;
|
|
|
|
params.mVoiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
|
|
|
|
params.mNoSettingsKey = Utils.inPrivateImeOptions(
|
2011-12-12 07:11:37 +00:00
|
|
|
packageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
|
|
|
|
final boolean forceAscii = Utils.inPrivateImeOptions(
|
|
|
|
packageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
|
|
|
|
final boolean asciiCapable = subtypeSwitcher.currentSubtypeContainsExtraValueKey(
|
|
|
|
LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
|
2011-12-16 07:22:20 +00:00
|
|
|
params.mLocale = (forceAscii && !asciiCapable)
|
|
|
|
? Locale.US : subtypeSwitcher.getInputLocale();
|
|
|
|
params.mOrientation = mResources.getConfiguration().orientation;
|
|
|
|
params.mWidth = mResources.getDisplayMetrics().widthPixels;
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public KeyboardSet build() {
|
2011-12-16 07:22:20 +00:00
|
|
|
final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mParams.mLocale);
|
2011-12-13 08:30:51 +00:00
|
|
|
try {
|
|
|
|
parseKeyboardSet(mResources, R.xml.keyboard_set);
|
|
|
|
} catch (Exception e) {
|
|
|
|
//
|
|
|
|
} finally {
|
|
|
|
LocaleUtils.setSystemLocale(mResources, savedLocale);
|
|
|
|
}
|
2011-12-15 05:45:14 +00:00
|
|
|
return new KeyboardSet(mContext, mParams);
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
|
2011-12-15 05:45:14 +00:00
|
|
|
// TODO: Move this method to KeyboardSet
|
|
|
|
static KeyboardId getKeyboardId(int elementState, boolean isSymbols, Params params) {
|
2011-12-16 07:22:20 +00:00
|
|
|
final boolean hasShortcutKey = params.mVoiceKeyEnabled
|
|
|
|
&& (isSymbols != params.mVoiceKeyOnMain);
|
2011-12-15 05:45:14 +00:00
|
|
|
return new KeyboardId(elementState, params.mLocale, params.mOrientation, params.mWidth,
|
|
|
|
params.mMode, params.mInputType, params.mImeOptions, params.mSettingsKeyEnabled,
|
|
|
|
params.mNoSettingsKey, params.mVoiceKeyEnabled, hasShortcutKey);
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
|
2011-12-15 05:45:14 +00:00
|
|
|
// TODO: Move this method to KeyboardSet
|
|
|
|
static int getElementState(int mode, boolean isSymbols, boolean isShift) {
|
2011-12-12 07:11:37 +00:00
|
|
|
switch (mode) {
|
|
|
|
case KeyboardId.MODE_PHONE:
|
2011-12-13 08:30:51 +00:00
|
|
|
return (isSymbols && isShift)
|
|
|
|
? KeyboardId.ELEMENT_PHONE_SHIFT : KeyboardId.ELEMENT_PHONE;
|
2011-12-12 07:11:37 +00:00
|
|
|
case KeyboardId.MODE_NUMBER:
|
2011-12-13 08:30:51 +00:00
|
|
|
return KeyboardId.ELEMENT_NUMBER;
|
2011-12-12 07:11:37 +00:00
|
|
|
default:
|
|
|
|
if (isSymbols) {
|
2011-12-13 08:30:51 +00:00
|
|
|
return isShift ? KeyboardId.ELEMENT_SYMBOLS_SHIFT : KeyboardId.ELEMENT_SYMBOLS;
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
2011-12-13 08:30:51 +00:00
|
|
|
return KeyboardId.ELEMENT_ALPHABET;
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-13 08:30:51 +00:00
|
|
|
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 {
|
2011-12-14 09:11:27 +00:00
|
|
|
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
|
2011-12-13 08:30:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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 {
|
2011-12-14 09:11:27 +00:00
|
|
|
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
|
2011-12-13 08:30:51 +00:00
|
|
|
}
|
|
|
|
} else if (event == XmlPullParser.END_TAG) {
|
|
|
|
final String tag = parser.getName();
|
|
|
|
if (TAG_KEYBOARD_SET.equals(tag)) {
|
|
|
|
break;
|
|
|
|
} else {
|
2011-12-14 09:11:27 +00:00
|
|
|
throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEYBOARD_SET);
|
2011-12-13 08:30:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void parseKeyboardSetElement(XmlPullParser parser) throws XmlPullParserException,
|
|
|
|
IOException {
|
|
|
|
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
|
|
|
R.styleable.KeyboardSet_Element);
|
|
|
|
try {
|
2011-12-14 09:11:27 +00:00
|
|
|
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);
|
2011-12-13 08:30:51 +00:00
|
|
|
|
|
|
|
final int elementName = a.getInt(
|
|
|
|
R.styleable.KeyboardSet_Element_elementName, 0);
|
|
|
|
final int elementKeyboard = a.getResourceId(
|
|
|
|
R.styleable.KeyboardSet_Element_elementKeyboard, 0);
|
2011-12-16 07:22:20 +00:00
|
|
|
mParams.mElementKeyboards.put(elementName, elementKeyboard);
|
2011-12-13 08:30:51 +00:00
|
|
|
} finally {
|
|
|
|
a.recycle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String parseKeyboardLocale(Resources res, int resId)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
final XmlPullParser parser = res.getXml(resId);
|
2011-12-16 07:22:20 +00:00
|
|
|
if (parser == null)
|
|
|
|
return "";
|
2011-12-13 08:30:51 +00:00
|
|
|
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)) {
|
|
|
|
final TypedArray keyboardSetAttr = res.obtainAttributes(
|
|
|
|
Xml.asAttributeSet(parser), R.styleable.KeyboardSet);
|
|
|
|
final String locale = keyboardSetAttr.getString(
|
|
|
|
R.styleable.KeyboardSet_keyboardLocale);
|
|
|
|
keyboardSetAttr.recycle();
|
|
|
|
return locale;
|
|
|
|
} else {
|
2011-12-14 09:11:27 +00:00
|
|
|
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
|
2011-12-13 08:30:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
2011-12-12 07:11:37 +00:00
|
|
|
}
|
|
|
|
}
|