();
@@ -144,27 +149,9 @@ public class Keyboard {
* Creates a keyboard from the given xml key layout file.
* @param context the application or service context
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- */
- public Keyboard(Context context, int xmlLayoutResId) {
- this(context, xmlLayoutResId, null);
- }
-
- /**
- * Creates a keyboard from the given keyboard identifier.
- * @param context the application or service context
* @param id keyboard identifier
*/
- public Keyboard(Context context, KeyboardId id) {
- this(context, id.getXmlId(), id);
- }
-
- /**
- * Creates a keyboard from the given xml key layout file.
- * @param context the application or service context
- * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- * @param id keyboard identifier
- */
- private Keyboard(Context context, int xmlLayoutResId, KeyboardId id) {
+ public Keyboard(Context context, int xmlLayoutResId, KeyboardId id) {
this(context, xmlLayoutResId, id,
context.getResources().getDisplayMetrics().widthPixels,
context.getResources().getDisplayMetrics().heightPixels);
@@ -188,49 +175,6 @@ public class Keyboard {
loadKeyboard(context, xmlLayoutResId);
}
- /**
- * Creates a blank keyboard from the given resource file and populates it with the specified
- * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
- *
- * If the specified number of columns is -1, then the keyboard will fit as many keys as
- * possible in each row.
- * @param context the application or service context
- * @param layoutTemplateResId the layout template file, containing no keys.
- * @param characters the list of characters to display on the keyboard. One key will be created
- * for each character.
- * @param columns the number of columns of keys to display. If this number is greater than the
- * number of keys that can fit in a row, it will be ignored. If this number is -1, the
- * keyboard will fit as many keys as possible in each row.
- */
- public Keyboard(Context context, int layoutTemplateResId,
- CharSequence characters, int columns, int horizontalPadding) {
- this(context, layoutTemplateResId);
- int x = 0;
- int y = 0;
- int column = 0;
- mTotalWidth = 0;
-
- final Row row = new Row(this);
- final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
- for (int i = 0; i < characters.length(); i++) {
- char c = characters.charAt(i);
- if (column >= maxColumns
- || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
- x = 0;
- y += mDefaultVerticalGap + mDefaultHeight;
- column = 0;
- }
- final Key key = new Key(row, c, x, y);
- column++;
- x += key.mWidth + key.mGap;
- mKeys.add(key);
- if (x > mTotalWidth) {
- mTotalWidth = x;
- }
- }
- mTotalHeight = y + mDefaultHeight;
- }
-
public List getKeys() {
return mKeys;
}
@@ -277,8 +221,16 @@ public class Keyboard {
return mTotalHeight;
}
+ public void setHeight(int height) {
+ mTotalHeight = height;
+ }
+
public int getMinWidth() {
- return mTotalWidth;
+ return mMinWidth;
+ }
+
+ public void setMinWidth(int minWidth) {
+ mMinWidth = minWidth;
}
public int getDisplayHeight() {
@@ -297,6 +249,22 @@ public class Keyboard {
mKeyboardHeight = height;
}
+ public int getPopupKeyboardResId() {
+ return mPopupKeyboardResId;
+ }
+
+ public void setPopupKeyboardResId(int resId) {
+ mPopupKeyboardResId = resId;
+ }
+
+ public int getMaxPopupKeyboardColumn() {
+ return mMaxPopupColumn;
+ }
+
+ public void setMaxPopupKeyboardColumn(int column) {
+ mMaxPopupColumn = column;
+ }
+
public List getShiftKeys() {
return mShiftKeys;
}
@@ -429,24 +397,12 @@ public class Keyboard {
return EMPTY_INT_ARRAY;
}
- // TODO should be private
- protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
- return new Row(res, this, parser);
- }
-
- // TODO should be private
- protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
- XmlResourceParser parser, KeyStyles keyStyles) {
- return new Key(res, parent, x, y, parser, keyStyles);
- }
-
private void loadKeyboard(Context context, int xmlLayoutResId) {
try {
- final Resources res = context.getResources();
- KeyboardParser parser = new KeyboardParser(this, res);
- parser.parseKeyboard(res.getXml(xmlLayoutResId));
- // mTotalWidth is the width of this keyboard which is maximum width of row.
- mTotalWidth = parser.getMaxRowWidth();
+ KeyboardParser parser = new KeyboardParser(this, context.getResources());
+ parser.parseKeyboard(xmlLayoutResId);
+ // mMinWidth is the width of this keyboard which is maximum width of row.
+ mMinWidth = parser.getMaxRowWidth();
mTotalHeight = parser.getTotalHeight();
} catch (XmlPullParserException e) {
Log.w(TAG, "keyboard XML parse error: " + e);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 883d2175c..f6577e747 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -46,12 +46,13 @@ public class KeyboardId {
public final boolean mHasVoiceKey;
public final int mImeOptions;
public final boolean mEnableShiftLock;
+ public final String mXmlName;
private final int mHashCode;
- public KeyboardId(Locale locale, int orientation, int mode,
- int xmlId, int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled,
- boolean hasVoiceKey, int imeOptions, boolean enableShiftLock) {
+ public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode,
+ int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey,
+ int imeOptions, boolean enableShiftLock) {
this.mLocale = locale;
this.mOrientation = orientation;
this.mMode = mode;
@@ -64,6 +65,7 @@ public class KeyboardId {
this.mImeOptions = imeOptions
& (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
this.mEnableShiftLock = enableShiftLock;
+ this.mXmlName = xmlName;
this.mHashCode = Arrays.hashCode(new Object[] {
locale,
@@ -120,12 +122,12 @@ public class KeyboardId {
@Override
public String toString() {
- return String.format("[%s %s %5s imeOptions=0x%08x xml=0x%08x %s%s%s%s%s]",
+ return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]",
+ mXmlName,
mLocale,
(mOrientation == 1 ? "port" : "land"),
modeName(mMode),
- mImeOptions,
- mXmlId,
+ imeOptionsName(mImeOptions),
colorSchemeName(mColorScheme),
(mHasSettingsKey ? " hasSettingsKey" : ""),
(mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
@@ -133,7 +135,7 @@ public class KeyboardId {
(mEnableShiftLock ? " enableShiftLock" : ""));
}
- private static String modeName(int mode) {
+ public static String modeName(int mode) {
switch (mode) {
case MODE_TEXT: return "text";
case MODE_URL: return "url";
@@ -146,11 +148,33 @@ public class KeyboardId {
return null;
}
- private static String colorSchemeName(int colorScheme) {
+ public static String colorSchemeName(int colorScheme) {
switch (colorScheme) {
case KeyboardView.COLOR_SCHEME_WHITE: return "white";
case KeyboardView.COLOR_SCHEME_BLACK: return "black";
}
return null;
}
+
+ public static String imeOptionsName(int imeOptions) {
+ if (imeOptions == -1) return null;
+ final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION;
+ final String action;
+ switch (actionNo) {
+ case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break;
+ case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break;
+ case EditorInfo.IME_ACTION_GO: action = "actionGo"; break;
+ case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break;
+ case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break;
+ case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break;
+ case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break;
+ default: action = "actionUnknown(" + actionNo + ")"; break;
+ }
+ if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ return "flagNoEnterAction|" + action;
+ } else {
+ return action;
+ }
+ }
}
+
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index 8a7d67451..ed59b8be5 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -30,6 +30,7 @@ import android.util.Xml;
import android.view.InflateException;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
/**
@@ -103,7 +104,7 @@ import java.util.List;
public class KeyboardParser {
private static final String TAG = "KeyboardParser";
- private static final boolean DEBUG_TAG = false;
+ private static final boolean DEBUG = false;
// Keyboard XML Tags
private static final String TAG_KEYBOARD = "Keyboard";
@@ -115,7 +116,7 @@ public class KeyboardParser {
private static final String TAG_SWITCH = "switch";
private static final String TAG_CASE = "case";
private static final String TAG_DEFAULT = "default";
- private static final String TAG_KEY_STYLE = "key-style";
+ public static final String TAG_KEY_STYLE = "key-style";
private final Keyboard mKeyboard;
private final Resources mResources;
@@ -140,13 +141,13 @@ public class KeyboardParser {
return mTotalHeight;
}
- public void parseKeyboard(XmlResourceParser parser)
- throws XmlPullParserException, IOException {
+ public void parseKeyboard(int resId) throws XmlPullParserException, IOException {
+ if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mKeyboard.mId));
+ final XmlResourceParser parser = mResources.getXml(resId);
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugStartTag("parseKeyboard", tag, false);
if (TAG_KEYBOARD.equals(tag)) {
parseKeyboardAttributes(parser);
parseKeyboardContent(parser, mKeyboard.getKeys());
@@ -160,28 +161,38 @@ public class KeyboardParser {
private void parseKeyboardAttributes(XmlResourceParser parser) {
final Keyboard keyboard = mKeyboard;
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ final TypedArray keyboardAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
- final int displayHeight = keyboard.getDisplayHeight();
- final int keyboardHeight = (int)a.getDimension(
- R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
- final int maxKeyboardHeight = getDimensionOrFraction(a,
- R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
- // Keyboard height will not exceed maxKeyboardHeight.
- final int height = Math.min(keyboardHeight, maxKeyboardHeight);
- final int width = keyboard.getDisplayWidth();
+ final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ try {
+ final int displayHeight = keyboard.getDisplayHeight();
+ final int keyboardHeight = (int)keyboardAttr.getDimension(
+ R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+ final int maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
+ // Keyboard height will not exceed maxKeyboardHeight.
+ final int height = Math.min(keyboardHeight, maxKeyboardHeight);
+ final int width = keyboard.getDisplayWidth();
- keyboard.setKeyboardHeight(height);
- keyboard.setKeyWidth(getDimensionOrFraction(a,
- R.styleable.Keyboard_keyWidth, width, width / 10));
- keyboard.setRowHeight(getDimensionOrFraction(a,
- R.styleable.Keyboard_rowHeight, height, 50));
- keyboard.setHorizontalGap(getDimensionOrFraction(a,
- R.styleable.Keyboard_horizontalGap, width, 0));
- keyboard.setVerticalGap(getDimensionOrFraction(a,
- R.styleable.Keyboard_verticalGap, height, 0));
- a.recycle();
- if (DEBUG_TAG) Log.d(TAG, "id=" + keyboard.mId);
+ keyboard.setKeyboardHeight(height);
+ keyboard.setKeyWidth(getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyWidth, width, width / 10));
+ keyboard.setRowHeight(getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight, height, 50));
+ keyboard.setHorizontalGap(getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_horizontalGap, width, 0));
+ keyboard.setVerticalGap(getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_verticalGap, height, 0));
+ keyboard.setPopupKeyboardResId(keyboardAttr.getResourceId(
+ R.styleable.Keyboard_popupKeyboardTemplate, 0));
+
+ keyboard.setMaxPopupKeyboardColumn(keyAttr.getInt(
+ R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5));
+ } finally {
+ keyAttr.recycle();
+ keyboardAttr.recycle();
+ }
}
private void parseKeyboardContent(XmlResourceParser parser, List keys)
@@ -190,9 +201,9 @@ public class KeyboardParser {
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugStartTag("parseKeyboardContent", tag, keys == null);
if (TAG_ROW.equals(tag)) {
- Row row = mKeyboard.createRowFromXml(mResources, parser);
+ Row row = new Row(mResources, mKeyboard, parser);
+ if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW));
if (keys != null)
startRow(row);
parseRowContent(parser, row, keys);
@@ -207,13 +218,12 @@ public class KeyboardParser {
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugEndTag("parseKeyboardContent", tag, keys == null);
if (TAG_KEYBOARD.equals(tag)) {
endKeyboard(mKeyboard.getVerticalGap());
break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) {
- break;
- } else if (TAG_MERGE.equals(tag)) {
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ if (DEBUG) Log.d(TAG, String.format("%s>", tag));
break;
} else if (TAG_KEY_STYLE.equals(tag)) {
continue;
@@ -230,7 +240,6 @@ public class KeyboardParser {
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugStartTag("parseRowContent", tag, keys == null);
if (TAG_KEY.equals(tag)) {
parseKey(parser, row, keys);
} else if (TAG_SPACER.equals(tag)) {
@@ -246,14 +255,14 @@ public class KeyboardParser {
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null);
if (TAG_ROW.equals(tag)) {
+ if (DEBUG) Log.d(TAG, String.format("%s>", TAG_ROW));
if (keys != null)
endRow();
break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) {
- break;
- } else if (TAG_MERGE.equals(tag)) {
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ if (DEBUG) Log.d(TAG, String.format("%s>", tag));
break;
} else if (TAG_KEY_STYLE.equals(tag)) {
continue;
@@ -269,8 +278,10 @@ public class KeyboardParser {
if (keys == null) {
checkEndTag(TAG_KEY, parser);
} else {
- Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser,
- mKeyStyles);
+ Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles);
+ if (DEBUG) Log.d(TAG, String.format("<%s keyLabel=%s codes=%s popupCharacters=%s />",
+ TAG_KEY, key.mLabel, Arrays.toString(key.mCodes),
+ Arrays.toString(key.mPopupCharacters)));
checkEndTag(TAG_KEY, parser);
keys.add(key);
if (key.mCodes[0] == Keyboard.CODE_SHIFT)
@@ -286,6 +297,7 @@ public class KeyboardParser {
if (keys == null) {
checkEndTag(TAG_SPACER, parser);
} else {
+ if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER));
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
final int gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap,
@@ -320,7 +332,8 @@ public class KeyboardParser {
checkEndTag(TAG_INCLUDE, parser);
if (keyboardLayout == 0)
throw new ParseException("No keyboardLayout attribute in ", parser);
- if (DEBUG_TAG) Log.d(TAG, String.format(" keyboardLayout=0x%08x", keyboardLayout));
+ if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />",
+ TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout)));
parseMerge(mResources.getLayout(keyboardLayout), row, keys);
}
}
@@ -331,7 +344,6 @@ public class KeyboardParser {
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugStartTag("parseMerge", tag, keys == null);
if (TAG_MERGE.equals(tag)) {
if (row == null) {
parseKeyboardContent(parser, keys);
@@ -359,13 +371,12 @@ public class KeyboardParser {
private void parseSwitchInternal(XmlResourceParser parser, Row row, List keys)
throws XmlPullParserException, IOException {
+ if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mKeyboard.mId));
boolean selected = false;
int event;
- if (DEBUG_TAG) Log.d(TAG, "parseSwitchInternal: id=" + mKeyboard.mId);
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugStartTag("parseSwitchInternal", tag, keys == null);
if (TAG_CASE.equals(tag)) {
selected |= parseCase(parser, row, selected ? null : keys);
} else if (TAG_DEFAULT.equals(tag)) {
@@ -375,8 +386,8 @@ public class KeyboardParser {
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
- if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null);
if (TAG_SWITCH.equals(tag)) {
+ if (DEBUG) Log.d(TAG, String.format("%s>", TAG_SWITCH));
break;
} else {
throw new IllegalEndTag(parser, TAG_KEY);
@@ -426,20 +437,17 @@ public class KeyboardParser {
final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched
&& voiceKeyMatched && colorSchemeMatched && imeOptionsMatched;
- if (DEBUG_TAG) {
- Log.d(TAG, String.format(
- "parseCaseCondition: %s%s%s%s%s%s%s",
- Boolean.toString(selected).toUpperCase(),
- debugInteger(a, R.styleable.Keyboard_Case_mode, "mode"),
- debugBoolean(a, R.styleable.Keyboard_Case_hasSettingsKey,
- "hasSettingsKey"),
- debugBoolean(a, R.styleable.Keyboard_Case_voiceKeyEnabled,
- "voiceKeyEnabled"),
- debugBoolean(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
- debugInteger(viewAttr, R.styleable.KeyboardView_colorScheme,
- "colorScheme"),
- debugInteger(a, R.styleable.Keyboard_Case_imeOptions, "imeOptions")));
- }
+ if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE,
+ textAttr(KeyboardId.modeName(
+ a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"),
+ textAttr(KeyboardId.colorSchemeName(
+ a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"),
+ booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
+ booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
+ textAttr(KeyboardId.imeOptionsName(
+ a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"),
+ Boolean.toString(selected)));
return selected;
} finally {
@@ -462,6 +470,7 @@ public class KeyboardParser {
private boolean parseDefault(XmlResourceParser parser, Row row, List keys)
throws XmlPullParserException, IOException {
+ if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
if (row == null) {
parseKeyboardContent(parser, keys);
} else {
@@ -471,18 +480,18 @@ public class KeyboardParser {
}
private void parseKeyStyle(XmlResourceParser parser, List keys) {
- TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_KeyStyle);
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
try {
- if (!a.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
+ if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
throw new ParseException("<" + TAG_KEY_STYLE
+ "/> needs styleName attribute", parser);
if (keys != null)
- mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser);
+ mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
} finally {
- a.recycle();
+ keyStyleAttr.recycle();
keyAttrs.recycle();
}
}
@@ -561,19 +570,11 @@ public class KeyboardParser {
}
}
- private static void debugStartTag(String title, String tag, boolean skip) {
- Log.d(TAG, title + ": <" + tag + ">" + (skip ? " skip" : ""));
+ private static String textAttr(String value, String name) {
+ return value != null ? String.format(" %s=%s", name, value) : "";
}
- private static void debugEndTag(String title, String tag, boolean skip) {
- Log.d(TAG, title + ": " + tag + ">" + (skip ? " skip" : ""));
- }
-
- private static String debugInteger(TypedArray a, int index, String name) {
- return a.hasValue(index) ? " " + name + "=" + a.getInt(index, 0) : "";
- }
-
- private static String debugBoolean(TypedArray a, int index, String name) {
- return a.hasValue(index) ? " " + name + "=" + a.getBoolean(index, false) : "";
+ private static String booleanAttr(TypedArray a, int index, String name) {
+ return a.hasValue(index) ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index cd57db360..17d01f89f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -115,7 +115,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private void makeSymbolsKeyboardIds() {
final Locale locale = mSubtypeSwitcher.getInputLocale();
- final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
+ final Resources res = mInputMethodService.getResources();
+ final int orientation = res.getConfiguration().orientation;
final int mode = mMode;
final int colorScheme = getColorScheme();
final boolean hasSettingsKey = mHasSettingsKey;
@@ -129,12 +130,14 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
// "more" and "locked more" key labels. To achieve these behavior, we should initialize
// mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard"
// respectively here for xlarge device's layout switching.
- mSymbolsId = new KeyboardId(locale, orientation, mode,
- mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols,
- colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
- mSymbolsShiftedId = new KeyboardId(locale, orientation, mode,
- mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift,
- colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
+ mSymbolsId = new KeyboardId(
+ res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
+ hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
+ mSymbolsShiftedId = new KeyboardId(
+ res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
+ hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
}
private boolean hasVoiceKey(boolean isSymbols) {
@@ -230,9 +233,11 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
enableShiftLock = true;
}
}
- final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
+ final Resources res = mInputMethodService.getResources();
+ final int orientation = res.getConfiguration().orientation;
final Locale locale = mSubtypeSwitcher.getInputLocale();
- return new KeyboardId(locale, orientation, mode, xmlId, charColorId,
+ return new KeyboardId(
+ res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId,
mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4a3a58b94..2ad414c90 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -1000,7 +1000,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
// Set the preview background state
mPreviewText.getBackground().setState(
- key.mPopupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
popupPreviewX += mOffsetInWindow[0];
popupPreviewY += mOffsetInWindow[1];
@@ -1100,7 +1100,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
private View inflateMiniKeyboardContainer(Key popupKey) {
- int popupKeyboardId = popupKey.mPopupResId;
+ int popupKeyboardResId = mKeyboard.getPopupKeyboardResId();
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View container = inflater.inflate(mPopupLayout, null);
@@ -1157,13 +1157,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
// Remove gesture detector on mini-keyboard
miniKeyboard.mGestureDetector = null;
- Keyboard keyboard;
- if (popupKey.mPopupCharacters != null) {
- keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.mPopupCharacters,
- -1, getPaddingLeft() + getPaddingRight());
- } else {
- keyboard = new Keyboard(getContext(), popupKeyboardId);
- }
+ Keyboard keyboard = new MiniKeyboardBuilder(getContext(), popupKeyboardResId, popupKey)
+ .build();
miniKeyboard.setKeyboard(keyboard);
miniKeyboard.setPopupParent(this);
@@ -1194,7 +1189,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
* method on the base class if the subclass doesn't wish to handle the call.
*/
protected boolean onLongPress(Key popupKey) {
- if (popupKey.mPopupResId == 0)
+ if (popupKey.mPopupCharacters == null)
return false;
View container = mMiniKeyboardCache.get(popupKey);
@@ -1272,15 +1267,14 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
private static boolean hasMultiplePopupChars(Key key) {
- if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 1) {
+ if (key.mPopupCharacters != null && key.mPopupCharacters.length > 1) {
return true;
}
return false;
}
private static boolean isNumberAtLeftmostPopupChar(Key key) {
- if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 0
- && isAsciiDigit(key.mPopupCharacters.charAt(0))) {
+ if (key.mPopupCharacters != null && isAsciiDigit(key.mPopupCharacters[0].charAt(0))) {
return true;
}
return false;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 7cae4f1df..b9041e36b 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -44,15 +44,13 @@ public class LatinKeyboard extends Keyboard {
public static final int OPACITY_FULLY_OPAQUE = 255;
private static final int SPACE_LED_LENGTH_PERCENT = 80;
- private Drawable mShiftLockPreviewIcon;
- private Drawable mSpaceAutoCorrectionIndicator;
+ private final Drawable mSpaceAutoCorrectionIndicator;
private final Drawable mButtonArrowLeftIcon;
private final Drawable mButtonArrowRightIcon;
private final int mSpaceBarTextShadowColor;
private int mSpaceKeyIndex = -1;
private int mSpaceDragStartX;
private int mSpaceDragLastDiff;
- private final Resources mRes;
private final Context mContext;
private boolean mCurrentlyInSpace;
private SlidingLocaleDrawable mSlidingLocaleIcon;
@@ -79,10 +77,9 @@ public class LatinKeyboard extends Keyboard {
private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
public LatinKeyboard(Context context, KeyboardId id) {
- super(context, id);
+ super(context, id.getXmlId(), id);
final Resources res = context.getResources();
mContext = context;
- mRes = res;
if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) {
mSpaceBarTextShadowColor = res.getColor(
R.color.latinkeyboard_bar_language_shadow_black);
@@ -90,8 +87,6 @@ public class LatinKeyboard extends Keyboard {
mSpaceBarTextShadowColor = res.getColor(
R.color.latinkeyboard_bar_language_shadow_white);
}
- mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
- setDefaultBounds(mShiftLockPreviewIcon);
mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
@@ -109,7 +104,7 @@ public class LatinKeyboard extends Keyboard {
}
private void updateSpaceBarForLocale(boolean isAutoCorrection) {
- final Resources res = mRes;
+ final Resources res = mContext.getResources();
// If application locales are explicitly selected.
if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) {
mSpaceKey.setIcon(new BitmapDrawable(res,
@@ -181,7 +176,7 @@ public class LatinKeyboard extends Keyboard {
final int height = mSpaceIcon.getIntrinsicHeight();
final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(buffer);
- final Resources res = mRes;
+ final Resources res = mContext.getResources();
canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
new file mode 100644
index 000000000..1eb0c3f37
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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;
+
+import java.util.List;
+
+public class MiniKeyboardBuilder {
+ private final Resources mRes;
+ private final Keyboard mKeyboard;
+ private final CharSequence[] mPopupCharacters;
+ private final int mMaxColumns;
+ private final int mNumRows;
+ private int mColPos;
+ private int mRowPos;
+ private Row mRow;
+ private int mX;
+ private int mY;
+
+ public MiniKeyboardBuilder(Context context, int layoutTemplateResId, Key popupKey) {
+ mRes = context.getResources();
+ mKeyboard = new Keyboard(context, layoutTemplateResId, null);
+ mPopupCharacters = popupKey.mPopupCharacters;
+ final int numKeys = mPopupCharacters.length;
+ final int maxColumns = popupKey.mMaxPopupColumn;
+ int numRows = numKeys / maxColumns;
+ if (numKeys % maxColumns != 0) numRows++;
+ mMaxColumns = maxColumns;
+ mNumRows = numRows;
+ // TODO: To determine key width we should pay attention to key label length.
+ mRow = new Row(mKeyboard, getRowFlags());
+ if (numRows > 1) {
+ mColPos = numKeys % maxColumns;
+ if (mColPos > 0) mColPos = maxColumns - mColPos;
+ // Centering top-row keys.
+ mX = mColPos * (mRow.mDefaultWidth + mRow.mDefaultHorizontalGap) / 2;
+ }
+ mKeyboard.setMinWidth(0);
+ }
+
+ public Keyboard build() {
+ List keys = mKeyboard.getKeys();
+ for (CharSequence label : mPopupCharacters) {
+ refresh();
+ final Key key = new Key(mRes, mRow, label, mX, mY);
+ keys.add(key);
+ advance();
+ }
+ finish();
+ return mKeyboard;
+ }
+
+ private int getRowFlags() {
+ final int rowPos = mRowPos;
+ int rowFlags = 0;
+ if (rowPos == 0) rowFlags |= Keyboard.EDGE_TOP;
+ if (rowPos == mNumRows - 1) rowFlags |= Keyboard.EDGE_BOTTOM;
+ return rowFlags;
+ }
+
+ private void refresh() {
+ if (mColPos >= mMaxColumns) {
+ final Row row = mRow;
+ // TODO: Allocate key position depending the precedence of popup characters.
+ mX = 0;
+ mY += row.mDefaultHeight + row.mVerticalGap;
+ mColPos = 0;
+ // TODO: To determine key width we should pay attention to key label length from
+ // bottom to up for rows.
+ mRow = new Row(mKeyboard, getRowFlags());
+ mRowPos++;
+ }
+ }
+
+ private void advance() {
+ final Row row = mRow;
+ final Keyboard keyboard = mKeyboard;
+ // TODO: Allocate key position depending the precedence of popup characters.
+ mX += row.mDefaultWidth + row.mDefaultHorizontalGap;
+ if (mX > keyboard.getMinWidth())
+ keyboard.setMinWidth(mX);
+ mColPos++;
+ }
+
+ private void finish() {
+ mKeyboard.setHeight(mY + mRow.mDefaultHeight);
+ }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java b/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java
new file mode 100644
index 000000000..cad3da03e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.android.inputmethod.latin.R;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+/**
+ * String parser of popupCharacters attribute of Key.
+ * The string is comma separated texts each of which represents one popup key.
+ * Each popup key text is one of the following:
+ * - A single letter (Letter)
+ * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
+ * - Icon followed by keyOutputText or code (@drawable/icon|@integer/key_code)
+ * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\'
+ * character.
+ * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well.
+ */
+public class PopupCharactersParser {
+ private static final char ESCAPE = '\\';
+ private static final String LABEL_END = "|";
+ private static final String PREFIX_AT = "@";
+ private static final String PREFIX_ICON = PREFIX_AT + "drawable/";
+ private static final String PREFIX_CODE = PREFIX_AT + "integer/";
+ private static final int[] DUMMY_CODES = { 0 };
+
+ private PopupCharactersParser() {
+ // Intentional empty constructor for utility class.
+ }
+
+ private static boolean hasIcon(String popupSpec) {
+ if (popupSpec.startsWith(PREFIX_ICON)) {
+ final int end = indexOfLabelEnd(popupSpec, 0);
+ if (end > 0)
+ return true;
+ throw new PopupCharactersParserError("outputText or code not specified: " + popupSpec);
+ }
+ return false;
+ }
+
+ private static boolean hasCode(String popupSpec) {
+ final int end = indexOfLabelEnd(popupSpec, 0);
+ if (end > 0 && end + 1 < popupSpec.length()
+ && popupSpec.substring(end + 1).startsWith(PREFIX_CODE)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String parseEscape(String text) {
+ if (text.indexOf(ESCAPE) < 0)
+ return text;
+ final int length = text.length();
+ final StringBuilder sb = new StringBuilder();
+ for (int pos = 0; pos < length; pos++) {
+ final char c = text.charAt(pos);
+ if (c == ESCAPE && pos + 1 < length) {
+ sb.append(text.charAt(++pos));
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static int indexOfLabelEnd(String popupSpec, int start) {
+ if (popupSpec.indexOf(ESCAPE, start) < 0) {
+ final int end = popupSpec.indexOf(LABEL_END, start);
+ if (end == 0)
+ throw new PopupCharactersParserError(LABEL_END + " at " + start + ": " + popupSpec);
+ return end;
+ }
+ final int length = popupSpec.length();
+ for (int pos = start; pos < length; pos++) {
+ final char c = popupSpec.charAt(pos);
+ if (c == ESCAPE && pos + 1 < length) {
+ pos++;
+ } else if (popupSpec.startsWith(LABEL_END, pos)) {
+ return pos;
+ }
+ }
+ return -1;
+ }
+
+ public static String getLabel(String popupSpec) {
+ if (hasIcon(popupSpec))
+ return null;
+ final int end = indexOfLabelEnd(popupSpec, 0);
+ final String label = (end > 0) ? parseEscape(popupSpec.substring(0, end))
+ : parseEscape(popupSpec);
+ if (TextUtils.isEmpty(label))
+ throw new PopupCharactersParserError("Empty label: " + popupSpec);
+ return label;
+ }
+
+ public static String getOutputText(String popupSpec) {
+ if (hasCode(popupSpec))
+ return null;
+ final int end = indexOfLabelEnd(popupSpec, 0);
+ if (end > 0) {
+ if (indexOfLabelEnd(popupSpec, end + 1) >= 0)
+ throw new PopupCharactersParserError("Multiple " + LABEL_END + ": "
+ + popupSpec);
+ final String outputText = parseEscape(popupSpec.substring(end + LABEL_END.length()));
+ if (!TextUtils.isEmpty(outputText))
+ return outputText;
+ throw new PopupCharactersParserError("Empty outputText: " + popupSpec);
+ }
+ final String label = getLabel(popupSpec);
+ if (label == null)
+ throw new PopupCharactersParserError("Empty label: " + popupSpec);
+ // Code is automatically generated for one letter label. See getCode().
+ if (label.length() == 1)
+ return null;
+ return label;
+ }
+
+ public static int[] getCodes(Resources res, String popupSpec) {
+ if (hasCode(popupSpec)) {
+ final int end = indexOfLabelEnd(popupSpec, 0);
+ if (indexOfLabelEnd(popupSpec, end + 1) >= 0)
+ throw new PopupCharactersParserError("Multiple " + LABEL_END + ": " + popupSpec);
+ final int resId = getResourceId(res,
+ popupSpec.substring(end + LABEL_END.length() + PREFIX_AT.length()));
+ final int code = res.getInteger(resId);
+ return new int[] { code };
+ }
+ if (indexOfLabelEnd(popupSpec, 0) > 0)
+ return DUMMY_CODES;
+ final String label = getLabel(popupSpec);
+ // Code is automatically generated for one letter label.
+ if (label != null && label.length() == 1)
+ return new int[] { label.charAt(0) };
+ return DUMMY_CODES;
+ }
+
+ public static Drawable getIcon(Resources res, String popupSpec) {
+ if (hasIcon(popupSpec)) {
+ int end = popupSpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1);
+ int resId = getResourceId(res, popupSpec.substring(PREFIX_AT.length(), end));
+ return res.getDrawable(resId);
+ }
+ return null;
+ }
+
+ private static int getResourceId(Resources res, String name) {
+ String packageName = res.getResourcePackageName(R.string.english_ime_name);
+ int resId = res.getIdentifier(name, null, packageName);
+ if (resId == 0)
+ throw new PopupCharactersParserError("Unknown resource: " + name);
+ return resId;
+ }
+
+ @SuppressWarnings("serial")
+ public static class PopupCharactersParserError extends RuntimeException {
+ public PopupCharactersParserError(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/Row.java b/java/src/com/android/inputmethod/keyboard/Row.java
index 7c158bca0..198f02ca8 100644
--- a/java/src/com/android/inputmethod/keyboard/Row.java
+++ b/java/src/com/android/inputmethod/keyboard/Row.java
@@ -45,13 +45,13 @@ public class Row {
private final Keyboard mKeyboard;
- public Row(Keyboard keyboard) {
+ public Row(Keyboard keyboard, int rowFlags) {
this.mKeyboard = keyboard;
mDefaultHeight = keyboard.getRowHeight();
mDefaultWidth = keyboard.getKeyWidth();
mDefaultHorizontalGap = keyboard.getHorizontalGap();
mVerticalGap = keyboard.getVerticalGap();
- mRowEdgeFlags = Keyboard.EDGE_TOP | Keyboard.EDGE_BOTTOM;
+ mRowEdgeFlags = rowFlags;
}
public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 243306a35..12a24e87a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2039,7 +2039,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
&& prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON,
- mResources.getBoolean(R.bool.default_popup_preview));
+ mResources.getBoolean(R.bool.config_default_popup_preview));
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
mQuickFixes = prefs.getBoolean(Settings.PREF_QUICK_FIXES, true);
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyStylesTests.java b/tests/src/com/android/inputmethod/keyboard/KeyStylesTests.java
new file mode 100644
index 000000000..3f2b7f4d5
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyStylesTests.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 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 com.android.inputmethod.keyboard.KeyStyles.EmptyKeyStyle;
+
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+
+public class KeyStylesTests extends AndroidTestCase {
+ private static void assertNumberFormatException(String message, String value) {
+ try {
+ EmptyKeyStyle.parseCsvInt(value);
+ fail(message);
+ } catch (NumberFormatException nfe) {
+ // success.
+ }
+ }
+
+ private static void assertIntArray(String message, String value, Integer ... expected) {
+ final int actual[] = EmptyKeyStyle.parseCsvInt(value);
+ assertSame(message + ": result length", expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ assertEquals(message + ": result at " + i + ":", (int)expected[i], actual[i]);
+ }
+ }
+
+ private static String format(String message, Object expected, Object actual) {
+ return message + " expected:<" + expected + "> but was:<" + actual + ">";
+ }
+
+ private static void assertTextArray(String message, CharSequence value,
+ CharSequence ... expected) {
+ final CharSequence actual[] = EmptyKeyStyle.parseCsvText(value);
+ if (expected.length == 0) {
+ assertNull(message, actual);
+ return;
+ }
+ assertSame(message + ": result length", expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ final boolean equals = TextUtils.equals(expected[i], actual[i]);
+ assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals);
+ }
+ }
+
+ public void testParseCsvInt() {
+ assertIntArray("Empty string", "");
+ assertNumberFormatException("Spaces", " ");
+ assertNumberFormatException("Non-decimal number", "abc");
+ assertIntArray("Single number", "123", 123);
+ assertIntArray("Negative number", "-123", -123);
+ assertNumberFormatException("Hexadecimal number", "1b2b");
+ assertIntArray("Multiple numbers", "123,456", 123, 456);
+ assertNumberFormatException("Non-decimal numbers", "123,abc");
+ assertNumberFormatException("Escaped comma", "123\\,456");
+ assertNumberFormatException("Escaped escape", "123\\\\,456");
+ }
+
+ public void testParseCsvTextZero() {
+ assertTextArray("Empty string", "");
+ }
+
+ public void testParseCsvTextSingle() {
+ assertTextArray("Single char", "a", "a");
+ assertTextArray("Space", " ", " ");
+ assertTextArray("Single label", "abc", "abc");
+ assertTextArray("Spaces", " ", " ");
+ assertTextArray("Spaces in label", "a b c", "a b c");
+ assertTextArray("Spaces at beginning of label", " abc", " abc");
+ assertTextArray("Spaces at end of label", "abc ", "abc ");
+ assertTextArray("label surrounded by spaces", " abc ", " abc ");
+ }
+
+ public void testParseCsvTextSingleEscaped() {
+ assertTextArray("Escaped char", "\\a", "a");
+ assertTextArray("Escaped comma", "\\,", ",");
+ assertTextArray("Escaped escape", "\\\\", "\\");
+ assertTextArray("Escaped label", "a\\bc", "abc");
+ assertTextArray("Escaped label at begininng", "\\abc", "abc");
+ assertTextArray("Escaped label with comma", "a\\,c", "a,c");
+ assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc");
+ assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc");
+ assertTextArray("Escaped label with escape", "a\\\\c", "a\\c");
+ }
+
+ public void testParseCsvTextMulti() {
+ assertTextArray("Multiple chars", "a,b,c", "a", "b", "c");
+ assertTextArray("Multiple chars surrounded by spaces", " a , b , c ", " a ", " b ", " c ");
+ assertTextArray("Multiple labels", "abc,def,ghi", "abc", "def", "ghi");
+ assertTextArray("Multiple labels surrounded by spaces", " abc , def , ghi ",
+ " abc ", " def ", " ghi ");
+ }
+
+ public void testParseCsvTextMultiEscaped() {
+ assertTextArray("Multiple chars with comma", "a,\\,,c", "a", ",", "c");
+ assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ",
+ " a ", " , ", " c ");
+ assertTextArray("Multiple labels with escape", "\\abc,d\\ef,gh\\i", "abc", "def", "ghi");
+ assertTextArray("Multiple labels with escape surrounded by spaces",
+ " \\abc , d\\ef , gh\\i ", " abc ", " def ", " ghi ");
+ assertTextArray("Multiple labels with comma and escape",
+ "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i");
+ assertTextArray("Multiple labels with comma and escape surrounded by spaces",
+ " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\ ", " d\\, ", " g,i ");
+ }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/PopupCharactersParserTests.java b/tests/src/com/android/inputmethod/keyboard/PopupCharactersParserTests.java
new file mode 100644
index 000000000..77b62ca5d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/PopupCharactersParserTests.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2010 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 com.android.inputmethod.latin.R;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+
+public class PopupCharactersParserTests extends AndroidTestCase {
+ private Resources mRes;
+
+ private static final String CODE_SETTINGS = "@integer/key_settings";
+ private static final String ICON_SETTINGS = "@drawable/sym_keyboard_settings";
+ private static final String CODE_NON_EXISTING = "@integer/non_existing";
+ private static final String ICON_NON_EXISTING = "@drawable/non_existing";
+
+ private int mCodeSettings;
+ private Drawable mIconSettings;
+
+ private static final Integer[] DUMMY_CODES = { 0 };
+
+ @Override
+ protected void setUp() {
+ Resources res = getContext().getResources();
+ mRes = res;
+
+ final String packageName = res.getResourcePackageName(R.string.english_ime_name);
+ final int codeId = res.getIdentifier(CODE_SETTINGS.substring(1), null, packageName);
+ final int iconId = res.getIdentifier(ICON_SETTINGS.substring(1), null, packageName);
+ mCodeSettings = res.getInteger(codeId);
+ mIconSettings = res.getDrawable(iconId);
+ }
+
+ private void assertParser(String message, String popupSpec, String expectedLabel,
+ String expectedOutputText, Drawable expectedIcon, Integer ... expectedCodes) {
+ String actualLabel = PopupCharactersParser.getLabel(popupSpec);
+ assertEquals(message + ": label:", expectedLabel, actualLabel);
+
+ String actualOutputText = PopupCharactersParser.getOutputText(popupSpec);
+ assertEquals(message + ": ouptputText:", expectedOutputText, actualOutputText);
+
+ Drawable actualIcon = PopupCharactersParser.getIcon(mRes, popupSpec);
+ // We can not compare drawables, checking null or non-null instead.
+ if (expectedIcon == null) {
+ assertNull(message + ": icon null:", actualIcon);
+ } else {
+ assertNotNull(message + ": icon non-null:", actualIcon);
+ }
+
+ int[] actualCodes = PopupCharactersParser.getCodes(mRes, popupSpec);
+ if (expectedCodes == null) {
+ assertNull(message + ": codes null:", actualCodes);
+ return;
+ }
+ assertSame(message + ": codes length:", expectedCodes.length, actualCodes.length);
+ for (int i = 0; i < actualCodes.length; i++) {
+ assertEquals(message + ": codes value at " + i + ":", (int)expectedCodes[i],
+ actualCodes[i]);
+ }
+ }
+
+ private void assertParserError(String message, String popupSpec, String expectedLabel,
+ String expectedOutputText, Drawable expectedIcon, Integer ... expectedCodes) {
+ try {
+ if (expectedCodes.length > 0) {
+ assertParser(message, popupSpec, expectedLabel, expectedOutputText, expectedIcon,
+ expectedCodes);
+ } else {
+ assertParser(message, popupSpec, expectedLabel, expectedOutputText, expectedIcon,
+ DUMMY_CODES);
+ }
+ fail(message);
+ } catch (PopupCharactersParser.PopupCharactersParserError pcpe) {
+ // success.
+ }
+ }
+
+ public void testSingleLetter() {
+ assertParser("Single letter", "a", "a", null, null, (int)'a');
+ assertParser("Single escaped bar", "\\|", "|", null, null, (int)'|');
+ assertParser("Single escaped escape", "\\\\", "\\", null, null, (int)'\\');
+ assertParser("Single comma", ",", ",", null, null, (int)',');
+ assertParser("Single escaped comma", "\\,", ",", null, null, (int)',');
+ assertParser("Single escaped letter", "\\a", "a", null, null, (int)'a');
+ assertParser("Single at", "@", "@", null, null, (int)'@');
+ assertParser("Single escaped at", "\\@", "@", null, null, (int)'@');
+ assertParser("Single letter with outputText", "a|abc", "a", "abc", null, DUMMY_CODES);
+ assertParser("Single letter with escaped outputText", "a|a\\|c", "a", "a|c", null,
+ DUMMY_CODES);
+ assertParser("Single letter with comma outputText", "a|a,b", "a", "a,b", null, DUMMY_CODES);
+ assertParser("Single letter with escaped comma outputText", "a|a\\,b", "a", "a,b", null,
+ DUMMY_CODES);
+ assertParser("Single letter with outputText starts with at", "a|@bc", "a", "@bc", null,
+ DUMMY_CODES);
+ assertParser("Single letter with outputText contains at", "a|a@c", "a", "a@c", null,
+ DUMMY_CODES);
+ assertParser("Single letter with escaped at outputText", "a|\\@bc", "a", "@bc", null,
+ DUMMY_CODES);
+ assertParser("Single escaped escape with outputText", "\\\\|\\\\", "\\", "\\", null,
+ DUMMY_CODES);
+ assertParser("Single escaped bar with outputText", "\\||\\|", "|", "|", null, DUMMY_CODES);
+ assertParser("Single letter with code", "a|" + CODE_SETTINGS, "a", null, null,
+ mCodeSettings);
+ }
+
+ public void testLabel() {
+ assertParser("Simple label", "abc", "abc", "abc", null, DUMMY_CODES);
+ assertParser("Label with escaped bar", "a\\|c", "a|c", "a|c", null, DUMMY_CODES);
+ assertParser("Label with escaped escape", "a\\\\c", "a\\c", "a\\c", null, DUMMY_CODES);
+ assertParser("Label with comma", "a,c", "a,c", "a,c", null, DUMMY_CODES);
+ assertParser("Label with escaped comma", "a\\,c", "a,c", "a,c", null, DUMMY_CODES);
+ assertParser("Label starts with at", "@bc", "@bc", "@bc", null, DUMMY_CODES);
+ assertParser("Label contains at", "a@c", "a@c", "a@c", null, DUMMY_CODES);
+ assertParser("Label with escaped at", "\\@bc", "@bc", "@bc", null, DUMMY_CODES);
+ assertParser("Label with escaped letter", "\\abc", "abc", "abc", null, DUMMY_CODES);
+ assertParser("Label with outputText", "abc|def", "abc", "def", null, DUMMY_CODES);
+ assertParser("Label with comma and outputText", "a,c|def", "a,c", "def", null, DUMMY_CODES);
+ assertParser("Escaped comma label with outputText", "a\\,c|def", "a,c", "def", null,
+ DUMMY_CODES);
+ assertParser("Escaped label with outputText", "a\\|c|def", "a|c", "def", null, DUMMY_CODES);
+ assertParser("Label with escaped bar outputText", "abc|d\\|f", "abc", "d|f", null,
+ DUMMY_CODES);
+ assertParser("Escaped escape label with outputText", "a\\\\|def", "a\\", "def", null,
+ DUMMY_CODES);
+ assertParser("Label starts with at and outputText", "@bc|def", "@bc", "def", null,
+ DUMMY_CODES);
+ assertParser("Label contains at label and outputText", "a@c|def", "a@c", "def", null,
+ DUMMY_CODES);
+ assertParser("Escaped at label with outputText", "\\@bc|def", "@bc", "def", null,
+ DUMMY_CODES);
+ assertParser("Label with comma outputText", "abc|a,b", "abc", "a,b", null, DUMMY_CODES);
+ assertParser("Label with escaped comma outputText", "abc|a\\,b", "abc", "a,b", null,
+ DUMMY_CODES);
+ assertParser("Label with outputText starts with at", "abc|@bc", "abc", "@bc", null,
+ DUMMY_CODES);
+ assertParser("Label with outputText contains at", "abc|a@c", "abc", "a@c", null,
+ DUMMY_CODES);
+ assertParser("Label with escaped at outputText", "abc|\\@bc", "abc", "@bc", null,
+ DUMMY_CODES);
+ assertParser("Label with escaped bar outputText", "abc|d\\|f", "abc", "d|f",
+ null, DUMMY_CODES);
+ assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f", "a|c", "d|f",
+ null, DUMMY_CODES);
+ assertParser("Label with code", "abc|" + CODE_SETTINGS, "abc", null, null, mCodeSettings);
+ assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS, "a|c", null, null,
+ mCodeSettings);
+ }
+
+ public void testIconAndCode() {
+ assertParser("Icon with outputText", ICON_SETTINGS + "|abc", null, "abc", mIconSettings,
+ DUMMY_CODES);
+ assertParser("Icon with outputText starts with at", ICON_SETTINGS + "|@bc", null, "@bc",
+ mIconSettings, DUMMY_CODES);
+ assertParser("Icon with outputText contains at", ICON_SETTINGS + "|a@c", null, "a@c",
+ mIconSettings, DUMMY_CODES);
+ assertParser("Icon with escaped at outputText", ICON_SETTINGS + "|\\@bc", null, "@bc",
+ mIconSettings, DUMMY_CODES);
+ assertParser("Label starts with at and code", "@bc|" + CODE_SETTINGS, "@bc", null, null,
+ mCodeSettings);
+ assertParser("Label contains at and code", "a@c|" + CODE_SETTINGS, "a@c", null, null,
+ mCodeSettings);
+ assertParser("Escaped at label with code", "\\@bc|" + CODE_SETTINGS, "@bc", null, null,
+ mCodeSettings);
+ assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS, null, null,
+ mIconSettings, mCodeSettings);
+ }
+
+ public void testFormatError() {
+ assertParserError("Empty spec", "", null, null, null);
+ assertParserError("Empty label with outputText", "|a", null, "a", null);
+ assertParserError("Empty label with code", "|" + CODE_SETTINGS, null, null, null,
+ mCodeSettings);
+ assertParserError("Empty outputText with label", "a|", "a", null, null);
+ assertParserError("Empty outputText with icon", ICON_SETTINGS + "|", null, null,
+ mIconSettings);
+ assertParserError("Empty icon and code", "|", null, null, null);
+ assertParserError("Icon without code", ICON_SETTINGS, null, null, mIconSettings);
+ assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc", null, "abc", null);
+ assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING, "abc", null, null);
+ assertParserError("Third bar at end", "a|b|", "a", null, null);
+ assertParserError("Multiple bar", "a|b|c", "a", null, null);
+ assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c", "a",
+ null, null, mCodeSettings);
+ assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c", null,
+ null, mIconSettings);
+ assertParserError("Multiple bar with icon and code",
+ ICON_SETTINGS + "|" + CODE_SETTINGS + "|c", null, null, mIconSettings,
+ mCodeSettings);
+ }
+}