| /* |
| * Copyright (C) 2012 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.internal; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.content.res.XmlResourceParser; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.util.Xml; |
| |
| import com.android.inputmethod.annotations.UsedForTesting; |
| import com.android.inputmethod.keyboard.Key; |
| import com.android.inputmethod.keyboard.Keyboard; |
| import com.android.inputmethod.keyboard.KeyboardId; |
| import com.android.inputmethod.latin.LocaleUtils.RunInLocale; |
| import com.android.inputmethod.latin.R; |
| import com.android.inputmethod.latin.ResourceUtils; |
| 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.util.Arrays; |
| import java.util.Locale; |
| |
| /** |
| * Keyboard Building helper. |
| * |
| * This class parses Keyboard XML file and eventually build a Keyboard. |
| * The Keyboard XML file looks like: |
| * <pre> |
| * <!-- xml/keyboard.xml --> |
| * <Keyboard keyboard_attributes*> |
| * <!-- Keyboard Content --> |
| * <Row row_attributes*> |
| * <!-- Row Content --> |
| * <Key key_attributes* /> |
| * <Spacer horizontalGap="32.0dp" /> |
| * <include keyboardLayout="@xml/other_keys"> |
| * ... |
| * </Row> |
| * <include keyboardLayout="@xml/other_rows"> |
| * ... |
| * </Keyboard> |
| * </pre> |
| * The XML file which is included in other file must have <merge> as root element, |
| * such as: |
| * <pre> |
| * <!-- xml/other_keys.xml --> |
| * <merge> |
| * <Key key_attributes* /> |
| * ... |
| * </merge> |
| * </pre> |
| * and |
| * <pre> |
| * <!-- xml/other_rows.xml --> |
| * <merge> |
| * <Row row_attributes*> |
| * <Key key_attributes* /> |
| * </Row> |
| * ... |
| * </merge> |
| * </pre> |
| * You can also use switch-case-default tags to select Rows and Keys. |
| * <pre> |
| * <switch> |
| * <case case_attribute*> |
| * <!-- Any valid tags at switch position --> |
| * </case> |
| * ... |
| * <default> |
| * <!-- Any valid tags at switch position --> |
| * </default> |
| * </switch> |
| * </pre> |
| * You can declare Key style and specify styles within Key tags. |
| * <pre> |
| * <switch> |
| * <case mode="email"> |
| * <key-style styleName="f1-key" parentStyle="modifier-key" |
| * keyLabel=".com" |
| * /> |
| * </case> |
| * <case mode="url"> |
| * <key-style styleName="f1-key" parentStyle="modifier-key" |
| * keyLabel="http://" |
| * /> |
| * </case> |
| * </switch> |
| * ... |
| * <Key keyStyle="shift-key" ... /> |
| * </pre> |
| */ |
| |
| public class KeyboardBuilder<KP extends KeyboardParams> { |
| private static final String BUILDER_TAG = "Keyboard.Builder"; |
| private static final boolean DEBUG = false; |
| |
| // Keyboard XML Tags |
| private static final String TAG_KEYBOARD = "Keyboard"; |
| private static final String TAG_ROW = "Row"; |
| private static final String TAG_KEY = "Key"; |
| private static final String TAG_SPACER = "Spacer"; |
| private static final String TAG_INCLUDE = "include"; |
| private static final String TAG_MERGE = "merge"; |
| private static final String TAG_SWITCH = "switch"; |
| private static final String TAG_CASE = "case"; |
| private static final String TAG_DEFAULT = "default"; |
| public static final String TAG_KEY_STYLE = "key-style"; |
| |
| private static final int DEFAULT_KEYBOARD_COLUMNS = 10; |
| private static final int DEFAULT_KEYBOARD_ROWS = 4; |
| |
| protected final KP mParams; |
| protected final Context mContext; |
| protected final Resources mResources; |
| |
| private int mCurrentY = 0; |
| private KeyboardRow mCurrentRow = null; |
| private boolean mLeftEdge; |
| private boolean mTopEdge; |
| private Key mRightEdgeKey = null; |
| |
| public KeyboardBuilder(final Context context, final KP params) { |
| mContext = context; |
| final Resources res = context.getResources(); |
| mResources = res; |
| |
| mParams = params; |
| |
| params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); |
| params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); |
| } |
| |
| public void setAutoGenerate(final KeysCache keysCache) { |
| mParams.mKeysCache = keysCache; |
| } |
| |
| public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) { |
| mParams.mId = id; |
| final XmlResourceParser parser = mResources.getXml(xmlId); |
| try { |
| parseKeyboard(parser); |
| } catch (XmlPullParserException e) { |
| Log.w(BUILDER_TAG, "keyboard XML parse error", e); |
| throw new IllegalArgumentException(e.getMessage(), e); |
| } catch (IOException e) { |
| Log.w(BUILDER_TAG, "keyboard XML parse error", e); |
| throw new RuntimeException(e.getMessage(), e); |
| } finally { |
| parser.close(); |
| } |
| return this; |
| } |
| |
| @UsedForTesting |
| public void disableTouchPositionCorrectionDataForTest() { |
| mParams.mTouchPositionCorrection.setEnabled(false); |
| } |
| |
| public void setProximityCharsCorrectionEnabled(final boolean enabled) { |
| mParams.mProximityCharsCorrectionEnabled = enabled; |
| } |
| |
| public Keyboard build() { |
| return new Keyboard(mParams); |
| } |
| |
| private int mIndent; |
| private static final String SPACES = " "; |
| |
| private static String spaces(final int count) { |
| return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES; |
| } |
| |
| private void startTag(final String format, final Object ... args) { |
| Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); |
| } |
| |
| private void endTag(final String format, final Object ... args) { |
| Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args)); |
| } |
| |
| private void startEndTag(final String format, final Object ... args) { |
| Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); |
| mIndent--; |
| } |
| |
| private void parseKeyboard(final XmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final int event = parser.next(); |
| if (event == XmlPullParser.START_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_KEYBOARD.equals(tag)) { |
| parseKeyboardAttributes(parser); |
| startKeyboard(); |
| parseKeyboardContent(parser, false); |
| break; |
| } else { |
| throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD); |
| } |
| } |
| } |
| } |
| |
| private void parseKeyboardAttributes(final XmlPullParser parser) { |
| final TypedArray keyboardAttr = mContext.obtainStyledAttributes( |
| Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle, |
| R.style.Keyboard); |
| final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), |
| R.styleable.Keyboard_Key); |
| try { |
| final KeyboardParams params = mParams; |
| final int height = params.mId.mHeight; |
| final int width = params.mId.mWidth; |
| params.mOccupiedHeight = height; |
| params.mOccupiedWidth = width; |
| params.mTopPadding = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_keyboardTopPadding, height, height, 0); |
| params.mBottomPadding = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_keyboardBottomPadding, height, height, 0); |
| params.mLeftPadding = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_keyboardLeftPadding, width, width, 0); |
| params.mRightPadding = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_keyboardRightPadding, width, width, 0); |
| |
| final int baseWidth = |
| params.mOccupiedWidth - params.mLeftPadding - params.mRightPadding; |
| params.mBaseWidth = baseWidth; |
| params.mDefaultKeyWidth = (int)keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, |
| baseWidth, baseWidth, baseWidth / DEFAULT_KEYBOARD_COLUMNS); |
| params.mHorizontalGap = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_horizontalGap, baseWidth, baseWidth, 0); |
| // TODO: Fix keyboard geometry calculation clearer. Historically vertical gap between |
| // rows are determined based on the entire keyboard height including top and bottom |
| // paddings. |
| params.mVerticalGap = (int)keyboardAttr.getFraction( |
| R.styleable.Keyboard_verticalGap, height, height, 0); |
| final int baseHeight = params.mOccupiedHeight - params.mTopPadding |
| - params.mBottomPadding + params.mVerticalGap; |
| params.mBaseHeight = baseHeight; |
| params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, |
| R.styleable.Keyboard_rowHeight, baseHeight, baseHeight / DEFAULT_KEYBOARD_ROWS); |
| |
| params.mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); |
| |
| params.mMoreKeysTemplate = keyboardAttr.getResourceId( |
| R.styleable.Keyboard_moreKeysTemplate, 0); |
| params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt( |
| R.styleable.Keyboard_Key_maxMoreKeysColumn, 5); |
| |
| params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0); |
| params.mIconsSet.loadIcons(keyboardAttr); |
| final String language = params.mId.mLocale.getLanguage(); |
| params.mCodesSet.setLanguage(language); |
| params.mTextsSet.setLanguage(language); |
| final RunInLocale<Void> job = new RunInLocale<Void>() { |
| @Override |
| protected Void job(Resources res) { |
| params.mTextsSet.loadStringResources(mContext); |
| return null; |
| } |
| }; |
| // Null means the current system locale. |
| final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype) |
| ? null : params.mId.mLocale; |
| job.runInLocale(mResources, locale); |
| |
| final int resourceId = keyboardAttr.getResourceId( |
| R.styleable.Keyboard_touchPositionCorrectionData, 0); |
| if (resourceId != 0) { |
| final String[] data = mResources.getStringArray(resourceId); |
| params.mTouchPositionCorrection.load(data); |
| } |
| } finally { |
| keyAttr.recycle(); |
| keyboardAttr.recycle(); |
| } |
| } |
| |
| private void parseKeyboardContent(final XmlPullParser parser, final boolean skip) |
| throws XmlPullParserException, IOException { |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final int event = parser.next(); |
| if (event == XmlPullParser.START_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_ROW.equals(tag)) { |
| final KeyboardRow row = parseRowAttributes(parser); |
| if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : ""); |
| if (!skip) { |
| startRow(row); |
| } |
| parseRowContent(parser, row, skip); |
| } else if (TAG_INCLUDE.equals(tag)) { |
| parseIncludeKeyboardContent(parser, skip); |
| } else if (TAG_SWITCH.equals(tag)) { |
| parseSwitchKeyboardContent(parser, skip); |
| } else if (TAG_KEY_STYLE.equals(tag)) { |
| parseKeyStyle(parser, skip); |
| } else { |
| throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW); |
| } |
| } else if (event == XmlPullParser.END_TAG) { |
| final String tag = parser.getName(); |
| if (DEBUG) endTag("</%s>", tag); |
| if (TAG_KEYBOARD.equals(tag)) { |
| endKeyboard(); |
| break; |
| } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) |
| || TAG_MERGE.equals(tag)) { |
| break; |
| } else { |
| throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW); |
| } |
| } |
| } |
| } |
| |
| private KeyboardRow parseRowAttributes(final XmlPullParser parser) |
| throws XmlPullParserException { |
| final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), |
| R.styleable.Keyboard); |
| try { |
| if (a.hasValue(R.styleable.Keyboard_horizontalGap)) { |
| throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "horizontalGap"); |
| } |
| if (a.hasValue(R.styleable.Keyboard_verticalGap)) { |
| throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "verticalGap"); |
| } |
| return new KeyboardRow(mResources, mParams, parser, mCurrentY); |
| } finally { |
| a.recycle(); |
| } |
| } |
| |
| private void parseRowContent(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final int event = parser.next(); |
| if (event == XmlPullParser.START_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_KEY.equals(tag)) { |
| parseKey(parser, row, skip); |
| } else if (TAG_SPACER.equals(tag)) { |
| parseSpacer(parser, row, skip); |
| } else if (TAG_INCLUDE.equals(tag)) { |
| parseIncludeRowContent(parser, row, skip); |
| } else if (TAG_SWITCH.equals(tag)) { |
| parseSwitchRowContent(parser, row, skip); |
| } else if (TAG_KEY_STYLE.equals(tag)) { |
| parseKeyStyle(parser, skip); |
| } else { |
| throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_ROW); |
| } |
| } else if (event == XmlPullParser.END_TAG) { |
| final String tag = parser.getName(); |
| if (DEBUG) endTag("</%s>", tag); |
| if (TAG_ROW.equals(tag)) { |
| if (!skip) { |
| endRow(row); |
| } |
| break; |
| } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) |
| || TAG_MERGE.equals(tag)) { |
| break; |
| } else { |
| throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW); |
| } |
| } |
| } |
| } |
| |
| private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip) |
| throws XmlPullParserException, IOException { |
| if (skip) { |
| XmlParseUtils.checkEndTag(TAG_KEY, parser); |
| if (DEBUG) { |
| startEndTag("<%s /> skipped", TAG_KEY); |
| } |
| } else { |
| final Key key = new Key(mResources, mParams, row, parser); |
| if (DEBUG) { |
| startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, |
| (key.isEnabled() ? "" : " disabled"), key, |
| Arrays.toString(key.mMoreKeys)); |
| } |
| XmlParseUtils.checkEndTag(TAG_KEY, parser); |
| endKey(key); |
| } |
| } |
| |
| private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip) |
| throws XmlPullParserException, IOException { |
| if (skip) { |
| XmlParseUtils.checkEndTag(TAG_SPACER, parser); |
| if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER); |
| } else { |
| final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser); |
| if (DEBUG) startEndTag("<%s />", TAG_SPACER); |
| XmlParseUtils.checkEndTag(TAG_SPACER, parser); |
| endKey(spacer); |
| } |
| } |
| |
| private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip) |
| throws XmlPullParserException, IOException { |
| parseIncludeInternal(parser, null, skip); |
| } |
| |
| private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| parseIncludeInternal(parser, row, skip); |
| } |
| |
| private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| if (skip) { |
| XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); |
| if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE); |
| } else { |
| final AttributeSet attr = Xml.asAttributeSet(parser); |
| final TypedArray keyboardAttr = mResources.obtainAttributes(attr, |
| R.styleable.Keyboard_Include); |
| final TypedArray keyAttr = mResources.obtainAttributes(attr, |
| R.styleable.Keyboard_Key); |
| int keyboardLayout = 0; |
| float savedDefaultKeyWidth = 0; |
| int savedDefaultKeyLabelFlags = 0; |
| int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL; |
| try { |
| XmlParseUtils.checkAttributeExists(keyboardAttr, |
| R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", |
| TAG_INCLUDE, parser); |
| keyboardLayout = keyboardAttr.getResourceId( |
| R.styleable.Keyboard_Include_keyboardLayout, 0); |
| if (row != null) { |
| if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { |
| // Override current x coordinate. |
| row.setXPos(row.getKeyX(keyAttr)); |
| } |
| // TODO: Remove this if-clause and do the same as backgroundType below. |
| savedDefaultKeyWidth = row.getDefaultKeyWidth(); |
| if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) { |
| // Override default key width. |
| row.setDefaultKeyWidth(row.getKeyWidth(keyAttr)); |
| } |
| savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags(); |
| // Bitwise-or default keyLabelFlag if exists. |
| row.setDefaultKeyLabelFlags(keyAttr.getInt( |
| R.styleable.Keyboard_Key_keyLabelFlags, 0) |
| | savedDefaultKeyLabelFlags); |
| savedDefaultBackgroundType = row.getDefaultBackgroundType(); |
| // Override default backgroundType if exists. |
| row.setDefaultBackgroundType(keyAttr.getInt( |
| R.styleable.Keyboard_Key_backgroundType, |
| savedDefaultBackgroundType)); |
| } |
| } finally { |
| keyboardAttr.recycle(); |
| keyAttr.recycle(); |
| } |
| |
| XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); |
| if (DEBUG) { |
| startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE, |
| mResources.getResourceEntryName(keyboardLayout)); |
| } |
| final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout); |
| try { |
| parseMerge(parserForInclude, row, skip); |
| } finally { |
| if (row != null) { |
| // Restore default keyWidth, keyLabelFlags, and backgroundType. |
| row.setDefaultKeyWidth(savedDefaultKeyWidth); |
| row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags); |
| row.setDefaultBackgroundType(savedDefaultBackgroundType); |
| } |
| parserForInclude.close(); |
| } |
| } |
| } |
| |
| private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip) |
| throws XmlPullParserException, IOException { |
| if (DEBUG) startTag("<%s>", TAG_MERGE); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final int event = parser.next(); |
| if (event == XmlPullParser.START_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_MERGE.equals(tag)) { |
| if (row == null) { |
| parseKeyboardContent(parser, skip); |
| } else { |
| parseRowContent(parser, row, skip); |
| } |
| break; |
| } else { |
| throw new XmlParseUtils.ParseException( |
| "Included keyboard layout must have <merge> root element", parser); |
| } |
| } |
| } |
| } |
| |
| private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip) |
| throws XmlPullParserException, IOException { |
| parseSwitchInternal(parser, null, skip); |
| } |
| |
| private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| parseSwitchInternal(parser, row, skip); |
| } |
| |
| private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId); |
| boolean selected = false; |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final int event = parser.next(); |
| if (event == XmlPullParser.START_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_CASE.equals(tag)) { |
| selected |= parseCase(parser, row, selected ? true : skip); |
| } else if (TAG_DEFAULT.equals(tag)) { |
| selected |= parseDefault(parser, row, selected ? true : skip); |
| } else { |
| throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_SWITCH); |
| } |
| } else if (event == XmlPullParser.END_TAG) { |
| final String tag = parser.getName(); |
| if (TAG_SWITCH.equals(tag)) { |
| if (DEBUG) endTag("</%s>", TAG_SWITCH); |
| break; |
| } else { |
| throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_SWITCH); |
| } |
| } |
| } |
| } |
| |
| private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip) |
| throws XmlPullParserException, IOException { |
| final boolean selected = parseCaseCondition(parser); |
| if (row == null) { |
| // Processing Rows. |
| parseKeyboardContent(parser, selected ? skip : true); |
| } else { |
| // Processing Keys. |
| parseRowContent(parser, row, selected ? skip : true); |
| } |
| return selected; |
| } |
| |
| private boolean parseCaseCondition(final XmlPullParser parser) { |
| final KeyboardId id = mParams.mId; |
| if (id == null) { |
| return true; |
| } |
| final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), |
| R.styleable.Keyboard_Case); |
| try { |
| 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)); |
| final boolean navigateNextMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); |
| final boolean navigatePreviousMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); |
| final boolean passwordInputMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); |
| final boolean clobberSettingsKeyMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); |
| final boolean shortcutKeyEnabledMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled); |
| final boolean shortcutKeyOnSymbolsMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_shortcutKeyOnSymbols, id.mShortcutKeyOnSymbols); |
| final boolean hasShortcutKeyMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); |
| final boolean languageSwitchKeyEnabledMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_languageSwitchKeyEnabled, |
| id.mLanguageSwitchKeyEnabled); |
| final boolean isMultiLineMatched = matchBoolean(a, |
| R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine()); |
| final boolean imeActionMatched = matchInteger(a, |
| R.styleable.Keyboard_Case_imeAction, id.imeAction()); |
| final boolean localeCodeMatched = matchString(a, |
| R.styleable.Keyboard_Case_localeCode, id.mLocale.toString()); |
| final boolean languageCodeMatched = matchString(a, |
| R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); |
| final boolean countryCodeMatched = matchString(a, |
| R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); |
| final boolean selected = keyboardLayoutSetElementMatched && modeMatched |
| && navigateNextMatched && navigatePreviousMatched && passwordInputMatched |
| && clobberSettingsKeyMatched && shortcutKeyEnabledMatched |
| && shortcutKeyOnSymbolsMatched && hasShortcutKeyMatched |
| && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched |
| && localeCodeMatched && languageCodeMatched && countryCodeMatched; |
| |
| if (DEBUG) { |
| startTag("<%s%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_keyboardLayoutSetElement), |
| "keyboardLayoutSetElement"), |
| textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), |
| textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), |
| "imeAction"), |
| booleanAttr(a, R.styleable.Keyboard_Case_navigateNext, |
| "navigateNext"), |
| booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious, |
| "navigatePrevious"), |
| booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey, |
| "clobberSettingsKey"), |
| booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, |
| "passwordInput"), |
| booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, |
| "shortcutKeyEnabled"), |
| booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyOnSymbols, |
| "shortcutKeyOnSymbols"), |
| booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, |
| "hasShortcutKey"), |
| booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, |
| "languageSwitchKeyEnabled"), |
| booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine, |
| "isMultiLine"), |
| textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), |
| "localeCode"), |
| textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), |
| "languageCode"), |
| textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), |
| "countryCode"), |
| selected ? "" : " skipped"); |
| } |
| |
| return selected; |
| } finally { |
| a.recycle(); |
| } |
| } |
| |
| private static boolean matchInteger(final TypedArray a, final int index, final int value) { |
| // If <case> does not have "index" attribute, that means this <case> is wild-card for |
| // the attribute. |
| return !a.hasValue(index) || a.getInt(index, 0) == value; |
| } |
| |
| private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) { |
| // If <case> does not have "index" attribute, that means this <case> is wild-card for |
| // the attribute. |
| return !a.hasValue(index) || a.getBoolean(index, false) == value; |
| } |
| |
| private static boolean matchString(final TypedArray a, final int index, final String value) { |
| // If <case> does not have "index" attribute, that means this <case> is wild-card for |
| // the attribute. |
| return !a.hasValue(index) |
| || StringUtils.containsInArray(value, a.getString(index).split("\\|")); |
| } |
| |
| private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue, |
| final String strValue) { |
| // If <case> does not have "index" attribute, that means this <case> is wild-card for |
| // the attribute. |
| final TypedValue v = a.peekValue(index); |
| if (v == null) { |
| return true; |
| } |
| if (ResourceUtils.isIntegerValue(v)) { |
| return intValue == a.getInt(index, 0); |
| } else if (ResourceUtils.isStringValue(v)) { |
| return StringUtils.containsInArray(strValue, a.getString(index).split("\\|")); |
| } |
| return false; |
| } |
| |
| private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row, |
| final boolean skip) throws XmlPullParserException, IOException { |
| if (DEBUG) startTag("<%s>", TAG_DEFAULT); |
| if (row == null) { |
| parseKeyboardContent(parser, skip); |
| } else { |
| parseRowContent(parser, row, skip); |
| } |
| return true; |
| } |
| |
| private void parseKeyStyle(final XmlPullParser parser, final boolean skip) |
| throws XmlPullParserException, IOException { |
| TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), |
| R.styleable.Keyboard_KeyStyle); |
| TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), |
| R.styleable.Keyboard_Key); |
| try { |
| if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) { |
| throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE |
| + "/> needs styleName attribute", parser); |
| } |
| if (DEBUG) { |
| startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, |
| keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), |
| skip ? " skipped" : ""); |
| } |
| if (!skip) { |
| mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); |
| } |
| } finally { |
| keyStyleAttr.recycle(); |
| keyAttrs.recycle(); |
| } |
| XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser); |
| } |
| |
| private void startKeyboard() { |
| mCurrentY += mParams.mTopPadding; |
| mTopEdge = true; |
| } |
| |
| private void startRow(final KeyboardRow row) { |
| addEdgeSpace(mParams.mLeftPadding, row); |
| mCurrentRow = row; |
| mLeftEdge = true; |
| mRightEdgeKey = null; |
| } |
| |
| private void endRow(final KeyboardRow row) { |
| if (mCurrentRow == null) { |
| throw new RuntimeException("orphan end row tag"); |
| } |
| if (mRightEdgeKey != null) { |
| mRightEdgeKey.markAsRightEdge(mParams); |
| mRightEdgeKey = null; |
| } |
| addEdgeSpace(mParams.mRightPadding, row); |
| mCurrentY += row.mRowHeight; |
| mCurrentRow = null; |
| mTopEdge = false; |
| } |
| |
| private void endKey(final Key key) { |
| mParams.onAddKey(key); |
| if (mLeftEdge) { |
| key.markAsLeftEdge(mParams); |
| mLeftEdge = false; |
| } |
| if (mTopEdge) { |
| key.markAsTopEdge(mParams); |
| } |
| mRightEdgeKey = key; |
| } |
| |
| private void endKeyboard() { |
| // nothing to do here. |
| } |
| |
| private void addEdgeSpace(final float width, final KeyboardRow row) { |
| row.advanceXPos(width); |
| mLeftEdge = false; |
| mRightEdgeKey = null; |
| } |
| |
| private static String textAttr(final String value, final String name) { |
| return value != null ? String.format(" %s=%s", name, value) : ""; |
| } |
| |
| private static String booleanAttr(final TypedArray a, final int index, final String name) { |
| return a.hasValue(index) |
| ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; |
| } |
| } |