private void parseIncludeInternal(
      final XmlPullParser parser, final KeyboardRow row, final boolean skip)
      throws XmlPullParserException, IOException {
    if (skip) {
      XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
      if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
      return;
    }
    final AttributeSet attr = Xml.asAttributeSet(parser);
    final TypedArray keyboardAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Include);
    final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
    int keyboardLayout = 0;
    try {
      XmlParseUtils.checkAttributeExists(
          keyboardAttr,
          R.styleable.Keyboard_Include_keyboardLayout,
          "keyboardLayout",
          TAG_INCLUDE,
          parser);
      keyboardLayout = keyboardAttr.getResourceId(R.styleable.Keyboard_Include_keyboardLayout, 0);
      if (row != null) {
        // Override current x coordinate.
        row.setXPos(row.getKeyX(keyAttr));
        // Push current Row attributes and update with new attributes.
        row.pushRowAttributes(keyAttr);
      }
    } finally {
      keyboardAttr.recycle();
      keyAttr.recycle();
    }

    XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
    if (DEBUG) {
      startEndTag(
          "<%s keyboardLayout=%s />", TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout));
    }
    final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
    try {
      parseMerge(parserForInclude, row, skip);
    } finally {
      if (row != null) {
        // Restore Row attributes.
        row.popRowAttributes();
      }
      parserForInclude.close();
    }
  }
 private void endRow(final KeyboardRow row) {
   if (mCurrentRow == null) {
     throw new RuntimeException("orphan end row tag");
   }
   if (mRightEdgeKey != null) {
     mRightEdgeKey.markAsRightEdge(mParams);
     mRightEdgeKey = null;
   }
   addEdgeSpace(mParams.mRightPadding, row);
   mCurrentY += row.getRowHeight();
   mCurrentRow = null;
   mTopEdge = false;
 }
 private void addEdgeSpace(final float width, final KeyboardRow row) {
   row.advanceXPos(width);
   mLeftEdge = false;
   mRightEdgeKey = null;
 }
  private void parseGridRows(final XmlPullParser parser, final boolean skip)
      throws XmlPullParserException, IOException {
    if (skip) {
      XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser);
      if (DEBUG) {
        startEndTag("<%s /> skipped", TAG_GRID_ROWS);
      }
      return;
    }
    final KeyboardRow gridRows = new KeyboardRow(mResources, mParams, parser, mCurrentY);
    final TypedArray gridRowAttr =
        mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_GridRows);
    final int codesArrayId = gridRowAttr.getResourceId(R.styleable.Keyboard_GridRows_codesArray, 0);
    final int textsArrayId = gridRowAttr.getResourceId(R.styleable.Keyboard_GridRows_textsArray, 0);
    gridRowAttr.recycle();
    if (codesArrayId == 0 && textsArrayId == 0) {
      throw new XmlParseUtils.ParseException("Missing codesArray or textsArray attributes", parser);
    }
    if (codesArrayId != 0 && textsArrayId != 0) {
      throw new XmlParseUtils.ParseException(
          "Both codesArray and textsArray attributes specifed", parser);
    }
    final String[] array =
        mResources.getStringArray(codesArrayId != 0 ? codesArrayId : textsArrayId);
    final int counts = array.length;
    final float keyWidth = gridRows.getKeyWidth(null, 0.0f);
    final int numColumns = (int) (mParams.mOccupiedWidth / keyWidth);
    for (int index = 0; index < counts; index += numColumns) {
      final KeyboardRow row = new KeyboardRow(mResources, mParams, parser, mCurrentY);
      startRow(row);
      for (int c = 0; c < numColumns; c++) {
        final int i = index + c;
        if (i >= counts) {
          break;
        }
        final String label;
        final int code;
        final String outputText;
        final int supportedMinSdkVersion;
        if (codesArrayId != 0) {
          final String codeArraySpec = array[i];
          label = CodesArrayParser.parseLabel(codeArraySpec);
          code = CodesArrayParser.parseCode(codeArraySpec);
          outputText = CodesArrayParser.parseOutputText(codeArraySpec);
          supportedMinSdkVersion = CodesArrayParser.getMinSupportSdkVersion(codeArraySpec);
        } else {
          final String textArraySpec = array[i];
          // TODO: Utilize KeySpecParser or write more generic TextsArrayParser.
          label = textArraySpec;
          code = Constants.CODE_OUTPUT_TEXT;
          outputText = textArraySpec + (char) Constants.CODE_SPACE;
          supportedMinSdkVersion = 0;
        }
        if (Build.VERSION.SDK_INT < supportedMinSdkVersion) {
          continue;
        }
        final int x = (int) row.getKeyX(null);
        final int y = row.getKeyY();
        final Key key =
            new Key(
                mParams,
                label,
                null /* hintLabel */,
                0 /* iconId */,
                code,
                outputText,
                x,
                y,
                (int) keyWidth,
                (int) row.getRowHeight(),
                row.getDefaultKeyLabelFlags(),
                row.getDefaultBackgroundType());
        endKey(key);
        row.advanceXPos(keyWidth);
      }
      endRow(row);
    }

    XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser);
  }