private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
    if (array == null) {
      throw new NullPointerException();
    }
    if (start < 0 || start > end || end > array.length) {
      throw new IllegalArgumentException();
    }

    final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
    for (int i = start; i < end; i++) {
      list.add(array[i]);
    }
    return list;
  }
  /**
   * Split the text containing multiple key specifications separated by commas into an array of key
   * specifications. A key specification can contain a character escaped by the backslash character,
   * including a comma character. Note that an empty key specification will be eliminated from the
   * result array.
   *
   * @param text the text containing multiple key specifications.
   * @return an array of key specification text. Null if the specified <code>text</code> is empty or
   *     has no key specifications.
   */
  public static String[] splitKeySpecs(final String text) {
    final int size = text.length();
    if (size == 0) {
      return null;
    }
    // Optimization for one-letter key specification.
    if (size == 1) {
      return text.charAt(0) == COMMA ? null : new String[] {text};
    }

    ArrayList<String> list = null;
    int start = 0;
    // The characters in question in this loop are COMMA and BACKSLASH. These characters never
    // match any high or low surrogate character. So it is OK to iterate through with char
    // index.
    for (int pos = 0; pos < size; pos++) {
      final char c = text.charAt(pos);
      if (c == COMMA) {
        // Skip empty entry.
        if (pos - start > 0) {
          if (list == null) {
            list = CollectionUtils.newArrayList();
          }
          list.add(text.substring(start, pos));
        }
        // Skip comma
        start = pos + 1;
      } else if (c == BACKSLASH) {
        // Skip escape character and escaped character.
        pos++;
      }
    }
    final String remain = (size - start > 0) ? text.substring(start) : null;
    if (list == null) {
      return remain != null ? new String[] {remain} : null;
    }
    if (remain != null) {
      list.add(remain);
    }
    return list.toArray(new String[list.size()]);
  }
public class KeyboardParams {
  public Locale mLocale;
  public int mThemeId;

  /** Total height and width of the keyboard, including the paddings and keys */
  public int mOccupiedHeight;

  public int mOccupiedWidth;

  /** Base height and width of the keyboard used to calculate rows' or keys' heights and widths */
  public int mBaseHeight;

  public int mBaseWidth;

  public int mTopPadding;
  public int mBottomPadding;
  public int mLeftPadding;
  public int mRightPadding;

  public KeyVisualAttributes mKeyVisualAttributes;

  public int mDefaultRowHeight;
  public int mDefaultKeyWidth;
  public int mHorizontalGap;
  public int mVerticalGap;

  public int mMoreKeysTemplate;
  public int mMaxMoreKeysKeyboardColumn;

  public int GRID_WIDTH;
  public int GRID_HEIGHT;

  public final TreeSet<Key> mKeys = CollectionUtils.newTreeSet(); // ordered set
  public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
  public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
  public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
  public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
  public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
  public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);

  public KeysCache mKeysCache;

  public int mMostCommonKeyHeight = 0;
  public int mMostCommonKeyWidth = 0;

  public boolean mProximityCharsCorrectionEnabled;

  public final TouchPositionCorrection mTouchPositionCorrection = new TouchPositionCorrection();

  protected void clearKeys() {
    mKeys.clear();
    mShiftKeys.clear();
    clearHistogram();
  }

  public void onAddKey(final Key newKey) {
    final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
    final boolean isSpacer = key.isSpacer();
    if (isSpacer && key.getWidth() == 0) {
      // Ignore zero width {@link Spacer}.
      return;
    }
    mKeys.add(key);
    if (isSpacer) {
      return;
    }
    updateHistogram(key);
    if (key.getCode() == KeyboardConstants.CODE_SHIFT) {
      mShiftKeys.add(key);
    }
    if (key.altCodeWhileTyping()) {
      mAltCodeKeysWhileTyping.add(key);
    }
  }

  private int mMaxHeightCount = 0;
  private int mMaxWidthCount = 0;
  private final SparseIntArray mHeightHistogram = new SparseIntArray();
  private final SparseIntArray mWidthHistogram = new SparseIntArray();
  public int mHeight;
  public int mWidth;
  public Drawable mDefaultKeyDrawable;

  private void clearHistogram() {
    mMostCommonKeyHeight = 0;
    mMaxHeightCount = 0;
    mHeightHistogram.clear();

    mMaxWidthCount = 0;
    mMostCommonKeyWidth = 0;
    mWidthHistogram.clear();
  }

  private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
    final int index = histogram.indexOfKey(key);
    final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
    histogram.put(key, count);
    return count;
  }

  private void updateHistogram(final Key key) {
    final int height = key.getHeight() + mVerticalGap;
    final int heightCount = updateHistogramCounter(mHeightHistogram, height);
    if (heightCount > mMaxHeightCount) {
      mMaxHeightCount = heightCount;
      mMostCommonKeyHeight = height;
    }

    final int width = key.getWidth() + mHorizontalGap;
    final int widthCount = updateHistogramCounter(mWidthHistogram, width);
    if (widthCount > mMaxWidthCount) {
      mMaxWidthCount = widthCount;
      mMostCommonKeyWidth = width;
    }
  }
}