/* * Copyright (c) 2013 Menny Even-Danan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.anysoftkeyboard.keyboards; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.SparseIntArray; import android.util.TypedValue; import android.util.Xml; import com.anysoftkeyboard.api.KeyCodes; import com.anysoftkeyboard.keyboards.views.KeyDrawableStateProvider; import com.anysoftkeyboard.utils.Log; import com.menny.android.anysoftkeyboard.R; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Loads an XML description of a keyboard and stores the attributes of the keys. * A keyboard consists of rows of keys. * <p> * The layout file for a keyboard contains XML that looks like the following * snippet: * </p> * <p/> * <pre> * <Keyboard * android:keyWidth="%10p" * android:keyHeight="50px" * android:horizontalGap="2px" * android:verticalGap="2px" > * <Row android:keyWidth="32px" > * <Key android:keyLabel="A" /> * ... * </Row> * ... * </Keyboard> * </pre> * * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_verticalGap */ public abstract class Keyboard { static final String TAG = "Keyboard"; // Keyboard XML Tags private static final String TAG_KEYBOARD = "Keyboard"; private static final String TAG_ROW = "Row"; private static final String TAG_KEY = "Key"; public static final int EDGE_LEFT = 0x01; public static final int EDGE_RIGHT = 0x02; public static final int EDGE_TOP = 0x04; public static final int EDGE_BOTTOM = 0x08; public static final int KEY_EMBLEM_NONE = 0x00; public static final int KEY_EMBLEM_TEXT = 0x01; public static final int KEY_EMBLEM_ICON = 0x02; protected final Context mKeyboardContext; protected final Context mASKContext; protected final int mLayoutResId; protected SparseIntArray attributeIdMap; protected int[] remoteKeyboardLayoutStyleable; protected int[] remoteKeyboardRowLayoutStyleable; protected int[] remoteKeyboardKeyLayoutStyleable; /** * Horizontal gap default for all rows */ private int mDefaultHorizontalGap; /** * Default key width */ private int mDefaultWidth; /** * Default key height */ private int mDefaultHeightCode; /** * Default gap between rows */ private int mDefaultVerticalGap; /** * Is the keyboard in the shifted state */ private boolean mShifted; /** * Key instance for the shift key, if present */ private Key mShiftKey; /** * Key index for the shift key, if present */ private int mShiftKeyIndex = -1; /** * Total height of the keyboard, including the padding and keys */ private int mTotalHeight; /** * Total width of the keyboard, including left side gaps and keys, but not * any gaps on the right side. */ private int mTotalWidth; /** * List of keys in this keyboard */ private List<Key> mKeys; /** * List of modifier keys such as Shift & Alt, if any */ private List<Key> mModifierKeys; /** * Width of the screen available to fit the keyboard */ private int mDisplayWidth; /** Height of the screen */ // private int mDisplayHeight; /** * Keyboard mode, or zero, if none. */ protected final int mKeyboardMode; // Variables for pre-computing nearest keys. private static final int GRID_WIDTH = 10; private static final int GRID_HEIGHT = 5; private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; private int mCellWidth; private int mCellHeight; private int[][] mGridNeighbors; private int mProximityThreshold; /** * Number of key widths from current touch point to search for nearest keys. */ private static float SEARCH_DISTANCE = 1.8f; /** * Container for keys in the keyboard. All keys in a row are at the same * Y-coordinate. Some of the key size defaults can be overridden per row * from what the {@link Keyboard} defines. * * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_verticalGap * @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags * @attr ref android.R.styleable#Keyboard_Row_keyboardMode */ public static class Row { /** * Default width of a key in this row. */ public int defaultWidth; /** * Default height of a key in this row. */ public int defaultHeightCode; /** * Default horizontal gap between keys in this row. */ public int defaultHorizontalGap; /** * Vertical gap following this row. */ public int verticalGap; /** * Edge flags for this row of keys. Possible values that can be assigned * are {@link Keyboard#EDGE_TOP EDGE_TOP} and * {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} */ public int rowEdgeFlags; /** * The keyboard mode for this row */ public int mode; protected Keyboard parent; public Row(Keyboard parent) { this.parent = parent; defaultWidth = parent.mDefaultWidth; defaultHeightCode = parent.mDefaultHeightCode; defaultHorizontalGap = parent.mDefaultHorizontalGap; verticalGap = parent.getVerticalGap(); rowEdgeFlags = EDGE_TOP + EDGE_BOTTOM; mode = parent.mKeyboardMode; } public Row(Context askContext, Resources res, Keyboard parent, XmlResourceParser parser) { this.parent = parent; //some defaults defaultWidth = parent.mDefaultWidth; defaultHeightCode = parent.mDefaultHeightCode; defaultHorizontalGap = parent.mDefaultHorizontalGap; verticalGap = parent.getVerticalGap(); //now reading from the XML SparseIntArray attributeIdMap = parent.attributeIdMap; int[] remoteKeyboardLayoutStyleable = parent.remoteKeyboardLayoutStyleable; TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), remoteKeyboardLayoutStyleable); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { final int remoteIndex = a.getIndex(i); final int localAttrId = attributeIdMap.get(remoteKeyboardLayoutStyleable[remoteIndex]); try { switch (localAttrId) { case android.R.attr.keyWidth: defaultWidth = getDimensionOrFraction(a, remoteIndex, parent.mDisplayWidth, parent.mDefaultWidth); break; case android.R.attr.keyHeight: defaultHeightCode = getKeyHeightCode(a, remoteIndex, parent.mDefaultHeightCode); break; case android.R.attr.horizontalGap: defaultHorizontalGap = getDimensionOrFraction(a, remoteIndex, parent.mDisplayWidth, parent.mDefaultHorizontalGap); break; } } catch (Exception e) { Log.w(TAG, "Failed to set data from XML!", e); } } a.recycle(); int[] remoteKeyboardRowLayoutStyleable = parent.remoteKeyboardRowLayoutStyleable; a = res.obtainAttributes(Xml.asAttributeSet(parser), remoteKeyboardRowLayoutStyleable); n = a.getIndexCount(); for (int i = 0; i < n; i++) { final int remoteIndex = a.getIndex(i); final int localAttrId = attributeIdMap.get(remoteKeyboardRowLayoutStyleable[remoteIndex]); try { switch (localAttrId) { case android.R.attr.rowEdgeFlags: rowEdgeFlags = a.getInt(remoteIndex, 0); break; case android.R.attr.keyboardMode: mode = a.getResourceId(remoteIndex, 0); break; } } catch (Exception e) { Log.w(TAG, "Failed to set data from XML!", e); } } a.recycle(); } } /** * Class for describing the position and characteristics of a single key in * the keyboard. * * @attr ref android.R.styleable#Keyboard_keyWidth * @attr ref android.R.styleable#Keyboard_keyHeight * @attr ref android.R.styleable#Keyboard_horizontalGap * @attr ref android.R.styleable#Keyboard_Key_codes * @attr ref android.R.styleable#Keyboard_Key_keyIcon * @attr ref android.R.styleable#Keyboard_Key_keyLabel * @attr ref android.R.styleable#Keyboard_Key_iconPreview * @attr ref android.R.styleable#Keyboard_Key_isSticky * @attr ref android.R.styleable#Keyboard_Key_isRepeatable * @attr ref android.R.styleable#Keyboard_Key_showPreview * @attr ref android.R.styleable#Keyboard_Key_isModifier * @attr ref android.R.styleable#Keyboard_Key_popupKeyboard * @attr ref android.R.styleable#Keyboard_Key_popupCharacters * @attr ref android.R.styleable#Keyboard_Key_keyOutputText * @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags */ public static abstract class Key { /** * All the key codes (unicode or custom code) that this key could * generate, zero'th being the most important. */ public int[] codes; /** * Label to display */ public CharSequence label; /** * Icon to display instead of a label. Icon takes precedence over a * label */ public Drawable icon; /** * Preview version of the icon, for the preview popup */ public Drawable iconPreview; /** * Width of the key, not including the gap */ public int width; /** * Height of the key, not including the gap */ public int height; /** * The horizontal gap before this key */ public int gap; /** * Whether this key is sticky, i.e., a toggle key */ public boolean sticky; /** * X coordinate of the key in the keyboard layout */ public int x; /** * Y coordinate of the key in the keyboard layout */ public int y; /** * The current pressed state of this key */ public boolean pressed; /** * If this is a sticky key, is it on? */ public boolean on; /** * Text to output when pressed. This can be multiple characters, like * ".com" */ public CharSequence text; /** * Popup characters */ public CharSequence popupCharacters; /** * Flags that specify the anchoring to edges of the keyboard for * detecting touch events that are just out of the boundary of the key. * This is a bit mask of {@link Keyboard#EDGE_LEFT}, * {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and * {@link Keyboard#EDGE_BOTTOM}. */ public int edgeFlags; /** * Whether this is a modifier key, such as Shift or Alt */ public boolean modifier; /** * The keyboard that this key belongs to */ private Keyboard keyboard; public final Row row; /** * If this key pops up a mini keyboard, this is the resource id for the * XML layout for that keyboard. */ public int popupResId; public boolean externalResourcePopupLayout = false; /** * Whether this key repeats itself when held down */ public boolean repeatable; /** * Whether this key should show previewPopup */ public boolean showPreview; public int dynamicEmblem; /** * Create an empty key with no attributes. */ public Key(Row parent, KeyboardDimens keyboardDimens) { row = parent; keyboard = parent.parent; height = KeyboardSupport.getKeyHeightFromHeightCode(keyboardDimens, parent.defaultHeightCode, row.parent.mASKContext.getResources().getConfiguration().orientation); width = Math.min(keyboardDimens.getKeyMaxWidth(), parent.defaultWidth); gap = parent.defaultHorizontalGap; edgeFlags = parent.rowEdgeFlags; } /** * Create a key with the given top-left coordinate and extract its * attributes from the XML parser. * * @param parent the row that this key belongs to. The row must already * be attached to a {@link Keyboard}. * @param x the x coordinate of the top-left * @param y the y coordinate of the top-left * @param parser the XML parser containing the attributes for this key */ public Key(Context askContext, Context keyboardContext, Row parent, KeyboardDimens keyboardDimens, int x, int y, XmlResourceParser parser) { this(parent, keyboardDimens); final Resources askResources = askContext.getResources(); SparseIntArray attributeIdMap = parent.parent.attributeIdMap; this.x = x; this.y = y; //setting up some defaults width = Math.min(keyboardDimens.getKeyMaxWidth(), parent.defaultWidth); height = KeyboardSupport.getKeyHeightFromHeightCode(keyboardDimens, parent.defaultHeightCode, askResources.getConfiguration().orientation); gap = parent.defaultHorizontalGap; codes = null; iconPreview = null; popupCharacters = null; popupResId = 0; repeatable = false; showPreview = true; dynamicEmblem = KEY_EMBLEM_NONE; modifier = false; sticky = false; //loading data from XML int[] remoteKeyboardLayoutStyleable = parent.parent.remoteKeyboardLayoutStyleable; TypedArray a = keyboardContext.obtainStyledAttributes(Xml.asAttributeSet(parser), remoteKeyboardLayoutStyleable); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { final int remoteIndex = a.getIndex(i); final int localAttrId = attributeIdMap.get(remoteKeyboardLayoutStyleable[remoteIndex]); setDataFromTypedArray(parent, keyboardDimens, askResources, a, remoteIndex, localAttrId); } a.recycle(); this.x += gap; int[] remoteKeyboardKeyLayoutStyleable = parent.parent.remoteKeyboardKeyLayoutStyleable; a = keyboardContext.obtainStyledAttributes(Xml.asAttributeSet(parser), remoteKeyboardKeyLayoutStyleable); n = a.getIndexCount(); for (int i = 0; i < n; i++) { final int remoteIndex = a.getIndex(i); final int localAttrId = attributeIdMap.get(remoteKeyboardKeyLayoutStyleable[remoteIndex]); setDataFromTypedArray(parent, keyboardDimens, askResources, a, remoteIndex, localAttrId); } externalResourcePopupLayout = popupResId != 0; if (codes == null && !TextUtils.isEmpty(label)) { codes = new int[]{ label.charAt(0) }; } a.recycle(); } private void setDataFromTypedArray(Row parent, KeyboardDimens keyboardDimens, Resources askResources, TypedArray a, int remoteIndex, int localAttrId) { try { switch (localAttrId) { case android.R.attr.keyWidth: width = getDimensionOrFraction(a, remoteIndex, keyboard.mDisplayWidth, parent.defaultWidth); width = Math.min(keyboardDimens.getKeyMaxWidth(), width); break; case android.R.attr.keyHeight: int heightCode = getKeyHeightCode(a, remoteIndex, parent.defaultHeightCode); height = KeyboardSupport.getKeyHeightFromHeightCode(keyboardDimens, heightCode, askResources.getConfiguration().orientation); break; case android.R.attr.horizontalGap: gap = getDimensionOrFraction(a, remoteIndex, keyboard.mDisplayWidth, parent.defaultHorizontalGap); break; case android.R.attr.codes: codes = KeyboardSupport.getKeyCodesFromTypedArray(a, remoteIndex); break; case android.R.attr.iconPreview: iconPreview = a.getDrawable(remoteIndex); KeyboardSupport.updateDrawableBounds(iconPreview); break; case android.R.attr.popupCharacters: popupCharacters = a.getText(remoteIndex); break; case android.R.attr.popupKeyboard: popupResId = a.getResourceId(remoteIndex, 0); break; case android.R.attr.isRepeatable: repeatable = a.getBoolean(remoteIndex, false); break; case R.attr.showPreview: showPreview = a.getBoolean(remoteIndex, true); break; case R.attr.keyDynamicEmblem: dynamicEmblem = a.getInt(remoteIndex, KEY_EMBLEM_NONE); break; case android.R.attr.isModifier: modifier = a.getBoolean(remoteIndex, false); break; case android.R.attr.isSticky: sticky = a.getBoolean(remoteIndex, false); break; case android.R.attr.keyEdgeFlags: edgeFlags = a.getInt(remoteIndex, 0); edgeFlags |= parent.rowEdgeFlags; break; case android.R.attr.keyIcon: icon = a.getDrawable(remoteIndex); KeyboardSupport.updateDrawableBounds(icon); break; case android.R.attr.keyLabel: label = a.getText(remoteIndex); break; case android.R.attr.keyOutputText: text = a.getText(remoteIndex); break; } } catch(Exception e){ Log.w(TAG, "Failed to load keyboard layout! ", e); } } /** * Informs the key that it has been pressed, in case it needs to change * its appearance or state. * * @see #onReleased(boolean) */ public void onPressed() { pressed = true; } /** * Changes the pressed state of the key. If it is a sticky key, it will * also change the toggled state of the key if the finger was release * inside. * * @param inside whether the finger was released inside the key * @see #onPressed() */ public void onReleased(boolean inside) { pressed = false; /* * No need for this code, I handle this expicitly in setShifted code * if (sticky) { on = !on; } */ } /** * Detects if a point falls inside this key. * * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return whether or not the point falls inside the key. If the key is * attached to an edge, it will assume that all points between * the key and the edge are considered to be inside the key. */ public boolean isInside(int x, int y) { boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; boolean topEdge = (edgeFlags & EDGE_TOP) > 0; boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; if ((x >= this.x || (leftEdge && x <= this.x + this.width)) && (x < this.x + this.width || (rightEdge && x >= this.x)) && (y >= this.y || (topEdge && y <= this.y + this.height)) && (y < this.y + this.height || (bottomEdge && y >= this.y))) { return true; } else { return false; } } /** * Returns the square of the distance between the closest point inside * the key and the given point. * * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return the square of the distance of the point from and the key */ public int squaredDistanceFrom(int x, int y) { final int closestX = (x < this.x) ? this.x : (x > (this.x + this.width)) ? (this.x + this.width) : x; final int closestY = (y < this.y) ? this.y : (y > (this.y + this.height)) ? (this.y + this.height) : y; final int xDist = closestX - x; final int yDist = closestY - y; /* * int xDist = this.x + width / 2 - x; int yDist = this.y + height / * 2 - y; */ return xDist * xDist + yDist * yDist; } /** * Returns the drawable state for the key, based on the current state * and type of the key. * * @return the drawable state of the key. * @see android.graphics.drawable.StateListDrawable#setState(int[]) */ public int[] getCurrentDrawableState(KeyDrawableStateProvider provider) { int[] states = provider.KEY_STATE_NORMAL; if (on) { if (pressed) { states = provider.KEY_STATE_PRESSED_ON; } else { states = provider.KEY_STATE_NORMAL_ON; } } else { if (sticky) { if (pressed) { states = provider.KEY_STATE_PRESSED_OFF; } else { states = provider.KEY_STATE_NORMAL_OFF; } } else { if (pressed) { states = provider.KEY_STATE_PRESSED; } } } return states; } } /** * Creates a keyboard from the given xml key layout file. * * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout * and keys. */ public Keyboard(Context askContext, Context context, int xmlLayoutResId) { this(askContext, context, xmlLayoutResId, 0); } protected static int getKeyHeightCode(TypedArray a, int remoteIndex, int defaultHeightCode) { TypedValue value = a.peekValue(remoteIndex); if (value == null) { // means that it was not provided. So I take my parent's return defaultHeightCode; } else if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT && value.data <= 0 && value.data >= -3) { return value.data; } else { Log.w(TAG, "Key height attribute is incorrectly set! Defaulting to regular height."); return -1; } } /** * Creates a keyboard from the given xml key layout file. Weeds out rows * that have a keyboard mode defined but don't match the specified mode. * * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout * and keys. * @param modeId keyboard mode identifier */ public Keyboard(Context askContext, Context context, int xmlLayoutResId, int modeId) { attributeIdMap = new SparseIntArray( R.styleable.KeyboardLayout.length + R.styleable.KeyboardLayout_Row.length + R.styleable.KeyboardLayout_Key.length); remoteKeyboardLayoutStyleable = KeyboardSupport.createBackwardCompatibleStyleable( R.styleable.KeyboardLayout, askContext, context, attributeIdMap); remoteKeyboardRowLayoutStyleable = KeyboardSupport.createBackwardCompatibleStyleable( R.styleable.KeyboardLayout_Row, askContext, context, attributeIdMap); remoteKeyboardKeyLayoutStyleable = KeyboardSupport.createBackwardCompatibleStyleable( R.styleable.KeyboardLayout_Key, askContext, context, attributeIdMap); mASKContext = askContext; mKeyboardContext = context; mLayoutResId = xmlLayoutResId; mKeyboardMode = modeId; mKeys = new ArrayList<>(); mModifierKeys = new ArrayList<>(); } public List<Key> getKeys() { return mKeys; } public List<Key> getModifierKeys() { return mModifierKeys; } protected int getHorizontalGap() { return mDefaultHorizontalGap; } protected void setHorizontalGap(int gap) { mDefaultHorizontalGap = gap; } protected int getVerticalGap() { return mDefaultVerticalGap; } /** * Returns the total height of the keyboard * * @return the total height of the keyboard */ public int getHeight() { return mTotalHeight; } public int getMinWidth() { return mTotalWidth; } public boolean setShifted(boolean shiftState) { if (mShiftKey != null) { mShiftKey.on = shiftState; } if (mShifted != shiftState) { mShifted = shiftState; return true; } return false; } public boolean isShifted() { return mShifted; } public int getShiftKeyIndex() { return mShiftKeyIndex; } protected final void computeNearestNeighbors() { // Round-up so we don't have any pixels outside the grid mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; mGridNeighbors = new int[GRID_SIZE][]; int[] indices = new int[mKeys.size()]; final int gridWidth = GRID_WIDTH * mCellWidth; final int gridHeight = GRID_HEIGHT * mCellHeight; for (int x = 0; x < gridWidth; x += mCellWidth) { for (int y = 0; y < gridHeight; y += mCellHeight) { int count = 0; for (int i = 0; i < mKeys.size(); i++) { final Key key = mKeys.get(i); if (key.squaredDistanceFrom(x, y) < mProximityThreshold || key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold || key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) < mProximityThreshold || key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) { indices[count++] = i; } } int[] cell = new int[count]; System.arraycopy(indices, 0, cell, 0, count); mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; } } } /** * Returns the indices of the keys that are closest to the given point. * * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return the array of integer indices for the nearest keys to the given * point. If the given point is out of range, then an array of size * zero is returned. */ public int[] getNearestKeys(int x, int y) { if (mGridNeighbors == null) computeNearestNeighbors(); if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); if (index < GRID_SIZE) { return mGridNeighbors[index]; } } return new int[0]; } protected Row createRowFromXml(Context askContext, Resources res, XmlResourceParser parser) { return new Row(askContext, res, this, parser); } protected abstract Key createKeyFromXml(Context askContext, Context keyboardContext, Row parent, KeyboardDimens keyboardDimens, int x, int y, XmlResourceParser parser); public void loadKeyboard(final KeyboardDimens keyboardDimens) { mDisplayWidth = keyboardDimens.getKeyboardMaxWidth(); final float rowVerticalGap = keyboardDimens.getRowVerticalGap(); final float keyHorizontalGap = keyboardDimens.getKeyHorizontalGap(); mDefaultHorizontalGap = 0; mDefaultWidth = mDisplayWidth / 10; mDefaultHeightCode = -1; XmlResourceParser parser = mKeyboardContext.getResources().getXml(mLayoutResId); boolean inKey = false; boolean inRow = false; boolean inUnknown = false; int row = 0; float x = 0; float y = rowVerticalGap;// starts with a gap int rowHeight = 0; Key key = null; Row currentRow = null; Resources res = mKeyboardContext.getResources(); boolean skipRow = false; int lastVerticalGap = 0; try { int event; while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { if (event == XmlResourceParser.START_TAG) { String tag = parser.getName(); if (TAG_ROW.equals(tag)) { inRow = true; x = 0; rowHeight = 0; currentRow = createRowFromXml(mASKContext, res, parser); skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; if (skipRow) { skipToEndOfRow(parser); inRow = false; } } else if (TAG_KEY.equals(tag)) { inKey = true; x += (keyHorizontalGap / 2); key = createKeyFromXml(mASKContext, mKeyboardContext, currentRow, keyboardDimens, (int) x, (int) y, parser); rowHeight = Math.max(rowHeight, key.height); key.width -= keyHorizontalGap;// the gap is on both // sides mKeys.add(key); if (key.codes[0] == KeyCodes.SHIFT) { mShiftKey = key; mShiftKeyIndex = mKeys.size() - 1; mModifierKeys.add(key); } else if (key.codes[0] == KeyCodes.ALT) { mModifierKeys.add(key); } } else if (TAG_KEYBOARD.equals(tag)) { parseKeyboardAttributes(mASKContext, res, parser); } else { inUnknown = true; onUnknownTagStart(mKeyboardContext, res, tag, parser); } } else if (event == XmlResourceParser.END_TAG) { if (inKey) { inKey = false; x += key.gap + key.width; x += (keyHorizontalGap / 2); if (x > mTotalWidth) { mTotalWidth = (int) x; } } else if (inRow) { inRow = false; lastVerticalGap = currentRow.verticalGap; y += currentRow.verticalGap; y += rowHeight; y += rowVerticalGap; row++; } else if (inUnknown) { inUnknown = false; onUnknownTagEnd(); } } } } catch (Exception e) { Log.e(TAG, "Parse error:" + e); e.printStackTrace(); } mTotalHeight = (int) (y - lastVerticalGap); } protected void onUnknownTagEnd() { } protected void onUnknownTagStart(Context context, Resources res, String tag2, XmlResourceParser parser) { } private void skipToEndOfRow(XmlResourceParser parser) throws XmlPullParserException, IOException { int event; while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { if (event == XmlResourceParser.END_TAG && parser.getName().equals(TAG_ROW)) { break; } } } private void parseKeyboardAttributes(Context askContext, Resources res, XmlResourceParser parser) { TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), remoteKeyboardLayoutStyleable); Resources askRes = askContext.getResources(); //some defaults mDefaultWidth = mDisplayWidth / 10; mDefaultHeightCode = -1; mDefaultHorizontalGap = 0; mDefaultVerticalGap = askRes.getDimensionPixelOffset(R.dimen.default_key_vertical_gap); //now reading from XML int n = a.getIndexCount(); for (int i = 0; i < n; i++) { final int remoteIndex = a.getIndex(i); final int localAttrId = attributeIdMap.get(remoteKeyboardLayoutStyleable[remoteIndex]); try { switch (localAttrId) { case android.R.attr.keyWidth: mDefaultWidth = getDimensionOrFraction(a, remoteIndex, mDisplayWidth, mDisplayWidth / 10); break; case android.R.attr.keyHeight: mDefaultHeightCode = getKeyHeightCode(a, remoteIndex, -1); break; case android.R.attr.horizontalGap: mDefaultHorizontalGap = getDimensionOrFraction(a, remoteIndex, mDisplayWidth, 0); break; /*vertical gap is part of the Theme, not the keyboard.*/ /*case android.R.attr.verticalGap: mDefaultVerticalGap = getDimensionOrFraction(a, remoteIndex, mDisplayWidth, mDefaultVerticalGap); break;*/ } } catch (Exception e) { Log.w(TAG, "Failed to set data from XML!", e); } } a.recycle(); mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE); // Square it for comparison mProximityThreshold = mProximityThreshold * mProximityThreshold; } static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { TypedValue value = a.peekValue(index); if (value == null) return defValue; if (value.type == TypedValue.TYPE_DIMENSION) { return a.getDimensionPixelOffset(index, defValue); } else if (value.type == TypedValue.TYPE_FRACTION) { // Round it to avoid values like 47.9999 from getting truncated return Math.round(a.getFraction(index, base, base, defValue)); } return defValue; } }