Keyboard.java revision e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476c
15a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka/*
28632bff2d5a8e1160989008dea6eff4b94b065ddTadashi G. Takaoka * Copyright (C) 2010 The Android Open Source Project
35a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
45a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not
55a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * use this file except in compliance with the License. You may obtain a copy of
65a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * the License at
75a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
85a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0
95a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *
105a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
115a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
125a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
135a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * License for the specific language governing permissions and limitations under
145a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * the License.
155a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka */
165a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
175a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokapackage com.android.inputmethod.keyboard;
185a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.content.Context;
204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.content.res.Resources;
214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.content.res.TypedArray;
224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.content.res.XmlResourceParser;
23e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatokimport android.text.TextUtils;
24a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaokaimport android.util.AttributeSet;
254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.util.DisplayMetrics;
267dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaokaimport android.util.Log;
274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.util.TypedValue;
284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.util.Xml;
294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport android.view.InflateException;
305a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport com.android.inputmethod.compat.EditorInfoCompatUtils;
324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyStyles;
33c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport com.android.inputmethod.latin.LatinImeLogger;
354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport com.android.inputmethod.latin.R;
364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport com.android.inputmethod.latin.XmlParseUtils;
37b7758d6f912093747d4b18fbc8d1dcd77c7d1f9bTadashi G. Takaoka
384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport org.xmlpull.v1.XmlPullParser;
394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport org.xmlpull.v1.XmlPullParserException;
404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport java.io.IOException;
42e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatokimport java.util.ArrayList;
434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport java.util.Arrays;
448da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaokaimport java.util.Collections;
452013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaokaimport java.util.HashMap;
464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaokaimport java.util.HashSet;
47e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatokimport java.util.List;
48167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaokaimport java.util.Map;
49167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaokaimport java.util.Set;
505a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
515a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka/**
525a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
535a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * consists of rows of keys.
545a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
555a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * <pre>
565a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * &lt;Keyboard
575a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         latin:keyWidth="%10p"
585a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         latin:keyHeight="50px"
595a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         latin:horizontalGap="2px"
605a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         latin:verticalGap="2px" &gt;
615a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *     &lt;Row latin:keyWidth="32px" &gt;
625a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         &lt;Key latin:keyLabel="A" /&gt;
635a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *         ...
645a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *     &lt;/Row&gt;
655a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka *     ...
665a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * &lt;/Keyboard&gt;
675a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka * </pre>
685a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka */
695a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaokapublic class Keyboard {
707dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka    private static final String TAG = Keyboard.class.getSimpleName();
717dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka
72ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka    /** Some common keys code. Must be positive.
73ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka     * These should be aligned with values/keycodes.xml
74ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka     */
75571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_ENTER = '\n';
76571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_TAB = '\t';
77571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_SPACE = ' ';
78571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_PERIOD = '.';
790730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard    public static final int CODE_DASH = '-';
800730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard    public static final int CODE_SINGLE_QUOTE = '\'';
810730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard    public static final int CODE_DOUBLE_QUOTE = '"';
822b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    // TODO: Check how this should work for right-to-left languages. It seems to stand
832b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
842b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    // managed by the font? Or is it a different char?
852b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    public static final int CODE_CLOSING_PARENTHESIS = ')';
862b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
872b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    public static final int CODE_CLOSING_CURLY_BRACKET = '}';
882b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard    public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
89851c3267d4ab21f892b4164783bb4959c88b44ffTadashi G. Takaoka    public static final int CODE_DIGIT0 = '0';
90851c3267d4ab21f892b4164783bb4959c88b44ffTadashi G. Takaoka    public static final int CODE_PLUS = '+';
918cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka    private static final int MINIMUM_LETTER_CODE = CODE_TAB;
922b4eabed2bfe982b91a994c145401d98894e6ef5Jean Chalard
938cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka    /** Special keys code. Must be negative.
94ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka     * These should be aligned with values/keycodes.xml
95ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka     */
96571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_SHIFT = -1;
97e18bd3e323e7d7448677bb66e8149eea0169c771Tadashi G. Takaoka    public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
988cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka    public static final int CODE_OUTPUT_TEXT = -4;
99571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public static final int CODE_DELETE = -5;
100d2c5fdda862f6dd2a1e020cf674c35fbbc63fc92Tadashi G. Takaoka    public static final int CODE_SETTINGS = -6;
101cadb2128f54b49be31bb4dc06374afe81ed028b7Ken Wakasa    public static final int CODE_SHORTCUT = -7;
102c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka    // Code value representing the code is not specified.
1038cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka    public static final int CODE_UNSPECIFIED = -9;
1045a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
105167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaoka    public final KeyboardId mId;
10663584323cab56c76debf6bb000621f2c605329a9Tadashi G. Takaoka    public final int mThemeId;
107167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaoka
108167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaoka    /** Total height of the keyboard, including the padding and keys */
1098da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    public final int mOccupiedHeight;
1108da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    /** Total width of the keyboard, including the padding and keys */
1118da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    public final int mOccupiedWidth;
112167e77f17084da5c223a3a790d3dd3d749e68ae3Tadashi G. Takaoka
1138fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka    /** The padding above the keyboard */
1148fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka    public final int mTopPadding;
1155a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    /** Default gap between rows */
1168da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    public final int mVerticalGap;
1178da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka
1188fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka    public final int mMostCommonKeyHeight;
1198da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    public final int mMostCommonKeyWidth;
1205a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
1219d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    /** More keys keyboard template */
1229d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka    public final int mMoreKeysTemplate;
1239b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1249237a72634be821c22911633ef0848130e162d58Tadashi G. Takaoka    /** Maximum column for mini keyboard */
1259237a72634be821c22911633ef0848130e162d58Tadashi G. Takaoka    public final int mMaxMiniKeyboardColumn;
1269b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1278da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    /** List of keys and icons in this keyboard */
1284a019a9c96b3a628265ef49f5522f20aeb5856cfTadashi G. Takaoka    public final Set<Key> mKeys;
1294a019a9c96b3a628265ef49f5522f20aeb5856cfTadashi G. Takaoka    public final Set<Key> mShiftKeys;
1308da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    public final KeyboardIconsSet mIconsSet;
1316fb97bf71cee2a0775410a05478ed6a667aa847fTadashi G. Takaoka
1322013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka    private final Map<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
133c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka
1348fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    private final ProximityInfo mProximityInfo;
1358fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
136e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok    public final Map<Integer, List<Integer>> mAdditionalProximityChars;
137e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok
1384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    public Keyboard(Params params) {
1398da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mId = params.mId;
14063584323cab56c76debf6bb000621f2c605329a9Tadashi G. Takaoka        mThemeId = params.mThemeId;
1418da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mOccupiedHeight = params.mOccupiedHeight;
1428da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mOccupiedWidth = params.mOccupiedWidth;
1438fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka        mMostCommonKeyHeight = params.mMostCommonKeyHeight;
1448da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mMostCommonKeyWidth = params.mMostCommonKeyWidth;
1459d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        mMoreKeysTemplate = params.mMoreKeysTemplate;
1469237a72634be821c22911633ef0848130e162d58Tadashi G. Takaoka        mMaxMiniKeyboardColumn = params.mMaxMiniKeyboardColumn;
1478da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka
1488fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka        mTopPadding = params.mTopPadding;
1498da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mVerticalGap = params.mVerticalGap;
1508da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka
1514a019a9c96b3a628265ef49f5522f20aeb5856cfTadashi G. Takaoka        mKeys = Collections.unmodifiableSet(params.mKeys);
1524a019a9c96b3a628265ef49f5522f20aeb5856cfTadashi G. Takaoka        mShiftKeys = Collections.unmodifiableSet(params.mShiftKeys);
1538da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mIconsSet = params.mIconsSet;
154e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        mAdditionalProximityChars = params.mAdditionalProximityChars;
1555a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
1560d5494c66ac3e5947040e8148091163a1c8716f7satok        mProximityInfo = new ProximityInfo(
1578da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka                params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
158e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection,
159e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                params.mAdditionalProximityChars);
1608fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    }
1618fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
162043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard    public ProximityInfo getProximityInfo() {
163043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard        return mProximityInfo;
1645a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    }
1655a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka
1662013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka    public Key getKey(int code) {
1678cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka        if (code == CODE_UNSPECIFIED) {
168623d0155b6a316fdc9335370cdd4005bbb474ef3Tadashi G. Takaoka            return null;
169623d0155b6a316fdc9335370cdd4005bbb474ef3Tadashi G. Takaoka        }
1702013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        final Integer keyCode = code;
1712013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        if (mKeyCache.containsKey(keyCode)) {
1722013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka            return mKeyCache.get(keyCode);
1732013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        }
1742013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka
1752013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        for (final Key key : mKeys) {
1762013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka            if (key.mCode == code) {
1772013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka                mKeyCache.put(keyCode, key);
1782013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka                return key;
1792013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka            }
1802013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        }
1812013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        mKeyCache.put(keyCode, null);
1822013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka        return null;
1832013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka    }
1842013bab89ca2f82589f99d98d9cf3b41ea5aac65Tadashi G. Takaoka
185c1859b8fd22b2c4cc2a700566f57f3e3d04e1580Tadashi G. Takaoka    // TODO: Remove this method.
186571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    public boolean isShiftLocked() {
187ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka        return mId.isAlphabetShiftLockedKeyboard();
18807145a3706d7692806b9c53548795fa2dbf8f4f0Tadashi G. Takaoka    }
18907145a3706d7692806b9c53548795fa2dbf8f4f0Tadashi G. Takaoka
190c1859b8fd22b2c4cc2a700566f57f3e3d04e1580Tadashi G. Takaoka    // TODO: Remove this method.
1915a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    public boolean isShiftedOrShiftLocked() {
192ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka        return mId.isAlphabetShiftedOrShiftLockedKeyboard();
193571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    }
194571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka
195c1859b8fd22b2c4cc2a700566f57f3e3d04e1580Tadashi G. Takaoka    // TODO: Remove this method.
196ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka    public boolean isManualShifted() {
197ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka        return mId.isAlphabetManualShiftedKeyboard();
198571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka    }
199571bdb401f670b92bd7710a12a990cb65a99b7d3Tadashi G. Takaoka
200ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka    public static boolean isLetterCode(int code) {
2018cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka        return code >= MINIMUM_LETTER_CODE;
202ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka    }
203ee4be6e3c6eed719683fd3019d48365ba76790e2Tadashi G. Takaoka
2044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    public static class Params {
2054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public KeyboardId mId;
2064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mThemeId;
2074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        /** Total height and width of the keyboard, including the paddings and keys */
2094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mOccupiedHeight;
2104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mOccupiedWidth;
2114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        /** Base height and width of the keyboard used to calculate rows' or keys' heights and
2134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         *  widths
2144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         */
2154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mBaseHeight;
2164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mBaseWidth;
2174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mTopPadding;
2194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mBottomPadding;
2204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mHorizontalEdgesPadding;
2214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mHorizontalCenterPadding;
2224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mDefaultRowHeight;
2244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mDefaultKeyWidth;
2254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mHorizontalGap;
2264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mVerticalGap;
2274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mMoreKeysTemplate;
2294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mMaxMiniKeyboardColumn;
2304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int GRID_WIDTH;
2324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int GRID_HEIGHT;
2334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public final Set<Key> mKeys = new HashSet<Key>();
2354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public final Set<Key> mShiftKeys = new HashSet<Key>();
2364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
237e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        // TODO: Should be in Key instead of Keyboard.Params?
238e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        public final Map<Integer, List<Integer>> mAdditionalProximityChars =
239e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                new HashMap<Integer, List<Integer>>();
2404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
24109f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka        public KeyboardSet.KeysCache mKeysCache;
24209f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka
2434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mMostCommonKeyHeight = 0;
2444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public int mMostCommonKeyWidth = 0;
2454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public final TouchPositionCorrection mTouchPositionCorrection =
2474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                new TouchPositionCorrection();
2484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public static class TouchPositionCorrection {
2504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
2514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public boolean mEnabled;
2534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public float[] mXs;
2544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public float[] mYs;
2554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public float[] mRadii;
2564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public void load(String[] data) {
2584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int dataLength = data.length;
2594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
2604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (LatinImeLogger.sDBG)
2614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new RuntimeException(
2624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "the size of touch position correction data is invalid");
2634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    return;
2644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
2654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
2674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mXs = new float[length];
2684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mYs = new float[length];
2694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mRadii = new float[length];
2704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                try {
2714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    for (int i = 0; i < dataLength; ++i) {
2724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
2734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
2744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        final float value = Float.parseFloat(data[i]);
2754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (type == 0) {
2764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            mXs[index] = value;
2774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        } else if (type == 1) {
2784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            mYs[index] = value;
2794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        } else {
2804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            mRadii[index] = value;
2814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        }
2824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
2834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } catch (NumberFormatException e) {
2844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (LatinImeLogger.sDBG) {
2854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new RuntimeException(
2864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "the number format for touch position correction data is invalid");
2874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
2884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    mXs = null;
2894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    mYs = null;
2904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    mRadii = null;
2914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
2924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
2934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public void setEnabled(boolean enabled) {
2954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mEnabled = enabled;
2964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
2974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
2984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public boolean isValid() {
2994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return mEnabled && mXs != null && mYs != null && mRadii != null
3004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
3014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
3024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        protected void clearKeys() {
3054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mKeys.clear();
3064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mShiftKeys.clear();
3074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            clearHistogram();
3084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
31009f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka        public void onAddKey(Key newKey) {
31109f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka            final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
3124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mKeys.add(key);
3134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            updateHistogram(key);
3144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (key.mCode == Keyboard.CODE_SHIFT) {
3154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mShiftKeys.add(key);
3164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
3174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private int mMaxHeightCount = 0;
3204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private int mMaxWidthCount = 0;
3214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private final Map<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
3224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private final Map<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
3234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void clearHistogram() {
3254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mMostCommonKeyHeight = 0;
3264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mMaxHeightCount = 0;
3274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mHeightHistogram.clear();
3284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mMaxWidthCount = 0;
3304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mMostCommonKeyWidth = 0;
3314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mWidthHistogram.clear();
3324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static int updateHistogramCounter(Map<Integer, Integer> histogram, Integer key) {
3354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1;
3364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            histogram.put(key, count);
3374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return count;
3384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void updateHistogram(Key key) {
3414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final Integer height = key.mHeight + key.mVerticalGap;
3424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final int heightCount = updateHistogramCounter(mHeightHistogram, height);
3434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (heightCount > mMaxHeightCount) {
3444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mMaxHeightCount = heightCount;
3454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mMostCommonKeyHeight = height;
3464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
3474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final Integer width = key.mWidth + key.mHorizontalGap;
3494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final int widthCount = updateHistogramCounter(mWidthHistogram, width);
3504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (widthCount > mMaxWidthCount) {
3514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mMaxWidthCount = widthCount;
3524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mMostCommonKeyWidth = width;
3534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
3544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
3554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    }
3564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3575a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    /**
3580c0ca874febee38fb5cb2c85c11ddd46cdf2b859Tadashi G. Takaoka     * Returns the array of the keys that are closest to the given point.
3595a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka     * @param x the x-coordinate of the point
3605a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka     * @param y the y-coordinate of the point
3610c0ca874febee38fb5cb2c85c11ddd46cdf2b859Tadashi G. Takaoka     * @return the array of the nearest keys to the given point. If the given
3625a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka     * point is out of range, then an array of size zero is returned.
3635a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka     */
3640c0ca874febee38fb5cb2c85c11ddd46cdf2b859Tadashi G. Takaoka    public Key[] getNearestKeys(int x, int y) {
3654f7d278af62a89ef3f45cc9ebbfb076a5a352c76Ken Wakasa        // Avoid dead pixels at edges of the keyboard
3664f7d278af62a89ef3f45cc9ebbfb076a5a352c76Ken Wakasa        final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
3674f7d278af62a89ef3f45cc9ebbfb076a5a352c76Ken Wakasa        final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
3684f7d278af62a89ef3f45cc9ebbfb076a5a352c76Ken Wakasa        return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
3695a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    }
37063584323cab56c76debf6bb000621f2c605329a9Tadashi G. Takaoka
371e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok    public Map<Integer, List<Integer>> getAdditionalProximityChars() {
372e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        return mAdditionalProximityChars;
373e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok    }
374e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok
3757dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka    public static String printableCode(int code) {
3767dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        switch (code) {
3777dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        case CODE_SHIFT: return "shift";
3787dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
3798cab0b56eb8db311f158b18a361d9ceb85cff482Tadashi G. Takaoka        case CODE_OUTPUT_TEXT: return "text";
3807dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        case CODE_DELETE: return "delete";
381a5c96f376ad57e78a88942bb618e067054ed818aTadashi G. Takaoka        case CODE_SETTINGS: return "settings";
3827dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        case CODE_SHORTCUT: return "shortcut";
3837dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        case CODE_UNSPECIFIED: return "unspec";
384ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka        case CODE_TAB: return "tab";
385ca2f051cc173acc3bce384ebfe08068564bc8e07Tadashi G. Takaoka        case CODE_ENTER: return "enter";
3867dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        default:
3872a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);
3882a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
3892a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            if (code < 0x100) return String.format("'%c'", code);
3902a88440419f49d100c73e067a823390f64aba3b1Tadashi G. Takaoka            return String.format("'\\u%04x'", code);
3917dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka        }
3927dfd5a3e833e14d5bf90d728d5a50b40c8a927d2Tadashi G. Takaoka    }
3934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
3944087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka    public static String toThemeName(int themeId) {
3954087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        // This should be aligned with theme-*.xml resource files' themeId attribute.
3964087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        switch (themeId) {
3974087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 0: return "Basic";
3984087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 1: return "BasicHighContrast";
3994087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 5: return "IceCreamSandwich";
4004087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 6: return "Stone";
4014087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 7: return "StoneBold";
4024087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        case 8: return "GingerBread";
4034087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        default: return null;
4044087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka        }
4054087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka    }
4064087ba69faec0dd89fae7d53d7527448091e0aadTadashi G. Takaoka
4074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    /**
4084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * Keyboard Building helper.
4094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *
4104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * This class parses Keyboard XML file and eventually build a Keyboard.
4114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * The Keyboard XML file looks like:
4124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * <pre>
4134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;!-- xml/keyboard.xml --&lt;
4144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;Keyboard keyboard_attributes*&lt;
4154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;!-- Keyboard Content --&lt;
4164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;Row row_attributes*&lt;
4174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;!-- Row Content --&lt;
4184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;Key key_attributes* /&lt;
4194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;Spacer horizontalGap="0.2in" /&lt;
4204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;include keyboardLayout="@xml/other_keys"&lt;
4214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       ...
4224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;/Row&lt;
4234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;include keyboardLayout="@xml/other_rows"&lt;
4244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     ...
4254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;/Keyboard&lt;
4264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * </pre>
4274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * The XML file which is included in other file must have &gt;merge&lt; as root element,
4284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * such as:
4294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * <pre>
4304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;!-- xml/other_keys.xml --&lt;
4314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;merge&lt;
4324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;Key key_attributes* /&lt;
4334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     ...
4344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;/merge&lt;
4354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * </pre>
4364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * and
4374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * <pre>
4384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;!-- xml/other_rows.xml --&lt;
4394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;merge&lt;
4404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;Row row_attributes*&lt;
4414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;Key key_attributes* /&lt;
4424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;/Row&lt;
4434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     ...
4444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;/merge&lt;
4454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * </pre>
4464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * You can also use switch-case-default tags to select Rows and Keys.
4474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * <pre>
4484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;switch&lt;
4494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;case case_attribute*&lt;
4504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;!-- Any valid tags at switch position --&lt;
4514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;/case&lt;
4524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     ...
4534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;default&lt;
4544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;!-- Any valid tags at switch position --&lt;
4554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;/default&lt;
4564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *   &gt;/switch&lt;
4574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * </pre>
4584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * You can declare Key style and specify styles within Key tags.
4594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * <pre>
4604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;switch&lt;
4614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;case mode="email"&lt;
4624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
4634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *           keyLabel=".com"
4644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *         /&lt;
4654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;/case&lt;
4664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;case mode="url"&lt;
4674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
4684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *           keyLabel="http://"
4694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *         /&lt;
4704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *       &gt;/case&lt;
4714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;/switch&lt;
4724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     ...
4734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     *     &gt;Key keyStyle="shift-key" ... /&lt;
4744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     * </pre>
4754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka     */
4764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
4774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    public static class Builder<KP extends Params> {
4784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG = Builder.class.getSimpleName();
4794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final boolean DEBUG = false;
4804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
4814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        // Keyboard XML Tags
4824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_KEYBOARD = "Keyboard";
4834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_ROW = "Row";
4844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_KEY = "Key";
4854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_SPACER = "Spacer";
4864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_INCLUDE = "include";
4874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_MERGE = "merge";
4884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_SWITCH = "switch";
4894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_CASE = "case";
4904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final String TAG_DEFAULT = "default";
4914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public static final String TAG_KEY_STYLE = "key-style";
4924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
4934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
4944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static final int DEFAULT_KEYBOARD_ROWS = 4;
4954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
4964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        protected final KP mParams;
4974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        protected final Context mContext;
4984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        protected final Resources mResources;
4994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private final DisplayMetrics mDisplayMetrics;
5004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private int mCurrentY = 0;
5024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private Row mCurrentRow = null;
5034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private boolean mLeftEdge;
5044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private boolean mTopEdge;
5054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private Key mRightEdgeKey = null;
5064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private final KeyStyles mKeyStyles = new KeyStyles();
5074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        /**
5094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
5104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
5114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         * defines.
5124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka         */
5134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public static class Row {
5144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // keyWidth enum constants
5154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private static final int KEYWIDTH_NOT_ENUM = 0;
5164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private static final int KEYWIDTH_FILL_RIGHT = -1;
5174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private static final int KEYWIDTH_FILL_BOTH = -2;
5184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private final Params mParams;
5204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            /** Default width of a key in this row. */
521a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            private float mDefaultKeyWidth;
5224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            /** Default height of a key in this row. */
5234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public final int mRowHeight;
5244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private final int mCurrentY;
5264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // Will be updated by {@link Key}'s constructor.
5274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            private float mCurrentX;
5284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public Row(Resources res, Params params, XmlPullParser parser, int y) {
5304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mParams = params;
5314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
5324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard);
5334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr,
5344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_rowHeight,
5354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        params.mBaseHeight, params.mDefaultRowHeight);
5364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyboardAttr.recycle();
5374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
5384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key);
5394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr,
5404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key_keyWidth,
5414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        params.mBaseWidth, params.mDefaultKeyWidth);
5424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyAttr.recycle();
5434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mCurrentY = y;
5454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mCurrentX = 0.0f;
5464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
5474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
548a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            public float getDefaultKeyWidth() {
549a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                return mDefaultKeyWidth;
550a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            }
551a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka
552a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            public void setDefaultKeyWidth(float defaultKeyWidth) {
553a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                mDefaultKeyWidth = defaultKeyWidth;
554a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            }
555a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka
5564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public void setXPos(float keyXPos) {
5574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mCurrentX = keyXPos;
5584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
5594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public void advanceXPos(float width) {
5614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mCurrentX += width;
5624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
5634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public int getKeyY() {
5654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return mCurrentY;
5664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
5674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public float getKeyX(TypedArray keyAttr) {
5694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int widthType = Builder.getEnumValue(keyAttr,
5704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
5714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (widthType == KEYWIDTH_FILL_BOTH) {
5724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // If keyWidth is fillBoth, the key width should start right after the nearest
5734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // key on the left hand side.
5744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    return mCurrentX;
5754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
5764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
5774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int keyboardRightEdge = mParams.mOccupiedWidth
5784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        - mParams.mHorizontalEdgesPadding;
5794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
5804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final float keyXPos = Builder.getDimensionOrFraction(keyAttr,
5814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
5824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (keyXPos < 0) {
5834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        // If keyXPos is negative, the actual x-coordinate will be
5844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        // keyboardWidth + keyXPos.
5854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        // keyXPos shouldn't be less than mCurrentX because drawable area for this
5864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
5874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        // its left hand side.
5884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
5894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
5904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        return keyXPos + mParams.mHorizontalEdgesPadding;
5914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
5924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
5934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return mCurrentX;
5944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
5954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
596a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            public float getKeyWidth(TypedArray keyAttr) {
597a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                return getKeyWidth(keyAttr, mCurrentX);
598a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka            }
599a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka
6004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
6014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int widthType = Builder.getEnumValue(keyAttr,
6024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
6034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                switch (widthType) {
6044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                case KEYWIDTH_FILL_RIGHT:
6054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                case KEYWIDTH_FILL_BOTH:
6064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final int keyboardRightEdge =
6074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
6084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // If keyWidth is fillRight, the actual key width will be determined to fill
6094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // out the area up to the right edge of the keyboard.
6104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // If keyWidth is fillBoth, the actual key width will be determined to fill out
6114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // the area between the nearest key on the left hand side and the right edge of
6124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // the keyboard.
6134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    return keyboardRightEdge - keyXPos;
6144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                default: // KEYWIDTH_NOT_ENUM
6154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    return Builder.getDimensionOrFraction(keyAttr,
6164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            R.styleable.Keyboard_Key_keyWidth,
6174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            mParams.mBaseWidth, mDefaultKeyWidth);
6184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
6194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
6204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
6214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public Builder(Context context, KP params) {
6234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mContext = context;
6244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final Resources res = context.getResources();
6254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mResources = res;
6264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mDisplayMetrics = res.getDisplayMetrics();
6274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mParams = params;
6294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            setTouchPositionCorrectionData(context, params);
631e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok            setAdditionalProximityChars(context, params);
6324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
6344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
6354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
6364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static void setTouchPositionCorrectionData(Context context, Params params) {
6384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedArray a = context.obtainStyledAttributes(
6394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    null, R.styleable.Keyboard, R.attr.keyboardStyle, 0);
6404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            params.mThemeId = a.getInt(R.styleable.Keyboard_themeId, 0);
6414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final int resourceId = a.getResourceId(
6424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard_touchPositionCorrectionData, 0);
6434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            a.recycle();
6444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (resourceId == 0) {
6454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (LatinImeLogger.sDBG)
6461e4684ad24914cde713980bb4174810bbf8bae10Jean Chalard                    Log.e(TAG, "touchPositionCorrectionData is not defined");
6474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return;
6484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
6494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final String[] data = context.getResources().getStringArray(resourceId);
6514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            params.mTouchPositionCorrection.load(data);
6524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
6534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
654e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        private static void setAdditionalProximityChars(Context context, Params params) {
655e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok            final String[] additionalChars =
656e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    context.getResources().getStringArray(R.array.additional_proximitychars);
657e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok            int currentPrimaryIndex = 0;
658e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok            for (int i = 0; i < additionalChars.length; ++i) {
659e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                final String additionalChar = additionalChars[i];
660e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                if (TextUtils.isEmpty(additionalChar)) {
661e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    currentPrimaryIndex = 0;
662e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                } else if (currentPrimaryIndex == 0) {
663e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    currentPrimaryIndex = additionalChar.charAt(0);
664e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    params.mAdditionalProximityChars.put(
665e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                            currentPrimaryIndex, new ArrayList<Integer>());
666e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                } else if (currentPrimaryIndex != 0) {
667e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    final int c = additionalChar.charAt(0);
668e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                    params.mAdditionalProximityChars.get(currentPrimaryIndex).add(c);
669e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok                }
670e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok            }
671e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok        }
672e05b3f4b3a57dcf99ade35bfbc1e1cdc3c3e476csatok
67309f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka        public void setAutoGenerate(KeyboardSet.KeysCache keysCache) {
67409f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka            mParams.mKeysCache = keysCache;
67509f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka        }
67609f8b126e532ca2ac6bbe00c0d78bf03e44b78a2Tadashi G. Takaoka
6774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public Builder<KP> load(int xmlId, KeyboardId id) {
6784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mParams.mId = id;
6794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final XmlResourceParser parser = mResources.getXml(xmlId);
6804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            try {
6814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parseKeyboard(parser);
6824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } catch (XmlPullParserException e) {
6834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                Log.w(TAG, "keyboard XML parse error: " + e);
6844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throw new IllegalArgumentException(e);
6854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } catch (IOException e) {
6864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                Log.w(TAG, "keyboard XML parse error: " + e);
6874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throw new RuntimeException(e);
6884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } finally {
6894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parser.close();
6904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
6914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return this;
6924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
6934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public void setTouchPositionCorrectionEnabled(boolean enabled) {
6954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mParams.mTouchPositionCorrection.setEnabled(enabled);
6964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
6974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
6984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public Keyboard build() {
6994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return new Keyboard(mParams);
7004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
7014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseKeyboard(XmlPullParser parser)
7034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
7044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mParams.mId));
7054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            int event;
7064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
7074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (event == XmlPullParser.START_TAG) {
7084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
7094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_KEYBOARD.equals(tag)) {
7104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseKeyboardAttributes(parser);
7114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        startKeyboard();
7124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseKeyboardContent(parser, false);
7134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
7144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
7154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
7164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
7174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
7184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
7194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
7204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseKeyboardAttributes(XmlPullParser parser) {
7224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final int displayWidth = mDisplayMetrics.widthPixels;
7234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
7244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
7254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.style.Keyboard);
7264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
7274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard_Key);
7284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            try {
7294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int displayHeight = mDisplayMetrics.heightPixels;
7304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int keyboardHeight = (int)keyboardAttr.getDimension(
7314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
7324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final int maxKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
7334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
7344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                int minKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
7354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
7364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (minKeyboardHeight < 0) {
7374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // Specified fraction was negative, so it should be calculated against display
7384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    // width.
7394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    minKeyboardHeight = -(int)getDimensionOrFraction(keyboardAttr,
7404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
7414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
7424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final Params params = mParams;
7434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // Keyboard height will not exceed maxKeyboardHeight and will not be less than
7444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // minKeyboardHeight.
7454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mOccupiedHeight = Math.max(
7464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
7474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mOccupiedWidth = params.mId.mWidth;
7484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
7494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
7504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
7514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
7524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
7534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
7544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        mParams.mOccupiedWidth, 0);
7554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
7574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        - params.mHorizontalCenterPadding;
7584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
7594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
7604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
7614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
7624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
7634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
7644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
7654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
7664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        - params.mBottomPadding + params.mVerticalGap;
7674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
7684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_rowHeight, params.mBaseHeight,
7694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
7704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mMoreKeysTemplate = keyboardAttr.getResourceId(
7724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_moreKeysTemplate, 0);
7734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mMaxMiniKeyboardColumn = keyAttr.getInt(
7744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
7754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                params.mIconsSet.loadIcons(keyboardAttr);
7774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } finally {
7784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyAttr.recycle();
7794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyboardAttr.recycle();
7804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
7814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
7824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
7834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseKeyboardContent(XmlPullParser parser, boolean skip)
7844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
7854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            int event;
7864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
7874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (event == XmlPullParser.START_TAG) {
7884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
7894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_ROW.equals(tag)) {
7904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        Row row = parseRowAttributes(parser);
7914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW));
7924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (!skip)
7934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            startRow(row);
7944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseRowContent(parser, row, skip);
7954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_INCLUDE.equals(tag)) {
7964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseIncludeKeyboardContent(parser, skip);
7974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_SWITCH.equals(tag)) {
7984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseSwitchKeyboardContent(parser, skip);
7994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_KEY_STYLE.equals(tag)) {
8004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseKeyStyle(parser, skip);
8014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
8024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
8034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
8044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } else if (event == XmlPullParser.END_TAG) {
8054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
8064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_KEYBOARD.equals(tag)) {
8074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        endKeyboard();
8084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
8094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
8104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            || TAG_MERGE.equals(tag)) {
8114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (DEBUG) Log.d(TAG, String.format("</%s>", tag));
8124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
8134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_KEY_STYLE.equals(tag)) {
8144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        continue;
8154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
8164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
8174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
8184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
8194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
8204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
8214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
8224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException {
8234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
8244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard);
8254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            try {
8264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (a.hasValue(R.styleable.Keyboard_horizontalGap))
8274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
8284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (a.hasValue(R.styleable.Keyboard_verticalGap))
8294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
8304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return new Row(mResources, mParams, parser, mCurrentY);
8314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } finally {
8324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                a.recycle();
8334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
8344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
8354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
8364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
8374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
8384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            int event;
8394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
8404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (event == XmlPullParser.START_TAG) {
8414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
8424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_KEY.equals(tag)) {
8434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseKey(parser, row, skip);
8444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_SPACER.equals(tag)) {
8454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseSpacer(parser, row, skip);
8464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_INCLUDE.equals(tag)) {
8474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseIncludeRowContent(parser, row, skip);
8484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_SWITCH.equals(tag)) {
8494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseSwitchRowContent(parser, row, skip);
8504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_KEY_STYLE.equals(tag)) {
8514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        parseKeyStyle(parser, skip);
8524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
8534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
8544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
8554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } else if (event == XmlPullParser.END_TAG) {
8564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
8574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_ROW.equals(tag)) {
8584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW));
8594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (!skip)
8604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            endRow(row);
8614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
8624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
8634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            || TAG_MERGE.equals(tag)) {
8644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (DEBUG) Log.d(TAG, String.format("</%s>", tag));
8654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
8664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_KEY_STYLE.equals(tag)) {
8674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        continue;
8684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
8694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
8704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
8714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
8724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
8734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
8744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
8754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseKey(XmlPullParser parser, Row row, boolean skip)
8764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
8774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (skip) {
8784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_KEY, parser);
8794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else {
8804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final Key key = new Key(mResources, mParams, row, parser, mKeyStyles);
8814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d moreKeys=%s />",
8824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        TAG_KEY, (key.isEnabled() ? "" : " disabled"), key.mLabel, key.mCode,
8834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        Arrays.toString(key.mMoreKeys)));
8844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_KEY, parser);
8854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                endKey(key);
8864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
8874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
8884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
8894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
8904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
8914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (skip) {
8924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
8934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else {
8944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final Key.Spacer spacer = new Key.Spacer(
8954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        mResources, mParams, row, parser, mKeyStyles);
8964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER));
8974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
8984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                endKey(spacer);
8994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
9004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
9034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            parseIncludeInternal(parser, null, skip);
9054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
9084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            parseIncludeInternal(parser, row, skip);
9104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
9134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (skip) {
9154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
9164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else {
917a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                final AttributeSet attr = Xml.asAttributeSet(parser);
918a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
9194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Include);
920a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                final TypedArray keyAttr = mResources.obtainAttributes(attr,
921a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        R.styleable.Keyboard_Key);
9224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                int keyboardLayout = 0;
923a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                float savedDefaultKeyWidth = 0;
9244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                try {
925a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    XmlParseUtils.checkAttributeExists(keyboardAttr,
9264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
9274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            TAG_INCLUDE, parser);
928a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    keyboardLayout = keyboardAttr.getResourceId(
9294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            R.styleable.Keyboard_Include_keyboardLayout, 0);
930a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    if (row != null) {
931a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        savedDefaultKeyWidth = row.getDefaultKeyWidth();
932a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
933a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                            // Override current x coordinate.
934a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                            row.setXPos(row.getKeyX(keyAttr));
935a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        }
936a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
937a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                            // Override default key width.
938a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                            row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
939a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        }
940a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    }
9414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } finally {
942a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    keyboardAttr.recycle();
943a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    keyAttr.recycle();
9444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
9454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
9474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />",
9484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout)));
9494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
9504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                try {
9514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    parseMerge(parserForInclude, row, skip);
9524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } finally {
953a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    if (row != null) {
954a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        // Restore default key width.
955a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                        row.setDefaultKeyWidth(savedDefaultKeyWidth);
956a1e370ee4fd033fae0f783d9120f6ab75d1ce711Tadashi G. Takaoka                    }
9574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    parserForInclude.close();
9584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
9594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
9604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseMerge(XmlPullParser parser, Row row, boolean skip)
9634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            int event;
9654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
9664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (event == XmlPullParser.START_TAG) {
9674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
9684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_MERGE.equals(tag)) {
9694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (row == null) {
9704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            parseKeyboardContent(parser, skip);
9714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        } else {
9724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            parseRowContent(parser, row, skip);
9734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        }
9744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
9754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
9764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.ParseException(
9774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "Included keyboard layout must have <merge> root element", parser);
9784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
9794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
9804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
9814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
9844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            parseSwitchInternal(parser, null, skip);
9864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
9894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            parseSwitchInternal(parser, row, skip);
9914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
9924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
9934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
9944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
9954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mParams.mId));
9964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            boolean selected = false;
9974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            int event;
9984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
9994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (event == XmlPullParser.START_TAG) {
10004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
10014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_CASE.equals(tag)) {
10024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        selected |= parseCase(parser, row, selected ? true : skip);
10034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else if (TAG_DEFAULT.equals(tag)) {
10044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        selected |= parseDefault(parser, row, selected ? true : skip);
10054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
10064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
10074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
10084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                } else if (event == XmlPullParser.END_TAG) {
10094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    final String tag = parser.getName();
10104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    if (TAG_SWITCH.equals(tag)) {
10114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_SWITCH));
10124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        break;
10134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    } else {
10144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
10154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    }
10164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                }
10174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
10184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
10194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
10204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
10214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
10224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final boolean selected = parseCaseCondition(parser);
10234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (row == null) {
10244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // Processing Rows.
10254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parseKeyboardContent(parser, selected ? skip : true);
10264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else {
10274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // Processing Keys.
10284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parseRowContent(parser, row, selected ? skip : true);
10294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
10304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return selected;
10314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
10324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
10334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private boolean parseCaseCondition(XmlPullParser parser) {
10344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final KeyboardId id = mParams.mId;
10354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (id == null)
10364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return true;
10374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
10384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
10394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard_Case);
10404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            try {
104183306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                final boolean keyboardSetElementMatched = matchTypedValue(a,
104283306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId,
104383306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        KeyboardId.elementIdToName(id.mElementId));
10444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean modeMatched = matchTypedValue(a,
10454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
10464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean navigateActionMatched = matchBoolean(a,
10474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_navigateAction, id.navigateAction());
10484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean passwordInputMatched = matchBoolean(a,
10494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
10504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean hasSettingsKeyMatched = matchBoolean(a,
10514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_hasSettingsKey, id.hasSettingsKey());
10524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean f2KeyModeMatched = matchInteger(a,
10534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_f2KeyMode, id.f2KeyMode());
10544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean clobberSettingsKeyMatched = matchBoolean(a,
10554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
10564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean shortcutKeyEnabledMatched = matchBoolean(a,
10574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
10584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean hasShortcutKeyMatched = matchBoolean(a,
10594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
10604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // As noted at {@link KeyboardId} class, we are interested only in enum value
10614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // masked by {@link android.view.inputmethod.EditorInfo#IME_MASK_ACTION} and
10624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. So matching
10634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                // this attribute with id.mImeOptions as integer value is enough for our purpose.
10644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean imeActionMatched = matchInteger(a,
10654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_imeAction, id.imeAction());
10664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean localeCodeMatched = matchString(a,
10674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
10684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean languageCodeMatched = matchString(a,
10694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
10704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                final boolean countryCodeMatched = matchString(a,
10714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
107283306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                final boolean selected = keyboardSetElementMatched && modeMatched
107383306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        && navigateActionMatched && passwordInputMatched && hasSettingsKeyMatched
107483306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        && f2KeyModeMatched && clobberSettingsKeyMatched
107583306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        && shortcutKeyEnabledMatched && hasShortcutKeyMatched && imeActionMatched
107683306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        && localeCodeMatched && languageCodeMatched && countryCodeMatched;
107783306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka
107883306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
107983306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                        textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement),
108083306b914f36871f785677ca13e1ce8033774b79Tadashi G. Takaoka                                "keyboardSetElement"),
10814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
10824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_navigateAction, "navigateAction"),
10834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"),
10844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
10854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(KeyboardId.f2KeyModeName(
10864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                a.getInt(R.styleable.Keyboard_Case_f2KeyMode, -1)), "f2KeyMode"),
10874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
10884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "clobberSettingsKey"),
10894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
10904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "shortcutKeyEnabled"),
10914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"),
10924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(EditorInfoCompatUtils.imeOptionsName(
10934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
10944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), "localeCode"),
10954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
10964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                                "languageCode"),
10974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"),
10984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                        Boolean.toString(selected)));
10994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return selected;
11014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } finally {
11024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                a.recycle();
11034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean matchInteger(TypedArray a, int index, int value) {
11074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // If <case> does not have "index" attribute, that means this <case> is wild-card for
11084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // the attribute.
11094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return !a.hasValue(index) || a.getInt(index, 0) == value;
11104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean matchBoolean(TypedArray a, int index, boolean value) {
11134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // If <case> does not have "index" attribute, that means this <case> is wild-card for
11144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // the attribute.
11154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return !a.hasValue(index) || a.getBoolean(index, false) == value;
11164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean matchString(TypedArray a, int index, String value) {
11194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // If <case> does not have "index" attribute, that means this <case> is wild-card for
11204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // the attribute.
11214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return !a.hasValue(index)
11224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    || stringArrayContains(a.getString(index).split("\\|"), value);
11234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean matchTypedValue(TypedArray a, int index, int intValue,
11264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                String strValue) {
11274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // If <case> does not have "index" attribute, that means this <case> is wild-card for
11284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // the attribute.
11294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedValue v = a.peekValue(index);
11304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (v == null)
11314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return true;
11324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (isIntegerValue(v)) {
11344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return intValue == a.getInt(index, 0);
11354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else if (isStringValue(v)) {
11364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return stringArrayContains(a.getString(index).split("\\|"), strValue);
11374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return false;
11394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean stringArrayContains(String[] array, String value) {
11424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            for (final String elem : array) {
11434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (elem.equals(value))
11444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    return true;
11454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return false;
11474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
11504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException, IOException {
11514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
11524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (row == null) {
11534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parseKeyboardContent(parser, skip);
11544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else {
11554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                parseRowContent(parser, row, skip);
11564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return true;
11584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void parseKeyStyle(XmlPullParser parser, boolean skip)
11614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throws XmlPullParserException {
11624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
11634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard_KeyStyle);
11644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
11654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    R.styleable.Keyboard_Key);
11664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            try {
11674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
11684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
11694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                            + "/> needs styleName attribute", parser);
11704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                if (!skip)
11714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
11724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } finally {
11734e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyStyleAttr.recycle();
11744e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                keyAttrs.recycle();
11754e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11764e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11774e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11784e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void startKeyboard() {
11794e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mCurrentY += mParams.mTopPadding;
11804e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mTopEdge = true;
11814e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11824e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11834e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void startRow(Row row) {
11844e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
11854e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mCurrentRow = row;
11864e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mLeftEdge = true;
11874e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mRightEdgeKey = null;
11884e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
11894e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
11904e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void endRow(Row row) {
11914e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (mCurrentRow == null)
11924e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                throw new InflateException("orphant end row tag");
11934e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (mRightEdgeKey != null) {
11944e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mRightEdgeKey.markAsRightEdge(mParams);
11954e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mRightEdgeKey = null;
11964e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
11974e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
11984e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mCurrentY += row.mRowHeight;
11994e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mCurrentRow = null;
12004e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mTopEdge = false;
12014e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12024e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12034e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void endKey(Key key) {
12044e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mParams.onAddKey(key);
12054e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (mLeftEdge) {
12064e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                key.markAsLeftEdge(mParams);
12074e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                mLeftEdge = false;
12084e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
12094e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (mTopEdge) {
12104e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                key.markAsTopEdge(mParams);
12114e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
12124e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mRightEdgeKey = key;
12134e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12144e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12154e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void endKeyboard() {
12164e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            // nothing to do here.
12174e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12184e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12194e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private void addEdgeSpace(float width, Row row) {
12204e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            row.advanceXPos(width);
12214e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mLeftEdge = false;
12224e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            mRightEdgeKey = null;
12234e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12244e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12254e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public static float getDimensionOrFraction(TypedArray a, int index, int base,
12264e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                float defValue) {
12274e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedValue value = a.peekValue(index);
12284e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (value == null)
12294e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return defValue;
12304e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (isFractionValue(value)) {
12314e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return a.getFraction(index, base, base, defValue);
12324e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            } else if (isDimensionValue(value)) {
12334e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return a.getDimension(index, defValue);
12344e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
12354e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return defValue;
12364e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12374e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12384e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        public static int getEnumValue(TypedArray a, int index, int defValue) {
12394e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            final TypedValue value = a.peekValue(index);
12404e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (value == null)
12414e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return defValue;
12424e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            if (isIntegerValue(value)) {
12434e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                return a.getInt(index, defValue);
12444e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            }
12454e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return defValue;
12464e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12474e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12484e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean isFractionValue(TypedValue v) {
12494e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return v.type == TypedValue.TYPE_FRACTION;
12504e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12514e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12524e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean isDimensionValue(TypedValue v) {
12534e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return v.type == TypedValue.TYPE_DIMENSION;
12544e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12554e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12564e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean isIntegerValue(TypedValue v) {
12574e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
12584e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12594e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12604e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static boolean isStringValue(TypedValue v) {
12614e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return v.type == TypedValue.TYPE_STRING;
12624e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12634e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12644e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static String textAttr(String value, String name) {
12654e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return value != null ? String.format(" %s=%s", name, value) : "";
12664e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12674e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka
12684e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        private static String booleanAttr(TypedArray a, int index, String name) {
12694e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka            return a.hasValue(index)
12704e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka                    ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
12714e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka        }
12724e1dab8cfaad891fe041ed8d71893186c05cef71Tadashi G. Takaoka    }
12735a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka}
1274