diff options
Diffstat (limited to 'tests/src')
16 files changed, 2155 insertions, 21 deletions
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java new file mode 100644 index 000000000..06139b808 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard; + +import android.content.res.Resources; +import android.test.suitebuilder.annotation.MediumTest; +import android.text.InputType; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.RunInLocale; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +@MediumTest +public final class KeyboardLayoutSetActionLabelTests extends KeyboardLayoutSetTestsBase { + private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet, + final int elementId, final String label, final int iconId) { + final Keyboard keyboard = layoutSet.getKeyboard(elementId); + final Key enterKey = keyboard.getKey(Constants.CODE_ENTER); + assertNotNull(tag + " enter key on " + keyboard.mId, enterKey); + assertEquals(tag + " enter label " + enterKey, label, enterKey.getLabel()); + assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId()); + } + + private void doTestActionLabel(final String tag, final InputMethodSubtype subtype, + final int actionId, final int labelResId) { + final EditorInfo editorInfo = new EditorInfo(); + editorInfo.imeOptions = actionId; + final RunInLocale<String> job = new RunInLocale<String>() { + @Override + protected String job(final Resources res) { + return res.getString(labelResId); + } + }; + final Resources res = getContext().getResources(); + final String label; + if (subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)) { + // Using system locale. + label = res.getString(labelResId); + } else { + label = job.runInLocale(res, SubtypeLocaleUtils.getSubtypeLocale(subtype)); + } + // Test text layouts. + editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, + label, KeyboardIconsSet.ICON_UNDEFINED); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, + label, KeyboardIconsSet.ICON_UNDEFINED); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, + label, KeyboardIconsSet.ICON_UNDEFINED); + // Test phone number layouts. + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, + label, KeyboardIconsSet.ICON_UNDEFINED); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, + label, KeyboardIconsSet.ICON_UNDEFINED); + // Test normal number layout. + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, + label, KeyboardIconsSet.ICON_UNDEFINED); + // Test number password layouts. + editorInfo.inputType = + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD; + final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo); + doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, + label, KeyboardIconsSet.ICON_UNDEFINED); + } + + private void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype, + final int actionId, final String iconName) { + final int iconId = KeyboardIconsSet.getIconId(iconName); + final EditorInfo editorInfo = new EditorInfo(); + editorInfo.imeOptions = actionId; + // Test text layouts. + editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, null /* label */, iconId); + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, null /* label */, iconId); + doTestActionKey( + tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, null /* label */, iconId); + // Test phone number layouts. + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, null /* label */, iconId); + doTestActionKey( + tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, null /* label */, iconId); + // Test normal number layout. + doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId); + // Test number password layout. + editorInfo.inputType = + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD; + final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo); + doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId); + } + + public void testActionUnspecified() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "unspecifiled " + + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED, "enter_key"); + } + } + + public void testActionNone() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE, "enter_key"); + } + } + + public void testActionGo() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_GO, R.string.label_go_key); + } + } + + public void testActionSearch() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH, "search_key"); + } + } + + public void testActionSend() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_SEND, R.string.label_send_key); + } + } + + public void testActionNext() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_NEXT, R.string.label_next_key); + } + } + + public void testActionDone() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_DONE, R.string.label_done_key); + } + } + + public void testActionPrevious() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionLabel( + tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, R.string.label_previous_key); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java new file mode 100644 index 000000000..e691639a8 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard; + +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.ArrayList; + +@SmallTest +public class KeyboardLayoutSetSubtypesCountTests extends KeyboardLayoutSetTestsBase { + private static final int NUMBER_OF_SUBTYPES = 63; + private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 40; + private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2; + + private static String toString(final ArrayList<InputMethodSubtype> subtypeList) { + final StringBuilder sb = new StringBuilder(); + for (int index = 0; index < subtypeList.size(); index++) { + final InputMethodSubtype subtype = subtypeList.get(index); + sb.append(index + ": "); + sb.append(SubtypeLocaleUtils.getSubtypeNameForLogging(subtype)); + sb.append("\n"); + } + return sb.toString(); + } + + public final void testAllSubtypesCount() { + final ArrayList<InputMethodSubtype> allSubtypesList = getAllSubtypesList(); + assertEquals(toString(allSubtypesList), NUMBER_OF_SUBTYPES, allSubtypesList.size()); + } + + public final void testAsciiCapableSubtypesCount() { + final ArrayList<InputMethodSubtype> asciiCapableSubtypesList = + getAsciiCapableSubtypesList(); + assertEquals(toString(asciiCapableSubtypesList), + NUMBER_OF_ASCII_CAPABLE_SUBTYPES, asciiCapableSubtypesList.size()); + } + + public final void testAdditionalSubtypesCount() { + final ArrayList<InputMethodSubtype> additionalSubtypesList = getAdditionalSubtypesList(); + assertEquals(toString(additionalSubtypesList), + NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES, additionalSubtypesList.size()); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java index 9939a4335..0993c4b67 100644 --- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java @@ -41,9 +41,6 @@ import java.util.Locale; @SmallTest public class KeyboardLayoutSetTestsBase extends AndroidTestCase { - private static final int NUMBER_OF_SUBTYPES = 63; - private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 40; - private static final KeyboardTheme DEFAULT_KEYBOARD_THEME = KeyboardSwitcher.KEYBOARD_THEMES[KeyboardSwitcher.THEME_INDEX_DEFAULT]; @@ -51,6 +48,8 @@ public class KeyboardLayoutSetTestsBase extends AndroidTestCase { private final ArrayList<InputMethodSubtype> mAllSubtypesList = CollectionUtils.newArrayList(); private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList = CollectionUtils.newArrayList(); + private final ArrayList<InputMethodSubtype> mAdditionalSubtypesList = + CollectionUtils.newArrayList(); private Context mThemeContext; private int mScreenMetrics; @@ -68,6 +67,10 @@ public class KeyboardLayoutSetTestsBase extends AndroidTestCase { final int subtypeCount = imi.getSubtypeCount(); for (int index = 0; index < subtypeCount; index++) { final InputMethodSubtype subtype = imi.getSubtypeAt(index); + if (AdditionalSubtypeUtils.isAdditionalSubtype(subtype)) { + mAdditionalSubtypesList.add(subtype); + continue; + } mAllSubtypesList.add(subtype); if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) { mAsciiCapableSubtypesList.add(subtype); @@ -75,30 +78,21 @@ public class KeyboardLayoutSetTestsBase extends AndroidTestCase { } } - protected final boolean isPhone() { - return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE - || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE; + protected final ArrayList<InputMethodSubtype> getAllSubtypesList() { + return mAllSubtypesList; } - private static String toString(final ArrayList<InputMethodSubtype> subtypeList) { - final StringBuilder sb = new StringBuilder(); - for (int index = 0; index < subtypeList.size(); index++) { - final InputMethodSubtype subtype = subtypeList.get(index); - sb.append((index + 1) + ": "); - sb.append(SubtypeLocaleUtils.getSubtypeNameForLogging(subtype)); - sb.append("\n"); - } - return sb.toString(); + protected final ArrayList<InputMethodSubtype> getAsciiCapableSubtypesList() { + return mAsciiCapableSubtypesList; } - public final void testAllSubtypesCount() { - assertEquals(toString(mAllSubtypesList), - NUMBER_OF_SUBTYPES, mAllSubtypesList.size()); + protected final ArrayList<InputMethodSubtype> getAdditionalSubtypesList() { + return mAdditionalSubtypesList; } - public final void testAsciiCapableSubtypesCount() { - assertEquals(toString(mAsciiCapableSubtypesList), - NUMBER_OF_ASCII_CAPABLE_SUBTYPES, mAsciiCapableSubtypesList.size()); + protected final boolean isPhone() { + return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE + || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE; } protected final InputMethodSubtype getSubtype(final Locale locale, diff --git a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java new file mode 100644 index 000000000..be3ed12de --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout; + +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.LayoutBase; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The generic upper case alphabet keyboard layout. + */ +public final class AlphabetShifted extends LayoutBase { + public static ExpectedKey[][] getAlphabet(final ExpectedKey[][] lowerCaseKeyboard, + final Locale locale) { + final ExpectedKey[][] upperCaseKeyboard = ExpectedKeyboardBuilder.toUpperCase( + lowerCaseKeyboard, locale); + return new ExpectedKeyboardBuilder(upperCaseKeyboard) + .replaceKeyOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY) + .build(); + } + + // Icon id. + private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId("shift_key_shifted"); + + // Functional key. + private static final ExpectedKey SHIFTED_SHIFT_KEY = key( + ICON_SHIFTED_SHIFT, Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java new file mode 100644 index 000000000..f7179b739 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout; + +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.LayoutBase; + +/** + * The QWERTY alphabet keyboard. + */ +public final class Qwerty extends LayoutBase { + public static ExpectedKey[][] getAlphabet(final boolean isPhone) { + return toCommonAlphabet(ALPHABET_COMMON, isPhone); + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 3) + .setLabelsOfRow(1, "q", "w", "e", "r", "t", "y", "u", "i", "o", "p") + .setMoreKeysOf("q", "1") + .setMoreKeysOf("w", "2") + .setMoreKeysOf("e", "3") + .setMoreKeysOf("r", "4") + .setMoreKeysOf("t", "5") + .setMoreKeysOf("y", "6") + .setMoreKeysOf("u", "7") + .setMoreKeysOf("i", "8") + .setMoreKeysOf("o", "9") + .setMoreKeysOf("p", "0") + .setLabelsOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l") + .setLabelsOfRow(3, "z", "x", "c", "v", "b", "n", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java new file mode 100644 index 000000000..03d7f07e9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout; + +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.LayoutBase; +import com.android.inputmethod.latin.Constants; + +/** + * The symbols keyboard layout. + */ +public final class Symbols extends LayoutBase { + public static ExpectedKey[][] getSymbols(final boolean isPhone) { + return isPhone ? toPhoneSymbol(SYMBOLS_COMMON) : toTabletSymbols(SYMBOLS_COMMON); + } + + // Functional keys. + public static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL); + public static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT); + public static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT); + + // Common symbols keyboard layout. + public static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 5) + .setLabelsOfRow(1, "1", "2", "3", "4", "5", "6", "7", "8", "9", "0") + // U+00B9: "¹" SUPERSCRIPT ONE + // U+00BD: "½" VULGAR FRACTION ONE HALF + // U+2153: "⅓" VULGAR FRACTION ONE THIRD + // U+00BC: "¼" VULGAR FRACTION ONE QUARTER + // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH + .setMoreKeysOf("1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B") + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + .setMoreKeysOf("2", "\u00B2", "\u2154") + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + .setMoreKeysOf("3", "\u00B3", "\u00BE", "\u215C") + // U+2074: "⁴" SUPERSCRIPT FOUR + .setMoreKeysOf("4", "\u2074") + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + .setMoreKeysOf("5", "\u215D") + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + .setMoreKeysOf("7", "\u215E") + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + .setMoreKeysOf("0", "\u207F", "\u2205") + .setLabelsOfRow(2, "@", "#", "$", "%", "&", "-", "+", "(", ")") + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + .setMoreKeysOf("$", "\u00A2", "\u00A3", "\u20AC", "\u00A5", "\u20B1") + // U+2030: "‰" PER MILLE SIGN + .setMoreKeysOf("%", "\u2030") + // U+2013: "–" EN DASH + // U+2014: "—" EM DASH + // U+00B7: "·" MIDDLE DOT + .setMoreKeysOf("-", "_", "\u2013", "\u2014", "\u00B7") + // U+00B1: "±" PLUS-MINUS SIGN + .setMoreKeysOf("+", "\u00B1") + .setMoreKeysOf("(", "<", "{", "[") + .setMoreKeysOf(")", ">", "}", "]") + .setLabelsOfRow(3, "*", "\"", "'", ":", ";", "!", "?") + // U+2020: "†" DAGGER + // U+2021: "‡" DOUBLE DAGGER + // U+2605: "★" BLACK STAR + .setMoreKeysOf("*", "\u2020", "\u2021", "\u2605") + // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + // U+201D: "”" RIGHT DOUBLE QUOTATION MARK + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + .setMoreKeysOf("\"", "\u201E", "\u201C", "\u201D", "\u00AB", "\u00BB") + // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK + // U+2018: "‘" LEFT SINGLE QUOTATION MARK + // U+2019: "’" RIGHT SINGLE QUOTATION MARK + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + .setMoreKeysOf("'", "\u201A", "\u2018", "\u2019", "\u2039", "\u203A") + // U+00A1: "¡" INVERTED EXCLAMATION MARK + .setMoreKeysOf("!", "\u00A1") + // U+00BF: "¿" INVERTED QUESTION MARK + .setMoreKeysOf("?", "\u00BF") + .setLabelsOfRow(4, "_", "/", " ", ",", ".") + // U+2026: "…" HORIZONTAL ELLIPSIS + .setMoreKeysOf(".", "\u2026") + .build(); + + private static ExpectedKey[][] toPhoneSymbol(final ExpectedKey[][] common) { + return new ExpectedKeyboardBuilder(common) + .addKeysOnTheLeftOfRow(3, Symbols.SYMBOLS_SHIFT_KEY) + .addKeysOnTheRightOfRow(3, DELETE_KEY) + .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)) + .build(); + } + + private static ExpectedKey[][] toTabletSymbols(final ExpectedKey[][] common) { + return new ExpectedKeyboardBuilder(common) + .addKeysOnTheLeftOfRow(3, + key("\\"), key("=")) + .addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .addKeysOnTheLeftOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY) + .addKeysOnTheRightOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY) + .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY) + .addKeysOnTheRightOfRow(4, EMOJI_KEY) + .build(); + } + + // Helper method to add currency symbols for Euro. + public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) { + return builder + // U+20AC: "€" EURO SIGN + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + .replaceKeyOfLabel("$", key("\u20AC", + moreKey("\u00A2"), moreKey("\u00A3"), moreKey("$"), + moreKey("\u00A5"), moreKey("\u20B1"))); + } + + // Helper method to add single quotes "more keys". + // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks". + public static ExpectedKeyboardBuilder singleQuotes9LLR(final ExpectedKeyboardBuilder builder) { + return builder + // U+2019: "’" RIGHT SINGLE QUOTATION MARK + // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK + // U+2018: "‘" LEFT SINGLE QUOTATION MARK + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u2039", "\u203A"); + } + + // Helper method to add single quotes "more keys". + // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks". + public static ExpectedKeyboardBuilder singleQuotes9LRL(final ExpectedKeyboardBuilder builder) { + return builder + // U+2019: "’" RIGHT SINGLE QUOTATION MARK + // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK + // U+2018: "‘" LEFT SINGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u203A", "\u2039"); + } + + // Helper method to add double quotes "more keys". + // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks". + public static ExpectedKeyboardBuilder doubleQuotes9LLR(final ExpectedKeyboardBuilder builder) { + return builder + // U+201D: "”" RIGHT DOUBLE QUOTATION MARK + // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00AB", "\u00BB"); + } + + // Helper method to add double quotes "more keys". + // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks". + public static ExpectedKeyboardBuilder doubleQuotes9LRL(final ExpectedKeyboardBuilder builder) { + return builder + // U+201D: "”" RIGHT DOUBLE QUOTATION MARK + // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00BB", "\u00AB"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java new file mode 100644 index 000000000..368f9db46 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout; + +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.LayoutBase; +import com.android.inputmethod.latin.Constants; + +/** + * The symbols shifted keyboard layout. + */ +public final class SymbolsShifted extends LayoutBase { + public static ExpectedKey[][] getSymbolsShifted(final boolean isPhone) { + return isPhone ? toPhoneSymbolsShifted(SYMBOLS_SHIFTED_COMMON) + : toTabletSymbolsShifted(SYMBOLS_SHIFTED_COMMON); + } + + // Functional key. + public static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT); + + // Common symbols shifted keyboard layout. + public static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON = + new ExpectedKeyboardBuilder(10, 9, 7, 5) + // U+0060: "`" GRAVE ACCENT + // U+2022: "•" BULLET + // U+221A: "√" SQUARE ROOT + // U+03C0: "π" GREEK SMALL LETTER PI + // U+00F7: "÷" DIVISION SIGN + // U+00D7: "×" MULTIPLICATION SIGN + // U+00B6: "¶" PILCROW SIGN + // U+2206: "∆" INCREMENT + .setLabelsOfRow(1, + "~", "\u0060", "|", "\u2022", "\u221A", + "\u03C0", "\u00F7", "\u00D7", "\u00B6", "\u2206") + // U+2022: "•" BULLET + // U+266A: "♪" EIGHTH NOTE + // U+2665: "♥" BLACK HEART SUIT + // U+2660: "♠" BLACK SPADE SUIT + // U+2666: "♦" BLACK DIAMOND SUIT + // U+2663: "♣" BLACK CLUB SUIT + .setMoreKeysOf("\u2022", "\u266A", "\u2665", "\u2660", "\u2666", "\u2663") + // U+03C0: "π" GREEK SMALL LETTER PI + // U+03A0: "Π" GREEK CAPITAL LETTER PI + .setMoreKeysOf("\u03C0", "\u03A0") + // U+00B6: "¶" PILCROW SIGN + // U+00A7: "§" SECTION SIGN + .setMoreKeysOf("\u00B6", "\u00A7") + // U+00A3: "£" POUND SIGN + // U+00A2: "¢" CENT SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+00B0: "°" DEGREE SIGN + .setLabelsOfRow(2, + "\u00A3", "\u00A2", "\u20AC", "\u00A5", "^", + "\u00B0", "=", "{", "}") + // U+2191: "↑" UPWARDS ARROW + // U+2193: "↓" DOWNWARDS ARROW + // U+2190: "←" LEFTWARDS ARROW + // U+2192: "→" RIGHTWARDS ARROW + .setMoreKeysOf("^", "\u2191", "\u2193", "\u2190", "\u2192") + // U+00B0: "°" DEGREE SIGN + // U+2032: "′" PRIME + // U+2033: "″" DOUBLE PRIME + .setMoreKeysOf("\u00B0", "\u2032", "\u2033") + // U+2260: "≠" NOT EQUAL TO + // U+2248: "≈" ALMOST EQUAL TO + // U+221E: "∞" INFINITY + .setMoreKeysOf("=", "\u2260", "\u2248", "\u221E") + // U+00A9: "©" COPYRIGHT SIGN + // U+00AE: "®" REGISTERED SIGN + // U+2122: "™" TRADE MARK SIGN + // U+2105: "℅" CARE OF + .setLabelsOfRow(3, + "\\", "\u00A9", "\u00AE", "\u2122", "\u2105", + "[", "]") + .setLabelsOfRow(4, + "<", ">", " ", ",", ".") + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + .setMoreKeysOf("<", "\u2039", "\u2264", "\u00AB") + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2265: "≥" GREATER-THAN EQUAL TO + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + .setMoreKeysOf(">", "\u203A", "\u2265", "\u00BB") + // U+2026: "…" HORIZONTAL ELLIPSIS + .setMoreKeysOf(".", "\u2026") + .build(); + + private static ExpectedKey[][] toPhoneSymbolsShifted(final ExpectedKey[][] common) { + return new ExpectedKeyboardBuilder(common) + .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY) + .addKeysOnTheRightOfRow(3, DELETE_KEY) + .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)) + .build(); + } + + private static ExpectedKey[][] toTabletSymbolsShifted(final ExpectedKey[][] common) { + return new ExpectedKeyboardBuilder(common) + // U+00BF: "¿" INVERTED QUESTION MARK + // U+00A1: "¡" INVERTED EXCLAMATION MARK + .addKeysOnTheRightOfRow(3, + key("\u00A1"), key("\u00BF")) + .addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY) + .addKeysOnTheRightOfRow(3, BACK_TO_SYMBOLS_KEY) + .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY) + .addKeysOnTheRightOfRow(4, EMOJI_KEY) + .build(); + } + + // Helper method to add currency symbols for Euro. + public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) { + return builder + // U+00A5: "¥" YEN SIGN + // U+00A2: "¢" CENT SIGN + .replaceKeyOfLabel("\u00A5", key("\u00A2")) + // U+20AC: "€" EURO SIGN + // U+00A2: "¢" CENT SIGN + .replaceKeyOfLabel("\u20AC", key("$", moreKey("\u00A2"))) + // U+00A2: "¢" CENT SIGN + // U+00A5: "¥" YEN SIGN + .replaceKeyOfLabel("\u00A2", key("\u00A5")); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java new file mode 100644 index 000000000..45449b762 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import java.util.Arrays; + +/** + * This class builds a keyboard that is a two dimensional array of elements <code>E</code>. + * + * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have + * different number of elements. A element of a keyboard can be specified by a row number and a + * column number, both numbers starts from 1. + * + * @param <E> the type of a keyboard element. A keyboard element must be an immutable object. + */ +abstract class AbstractKeyboardBuilder<E> { + // A building array of rows. + private final E[][] mRows; + + // Returns an instance of default element. + abstract E defaultElement(); + // Returns an <code>E</code> array instance of the <code>size</code>. + abstract E[] newArray(final int size); + // Returns an <code>E[]</code> array instance of the <code>size</code>. + abstract E[][] newArrayOfArray(final int size); + + /** + * Construct a builder filled with the default element. + * @param dimensions the integer array of each row's size. + */ + AbstractKeyboardBuilder(final int ... dimensions) { + mRows = newArrayOfArray(dimensions.length); + for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) { + mRows[rowIndex] = newArray(dimensions[rowIndex]); + Arrays.fill(mRows[rowIndex], defaultElement()); + } + } + + /** + * Construct a builder from template keyboard. This builder has the same dimensions and + * elements of <code>rows</rows>. + * @param rows the template keyboard rows. The elements of the <code>rows</code> will be + * shared with this builder. Therefore a element must be an immutable object. + */ + AbstractKeyboardBuilder(final E[][] rows) { + mRows = newArrayOfArray(rows.length); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final E[] row = rows[rowIndex]; + mRows[rowIndex] = Arrays.copyOf(row, row.length); + } + } + + /** + * Return current constructing keyboard. + * @return the array of the array of the element being constructed. + */ + E[][] build() { + return mRows; + } + + /** + * Get the current contents of the specified row. + * @param row the row number to get the contents. + * @return the array of elements at row number <code>row</code>. + * @throws {@link RuntimeException} if <code>row</code> is illegal. + */ + E[] getRowAt(final int row) { + final int rowIndex = row - 1; + if (rowIndex < 0 || rowIndex >= mRows.length) { + throw new RuntimeException("Illegal row number: " + row); + } + return mRows[rowIndex]; + } + + /** + * Set an array of elements to the specified row. + * @param row the row number to set <code>elements</code>. + * @param elements the array of elements to set at row number <code>row</code>. + * @throws {@link RuntimeException} if <code>row</code> is illegal. + */ + void setRowAt(final int row, final E[] elements) { + final int rowIndex = row - 1; + if (rowIndex < 0 || rowIndex >= mRows.length) { + throw new RuntimeException("Illegal row number: " + row); + } + mRows[rowIndex] = elements; + } + + /** + * Set or insert an element at specified position. + * @param row the row number to set or insert the <code>element</code>. + * @param column the column number to set or insert the <code>element</code>. + * @param element the element to set or insert at <code>row,column</code>. + * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>. + * Otherwise the <code>element</code> replace the element at <code>row,column</code>. + * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal. + */ + void setElementAt(final int row, final int column, final E element, final boolean insert) { + final E[] elements = getRowAt(row); + final int columnIndex = column - 1; + if (insert) { + if (columnIndex < 0 || columnIndex >= elements.length + 1) { + throw new RuntimeException("Illegal column number: " + column); + } + final E[] newElements = Arrays.copyOf(elements, elements.length + 1); + // Shift the remaining elements. + System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1, + elements.length - columnIndex); + // Insert the element at <code>row,column</code>. + newElements[columnIndex] = element; + // Replace the current row with one. + setRowAt(row, newElements); + return; + } + if (columnIndex < 0 || columnIndex >= elements.length) { + throw new RuntimeException("Illegal column number: " + column); + } + elements[columnIndex] = element; + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java new file mode 100644 index 000000000..b918d47f8 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +/** + * This class builds an actual keyboard for unit test. + */ +public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> { + // Comparator to sort {@link Key}s from top-left to bottom-right order. + private static final Comparator<Key> ROW_COLUMN_COMPARATOR = new Comparator<Key>() { + @Override + public int compare(final Key lhs, final Key rhs) { + if (lhs.getY() < rhs.getY()) return -1; + if (lhs.getY() > rhs.getY()) return 1; + if (lhs.getX() < rhs.getX()) return -1; + if (lhs.getX() > rhs.getX()) return 1; + return 0; + } + }; + + /** + * Create the keyboard that consists of the array of rows of the actual keyboard's keys. + * @param keys the array of keys of the actual keyboard. + * @return the actual keyboard grouped with rows. + */ + public static Key[][] buildKeyboard(final Key[] keys) { + // Sort keys from top-left to bottom-right order to prepare to create rows. + final ArrayList<Key> sortedKeys = CollectionUtils.newArrayList(Arrays.asList(keys)); + Collections.sort(sortedKeys, ROW_COLUMN_COMPARATOR); + + // Grouping keys into rows. + final ArrayList<ArrayList<Key>> rows = CollectionUtils.newArrayList(); + ArrayList<Key> elements = CollectionUtils.newArrayList(); + int lastY = sortedKeys.get(0).getY(); + for (final Key key : sortedKeys) { + if (lastY != key.getY()) { + // A new row is starting. + lastY = key.getY(); + rows.add(elements); + elements = CollectionUtils.newArrayList(); + } + elements.add(key); + } + rows.add(elements); // Add the last row. + + // Calculate each dimension of rows and create a builder. + final int[] dimensions = new int[rows.size()]; + for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) { + dimensions[rowIndex] = rows.get(rowIndex).size(); + } + final ActualKeyboardBuilder builder = new ActualKeyboardBuilder(dimensions); + + for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { + final int row = rowIndex + 1; + final ArrayList<Key> rowKeys = rows.get(rowIndex); + builder.setRowAt(row, rowKeys.toArray(new Key[rowKeys.size()])); + } + return builder.build(); + } + + private ActualKeyboardBuilder(final int ... dimensions) { + super(dimensions); + } + + @Override + Key defaultElement() { return null; } + + @Override + Key[] newArray(final int size) { return new Key[size]; } + + @Override + Key[][] newArrayOfArray(final int size) { return new Key[size][]; } + + // Helper class to create concise representation from the key specification. + static class MoreKeySpecStringizer extends StringUtils.Stringizer<MoreKeySpec> { + static final MoreKeySpecStringizer STRINGIZER = new MoreKeySpecStringizer(); + + @Override + public String stringize(final MoreKeySpec spec) { + return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode); + } + + static String toString(final String label, final int iconId, final String outputText, + final int code) { + final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED) + ? KeyboardIconsSet.getIconName(iconId) : label; + final String output; + if (code == Constants.CODE_OUTPUT_TEXT) { + output = outputText; + } else if (code < Constants.CODE_SPACE) { + output = Constants.printableCode(code); + } else { + output = StringUtils.newSingleCodePointString(code); + } + if (visual.equals(output)) { + return visual; + } + return visual + "|" + output; + } + } + + // Helper class to create concise representation from the key. + static class KeyStringizer extends StringUtils.Stringizer<Key> { + static final KeyStringizer STRINGIZER = new KeyStringizer(); + + @Override + public String stringize(final Key key) { + if (key == null) { + return "NULL"; + } + if (key.isSpacer()) { + return "SPACER"; + } + final StringBuilder sb = new StringBuilder(); + sb.append(MoreKeySpecStringizer.toString( + key.getLabel(), key.getIconId(), key.getOutputText(), key.getCode())); + final MoreKeySpec[] moreKeys = key.getMoreKeys(); + if (moreKeys == null) { + return sb.toString(); + } + sb.append("^"); + sb.append(MoreKeySpecStringizer.STRINGIZER.join(moreKeys)); + return sb.toString(); + } + } + + /** + * Convert the key to human readable string. + * @param key the key to be converted to string. + * @return the human readable representation of <code>key</code>. + */ + public static String toString(final Key key) { + return KeyStringizer.STRINGIZER.stringize(key); + } + + /** + * Convert the keyboard row to human readable string. + * @param keys the keyboard row to be converted to string. + * @return the human readable representation of <code>keys</code>. + */ + public static String toString(final Key[] keys) { + return KeyStringizer.STRINGIZER.join(keys); + } + + // Helper class to create concise representation from the array of the key. + static class KeyArrayStringizer extends StringUtils.Stringizer<Key[]> { + static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer(); + + @Override + public String stringize(final Key[] keyArray) { + return KeyStringizer.STRINGIZER.join(keyArray); + } + } + + /** + * Convert the keyboard to human readable string. + * @param rows the keyboard to be converted to string. + * @return the human readable representation of <code>rows</code>. + */ + public static String toString(final Key[][] rows) { + return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java new file mode 100644 index 000000000..e22d75cd7 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; + +import java.util.Arrays; +import java.util.Locale; + +/** + * This class represents an expected key. + */ +public class ExpectedKey { + static ExpectedKey EMPTY_KEY = newInstance(""); + + // A key that has a string label and may have "more keys". + static ExpectedKey newInstance(final String label, final ExpectedKey ... moreKeys) { + return newInstance(label, label, moreKeys); + } + + // A key that has a string label and a different output text and may have "more keys". + static ExpectedKey newInstance(final String label, final String outputText, + final ExpectedKey ... moreKeys) { + return newInstance(ExpectedKeyVisual.newInstance(label), + ExpectedKeyOutput.newInstance(outputText), moreKeys); + } + + // A key that has a string label and a code point output and may have "more keys". + static ExpectedKey newInstance(final String label, final int code, + final ExpectedKey ... moreKeys) { + return newInstance(ExpectedKeyVisual.newInstance(label), + ExpectedKeyOutput.newInstance(code), moreKeys); + } + + // A key that has an icon and a code point output and may have "more keys". + static ExpectedKey newInstance(final int iconId, final int code, + final ExpectedKey ... moreKeys) { + return newInstance(ExpectedKeyVisual.newInstance(iconId), + ExpectedKeyOutput.newInstance(code), moreKeys); + } + + static ExpectedKey newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, + final ExpectedKey ... moreKeys) { + if (moreKeys.length == 0) { + return new ExpectedKey(visual, output); + } + return new ExpectedKeyWithMoreKeys(visual, output, moreKeys); + } + + private static final ExpectedKey[] EMPTY_KEYS = new ExpectedKey[0]; + + // The expected visual outlook of this key. + private final ExpectedKeyVisual mVisual; + // The expected output of this key. + private final ExpectedKeyOutput mOutput; + + public final ExpectedKeyVisual getVisual() { + return mVisual; + } + + public final ExpectedKeyOutput getOutput() { + return mOutput; + } + + public ExpectedKey[] getMoreKeys() { + // This key has no "more keys". + return EMPTY_KEYS; + } + + protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { + mVisual = visual; + mOutput = output; + } + + public ExpectedKey toUpperCase(Locale locale) { + return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale)); + } + + public boolean equalsTo(final Key key) { + // This key has no "more keys". + return mVisual.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null; + } + + public boolean equalsTo(final MoreKeySpec moreKeySpec) { + return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec); + } + + @Override + public boolean equals(final Object object) { + if (object instanceof ExpectedKey) { + final ExpectedKey key = (ExpectedKey)object; + return mVisual.equalsTo(key.mVisual) && mOutput.equalsTo(key.mOutput) + && Arrays.equals(getMoreKeys(), key.getMoreKeys()); + } + return false; + } + + private static int hashCode(final Object ... objects) { + return Arrays.hashCode(objects); + } + + @Override + public int hashCode() { + return hashCode(mVisual, mOutput, getMoreKeys()); + } + + @Override + public String toString() { + if (mVisual.equalsTo(mOutput)) { + return mVisual.toString(); + } + return mVisual + "|" + mOutput; + } + + /** + * This class represents an expected key that has "more keys". + */ + private static final class ExpectedKeyWithMoreKeys extends ExpectedKey { + private final ExpectedKey[] mMoreKeys; + + ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, + final ExpectedKeyOutput output, final ExpectedKey ... moreKeys) { + super(visual, output); + mMoreKeys = moreKeys; + } + + @Override + public ExpectedKey toUpperCase(final Locale locale) { + final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length]; + for (int i = 0; i < mMoreKeys.length; i++) { + upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale); + } + return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), + upperCaseMoreKeys); + } + + @Override + public ExpectedKey[] getMoreKeys() { + return mMoreKeys; + } + + @Override + public boolean equalsTo(final Key key) { + if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) { + final MoreKeySpec[] moreKeys = key.getMoreKeys(); + // This key should have at least one "more key". + if (moreKeys == null || moreKeys.length != mMoreKeys.length) { + return false; + } + for (int index = 0; index < moreKeys.length; index++) { + if (!mMoreKeys[index].equalsTo(moreKeys[index])) { + return false; + } + } + return true; + } + return false; + } + + @Override + public boolean equalsTo(final MoreKeySpec moreKeySpec) { + // MoreKeySpec has no "more keys". + return false; + } + + @Override + public String toString() { + return super.toString() + "^" + Arrays.toString(mMoreKeys); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java new file mode 100644 index 000000000..1be51e60b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.StringUtils; + +import java.util.Locale; + +/** + * This class represents an expected output of a key. + * + * There are two types of expected output, an integer code point and a string output text. + */ +abstract class ExpectedKeyOutput { + static ExpectedKeyOutput newInstance(final int code) { + return new Code(code); + } + + static ExpectedKeyOutput newInstance(final String outputText) { + // If the <code>outputText</code> is one code point string, use {@link CodePoint} object. + if (StringUtils.codePointCount(outputText) == 1) { + return new Code(outputText.codePointAt(0)); + } + return new Text(outputText); + } + + abstract ExpectedKeyOutput toUpperCase(final Locale locale); + abstract boolean equalsTo(final String text); + abstract boolean equalsTo(final Key key); + abstract boolean equalsTo(final MoreKeySpec moreKeySpec); + abstract boolean equalsTo(final ExpectedKeyOutput output); + + /** + * This class represents an integer code point. + */ + private static class Code extends ExpectedKeyOutput { + // UNICODE code point or a special negative value defined in {@link Constants}. + private final int mCode; + + Code(final int code) { mCode = code; } + + @Override + ExpectedKeyOutput toUpperCase(final Locale locale) { + if (Constants.isLetterCode(mCode)) { + final String codeString = StringUtils.newSingleCodePointString(mCode); + // A letter may have an upper case counterpart that consists of multiple code + // points, for instance the upper case of "ß" is "SS". + return newInstance(codeString.toUpperCase(locale)); + } + // A special negative value has no upper case. + return this; + } + + @Override + boolean equalsTo(final String text) { + return StringUtils.codePointCount(text) == 1 && text.codePointAt(0) == mCode; + } + + @Override + boolean equalsTo(final Key key) { + return mCode == key.getCode(); + } + + @Override + boolean equalsTo(final MoreKeySpec moreKeySpec) { + return mCode == moreKeySpec.mCode; + } + + @Override + boolean equalsTo(final ExpectedKeyOutput output) { + return (output instanceof Code) && mCode == ((Code)output).mCode; + } + + @Override + public String toString() { + return Constants.isLetterCode(mCode) ? StringUtils.newSingleCodePointString(mCode) + : Constants.printableCode(mCode); + } + } + + /** + * This class represents a string output text. + */ + private static class Text extends ExpectedKeyOutput { + private final String mText; + + Text(final String text) { mText = text; } + + @Override + ExpectedKeyOutput toUpperCase(final Locale locale) { + return newInstance(mText.toUpperCase(locale)); + } + + @Override + boolean equalsTo(final String text) { + return text.equals(text); + } + + @Override + boolean equalsTo(final Key key) { + return key.getCode() == Constants.CODE_OUTPUT_TEXT + && mText.equals(key.getOutputText()); + } + + @Override + boolean equalsTo(final MoreKeySpec moreKeySpec) { + return moreKeySpec.mCode == Constants.CODE_OUTPUT_TEXT + && mText.equals(moreKeySpec.mOutputText); + } + + @Override + boolean equalsTo(final ExpectedKeyOutput output) { + return (output instanceof Text) && mText == ((Text)output).mText; + } + + @Override + public String toString() { + return mText; + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java new file mode 100644 index 000000000..0a0da32b6 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; + +import java.util.Locale; + +/** + * This class represents an expected visual outlook of a key. + * + * There are two types of expected visual, an integer icon id and a string label. + */ +abstract class ExpectedKeyVisual { + static ExpectedKeyVisual newInstance(final String label) { + return new Label(label); + } + + static ExpectedKeyVisual newInstance(final int iconId) { + return new Icon(iconId); + } + + abstract ExpectedKeyVisual toUpperCase(final Locale locale); + abstract boolean equalsTo(final String text); + abstract boolean equalsTo(final Key key); + abstract boolean equalsTo(final MoreKeySpec moreKeySpec); + abstract boolean equalsTo(final ExpectedKeyOutput output); + abstract boolean equalsTo(final ExpectedKeyVisual visual); + + /** + * This class represents an integer icon id. + */ + private static class Icon extends ExpectedKeyVisual { + private final int mIconId; + + Icon(final int iconId) { + mIconId = iconId; + } + + @Override + ExpectedKeyVisual toUpperCase(final Locale locale) { + return this; + } + + @Override + boolean equalsTo(final String text) { + return false; + } + + @Override + boolean equalsTo(final Key key) { + return mIconId == key.getIconId(); + } + + @Override + boolean equalsTo(final MoreKeySpec moreKeySpec) { + return mIconId == moreKeySpec.mIconId; + } + + @Override + boolean equalsTo(final ExpectedKeyOutput output) { + return false; + } + + @Override + boolean equalsTo(final ExpectedKeyVisual visual) { + return (visual instanceof Icon) && mIconId == ((Icon)visual).mIconId; + } + + @Override + public String toString() { + return KeyboardIconsSet.getIconName(mIconId); + } + } + + /** + * This class represents a string label. + */ + private static class Label extends ExpectedKeyVisual { + private final String mLabel; + + Label(final String label) { mLabel = label; } + + @Override + ExpectedKeyVisual toUpperCase(final Locale locale) { + return new Label(mLabel.toUpperCase(locale)); + } + + @Override + boolean equalsTo(final String text) { + return mLabel.equals(text); + } + + @Override + boolean equalsTo(final Key key) { + return mLabel.equals(key.getLabel()); + } + + @Override + boolean equalsTo(final MoreKeySpec moreKeySpec) { + return mLabel.equals(moreKeySpec.mLabel); + } + + @Override + boolean equalsTo(final ExpectedKeyOutput output) { + return output.equalsTo(mLabel); + } + + @Override + boolean equalsTo(final ExpectedKeyVisual visual) { + return (visual instanceof Label) && mLabel.equals(((Label)visual).mLabel); + } + + @Override + public String toString() { + return mLabel; + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java new file mode 100644 index 000000000..61288f048 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import java.util.Arrays; +import java.util.Locale; + +/** + * This class builds an expected keyboard for unit test. + */ +public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> { + public ExpectedKeyboardBuilder(final int ... dimensions) { + super(dimensions); + } + + public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) { + super(rows); + } + + @Override + protected ExpectedKey defaultElement() { + return ExpectedKey.EMPTY_KEY; + } + + @Override + ExpectedKey[] newArray(final int size) { + return new ExpectedKey[size]; + } + + @Override + ExpectedKey[][] newArrayOfArray(final int size) { + return new ExpectedKey[size][]; + } + + @Override + public ExpectedKey[][] build() { + return super.build(); + } + + // A replacement job to be performed. + interface ReplaceJob { + // Returns a {@link ExpectedKey} object to replace. + ExpectedKey replace(final ExpectedKey oldKey); + // Return true if replacing should be stopped at first occurrence. + boolean stopAtFirstOccurrence(); + } + + // Replace key(s) that has the specified visual. + private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) { + int replacedCount = 0; + final ExpectedKey[][] rows = build(); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final ExpectedKey[] keys = rows[rowIndex]; + for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) { + if (keys[columnIndex].getVisual().equalsTo(visual)) { + keys[columnIndex] = job.replace(keys[columnIndex]); + replacedCount++; + if (job.stopAtFirstOccurrence()) { + return; + } + } + } + } + if (replacedCount == 0) { + throw new RuntimeException( + "Can't find key that has visual: " + visual + " in\n" + toString(rows)); + } + } + + /** + * Set the row with specified keys that have specified labels. + * @param row the row number to set keys. + * @param labels the label texts of the keys. + * @return this builder. + */ + public ExpectedKeyboardBuilder setLabelsOfRow(final int row, final String ... labels) { + final ExpectedKey[] keys = new ExpectedKey[labels.length]; + for (int columnIndex = 0; columnIndex < labels.length; columnIndex++) { + keys[columnIndex] = ExpectedKey.newInstance(labels[columnIndex]); + } + setRowAt(row, keys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified label. + * @param label the label of the key to set the "more keys". + * @param moreKeys the array of labels of the "more keys" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final String ... moreKeys) { + final ExpectedKey[] expectedMoreKeys = new ExpectedKey[moreKeys.length]; + for (int index = 0; index < moreKeys.length; index++) { + expectedMoreKeys[index] = ExpectedKey.newInstance(moreKeys[index]); + } + setMoreKeysOf(label, expectedMoreKeys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified label. + * @param label the label of the key to set the "more keys". + * @param moreKeys the array of "more key" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final String label, + final ExpectedKey ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(label), moreKeys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified icon. + * @param iconId the icon id of the key to set the "more keys". + * @param moreKeys the array of "more key" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final ExpectedKey ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), moreKeys); + return this; + } + + private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) { + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys); + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + } + + /** + * Insert the keys at specified position. + * @param row the row number to insert the <code>keys</code>. + * @param column the column number to insert the <code>keys</code>. + * @param keys the array of keys to insert at <code>row,column</code>. + * @return this builder. + * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal. + */ + public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column, + final ExpectedKey ... keys) { + for (int index = 0; index < keys.length; index++) { + setElementAt(row, column + index, keys[index], true /* insert */); + } + return this; + } + + /** + * Add the keys on the left most of the row. + * @param row the row number to add the <code>keys</code>. + * @param keys the array of keys to add on the left most of the row. + * @return this builder. + * @throws {@link RuntimeException} if <code>row</code> is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row, + final ExpectedKey ... keys) { + // Keys should be inserted from the last to preserve the order. + for (int index = keys.length - 1; index >= 0; index--) { + setElementAt(row, 1, keys[index], true /* insert */); + } + return this; + } + + /** + * Add the keys on the right most of the row. + * @param row the row number to add the <code>keys</code>. + * @param keys the array of keys to add on the right most of the row. + * @return this builder. + * @throws {@link RuntimeException} if <code>row</code> is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row, + final ExpectedKey ... keys) { + final int rightEnd = getRowAt(row).length + 1; + insertKeysAtRow(row, rightEnd, keys); + return this; + } + + /** + * Replace the most top-left key that has the specified label with the new key. + * @param label the label of the key to set <code>newKey</code>. + * @param newKey the key to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) { + final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label); + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return newKey; + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + return this; + } + + /** + * Replace the all specified keys with the new key. + * @param key the key to be replaced by <code>newKey</code>. + * @param newKey the key to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key, + final ExpectedKey newKey) { + replaceKeyOf(key.getVisual(), new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return newKey; + } + @Override + public boolean stopAtFirstOccurrence() { + return false; + } + }); + return this; + } + + /** + * Returns new keyboard instance that has upper case keys of the specified keyboard. + * @param rows the lower case keyboard. + * @param locale the locale used to convert cases. + * @return the upper case keyboard. + */ + public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) { + final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][]; + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final ExpectedKey[] lowerCaseKeys = rows[rowIndex]; + final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length]; + for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) { + upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale); + } + upperCaseRows[rowIndex] = upperCaseKeys; + } + return upperCaseRows; + } + + /** + * Convert the keyboard to human readable string. + * @param rows the keyboard to be converted to string. + * @return the human readable representation of <code>rows</code>. + */ + public static String toString(final ExpectedKey[][] rows) { + final StringBuilder sb = new StringBuilder(); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + if (rowIndex > 0) { + sb.append("\n"); + } + sb.append(Arrays.toString(rows[rowIndex])); + } + return sb.toString(); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java new file mode 100644 index 000000000..1aeb8c0cd --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.latin.Constants; + +/** + * Base class to create an expected keyboard for unit test. + */ +public class LayoutBase { + // Those helper methods have a lower case name to be readable when defining expected keyboard + // layouts. + + // Helper method to create {@link ExpectedKey} object that has the label. + public static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(label, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object that has the label and the output text. + public static ExpectedKey key(final String label, final String outputText, + final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(label, outputText, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object that has the label and the output code. + public static ExpectedKey key(final String label, final int code, + final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(label, code, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object that has the icon and the output code. + public static ExpectedKey key(final int iconId, final int code, + final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(iconId, code, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object that has new "more keys". + public static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(key.getVisual(), key.getOutput(), moreKeys); + } + + // Helper method to create {@link ExpectedKey} object for "more key" that has the label. + public static ExpectedKey moreKey(final String label) { + return ExpectedKey.newInstance(label); + } + + // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the + // output text. + public static ExpectedKey moreKey(final String label, final String outputText) { + return ExpectedKey.newInstance(label, outputText); + } + + // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the + // output code. + public static ExpectedKey moreKey(final String label, final int code) { + return ExpectedKey.newInstance(label, code); + } + + // Icon ids. + private static final int ICON_SHIFT = KeyboardIconsSet.getIconId("shift_key"); + private static final int ICON_DELETE = KeyboardIconsSet.getIconId("delete_key"); + private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId("settings_key"); + private static final int ICON_ENTER = KeyboardIconsSet.getIconId("enter_key"); + private static final int ICON_EMOJI = KeyboardIconsSet.getIconId("emoji_key"); + + // Functional keys. + public static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK); + public static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT, Constants.CODE_SHIFT); + public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE); + public static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL); + public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS); + public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER); + public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI); + + // Punctuation more keys for phone form factor. + public static final String[] PHONE_PUNCTUATION_MORE_KEYS = { + ";", "/", "(", ")", "#", "!", ",", "?", + "&", "%", "+", "\"", "-", ":", "'", "@" + }; + + // Punctuation more keys for tablet form factor. + public static final String[] TABLET_PUNCTUATION_MORE_KEYS = { + ";", "/", "(", ")", "#", "'", ",", + "&", "%", "+", "\"", "-", ":", "@" + }; + + private static ExpectedKeyboardBuilder toPhoneAlphabet(final ExpectedKeyboardBuilder builder) { + return builder + .addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY)) + .addKeysOnTheRightOfRow(3, DELETE_KEY) + .setLabelsOfRow(4, ",", " ", ".") + .setMoreKeysOf(",", SETTINGS_KEY) + .setMoreKeysOf(".", PHONE_PUNCTUATION_MORE_KEYS) + .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)); + } + + // Helper method to create alphabet layout for tablet by adding special function keys except + // shift key. + public static ExpectedKeyboardBuilder toTabletAlphabetWithoutShiftKeys( + final ExpectedKeyboardBuilder builder) { + return builder + // U+00BF: "¿" INVERTED QUESTION MARK + // U+00A1: "¡" INVERTED EXCLAMATION MARK + .addKeysOnTheRightOfRow(3, + key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF"))) + .addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .setLabelsOfRow(4, "/", " ", ",", ".") + .setMoreKeysOf(".", TABLET_PUNCTUATION_MORE_KEYS) + .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY, SETTINGS_KEY) + .addKeysOnTheRightOfRow(4, EMOJI_KEY); + } + + // Helper method to create alphabet layout by adding special function keys. + public static ExpectedKey[][] toCommonAlphabet(final ExpectedKey[][] common, + final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(common); + if (isPhone) { + toPhoneAlphabet(builder); + } else { + toTabletAlphabetWithoutShiftKeys(builder); + builder.addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY)) + .addKeysOnTheRightOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY)); + } + return builder.build(); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java new file mode 100644 index 000000000..427e7de49 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.tests; + +import android.util.Log; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; +import com.android.inputmethod.keyboard.KeyboardLayoutSetTestsBase; +import com.android.inputmethod.keyboard.layout.AlphabetShifted; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ActualKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.LayoutBase; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.Arrays; +import java.util.Locale; + +/** + * Base class for keyboard layout unit test. + */ +abstract class LayoutTestsBase extends KeyboardLayoutSetTestsBase { + private InputMethodSubtype mSubtype; + private String mLogTag; + private KeyboardLayoutSet mKeyboardLayoutSet; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mSubtype = getSubtype(getTestLocale(), getTestKeyboardLayout()); + mLogTag = SubtypeLocaleUtils.getSubtypeNameForLogging(mSubtype) + "/" + + (isPhone() ? "phone" : "tablet"); + mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */); + } + + // Those helper methods have a lower case name to be readable when defining expected keyboard + // layouts. + + // Helper method to create {@link ExpectedKey} object that has the label. + static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) { + return LayoutBase.key(label, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object that has the label and the output text. + static ExpectedKey key(final String label, final String outputText, + final ExpectedKey ... moreKeys) { + return LayoutBase.key(label, outputText, moreKeys); + } + + // Helper method to create {@link ExpectedKey} object for "more key" that has the label. + static ExpectedKey moreKey(final String label) { + return LayoutBase.moreKey(label); + } + + // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the + // output text. + static ExpectedKey moreKey(final String label, final String outputText) { + return LayoutBase.moreKey(label, outputText); + } + + // Locale for testing subtype. + abstract Locale getTestLocale(); + + // Keyboard layout name for testing subtype. + abstract String getTestKeyboardLayout(); + + // Alphabet keyboard for testing subtype. + abstract ExpectedKey[][] getAlphabet(final boolean isPhone); + + // Alphabet automatic shifted keyboard for testing subtype. + ExpectedKey[][] getAlphabetAutomaticShifted(final boolean isPhone) { + return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale()); + } + + // Alphabet manual shifted keyboard for testing subtype. + ExpectedKey[][] getAlphabetManualShifted(final boolean isPhone) { + return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale()); + } + + // Alphabet shift locked keyboard for testing subtype. + ExpectedKey[][] getAlphabetShiftLocked(final boolean isPhone) { + return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale()); + } + + // Alphabet shift lock shifted keyboard for testing subtype. + ExpectedKey[][] getAlphabetShiftLockShifted(final boolean isPhone) { + return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale()); + } + + // Symbols keyboard for testing subtype. + ExpectedKey[][] getSymbols(final boolean isPhone) { + return Symbols.getSymbols(isPhone); + } + + // Symbols shifted keyboard for testing subtype. + ExpectedKey[][] getSymbolsShifted(final boolean isPhone) { + return SymbolsShifted.getSymbolsShifted(isPhone); + } + + // TODO: Add phone, phone symbols, number, number password layout tests. + + public final void testAlphabet() { + final int elementId = KeyboardId.ELEMENT_ALPHABET; + doKeyboardTests(elementId, getAlphabet(isPhone())); + } + + public final void testAlphabetAutomaticShifted() { + final int elementId = KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED; + doKeyboardTests(elementId, getAlphabetAutomaticShifted(isPhone())); + } + + public final void testAlphabetManualShifted() { + final int elementId = KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED; + doKeyboardTests(elementId, getAlphabetManualShifted(isPhone())); + } + + public final void testAlphabetShiftLocked() { + final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED; + doKeyboardTests(elementId, getAlphabetShiftLocked(isPhone())); + } + + public final void testAlphabetShiftLockShifted() { + final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED; + doKeyboardTests(elementId, getAlphabetShiftLockShifted(isPhone())); + } + + public final void testSymbols() { + final int elementId = KeyboardId.ELEMENT_SYMBOLS; + doKeyboardTests(elementId, getSymbols(isPhone())); + } + + public final void testSymbolsShifted() { + final int elementId = KeyboardId.ELEMENT_SYMBOLS_SHIFTED; + doKeyboardTests(elementId, getSymbolsShifted(isPhone())); + } + + // Comparing expected keyboard and actual keyboard. + private void doKeyboardTests(final int elementId, final ExpectedKey[][] expectedKeyboard) { + // Skip test if no keyboard is defined. + if (expectedKeyboard == null) { + return; + } + final String tag = mLogTag + "/" + KeyboardId.elementIdToName(elementId); + // Create actual keyboard object. + final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(elementId); + // Create actual keyboard to be compared with the expected keyboard. + final Key[][] actualKeyboard = ActualKeyboardBuilder.buildKeyboard(keyboard.getKeys()); + + // Dump human readable definition of expected/actual keyboards. + Log.d(tag, "expected=\n" + ExpectedKeyboardBuilder.toString(expectedKeyboard)); + Log.d(tag, "actual =\n" + ActualKeyboardBuilder.toString(actualKeyboard)); + // Test both keyboards have the same number of rows. + assertEquals(tag + " labels" + + "\nexpected=" + Arrays.deepToString(expectedKeyboard) + + "\nactual =" + ActualKeyboardBuilder.toString(actualKeyboard), + expectedKeyboard.length, actualKeyboard.length); + for (int r = 0; r < actualKeyboard.length; r++) { + final int row = r + 1; + // Test both keyboards' rows have the same number of columns. + assertEquals(tag + " labels row=" + row + + "\nexpected=" + Arrays.toString(expectedKeyboard[r]) + + "\nactual =" + ActualKeyboardBuilder.toString(actualKeyboard[r]), + expectedKeyboard[r].length, actualKeyboard[r].length); + for (int c = 0; c < actualKeyboard[r].length; c++) { + final int column = c + 1; + final Key actualKey = actualKeyboard[r][c]; + final ExpectedKey expectedKey = expectedKeyboard[r][c]; + // Test both keyboards' keys have the same visual outlook and key output. + assertTrue(tag + " labels row,column=" + row + "," + column + + "\nexpected=" + expectedKey + + "\nactual =" + ActualKeyboardBuilder.toString(actualKey), + expectedKey.equalsTo(actualKey)); + } + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java new file mode 100644 index 000000000..0792a5789 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.tests; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * en_US: English (United States)/qwerty + */ +@SmallTest +public final class TestsEnglishUS extends LayoutTestsBase { + @Override + Locale getTestLocale() { + return new Locale("en", "US"); + } + + @Override + String getTestKeyboardLayout() { + return "qwerty"; + } + + @Override + ExpectedKey[][] getAlphabet(final boolean isPhone) { + final ExpectedKey[][] keyboard = Qwerty.getAlphabet(isPhone); + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(keyboard); + setAccentedLetters(builder); + return builder.build(); + } + + static ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", "3", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113") + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "7", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B") + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + .setMoreKeysOf("i", "8", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC") + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + .setMoreKeysOf("o", + "9", "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8", "\u014D", + "\u00F5") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", + "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5", + "\u0101") + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + .setMoreKeysOf("s", "\u00DF") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + .setMoreKeysOf("c", "\u00E7") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + .setMoreKeysOf("n", "\u00F1"); + } +} |