diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboard.java b/java/src/com/android/inputmethod/latin/BaseKeyboard.java index 517bfbfda..cb41ad047 100644 --- a/java/src/com/android/inputmethod/latin/BaseKeyboard.java +++ b/java/src/com/android/inputmethod/latin/BaseKeyboard.java @@ -121,7 +121,7 @@ public class BaseKeyboard { /** * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. - * Some of the key size defaults can be overridden per row from what the {@link Keyboard} + * Some of the key size defaults can be overridden per row from what the {@link BaseKeyboard} * defines. */ public static class Row { @@ -135,7 +135,7 @@ public class BaseKeyboard { public int verticalGap; /** * Edge flags for this row of keys. Possible values that can be assigned are - * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} + * {@link BaseKeyboard#EDGE_TOP EDGE_TOP} and {@link BaseKeyboard#EDGE_BOTTOM EDGE_BOTTOM} */ public int rowEdgeFlags; @@ -217,8 +217,8 @@ public class BaseKeyboard { /** * Flags that specify the anchoring to edges of the keyboard for detecting touch events * that are just out of the boundary of the key. This is a bit mask of - * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and - * {@link Keyboard#EDGE_BOTTOM}. + * {@link BaseKeyboard#EDGE_LEFT}, {@link BaseKeyboard#EDGE_RIGHT}, + * {@link BaseKeyboard#EDGE_TOP} and {@link BaseKeyboard#EDGE_BOTTOM}. */ public int edgeFlags; /** Whether this is a modifier key, such as Shift or Alt */ @@ -274,7 +274,7 @@ public class BaseKeyboard { * the XML parser. * @param res resources associated with the caller's context * @param parent the row that this key belongs to. The row must already be attached to - * a {@link Keyboard}. + * a {@link BaseKeyboard}. * @param x the x coordinate of the top-left * @param y the y coordinate of the top-left * @param parser the XML parser containing the attributes for this key diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java index b718c14be..628e764b5 100644 --- a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java +++ b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java @@ -24,13 +24,58 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.util.Log; import android.util.TypedValue; import android.util.Xml; import android.view.InflateException; import java.io.IOException; +import java.util.List; +/** + * Parser for BaseKeyboard. + * + * This class parses Keyboard XML file and fill out keys in BaseKeyboard. + * The Keyboard XML file looks like: + *
+ *   >!-- xml/keyboard.xml --<
+ *   >Keyboard keyboard_attributes*<
+ *     >!-- Keyboard Content --<
+ *     >Row row_attributes*<
+ *       >!-- Row Content --<
+ *       >Key key_attributes* /<
+ *       >Spacer horizontalGap="0.2in" /<
+ *       >include keyboardLayout="@xml/other_keys"<
+ *       ...
+ *     >/Row<
+ *     >include keyboardLayout="@xml/other_rows"<
+ *     ...
+ *   >/Keyboard<
+ * 
+ * The xml file which is included in other file must have >merge< as root element, such as: + *
+ *   >!-- xml/other_keys.xml --<
+ *   >merge<
+ *     >Key key_attributes* /<
+ *     ...
+ *   >/merge<
+ * 
+ * and + *
+ *   >!-- xml/other_rows.xml --<
+ *   >merge<
+ *     >Row row_attributes*<
+ *       >Key key_attributes* /<
+ *     >/Row<
+ *     ...
+ *   >/merge<
+ * 
+ */ public class BaseKeyboardParser { + private static final String TAG = "BaseKeyboardParser"; + private static final boolean DEBUG_TAG = false; + private static final boolean DEBUG_PARSER = false; + // Keyboard XML Tags private static final String TAG_KEYBOARD = "Keyboard"; private static final String TAG_ROW = "Row"; @@ -63,71 +108,216 @@ public class BaseKeyboardParser { public void parseKeyboard(XmlResourceParser parser) throws XmlPullParserException, IOException { - final BaseKeyboard keyboard = mKeyboard; - Key key = null; + if (DEBUG_PARSER) debugEnterMethod("parseKeyboard", false); + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.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()); + break; + } else { + throw new IllegalStartTag(parser, TAG_KEYBOARD); + } + } + } + if (DEBUG_PARSER) debugLeaveMethod("parseKeyboard", false); + } + private void parseKeyboardAttributes(XmlResourceParser parser) { + final BaseKeyboard keyboard = mKeyboard; + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard); + final int width = keyboard.getKeyboardWidth(); + final int height = keyboard.getKeyboardHeight(); + keyboard.setKeyWidth(getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyWidth, width, width / 10)); + keyboard.setKeyHeight(getDimensionOrFraction(a, + R.styleable.BaseKeyboard_keyHeight, height, 50)); + keyboard.setHorizontalGap(getDimensionOrFraction(a, + R.styleable.BaseKeyboard_horizontalGap, width, 0)); + keyboard.setVerticalGap(getDimensionOrFraction(a, + R.styleable.BaseKeyboard_verticalGap, height, 0)); + a.recycle(); + } + + private void parseKeyboardContent(XmlResourceParser parser, final List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseKeyboardContent", keys == null); + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.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); + if (keys != null && maybeStartRow(row)) { + parseRowContent(parser, row, keys); + } else { + // Skip entire + parseRowContent(parser, row, null); + } + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeKeyboardContent(parser, keys); + } else { + throw new IllegalStartTag(parser, TAG_ROW); + } + } else if (event == XmlResourceParser.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_MERGE.equals(tag)) { + break; + } else { + throw new IllegalEndTag(parser, TAG_ROW); + } + } + } + if (DEBUG_PARSER) debugLeaveMethod("parseKeyboardContent", keys == null); + } + + private void parseRowContent(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseRowContent", keys == null); + int event; + while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { + if (event == XmlResourceParser.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)) { + parseSpacer(parser, keys); + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeRowContent(parser, row, keys); + } else { + throw new IllegalStartTag(parser, TAG_KEY); + } + } else if (event == XmlResourceParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); + if (TAG_ROW.equals(tag)) { + if (keys != null) + endRow(); + break; + } else if (TAG_MERGE.equals(tag)) { + break; + } else { + throw new IllegalEndTag(parser, TAG_KEY); + } + } + } + if (DEBUG_PARSER) debugLeaveMethod("parseRowContent", keys == null); + } + + private void parseKey(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseKey", keys == null); + if (keys == null) { + checkEndTag(TAG_KEY, parser); + } else { + Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser); + checkEndTag(TAG_KEY, parser); + keys.add(key); + if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT) + mKeyboard.getShiftKeys().add(key); + endKey(key); + } + } + + private void parseSpacer(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseSpacer", keys == null); + if (keys == null) { + checkEndTag(TAG_SPACER, parser); + } else { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard); + int gap = getDimensionOrFraction(a, R.styleable.BaseKeyboard_horizontalGap, + mKeyboard.getKeyboardWidth(), 0); + a.recycle(); + checkEndTag(TAG_SPACER, parser); + setSpacer(gap); + } + } + + private void parseIncludeKeyboardContent(XmlResourceParser parser, List keys) + throws XmlPullParserException, IOException { + parseIncludeInternal(parser, null, keys); + } + + private void parseIncludeRowContent(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + parseIncludeInternal(parser, row, keys); + } + + private void parseIncludeInternal(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseInclude", keys == null); + if (keys == null) { + checkEndTag(TAG_INCLUDE, parser); + } else { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.BaseKeyboard_Include); + final int keyboardLayout = a.getResourceId( + R.styleable.BaseKeyboard_Include_keyboardLayout, 0); + a.recycle(); + + checkEndTag(TAG_INCLUDE, parser); + if (keyboardLayout == 0) + throw new ParseException("No keyboardLayout attribute in ", parser); + parseMerge(mResources.getLayout(keyboardLayout), row, keys); + } + if (DEBUG_PARSER) debugLeaveMethod("parseInclude", keys == null); + } + + private void parseMerge(XmlResourceParser parser, Row row, List keys) + throws XmlPullParserException, IOException { + if (DEBUG_PARSER) debugEnterMethod("parseMerge", keys == null); int event; while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { if (event == XmlResourceParser.START_TAG) { String tag = parser.getName(); - if (TAG_ROW.equals(tag)) { - // TODO createRowFromXml should not be called from - // BaseKeyboard constructor. - Row row = keyboard.createRowFromXml(mResources, parser); - if (!startRow(row)) - skipToEndOfRow(parser); - } else if (TAG_KEY.equals(tag)) { - // TODO createKeyFromXml should not be called from - // BaseKeyboard constructor. - key = keyboard.createKeyFromXml(mResources, mCurrentRow, mCurrentX, mCurrentY, - parser); - keyboard.getKeys().add(key); - if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT) - keyboard.getShiftKeys().add(key); - } else if (TAG_SPACER.equals(tag)) { - parseSpacerAttribute(parser); - } else if (TAG_KEYBOARD.equals(tag)) { - parseKeyboardAttributes(parser); - } else if (TAG_INCLUDE.equals(tag)) { - if (parser.getDepth() == 0) - throw new InflateException(" cannot be the root element"); - parseInclude(parser); - } else if (TAG_MERGE.equals(tag)) { - throw new InflateException( - " must not be appeared in keyboard XML file"); + if (DEBUG_TAG) debugStartTag("parseMerge", tag, keys == null); + if (TAG_MERGE.equals(tag)) { + if (row == null) { + parseKeyboardContent(parser, keys); + } else { + parseRowContent(parser, row, keys); + } + break; } else { - throw new InflateException("unknown start tag: " + tag); - } - } else if (event == XmlResourceParser.END_TAG) { - String tag = parser.getName(); - if (TAG_KEY.equals(tag)) { - endKey(key); - } else if (TAG_ROW.equals(tag)) { - endRow(); - } else if (TAG_SPACER.equals(tag)) { - ; - } else if (TAG_KEYBOARD.equals(tag)) { - endKeyboard(mKeyboard.getVerticalGap()); - } else if (TAG_INCLUDE.equals(tag)) { - ; - } else if (TAG_MERGE.equals(tag)) { - return; - } else { - throw new InflateException("unknown end tag: " + tag); + throw new ParseException( + "Included keyboard layout must have root element", parser); } } } + if (DEBUG_PARSER) debugLeaveMethod("parseMerge", keys == null); + } + + private static void checkEndTag(String tag, XmlResourceParser parser) + throws XmlPullParserException, IOException { + if (parser.next() == XmlResourceParser.END_TAG && tag.equals(parser.getName())) + return; + throw new NonEmptyTag(tag, parser); } // return true if the row is valid for this keyboard mode - private boolean startRow(Row row) { - mCurrentX = 0; - mCurrentRow = row; - return row.mode == 0 || row.mode == mKeyboard.getKeyboardMode(); - } - - private void skipRow() { - mCurrentRow = null; + private boolean maybeStartRow(Row row) { + if (DEBUG_TAG) + Log.d(TAG, String.format("startRow: mode=0x%08x keyboardMode=0x%08x", + row.mode, mKeyboard.getKeyboardMode())); + if (row.mode == 0 || row.mode == mKeyboard.getKeyboardMode()) { + mCurrentX = 0; + mCurrentRow = row; + return true; + } else { + return false; + } } private void endRow() { @@ -151,73 +341,6 @@ public class BaseKeyboardParser { mCurrentX += gap; } - private void parseSpacerAttribute(XmlResourceParser parser) { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - int gap = getDimensionOrFraction(a, R.styleable.BaseKeyboard_horizontalGap, - mKeyboard.getKeyboardWidth(), 0); - a.recycle(); - setSpacer(gap); - } - - private void parseInclude(XmlResourceParser parent) - throws XmlPullParserException, IOException { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parent), - R.styleable.BaseKeyboard_Include); - final int keyboardLayout = a.getResourceId( - R.styleable.BaseKeyboard_Include_keyboardLayout, 0); - a.recycle(); - if (keyboardLayout == 0) - throw new InflateException(" must have keyboardLayout attribute"); - final XmlResourceParser parser = mResources.getLayout(keyboardLayout); - - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.START_TAG) { - String name = parser.getName(); - if (TAG_MERGE.equals(name)) { - parseKeyboard(parser); - return; - } else { - throw new InflateException( - "include keyboard layout must have root element"); - } - } - } - } - - private void skipToEndOfRow(XmlResourceParser parser) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { - if (event == XmlResourceParser.END_TAG) { - String tag = parser.getName(); - if (TAG_ROW.equals(tag)) { - skipRow(); - return; - } - } - } - throw new InflateException("can not find "); - } - - private void parseKeyboardAttributes(XmlResourceParser parser) { - final BaseKeyboard keyboard = mKeyboard; - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.BaseKeyboard); - final int width = keyboard.getKeyboardWidth(); - final int height = keyboard.getKeyboardHeight(); - keyboard.setKeyWidth(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyWidth, width, width / 10)); - keyboard.setKeyHeight(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_keyHeight, height, 50)); - keyboard.setHorizontalGap(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_horizontalGap, width, 0)); - keyboard.setVerticalGap(getDimensionOrFraction(a, - R.styleable.BaseKeyboard_verticalGap, height, 0)); - a.recycle(); - } - public static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { final TypedValue value = a.peekValue(index); if (value == null) @@ -230,4 +353,48 @@ public class BaseKeyboardParser { } return defValue; } -} + + @SuppressWarnings("serial") + private static class ParseException extends InflateException { + public ParseException(String msg, XmlResourceParser parser) { + super(msg + " at line " + parser.getLineNumber()); + } + } + + @SuppressWarnings("serial") + private static class IllegalStartTag extends ParseException { + public IllegalStartTag(XmlResourceParser parser, String parent) { + super("Illegal start tag " + parser.getName() + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + private static class IllegalEndTag extends ParseException { + public IllegalEndTag(XmlResourceParser parser, String parent) { + super("Illegal end tag " + parser.getName() + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + private static class NonEmptyTag extends ParseException { + public NonEmptyTag(String tag, XmlResourceParser parser) { + super(tag + " must be empty tag", parser); + } + } + + private static void debugEnterMethod(String title, boolean skip) { + Log.d(TAG, title + ": enter" + (skip ? " skip" : "")); + } + + private static void debugLeaveMethod(String title, boolean skip) { + Log.d(TAG, title + ": leave" + (skip ? " skip" : "")); + } + + private static void debugStartTag(String title, String tag, boolean skip) { + Log.d(TAG, title + ": <" + tag + ">" + (skip ? " skip" : "")); + } + + private static void debugEndTag(String title, String tag, boolean skip) { + Log.d(TAG, title + ": " + (skip ? " skip" : "")); + } + }