diff options
Diffstat (limited to 'tests/src')
206 files changed, 24745 insertions, 3166 deletions
diff --git a/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java b/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java new file mode 100644 index 000000000..319302c71 --- /dev/null +++ b/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java @@ -0,0 +1,212 @@ +/* + * 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.compat; + +import android.graphics.Typeface; +import android.os.Build; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.StyleSpan; + +import java.util.Locale; + +@SmallTest +public class LocaleSpanCompatUtilsTests extends AndroidTestCase { + public void testInstantiatable() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + // LocaleSpan isn't yet available. + return; + } + assertTrue(LocaleSpanCompatUtils.isLocaleSpanAvailable()); + final Object japaneseLocaleSpan = LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE); + assertNotNull(japaneseLocaleSpan); + assertEquals(Locale.JAPANESE, + LocaleSpanCompatUtils.getLocaleFromLocaleSpan(japaneseLocaleSpan)); + } + + private static void assertLocaleSpan(final Spanned spanned, final int index, + final int expectedStart, final int expectedEnd, + final Locale expectedLocale, final int expectedSpanFlags) { + final Object span = spanned.getSpans(0, spanned.length(), Object.class)[index]; + assertEquals(expectedLocale, LocaleSpanCompatUtils.getLocaleFromLocaleSpan(span)); + assertEquals(expectedStart, spanned.getSpanStart(span)); + assertEquals(expectedEnd, spanned.getSpanEnd(span)); + assertEquals(expectedSpanFlags, spanned.getSpanFlags(span)); + } + + private static void assertSpanEquals(final Object expectedSpan, final Spanned spanned, + final int index) { + final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); + assertEquals(expectedSpan, spans[index]); + } + + private static void assertSpanCount(final int expectedCount, final Spanned spanned) { + final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class); + assertEquals(expectedCount, spans.length); + } + + public void testUpdateLocaleSpan() { + if (!LocaleSpanCompatUtils.isLocaleSpanAvailable()) { + return; + } + + // Test if the simplest case works. + { + final SpannableString text = new SpannableString("0123456789"); + LocaleSpanCompatUtils.updateLocaleSpan(text, 1, 5, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 5, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if only LocaleSpans are updated. + { + final SpannableString text = new SpannableString("0123456789"); + final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD); + text.setSpan(styleSpan, 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 1, 5, Locale.JAPANESE); + assertSpanCount(2, text); + assertSpanEquals(styleSpan, text, 0); + assertLocaleSpan(text, 1, 1, 5, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if two jointed spans are merged into one span. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 3, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 3, 5, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 5, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if two overlapped spans are merged into one span. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 4, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 3, 5, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 5, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if three overlapped spans are merged into one span. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 4, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 5, 6, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 2, 8, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 8, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if disjoint spans remain disjoint. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 3, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 5, 6, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 8, 9, Locale.JAPANESE); + assertSpanCount(3, text); + assertLocaleSpan(text, 0, 1, 3, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 1, 5, 6, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 2, 8, 9, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if existing span flags are preserved during merge. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 5, + Spannable.SPAN_INCLUSIVE_INCLUSIVE | Spannable.SPAN_INTERMEDIATE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 3, 4, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 5, Locale.JAPANESE, + Spannable.SPAN_INCLUSIVE_INCLUSIVE | Spannable.SPAN_INTERMEDIATE); + } + + // Test if existing span flags are preserved even when partially overlapped (leading edge). + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 1, 5, + Spannable.SPAN_INCLUSIVE_INCLUSIVE | Spannable.SPAN_INTERMEDIATE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 3, 7, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 7, Locale.JAPANESE, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE | Spannable.SPAN_INTERMEDIATE); + } + + // Test if existing span flags are preserved even when partially overlapped (trailing edge). + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE), 3, 7, + Spannable.SPAN_INCLUSIVE_INCLUSIVE | Spannable.SPAN_INTERMEDIATE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 1, 5, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 7, Locale.JAPANESE, + Spannable.SPAN_EXCLUSIVE_INCLUSIVE | Spannable.SPAN_INTERMEDIATE); + } + + // Test if existing locale span will be removed when the locale doesn't match. + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.ENGLISH), 3, 5, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 1, 7, Locale.JAPANESE); + assertSpanCount(1, text); + assertLocaleSpan(text, 0, 1, 7, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if existing locale span will be removed when the locale doesn't match. (case 2) + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.ENGLISH), 3, 7, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 5, 6, Locale.JAPANESE); + assertSpanCount(3, text); + assertLocaleSpan(text, 0, 3, 5, Locale.ENGLISH, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 1, 6, 7, Locale.ENGLISH, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 2, 5, 6, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if existing locale span will be removed when the locale doesn't match. (case 3) + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.ENGLISH), 3, 7, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 2, 5, Locale.JAPANESE); + assertSpanCount(2, text); + assertLocaleSpan(text, 0, 5, 7, Locale.ENGLISH, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 1, 2, 5, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + // Test if existing locale span will be removed when the locale doesn't match. (case 3) + { + final SpannableString text = new SpannableString("0123456789"); + text.setSpan(LocaleSpanCompatUtils.newLocaleSpan(Locale.ENGLISH), 3, 7, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + LocaleSpanCompatUtils.updateLocaleSpan(text, 5, 8, Locale.JAPANESE); + assertSpanCount(2, text); + assertLocaleSpan(text, 0, 3, 5, Locale.ENGLISH, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + assertLocaleSpan(text, 1, 5, 8, Locale.JAPANESE, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java new file mode 100644 index 000000000..96f925554 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java @@ -0,0 +1,191 @@ +/* + * 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 class KeyboardLayoutSetActionLabelKlpTests extends KeyboardLayoutSetTestsBase { + @Override + protected int getKeyboardThemeForTests() { + return KeyboardTheme.THEME_ID_KLP; + } + + private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet, + final int elementId, final CharSequence 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()); + } + + protected 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)); + } + doTestActionLabel(tag, subtype, editorInfo, label); + } + + protected void doTestActionLabel(final String tag, final InputMethodSubtype subtype, + final EditorInfo editorInfo, final CharSequence label) { + // 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); + } + + protected 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, + KeyboardIconsSet.NAME_ENTER_KEY); + } + } + + public void testActionNone() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE, + KeyboardIconsSet.NAME_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, + KeyboardIconsSet.NAME_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); + } + } + + public void testActionCustom() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "custom " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + final CharSequence customLabel = "customLabel"; + final EditorInfo editorInfo = new EditorInfo(); + editorInfo.imeOptions = EditorInfo.IME_ACTION_UNSPECIFIED; + editorInfo.actionLabel = customLabel; + doTestActionLabel(tag, subtype, editorInfo, customLabel); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java new file mode 100644 index 000000000..7747ac5f9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java @@ -0,0 +1,97 @@ +/* + * 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.MediumTest; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +@MediumTest +public class KeyboardLayoutSetActionLabelLxxTests extends KeyboardLayoutSetActionLabelKlpTests { + @Override + protected int getKeyboardThemeForTests() { + return KeyboardTheme.THEME_ID_LXX_DARK; + } + + @Override + public void testActionUnspecified() { + super.testActionUnspecified(); + } + + @Override + public void testActionNone() { + super.testActionNone(); + } + + @Override + public void testActionGo() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_GO, + KeyboardIconsSet.NAME_GO_KEY); + } + } + + @Override + public void testActionSearch() { + super.testActionSearch(); + } + + @Override + public void testActionSend() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEND, + KeyboardIconsSet.NAME_SEND_KEY); + } + } + + @Override + public void testActionNext() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NEXT, + KeyboardIconsSet.NAME_NEXT_KEY); + } + } + + @Override + public void testActionDone() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_DONE, + KeyboardIconsSet.NAME_DONE_KEY); + } + } + + @Override + public void testActionPrevious() { + for (final InputMethodSubtype subtype : getAllSubtypesList()) { + final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype); + doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, + KeyboardIconsSet.NAME_PREVIOUS_KEY); + } + } + + @Override + public void testActionCustom() { + super.testActionCustom(); + } +} 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..6ee6adbba --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java @@ -0,0 +1,65 @@ +/* + * 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 = 73; + private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 45; + private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2; + + @Override + protected int getKeyboardThemeForTests() { + return KeyboardTheme.THEME_ID_KLP; + } + + 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 new file mode 100644 index 000000000..cf884bfea --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.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; + +import android.content.Context; +import android.content.res.Resources; +import android.test.AndroidTestCase; +import android.view.ContextThemeWrapper; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; +import com.android.inputmethod.keyboard.KeyboardLayoutSet.Builder; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.ArrayList; +import java.util.Locale; + +public abstract class KeyboardLayoutSetTestsBase extends AndroidTestCase { + // All input method subtypes of LatinIME. + private final ArrayList<InputMethodSubtype> mAllSubtypesList = new ArrayList<>(); + private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList = new ArrayList<>(); + private final ArrayList<InputMethodSubtype> mAdditionalSubtypesList = new ArrayList<>(); + + private Context mThemeContext; + private int mScreenMetrics; + + protected abstract int getKeyboardThemeForTests(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics); + + final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById( + getKeyboardThemeForTests()); + mThemeContext = new ContextThemeWrapper(mContext, keyboardTheme.mStyleId); + RichInputMethodManager.init(mThemeContext); + final RichInputMethodManager richImm = RichInputMethodManager.getInstance(); + + final InputMethodInfo imi = richImm.getInputMethodInfoOfThisIme(); + 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); + } + } + } + + protected final ArrayList<InputMethodSubtype> getAllSubtypesList() { + return mAllSubtypesList; + } + + protected final ArrayList<InputMethodSubtype> getAsciiCapableSubtypesList() { + return mAsciiCapableSubtypesList; + } + + protected final ArrayList<InputMethodSubtype> getAdditionalSubtypesList() { + return mAdditionalSubtypesList; + } + + protected final boolean isPhone() { + return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE + || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE; + } + + protected final InputMethodSubtype getSubtype(final Locale locale, + final String keyboardLayout) { + for (final InputMethodSubtype subtype : mAllSubtypesList) { + final Locale subtypeLocale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + final String subtypeLayout = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); + if (locale.equals(subtypeLocale) && keyboardLayout.equals(subtypeLayout)) { + // Found subtype that matches locale and keyboard layout. + return subtype; + } + } + for (final InputMethodSubtype subtype : mAsciiCapableSubtypesList) { + final Locale subtypeLocale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + if (locale.equals(subtypeLocale)) { + // Create additional subtype. + return AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + locale.toString(), keyboardLayout); + } + } + throw new RuntimeException( + "Unknown subtype: locale=" + locale + " keyboardLayout=" + keyboardLayout); + } + + protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype, + final EditorInfo editorInfo) { + return createKeyboardLayoutSet(subtype, editorInfo, false /* voiceInputKeyEnabled */, + false /* languageSwitchKeyEnabled */); + } + + protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype, + final EditorInfo editorInfo, final boolean voiceInputKeyEnabled, + final boolean languageSwitchKeyEnabled) { + final Context context = mThemeContext; + final Resources res = context.getResources(); + final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); + final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); + final Builder builder = new Builder(context, editorInfo); + builder.setKeyboardGeometry(keyboardWidth, keyboardHeight) + .setSubtype(subtype) + .setVoiceInputKeyEnabled(voiceInputKeyEnabled) + .setLanguageSwitchKeyEnabled(languageSwitchKeyEnabled); + return builder.build(); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java new file mode 100644 index 000000000..f9d98afa2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java @@ -0,0 +1,337 @@ +/* + * 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.SharedPreferences; +import android.os.Build.VERSION_CODES; +import android.preference.PreferenceManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +@SmallTest +public class KeyboardThemeTests extends AndroidTestCase { + private SharedPreferences mPrefs; + + // TODO: Remove this constant once the *next* version becomes available. + private static final int VERSION_CODES_LXX = VERSION_CODES.CUR_DEVELOPMENT; + + private static final int THEME_ID_NULL = -1; + private static final int THEME_ID_UNKNOWN = -2; + private static final int THEME_ID_ILLEGAL = -3; + private static final String ILLEGAL_THEME_ID_STRING = "ThisCausesNumberFormatExecption"; + private static final int THEME_ID_ICS = KeyboardTheme.THEME_ID_ICS; + private static final int THEME_ID_KLP = KeyboardTheme.THEME_ID_KLP; + private static final int THEME_ID_LXX_DARK = KeyboardTheme.THEME_ID_LXX_DARK; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + } + + /* + * Helper functions. + */ + + private static boolean isValidKeyboardThemeId(final int themeId) { + switch (themeId) { + case THEME_ID_ICS: + case THEME_ID_KLP: + case THEME_ID_LXX_DARK: + return true; + default: + return false; + } + } + + private void setKeyboardThemePreference(final String prefKey, final int themeId) { + final String themeIdString = Integer.toString(themeId); + if (isValidKeyboardThemeId(themeId) || themeId == THEME_ID_UNKNOWN) { + // Set valid theme id to preference. + mPrefs.edit().putString(prefKey, themeIdString).apply(); + return; + } + if (themeId == THEME_ID_NULL) { + // Simulate undefined preference. + mPrefs.edit().remove(prefKey).apply(); + return; + } + // themeId == THEME_ID_ILLEGAL + // Simulate illegal format theme id in preference. + mPrefs.edit().putString(prefKey, ILLEGAL_THEME_ID_STRING).apply(); + } + + private void assertKeyboardTheme(final int sdkVersion, final int expectedThemeId) { + assertEquals(expectedThemeId, KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion).mThemeId); + } + + /* + * Test keyboard theme preference on the same platform version and the same keyboard version. + */ + + private void assertKeyboardThemePreference(final int sdkVersion, final int previousThemeId, + final int expectedThemeId) { + // Clear preferences before testing. + setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL); + setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL); + // Set the preference of the sdkVersion to previousThemeId. + final String prefKey = KeyboardTheme.getPreferenceKey(sdkVersion); + setKeyboardThemePreference(prefKey, previousThemeId); + assertKeyboardTheme(sdkVersion, expectedThemeId); + } + + private void assertKeyboardThemePreferenceOnKlp(final int sdkVersion) { + final int defaultThemeId = THEME_ID_KLP; + assertKeyboardThemePreference(sdkVersion, THEME_ID_NULL, defaultThemeId); + assertKeyboardThemePreference(sdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertKeyboardThemePreference(sdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertKeyboardThemePreference(sdkVersion, THEME_ID_LXX_DARK, THEME_ID_LXX_DARK); + assertKeyboardThemePreference(sdkVersion, THEME_ID_UNKNOWN, defaultThemeId); + assertKeyboardThemePreference(sdkVersion, THEME_ID_ILLEGAL, defaultThemeId); + } + + public void testKeyboardThemePreferenceOnKlp() { + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH); + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN); + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN_MR1); + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN_MR2); + assertKeyboardThemePreferenceOnKlp(VERSION_CODES.KITKAT); + } + + private void assertKeyboardThemePreferenceOnLxx(final int sdkVersion) { + final int defaultThemeId = THEME_ID_LXX_DARK; + assertKeyboardThemePreference(sdkVersion, THEME_ID_NULL, defaultThemeId); + assertKeyboardThemePreference(sdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertKeyboardThemePreference(sdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertKeyboardThemePreference(sdkVersion, THEME_ID_LXX_DARK, THEME_ID_LXX_DARK); + assertKeyboardThemePreference(sdkVersion, THEME_ID_UNKNOWN, defaultThemeId); + assertKeyboardThemePreference(sdkVersion, THEME_ID_ILLEGAL, defaultThemeId); + } + + public void testKeyboardThemePreferenceOnLxx() { + assertKeyboardThemePreferenceOnLxx(VERSION_CODES_LXX); + } + + /* + * Test default keyboard theme based on the platform version. + */ + + private void assertDefaultKeyboardTheme(final int sdkVersion, final int previousThemeId, + final int expectedThemeId) { + final String oldPrefKey = KeyboardTheme.KLP_KEYBOARD_THEME_KEY; + setKeyboardThemePreference(oldPrefKey, previousThemeId); + + final KeyboardTheme defaultTheme = + KeyboardTheme.getDefaultKeyboardTheme(mPrefs, sdkVersion); + + assertNotNull(defaultTheme); + assertEquals(expectedThemeId, defaultTheme.mThemeId); + if (sdkVersion <= VERSION_CODES.KITKAT) { + // Old preference must be retained if it is valid. Otherwise it must be pruned. + assertEquals(isValidKeyboardThemeId(previousThemeId), mPrefs.contains(oldPrefKey)); + return; + } + // Old preference must be removed. + assertFalse(mPrefs.contains(oldPrefKey)); + } + + private void assertDefaultKeyboardThemeOnKlp(final int sdkVersion) { + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_KLP); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP); + } + + public void testDefaultKeyboardThemeOnKlp() { + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH); + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN); + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN_MR1); + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN_MR2); + assertDefaultKeyboardThemeOnKlp(VERSION_CODES.KITKAT); + } + + private void assertDefaultKeyboardThemeOnLxx(final int sdkVersion) { + // Forced to switch to LXX theme. + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_LXX_DARK); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_LXX_DARK); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_LXX_DARK); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX_DARK); + assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX_DARK); + } + + public void testDefaultKeyboardThemeOnLxx() { + assertDefaultKeyboardThemeOnLxx(VERSION_CODES_LXX); + } + + /* + * Test keyboard theme preference while upgrading the keyboard that doesn't support LXX theme + * to the keyboard that supports LXX theme. + */ + + private void assertUpgradeKeyboardToLxxOn(final int sdkVersion, final int previousThemeId, + final int expectedThemeId) { + setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, previousThemeId); + // Clean up new keyboard theme preference to simulate "upgrade to LXX keyboard". + setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL); + + final KeyboardTheme theme = KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion); + + assertNotNull(theme); + assertEquals(expectedThemeId, theme.mThemeId); + if (sdkVersion <= VERSION_CODES.KITKAT) { + // New preference must not exist. + assertFalse(mPrefs.contains(KeyboardTheme.LXX_KEYBOARD_THEME_KEY)); + // Old preference must be retained if it is valid. Otherwise it must be pruned. + assertEquals(isValidKeyboardThemeId(previousThemeId), + mPrefs.contains(KeyboardTheme.KLP_KEYBOARD_THEME_KEY)); + if (isValidKeyboardThemeId(previousThemeId)) { + // Old preference must have an expected value. + assertEquals(mPrefs.getString(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, null), + Integer.toString(expectedThemeId)); + } + return; + } + // Old preference must be removed. + assertFalse(mPrefs.contains(KeyboardTheme.KLP_KEYBOARD_THEME_KEY)); + // New preference must not exist. + assertFalse(mPrefs.contains(KeyboardTheme.LXX_KEYBOARD_THEME_KEY)); + } + + private void assertUpgradeKeyboardToLxxOnKlp(final int sdkVersion) { + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_NULL, THEME_ID_KLP); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP); + } + + // Upgrading keyboard on I,J and K. + public void testUpgradeKeyboardToLxxOnKlp() { + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH); + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN); + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN_MR1); + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN_MR2); + assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.KITKAT); + } + + private void assertUpgradeKeyboardToLxxOnLxx(final int sdkVersion) { + // Forced to switch to LXX theme. + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_NULL, THEME_ID_LXX_DARK); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ICS, THEME_ID_LXX_DARK); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_KLP, THEME_ID_LXX_DARK); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX_DARK); + assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX_DARK); + } + + // Upgrading keyboard on L. + public void testUpgradeKeyboardToLxxOnLxx() { + assertUpgradeKeyboardToLxxOnLxx(VERSION_CODES_LXX); + } + + /* + * Test keyboard theme preference while upgrading platform version. + */ + + private void assertUpgradePlatformFromTo(final int oldSdkVersion, final int newSdkVersion, + final int previousThemeId, final int expectedThemeId) { + if (newSdkVersion < oldSdkVersion) { + // No need to test. + return; + } + // Clean up preferences. + setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL); + setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL); + + final String oldPrefKey = KeyboardTheme.getPreferenceKey(oldSdkVersion); + setKeyboardThemePreference(oldPrefKey, previousThemeId); + + assertKeyboardTheme(newSdkVersion, expectedThemeId); + } + + private void assertUpgradePlatformFromKlpToKlp(final int oldSdkVersion, + final int newSdkVersion) { + assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_KLP); + assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP); + assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP); + } + + private void assertUpgradePlatformToKlpFrom(final int oldSdkVersion) { + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.ICE_CREAM_SANDWICH); + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN); + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN_MR1); + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN_MR2); + assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.KITKAT); + } + + // Update platform from I,J, and K to I,J, and K + public void testUpgradePlatformToKlpFromKlp() { + assertUpgradePlatformToKlpFrom(VERSION_CODES.ICE_CREAM_SANDWICH); + assertUpgradePlatformToKlpFrom(VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN); + assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN_MR1); + assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN_MR2); + assertUpgradePlatformToKlpFrom(VERSION_CODES.KITKAT); + } + + private void assertUpgradePlatformToLxxFrom(final int oldSdkVersion) { + // Forced to switch to LXX theme. + final int newSdkVersion = VERSION_CODES_LXX; + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX_DARK); + } + + // Update platform from I,J, and K to L + public void testUpgradePlatformToLxx() { + assertUpgradePlatformToLxxFrom(VERSION_CODES.ICE_CREAM_SANDWICH); + assertUpgradePlatformToLxxFrom(VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN); + assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN_MR1); + assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN_MR2); + assertUpgradePlatformToLxxFrom(VERSION_CODES.KITKAT); + } + + // Update platform from L to L. + public void testUpgradePlatformToLxxFromLxx() { + final int oldSdkVersion = VERSION_CODES_LXX; + final int newSdkVersion = VERSION_CODES_LXX; + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_ICS); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_KLP); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX_DARK); + assertUpgradePlatformFromTo( + oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX_DARK); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java index afb2b0343..8e26e7fc7 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -17,643 +17,39 @@ package com.android.inputmethod.keyboard.internal; import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; -import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; -import android.content.Context; -import android.content.res.Resources; -import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.RunInLocale; - -import java.util.Arrays; -import java.util.Locale; @SmallTest -public class KeySpecParserTests extends AndroidTestCase { - private final static Locale TEST_LOCALE = Locale.ENGLISH; - final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); - final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); - - private static final String CODE_SETTINGS = "!code/key_settings"; - private static final String ICON_SETTINGS = "!icon/settings_key"; - private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT); - private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT); - private static final String CODE_NON_EXISTING = "!code/non_existing"; - private static final String ICON_NON_EXISTING = "!icon/non_existing"; - - private int mCodeSettings; - private int mCodeActionNext; - private int mSettingsIconId; - +public final class KeySpecParserTests extends KeySpecParserTestsBase { @Override - protected void setUp() throws Exception { - super.setUp(); - - final String language = TEST_LOCALE.getLanguage(); - mCodesSet.setLanguage(language); - mTextsSet.setLanguage(language); - final Context context = getContext(); - new RunInLocale<Void>() { - @Override - protected Void job(final Resources res) { - mTextsSet.loadStringResources(context); - return null; - } - }.runInLocale(context.getResources(), TEST_LOCALE); - - mCodeSettings = KeySpecParser.parseCode( - CODE_SETTINGS, mCodesSet, CODE_UNSPECIFIED); - mCodeActionNext = KeySpecParser.parseCode( - "!code/key_action_next", mCodesSet, CODE_UNSPECIFIED); - mSettingsIconId = KeySpecParser.getIconId(ICON_SETTINGS); - } - - private void assertParser(String message, String moreKeySpec, String expectedLabel, - String expectedOutputText, int expectedIcon, int expectedCode) { - final String labelResolved = KeySpecParser.resolveTextReference(moreKeySpec, mTextsSet); - final MoreKeySpec spec = new MoreKeySpec(labelResolved, false /* needsToUpperCase */, - Locale.US, mCodesSet); - assertEquals(message + " [label]", expectedLabel, spec.mLabel); - assertEquals(message + " [ouptputText]", expectedOutputText, spec.mOutputText); + protected void assertParser(final String message, final String keySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIcon, + final int expectedCode) { + final String keySpecResolved = mTextsSet.resolveTextReference(keySpec); + final String actualLabel = KeySpecParser.getLabel(keySpecResolved); + final String actualOutputText = KeySpecParser.getOutputText(keySpecResolved); + final int actualIcon = KeySpecParser.getIconId(keySpecResolved); + final int actualCode = KeySpecParser.getCode(keySpecResolved); + assertEquals(message + " [label]", expectedLabel, actualLabel); + assertEquals(message + " [ouptputText]", expectedOutputText, actualOutputText); assertEquals(message + " [icon]", KeyboardIconsSet.getIconName(expectedIcon), - KeyboardIconsSet.getIconName(spec.mIconId)); + KeyboardIconsSet.getIconName(actualIcon)); assertEquals(message + " [code]", Constants.printableCode(expectedCode), - Constants.printableCode(spec.mCode)); - } - - private void assertParserError(String message, String moreKeySpec, String expectedLabel, - String expectedOutputText, int expectedIcon, int expectedCode) { - try { - assertParser(message, moreKeySpec, expectedLabel, expectedOutputText, expectedIcon, - expectedCode); - fail(message); - } catch (Exception pcpe) { - // success. - } - } - - // \U001d11e: MUSICAL SYMBOL G CLEF - private static final String PAIR1 = "\ud834\udd1e"; - private static final int CODE1 = PAIR1.codePointAt(0); - // \U001d122: MUSICAL SYMBOL F CLEF - private static final String PAIR2 = "\ud834\udd22"; - private static final int CODE2 = PAIR2.codePointAt(0); - // \U002f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6; variant character of \u6148. - private static final String PAIR3 = "\ud87e\udca6"; - private static final String SURROGATE1 = PAIR1 + PAIR2; - private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3; - - public void testSingleLetter() { - assertParser("Single letter", "a", - "a", null, ICON_UNDEFINED, 'a'); - assertParser("Single surrogate", PAIR1, - PAIR1, null, ICON_UNDEFINED, CODE1); - assertParser("Single escaped bar", "\\|", - "|", null, ICON_UNDEFINED, '|'); - assertParser("Single escaped escape", "\\\\", - "\\", null, ICON_UNDEFINED, '\\'); - assertParser("Single comma", ",", - ",", null, ICON_UNDEFINED, ','); - assertParser("Single escaped comma", "\\,", - ",", null, ICON_UNDEFINED, ','); - assertParser("Single escaped letter", "\\a", - "a", null, ICON_UNDEFINED, 'a'); - assertParser("Single escaped surrogate", "\\" + PAIR2, - PAIR2, null, ICON_UNDEFINED, CODE2); - assertParser("Single bang", "!", - "!", null, ICON_UNDEFINED, '!'); - assertParser("Single escaped bang", "\\!", - "!", null, ICON_UNDEFINED, '!'); - assertParser("Single output text letter", "a|a", - "a", null, ICON_UNDEFINED, 'a'); - assertParser("Single surrogate pair outputText", "G Clef|" + PAIR1, - "G Clef", null, ICON_UNDEFINED, CODE1); - assertParser("Single letter with outputText", "a|abc", - "a", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with surrogate outputText", "a|" + SURROGATE1, - "a", SURROGATE1, ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single surrogate with outputText", PAIR3 + "|abc", - PAIR3, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with escaped outputText", "a|a\\|c", - "a", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with escaped surrogate outputText", - "a|" + PAIR1 + "\\|" + PAIR2, - "a", PAIR1 + "|" + PAIR2, ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with comma outputText", "a|a,b", - "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with escaped comma outputText", "a|a\\,b", - "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with outputText starts with bang", "a|!bc", - "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with surrogate outputText starts with bang", "a|!" + SURROGATE2, - "a", "!" + SURROGATE2, ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with outputText contains bang", "a|a!c", - "a", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single letter with escaped bang outputText", "a|\\!bc", - "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Single escaped escape with single outputText", "\\\\|\\\\", - "\\", null, ICON_UNDEFINED, '\\'); - assertParser("Single escaped bar with single outputText", "\\||\\|", - "|", null, ICON_UNDEFINED, '|'); - assertParser("Single letter with code", "a|" + CODE_SETTINGS, - "a", null, ICON_UNDEFINED, mCodeSettings); - } - - public void testLabel() { - assertParser("Simple label", "abc", - "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Simple surrogate label", SURROGATE1, - SURROGATE1, SURROGATE1, ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped bar", "a\\|c", - "a|c", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Surrogate label with escaped bar", PAIR1 + "\\|" + PAIR2, - PAIR1 + "|" + PAIR2, PAIR1 + "|" + PAIR2, - ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped escape", "a\\\\c", - "a\\c", "a\\c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with comma", "a,c", - "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped comma", "a\\,c", - "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label starts with bang", "!bc", - "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Surrogate label starts with bang", "!" + SURROGATE1, - "!" + SURROGATE1, "!" + SURROGATE1, ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label contains bang", "a!c", - "a!c", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped bang", "\\!bc", - "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped letter", "\\abc", - "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with outputText", "abc|def", - "abc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with comma and outputText", "a,c|def", - "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped comma label with outputText", "a\\,c|def", - "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped label with outputText", "a\\|c|def", - "a|c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped bar outputText", "abc|d\\|f", - "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped escape label with outputText", "a\\\\|def", - "a\\", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label starts with bang and outputText", "!bc|def", - "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label contains bang label and outputText", "a!c|def", - "a!c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped bang label with outputText", "\\!bc|def", - "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with comma outputText", "abc|a,b", - "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped comma outputText", "abc|a\\,b", - "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with outputText starts with bang", "abc|!bc", - "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with outputText contains bang", "abc|a!c", - "abc", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped bang outputText", "abc|\\!bc", - "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with escaped bar outputText", "abc|d\\|f", - "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f", - "a|c", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with code", "abc|" + CODE_SETTINGS, - "abc", null, ICON_UNDEFINED, mCodeSettings); - assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS, - "a|c", null, ICON_UNDEFINED, mCodeSettings); - } - - public void testIconAndCode() { - assertParser("Icon with outputText", ICON_SETTINGS + "|abc", - null, "abc", mSettingsIconId, CODE_OUTPUT_TEXT); - assertParser("Icon with outputText starts with bang", ICON_SETTINGS + "|!bc", - null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); - assertParser("Icon with outputText contains bang", ICON_SETTINGS + "|a!c", - null, "a!c", mSettingsIconId, CODE_OUTPUT_TEXT); - assertParser("Icon with escaped bang outputText", ICON_SETTINGS + "|\\!bc", - null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); - assertParser("Label starts with bang and code", "!bc|" + CODE_SETTINGS, - "!bc", null, ICON_UNDEFINED, mCodeSettings); - assertParser("Label contains bang and code", "a!c|" + CODE_SETTINGS, - "a!c", null, ICON_UNDEFINED, mCodeSettings); - assertParser("Escaped bang label with code", "\\!bc|" + CODE_SETTINGS, - "!bc", null, ICON_UNDEFINED, mCodeSettings); - assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS, - null, null, mSettingsIconId, mCodeSettings); - } - - public void testResourceReference() { - assertParser("Settings as more key", "!text/settings_as_more_key", - null, null, mSettingsIconId, mCodeSettings); - - assertParser("Action next as more key", "!text/label_next_key|!code/key_action_next", - "Next", null, ICON_UNDEFINED, mCodeActionNext); - - assertParser("Popular domain", - "!text/keylabel_for_popular_domain|!text/keylabel_for_popular_domain ", - ".com", ".com ", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + Constants.printableCode(actualCode)); } - public void testFormatError() { - assertParserError("Empty spec", "", null, - null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Empty label with outputText", "|a", - null, "a", ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Empty label with code", "|" + CODE_SETTINGS, - null, null, ICON_UNDEFINED, mCodeSettings); - assertParserError("Empty outputText with label", "a|", - "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Empty outputText with icon", ICON_SETTINGS + "|", - null, null, mSettingsIconId, CODE_UNSPECIFIED); - assertParserError("Empty icon and code", "|", + // TODO: Remove this method. + // These should throw {@link KeySpecParserError} when Key.keyLabel attribute become mandatory. + public void testEmptySpec() { + assertParser("Null spec", null, + null, null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParser("Empty spec", "", null, null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Icon without code", ICON_SETTINGS, - null, null, mSettingsIconId, CODE_UNSPECIFIED); - assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc", - null, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING, - "abc", null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Third bar at end", "a|b|", - "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Multiple bar", "a|b|c", - "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); - assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c", - "a", null, ICON_UNDEFINED, mCodeSettings); - assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c", - null, null, mSettingsIconId, CODE_UNSPECIFIED); - assertParserError("Multiple bar with icon and code", - ICON_SETTINGS + "|" + CODE_SETTINGS + "|c", - null, null, mSettingsIconId, mCodeSettings); - } - - public void testUselessUpperCaseSpecifier() { - assertParser("Single letter with CODE", "a|" + CODE_SETTINGS_UPPERCASE, - "a", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label with CODE", "abc|" + CODE_SETTINGS_UPPERCASE, - "abc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped label with CODE", "a\\|c|" + CODE_SETTINGS_UPPERCASE, - "a|c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("ICON with outputText", ICON_SETTINGS_UPPERCASE + "|abc", - "!ICON/SETTINGS_KEY", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("ICON with outputText starts with bang", ICON_SETTINGS_UPPERCASE + "|!bc", - "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("ICON with outputText contains bang", ICON_SETTINGS_UPPERCASE + "|a!c", - "!ICON/SETTINGS_KEY", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("ICON with escaped bang outputText", ICON_SETTINGS_UPPERCASE + "|\\!bc", - "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label starts with bang and CODE", "!bc|" + CODE_SETTINGS_UPPERCASE, - "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Label contains bang and CODE", "a!c|" + CODE_SETTINGS_UPPERCASE, - "a!c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("Escaped bang label with CODE", "\\!bc|" + CODE_SETTINGS_UPPERCASE, - "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("ICON with CODE", ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE, - "!ICON/SETTINGS_KEY", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParser("SETTINGS AS MORE KEY", "!TEXT/SETTINGS_AS_MORE_KEY", - "!TEXT/SETTINGS_AS_MORE_KEY", "!TEXT/SETTINGS_AS_MORE_KEY", ICON_UNDEFINED, - CODE_OUTPUT_TEXT); - assertParser("ACTION NEXT AS MORE KEY", "!TEXT/LABEL_NEXT_KEY|!CODE/KEY_ACTION_NEXT", - "!TEXT/LABEL_NEXT_KEY", "!CODE/KEY_ACTION_NEXT", ICON_UNDEFINED, - CODE_OUTPUT_TEXT); - assertParser("POPULAR DOMAIN", - "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN|!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", - "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN", "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", - ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParserError("Empty label with CODE", "|" + CODE_SETTINGS_UPPERCASE, - null, null, ICON_UNDEFINED, mCodeSettings); - assertParserError("Empty outputText with ICON", ICON_SETTINGS_UPPERCASE + "|", - null, null, mSettingsIconId, CODE_UNSPECIFIED); - assertParser("ICON without code", ICON_SETTINGS_UPPERCASE, - "!ICON/SETTINGS_KEY", "!ICON/SETTINGS_KEY", ICON_UNDEFINED, CODE_OUTPUT_TEXT); - assertParserError("Multiple bar with label and CODE", "a|" + CODE_SETTINGS_UPPERCASE + "|c", - "a", null, ICON_UNDEFINED, mCodeSettings); - assertParserError("Multiple bar with ICON and outputText", ICON_SETTINGS_UPPERCASE + "|b|c", - null, null, mSettingsIconId, CODE_UNSPECIFIED); - assertParserError("Multiple bar with ICON and CODE", - ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE + "|c", - null, null, mSettingsIconId, mCodeSettings); - } - - private static void assertArrayEquals(String message, Object[] expected, Object[] actual) { - if (expected == actual) { - return; - } - if (expected == null || actual == null) { - assertEquals(message, Arrays.toString(expected), Arrays.toString(actual)); - return; - } - if (expected.length != actual.length) { - assertEquals(message + " [length]", Arrays.toString(expected), Arrays.toString(actual)); - return; - } - for (int i = 0; i < expected.length; i++) { - assertEquals(message + " [" + i + "]", - Arrays.toString(expected), Arrays.toString(actual)); - } - } - - private static void assertInsertAdditionalMoreKeys(String message, String[] moreKeys, - String[] additionalMoreKeys, String[] expected) { - final String[] actual = - KeySpecParser.insertAdditionalMoreKeys( moreKeys, additionalMoreKeys); - assertArrayEquals(message, expected, actual); - } - - public void testEmptyEntry() { - assertInsertAdditionalMoreKeys("null more keys and null additons", - null, - null, - null); - assertInsertAdditionalMoreKeys("null more keys and empty additons", - null, - new String[0], - null); - assertInsertAdditionalMoreKeys("empty more keys and null additons", - new String[0], - null, - null); - assertInsertAdditionalMoreKeys("empty more keys and empty additons", - new String[0], - new String[0], - null); - - assertInsertAdditionalMoreKeys("filter out empty more keys", - new String[] { null, "a", "", "b", null }, - null, - new String[] { "a", "b" }); - assertInsertAdditionalMoreKeys("filter out empty additons", - new String[] { "a", "%", "b", "%", "c", "%", "d" }, - new String[] { null, "A", "", "B", null }, - new String[] { "a", "A", "b", "B", "c", "d" }); - } - - public void testInsertAdditionalMoreKeys() { - // Escaped marker. - assertInsertAdditionalMoreKeys("escaped marker", - new String[] { "\\%", "%-)" }, - new String[] { "1", "2" }, - new String[] { "1", "2", "\\%", "%-)" }); - - // 0 more key. - assertInsertAdditionalMoreKeys("null & null", null, null, null); - assertInsertAdditionalMoreKeys("null & 1 additon", - null, - new String[] { "1" }, - new String[] { "1" }); - assertInsertAdditionalMoreKeys("null & 2 additons", - null, - new String[] { "1", "2" }, - new String[] { "1", "2" }); - - // 0 additional more key. - assertInsertAdditionalMoreKeys("1 more key & null", - new String[] { "A" }, - null, - new String[] { "A" }); - assertInsertAdditionalMoreKeys("2 more keys & null", - new String[] { "A", "B" }, - null, - new String[] { "A", "B" }); - - // No marker. - assertInsertAdditionalMoreKeys("1 more key & 1 addtional & no marker", - new String[] { "A" }, - new String[] { "1" }, - new String[] { "1", "A" }); - assertInsertAdditionalMoreKeys("1 more key & 2 addtionals & no marker", - new String[] { "A" }, - new String[] { "1", "2" }, - new String[] { "1", "2", "A" }); - assertInsertAdditionalMoreKeys("2 more keys & 1 addtional & no marker", - new String[] { "A", "B" }, - new String[] { "1" }, - new String[] { "1", "A", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 addtionals & no marker", - new String[] { "A", "B" }, - new String[] { "1", "2" }, - new String[] { "1", "2", "A", "B" }); - - // 1 marker. - assertInsertAdditionalMoreKeys("1 more key & 1 additon & marker at head", - new String[] { "%", "A" }, - new String[] { "1" }, - new String[] { "1", "A" }); - assertInsertAdditionalMoreKeys("1 more key & 1 additon & marker at tail", - new String[] { "A", "%" }, - new String[] { "1" }, - new String[] { "A", "1" }); - assertInsertAdditionalMoreKeys("2 more keys & 1 additon & marker at middle", - new String[] { "A", "%", "B" }, - new String[] { "1" }, - new String[] { "A", "1", "B" }); - - // 1 marker & excess additional more keys. - assertInsertAdditionalMoreKeys("1 more key & 2 additons & marker at head", - new String[] { "%", "A", "B" }, - new String[] { "1", "2" }, - new String[] { "1", "A", "B", "2" }); - assertInsertAdditionalMoreKeys("1 more key & 2 additons & marker at tail", - new String[] { "A", "B", "%" }, - new String[] { "1", "2" }, - new String[] { "A", "B", "1", "2" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & marker at middle", - new String[] { "A", "%", "B" }, - new String[] { "1", "2" }, - new String[] { "A", "1", "B", "2" }); - - // 2 markers. - assertInsertAdditionalMoreKeys("0 more key & 2 addtional & 2 markers", - new String[] { "%", "%" }, - new String[] { "1", "2" }, - new String[] { "1", "2" }); - assertInsertAdditionalMoreKeys("1 more key & 2 addtional & 2 markers at head", - new String[] { "%", "%", "A" }, - new String[] { "1", "2" }, - new String[] { "1", "2", "A" }); - assertInsertAdditionalMoreKeys("1 more key & 2 addtional & 2 markers at tail", - new String[] { "A", "%", "%" }, - new String[] { "1", "2" }, - new String[] { "A", "1", "2" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at middle", - new String[] { "A", "%", "%", "B" }, - new String[] { "1", "2" }, - new String[] { "A", "1", "2", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at head & middle", - new String[] { "%", "A", "%", "B" }, - new String[] { "1", "2" }, - new String[] { "1", "A", "2", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at head & tail", - new String[] { "%", "A", "B", "%" }, - new String[] { "1", "2" }, - new String[] { "1", "A", "B", "2" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at middle & tail", - new String[] { "A", "%", "B", "%" }, - new String[] { "1", "2" }, - new String[] { "A", "1", "B", "2" }); - - // 2 markers & excess additional more keys. - assertInsertAdditionalMoreKeys("0 more key & 2 additons & 2 markers", - new String[] { "%", "%" }, - new String[] { "1", "2", "3" }, - new String[] { "1", "2", "3" }); - assertInsertAdditionalMoreKeys("1 more key & 2 additons & 2 markers at head", - new String[] { "%", "%", "A" }, - new String[] { "1", "2", "3" }, - new String[] { "1", "2", "A", "3" }); - assertInsertAdditionalMoreKeys("1 more key & 2 additons & 2 markers at tail", - new String[] { "A", "%", "%" }, - new String[] { "1", "2", "3" }, - new String[] { "A", "1", "2", "3" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at middle", - new String[] { "A", "%", "%", "B" }, - new String[] { "1", "2", "3" }, - new String[] { "A", "1", "2", "B", "3" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at head & middle", - new String[] { "%", "A", "%", "B" }, - new String[] { "1", "2", "3" }, - new String[] { "1", "A", "2", "B", "3" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at head & tail", - new String[] { "%", "A", "B", "%" }, - new String[] { "1", "2", "3" }, - new String[] { "1", "A", "B", "2", "3" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at middle & tail", - new String[] { "A", "%", "B", "%" }, - new String[] { "1", "2", "3" }, - new String[] { "A", "1", "B", "2", "3" }); - - // 0 addtional more key and excess markers. - assertInsertAdditionalMoreKeys("0 more key & null & excess marker", - new String[] { "%" }, - null, - null); - assertInsertAdditionalMoreKeys("1 more key & null & excess marker at head", - new String[] { "%", "A" }, - null, - new String[] { "A" }); - assertInsertAdditionalMoreKeys("1 more key & null & excess marker at tail", - new String[] { "A", "%" }, - null, - new String[] { "A" }); - assertInsertAdditionalMoreKeys("2 more keys & null & excess marker at middle", - new String[] { "A", "%", "B" }, - null, - new String[] { "A", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & null & excess markers", - new String[] { "%", "A", "%", "B", "%" }, - null, - new String[] { "A", "B" }); - - // Excess markers. - assertInsertAdditionalMoreKeys("0 more key & 1 additon & excess marker", - new String[] { "%", "%" }, - new String[] { "1" }, - new String[] { "1" }); - assertInsertAdditionalMoreKeys("1 more key & 1 additon & excess marker at head", - new String[] { "%", "%", "A" }, - new String[] { "1" }, - new String[] { "1", "A" }); - assertInsertAdditionalMoreKeys("1 more key & 1 additon & excess marker at tail", - new String[] { "A", "%", "%" }, - new String[] { "1" }, - new String[] { "A", "1" }); - assertInsertAdditionalMoreKeys("2 more keys & 1 additon & excess marker at middle", - new String[] { "A", "%", "%", "B" }, - new String[] { "1" }, - new String[] { "A", "1", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 1 additon & excess markers", - new String[] { "%", "A", "%", "B", "%" }, - new String[] { "1" }, - new String[] { "1", "A", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 2 additons & excess markers", - new String[] { "%", "A", "%", "B", "%" }, - new String[] { "1", "2" }, - new String[] { "1", "A", "2", "B" }); - assertInsertAdditionalMoreKeys("2 more keys & 3 additons & excess markers", - new String[] { "%", "A", "%", "%", "B", "%" }, - new String[] { "1", "2", "3" }, - new String[] { "1", "A", "2", "3", "B" }); - } - - private static final String HAS_LABEL = "!hasLabel!"; - private static final String NEEDS_DIVIDER = "!needsDividers!"; - private static final String AUTO_COLUMN_ORDER = "!autoColumnOrder!"; - private static final String FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; - - private static void assertGetBooleanValue(String message, String key, String[] moreKeys, - String[] expected, boolean expectedValue) { - final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); - final boolean actualValue = KeySpecParser.getBooleanValue(actual, key); - assertEquals(message + " [value]", expectedValue, actualValue); - assertArrayEquals(message, expected, actual); - } - - public void testGetBooleanValue() { - assertGetBooleanValue("Has label", HAS_LABEL, - new String[] { HAS_LABEL, "a", "b", "c" }, - new String[] { null, "a", "b", "c" }, true); - // Upper case specification will not work. - assertGetBooleanValue("HAS LABEL", HAS_LABEL, - new String[] { HAS_LABEL.toUpperCase(Locale.ROOT), "a", "b", "c" }, - new String[] { "!HASLABEL!", "a", "b", "c" }, false); - - assertGetBooleanValue("No has label", HAS_LABEL, - new String[] { "a", "b", "c" }, - new String[] { "a", "b", "c" }, false); - assertGetBooleanValue("No has label with fixed clumn order", HAS_LABEL, - new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, - new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, false); - - // Upper case specification will not work. - assertGetBooleanValue("Multiple has label", HAS_LABEL, - new String[] { - "a", HAS_LABEL.toUpperCase(Locale.ROOT), "b", "c", HAS_LABEL, "d" }, - new String[] { - "a", "!HASLABEL!", "b", "c", null, "d" }, true); - // Upper case specification will not work. - assertGetBooleanValue("Multiple has label with needs dividers", HAS_LABEL, - new String[] { - "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(Locale.ROOT), "d" }, - new String[] { - "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true); - } - - private static void assertGetIntValue(String message, String key, int defaultValue, - String[] moreKeys, String[] expected, int expectedValue) { - final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); - final int actualValue = KeySpecParser.getIntValue(actual, key, defaultValue); - assertEquals(message + " [value]", expectedValue, actualValue); - assertArrayEquals(message, expected, actual); - } - - public void testGetIntValue() { - assertGetIntValue("Fixed column order 3", FIXED_COLUMN_ORDER, -1, - new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, - new String[] { null, "a", "b", "c" }, 3); - // Upper case specification will not work. - assertGetIntValue("FIXED COLUMN ORDER 3", FIXED_COLUMN_ORDER, -1, - new String[] { FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "3", "a", "b", "c" }, - new String[] { "!FIXEDCOLUMNORDER!3", "a", "b", "c" }, -1); - - assertGetIntValue("No fixed column order", FIXED_COLUMN_ORDER, -1, - new String[] { "a", "b", "c" }, - new String[] { "a", "b", "c" }, -1); - assertGetIntValue("No fixed column order with auto column order", FIXED_COLUMN_ORDER, -1, - new String[] { AUTO_COLUMN_ORDER + "5", "a", "b", "c" }, - new String[] { AUTO_COLUMN_ORDER + "5", "a", "b", "c" }, -1); - - assertGetIntValue("Multiple fixed column order 3,5", FIXED_COLUMN_ORDER, -1, - new String[] { FIXED_COLUMN_ORDER + "3", "a", FIXED_COLUMN_ORDER + "5", "b" }, - new String[] { null, "a", null, "b" }, 3); - // Upper case specification will not work. - assertGetIntValue("Multiple fixed column order 5,3 with has label", FIXED_COLUMN_ORDER, -1, - new String[] { - FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "5", HAS_LABEL, "a", - FIXED_COLUMN_ORDER + "3", "b" }, - new String[] { "!FIXEDCOLUMNORDER!5", HAS_LABEL, "a", null, "b" }, 3); } } diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java new file mode 100644 index 000000000..b8cb11b6b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java @@ -0,0 +1,340 @@ +/* + * 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.internal; + +import static com.android.inputmethod.keyboard.internal.KeyboardCodesSet.PREFIX_CODE; +import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; +import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.PREFIX_ICON; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; + +import android.test.AndroidTestCase; + +import java.util.Locale; + +abstract class KeySpecParserTestsBase extends AndroidTestCase { + private final static Locale TEST_LOCALE = Locale.ENGLISH; + protected final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); + + private static final String CODE_SETTINGS_NAME = "key_settings"; + private static final String CODE_SETTINGS = PREFIX_CODE + CODE_SETTINGS_NAME; + private static final String ICON_SETTINGS_NAME = "settings_key"; + private static final String ICON_SETTINGS = PREFIX_ICON + ICON_SETTINGS_NAME; + private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT); + private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT); + private static final String CODE_NON_EXISTING = PREFIX_CODE + "non_existing"; + private static final String ICON_NON_EXISTING = PREFIX_ICON + "non_existing"; + + private int mCodeSettings; + private int mCodeActionNext; + private int mSettingsIconId; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mTextsSet.setLocale(TEST_LOCALE, getContext()); + mCodeSettings = KeyboardCodesSet.getCode(CODE_SETTINGS_NAME); + mCodeActionNext = KeyboardCodesSet.getCode("key_action_next"); + mSettingsIconId = KeyboardIconsSet.getIconId(ICON_SETTINGS_NAME); + } + + abstract protected void assertParser(final String message, final String keySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIcon, + final int expectedCode); + + protected void assertParserError(final String message, final String keySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIconId, + final int expectedCode) { + try { + assertParser(message, keySpec, expectedLabel, expectedOutputText, expectedIconId, + expectedCode); + fail(message); + } catch (Exception pcpe) { + // success. + } + } + + // \U001d11e: MUSICAL SYMBOL G CLEF + private static final String SURROGATE_PAIR1 = "\ud834\udd1e"; + private static final int SURROGATE_CODE1 = SURROGATE_PAIR1.codePointAt(0); + // \U001d122: MUSICAL SYMBOL F CLEF + private static final String SURROGATE_PAIR2 = "\ud834\udd22"; + private static final int SURROGATE_CODE2 = SURROGATE_PAIR2.codePointAt(0); + // \U002f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6; variant character of \u6148. + private static final String SURROGATE_PAIR3 = "\ud87e\udca6"; + private static final String SURROGATE_PAIRS4 = SURROGATE_PAIR1 + SURROGATE_PAIR2; + private static final String SURROGATE_PAIRS5 = SURROGATE_PAIRS4 + SURROGATE_PAIR3; + + public void testSingleLetter() { + assertParser("Single letter", "a", + "a", null, ICON_UNDEFINED, 'a'); + assertParser("Single surrogate", SURROGATE_PAIR1, + SURROGATE_PAIR1, null, ICON_UNDEFINED, SURROGATE_CODE1); + assertParser("Sole vertical bar", "|", + "|", null, ICON_UNDEFINED, '|'); + assertParser("Single escaped vertical bar", "\\|", + "|", null, ICON_UNDEFINED, '|'); + assertParser("Single escaped escape", "\\\\", + "\\", null, ICON_UNDEFINED, '\\'); + assertParser("Single comma", ",", + ",", null, ICON_UNDEFINED, ','); + assertParser("Single escaped comma", "\\,", + ",", null, ICON_UNDEFINED, ','); + assertParser("Single escaped letter", "\\a", + "a", null, ICON_UNDEFINED, 'a'); + assertParser("Single escaped surrogate", "\\" + SURROGATE_PAIR2, + SURROGATE_PAIR2, null, ICON_UNDEFINED, SURROGATE_CODE2); + assertParser("Single bang", "!", + "!", null, ICON_UNDEFINED, '!'); + assertParser("Single escaped bang", "\\!", + "!", null, ICON_UNDEFINED, '!'); + assertParser("Single output text letter", "a|a", + "a", null, ICON_UNDEFINED, 'a'); + assertParser("Single surrogate pair outputText", "G Clef|" + SURROGATE_PAIR1, + "G Clef", null, ICON_UNDEFINED, SURROGATE_CODE1); + assertParser("Single letter with outputText", "a|abc", + "a", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with surrogate outputText", "a|" + SURROGATE_PAIRS4, + "a", SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single surrogate with outputText", SURROGATE_PAIR3 + "|abc", + SURROGATE_PAIR3, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with escaped outputText", "a|a\\|c", + "a", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with escaped surrogate outputText", + "a|" + SURROGATE_PAIR1 + "\\|" + SURROGATE_PAIR2, + "a", SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with comma outputText", "a|a,b", + "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with escaped comma outputText", "a|a\\,b", + "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with outputText starts with bang", "a|!bc", + "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with surrogate outputText starts with bang", + "a|!" + SURROGATE_PAIRS5, + "a", "!" + SURROGATE_PAIRS5, ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with outputText contains bang", "a|a!c", + "a", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single letter with escaped bang outputText", "a|\\!bc", + "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Single escaped escape with single outputText", "\\\\|\\\\", + "\\", null, ICON_UNDEFINED, '\\'); + assertParser("Single escaped bar with single outputText", "\\||\\|", + "|", null, ICON_UNDEFINED, '|'); + assertParser("Single letter with code", "a|" + CODE_SETTINGS, + "a", null, ICON_UNDEFINED, mCodeSettings); + } + + public void testLabel() { + assertParser("Simple label", "abc", + "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Simple surrogate label", SURROGATE_PAIRS4, + SURROGATE_PAIRS4, SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped bar", "a\\|c", + "a|c", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Surrogate label with escaped bar", SURROGATE_PAIR1 + "\\|" + SURROGATE_PAIR2, + SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, + ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped escape", "a\\\\c", + "a\\c", "a\\c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with comma", "a,c", + "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped comma", "a\\,c", + "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label starts with bang", "!bc", + "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Surrogate label starts with bang", "!" + SURROGATE_PAIRS4, + "!" + SURROGATE_PAIRS4, "!" + SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label contains bang", "a!c", + "a!c", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped bang", "\\!bc", + "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped letter", "\\abc", + "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with outputText", "abc|def", + "abc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with comma and outputText", "a,c|def", + "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped comma label with outputText", "a\\,c|def", + "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped label with outputText", "a\\|c|def", + "a|c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped bar outputText", "abc|d\\|f", + "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped escape label with outputText", "a\\\\|def", + "a\\", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label starts with bang and outputText", "!bc|def", + "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label contains bang label and outputText", "a!c|def", + "a!c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped bang label with outputText", "\\!bc|def", + "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with comma outputText", "abc|a,b", + "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped comma outputText", "abc|a\\,b", + "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with outputText starts with bang", "abc|!bc", + "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with outputText contains bang", "abc|a!c", + "abc", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped bang outputText", "abc|\\!bc", + "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with escaped bar outputText", "abc|d\\|f", + "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f", + "a|c", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with code", "abc|" + CODE_SETTINGS, + "abc", null, ICON_UNDEFINED, mCodeSettings); + assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS, + "a|c", null, ICON_UNDEFINED, mCodeSettings); + } + + public void testCodes() { + assertParser("Hexadecimal code", "a|0x1000", + "a", null, ICON_UNDEFINED, 0x1000); + assertParserError("Illegal hexadecimal code", "a|0x100X", + "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParser("Escaped hexadecimal code 1", "a|\\0x1000", + "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped hexadecimal code 2", "a|0\\x1000", + "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped hexadecimal code 2", "a|0\\x1000", + "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParserError("Illegally escaped hexadecimal code", "a|0x1\\000", + "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + // This is a workaround to have a key that has a supplementary code point. We can't put a + // string in resource as a XML entity of a supplementary code point or a surrogate pair. + // TODO: Should pass this test. +// assertParser("Hexadecimal supplementary code", String.format("a|0x%06x", SURROGATE_CODE2), +// SURROGATE_PAIR2, null, ICON_UNDEFINED, SURROGATE_CODE2); + assertParser("Zero is treated as output text", "a|0", + "a", null, ICON_UNDEFINED, '0'); + assertParser("Digit is treated as output text", "a|3", + "a", null, ICON_UNDEFINED, '3'); + assertParser("Decimal number is treated as an output text", "a|2014", + "a", "2014", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + } + + public void testIcons() { + assertParser("Icon with single letter", ICON_SETTINGS + "|a", + null, null, mSettingsIconId, 'a'); + assertParser("Icon with outputText", ICON_SETTINGS + "|abc", + null, "abc", mSettingsIconId, CODE_OUTPUT_TEXT); + assertParser("Icon with outputText starts with bang", ICON_SETTINGS + "|!bc", + null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); + assertParser("Icon with outputText contains bang", ICON_SETTINGS + "|a!c", + null, "a!c", mSettingsIconId, CODE_OUTPUT_TEXT); + assertParser("Icon with escaped bang outputText", ICON_SETTINGS + "|\\!bc", + null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); + assertParser("Label starts with bang and code", "!bc|" + CODE_SETTINGS, + "!bc", null, ICON_UNDEFINED, mCodeSettings); + assertParser("Label contains bang and code", "a!c|" + CODE_SETTINGS, + "a!c", null, ICON_UNDEFINED, mCodeSettings); + assertParser("Escaped bang label with code", "\\!bc|" + CODE_SETTINGS, + "!bc", null, ICON_UNDEFINED, mCodeSettings); + assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS, + null, null, mSettingsIconId, mCodeSettings); + } + + public void testResourceReference() { + assertParser("Settings as more key", "!text/keyspec_settings", + null, null, mSettingsIconId, mCodeSettings); + + assertParser("Action next as more key", "!text/label_next_key|!code/key_action_next", + "Next", null, ICON_UNDEFINED, mCodeActionNext); + + assertParser("Popular domain", + "!text/keyspec_popular_domain|!text/keyspec_popular_domain ", + ".com", ".com ", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + } + + public void testFormatError() { + assertParserError("Empty label with outputText", "|a", + null, "a", ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Empty label with code", "|" + CODE_SETTINGS, + null, null, ICON_UNDEFINED, mCodeSettings); + assertParserError("Empty outputText with label", "a|", + "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Empty outputText with icon", ICON_SETTINGS + "|", + null, null, mSettingsIconId, CODE_UNSPECIFIED); + assertParserError("Icon without code", ICON_SETTINGS, + null, null, mSettingsIconId, CODE_UNSPECIFIED); + assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc", + null, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING, + "abc", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Third bar at end", "a|b|", + "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Multiple bar", "a|b|c", + "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c", + "a", null, ICON_UNDEFINED, mCodeSettings); + assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c", + null, null, mSettingsIconId, CODE_UNSPECIFIED); + assertParserError("Multiple bar with icon and code", + ICON_SETTINGS + "|" + CODE_SETTINGS + "|c", + null, null, mSettingsIconId, mCodeSettings); + } + + public void testUselessUpperCaseSpecifier() { + assertParser("Single letter with CODE", "a|" + CODE_SETTINGS_UPPERCASE, + "a", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label with CODE", "abc|" + CODE_SETTINGS_UPPERCASE, + "abc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped label with CODE", "a\\|c|" + CODE_SETTINGS_UPPERCASE, + "a|c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("ICON with outputText", ICON_SETTINGS_UPPERCASE + "|abc", + "!ICON/SETTINGS_KEY", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("ICON with outputText starts with bang", ICON_SETTINGS_UPPERCASE + "|!bc", + "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("ICON with outputText contains bang", ICON_SETTINGS_UPPERCASE + "|a!c", + "!ICON/SETTINGS_KEY", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("ICON with escaped bang outputText", ICON_SETTINGS_UPPERCASE + "|\\!bc", + "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label starts with bang and CODE", "!bc|" + CODE_SETTINGS_UPPERCASE, + "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Label contains bang and CODE", "a!c|" + CODE_SETTINGS_UPPERCASE, + "a!c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("Escaped bang label with CODE", "\\!bc|" + CODE_SETTINGS_UPPERCASE, + "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("ICON with CODE", ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE, + "!ICON/SETTINGS_KEY", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParser("SETTINGS AS MORE KEY", "!TEXT/SETTINGS_AS_MORE_KEY", + "!TEXT/SETTINGS_AS_MORE_KEY", "!TEXT/SETTINGS_AS_MORE_KEY", ICON_UNDEFINED, + CODE_OUTPUT_TEXT); + assertParser("ACTION NEXT AS MORE KEY", "!TEXT/LABEL_NEXT_KEY|!CODE/KEY_ACTION_NEXT", + "!TEXT/LABEL_NEXT_KEY", "!CODE/KEY_ACTION_NEXT", ICON_UNDEFINED, + CODE_OUTPUT_TEXT); + assertParser("POPULAR DOMAIN", + "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN|!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", + "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN", "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", + ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParserError("Empty label with CODE", "|" + CODE_SETTINGS_UPPERCASE, + null, null, ICON_UNDEFINED, mCodeSettings); + assertParserError("Empty outputText with ICON", ICON_SETTINGS_UPPERCASE + "|", + null, null, mSettingsIconId, CODE_UNSPECIFIED); + assertParser("ICON without code", ICON_SETTINGS_UPPERCASE, + "!ICON/SETTINGS_KEY", "!ICON/SETTINGS_KEY", ICON_UNDEFINED, CODE_OUTPUT_TEXT); + assertParserError("Multiple bar with label and CODE", "a|" + CODE_SETTINGS_UPPERCASE + "|c", + "a", null, ICON_UNDEFINED, mCodeSettings); + assertParserError("Multiple bar with ICON and outputText", ICON_SETTINGS_UPPERCASE + "|b|c", + null, null, mSettingsIconId, CODE_UNSPECIFIED); + assertParserError("Multiple bar with ICON and CODE", + ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE + "|c", + null, null, mSettingsIconId, mCodeSettings); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java new file mode 100644 index 000000000..72211015f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java @@ -0,0 +1,96 @@ +/* + * 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.internal; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +@SmallTest +public final class KeyboardTextsSetTests extends AndroidTestCase { + // All input method subtypes of LatinIME. + private List<InputMethodSubtype> mAllSubtypesList; + + @Override + protected void setUp() throws Exception { + super.setUp(); + RichInputMethodManager.init(getContext()); + final RichInputMethodManager richImm = RichInputMethodManager.getInstance(); + + final ArrayList<InputMethodSubtype> allSubtypesList = new ArrayList<>(); + final InputMethodInfo imi = richImm.getInputMethodInfoOfThisIme(); + final int subtypeCount = imi.getSubtypeCount(); + for (int index = 0; index < subtypeCount; index++) { + final InputMethodSubtype subtype = imi.getSubtypeAt(index); + allSubtypesList.add(subtype); + } + mAllSubtypesList = Collections.unmodifiableList(allSubtypesList); + } + + // Test that the text {@link KeyboardTextsSet#SWITCH_TO_ALPHA_KEY_LABEL} exists for all + // subtypes. The text is needed to implement Emoji Keyboard, see + // {@link KeyboardSwitcher#setEmojiKeyboard()}. + public void testSwitchToAlphaKeyLabel() { + final Context context = getContext(); + final KeyboardTextsSet textsSet = new KeyboardTextsSet(); + for (final InputMethodSubtype subtype : mAllSubtypesList) { + final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + textsSet.setLocale(locale, context); + final String switchToAlphaKeyLabel = textsSet.getText( + KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL); + assertNotNull("Switch to alpha key label of " + locale, switchToAlphaKeyLabel); + assertFalse("Switch to alpha key label of " + locale, switchToAlphaKeyLabel.isEmpty()); + } + } + + private static final String[] TEXT_NAMES_FROM_RESOURCE = { + // Labels for action. + "label_go_key", + "label_send_key", + "label_next_key", + "label_done_key", + "label_previous_key", + // Other labels. + "label_pause_key", + "label_wait_key", + }; + + // Test that the text from resources are correctly loaded for all subtypes. + public void testTextFromResources() { + final Context context = getContext(); + final KeyboardTextsSet textsSet = new KeyboardTextsSet(); + for (final InputMethodSubtype subtype : mAllSubtypesList) { + final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + textsSet.setLocale(locale, context); + for (final String name : TEXT_NAMES_FROM_RESOURCE) { + final String text = textsSet.getText(name); + assertNotNull(name + " of " + locale, text); + assertFalse(name + " of " + locale, text.isEmpty()); + } + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java new file mode 100644 index 000000000..6ea27588e --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.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.internal; + +import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE; +import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_LANGUAGE_ONLY; +import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_NONE; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +@SmallTest +public class LanguageOnSpacebarHelperTests extends AndroidTestCase { + private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper = + new LanguageOnSpacebarHelper(); + + private RichInputMethodManager mRichImm; + + InputMethodSubtype EN_US_QWERTY; + InputMethodSubtype EN_GB_QWERTY; + InputMethodSubtype FR_AZERTY; + InputMethodSubtype FR_CA_QWERTY; + InputMethodSubtype FR_CH_SWISS; + InputMethodSubtype FR_CH_QWERTY; + InputMethodSubtype FR_CH_QWERTZ; + InputMethodSubtype ZZ_QWERTY; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getContext(); + RichInputMethodManager.init(context); + mRichImm = RichInputMethodManager.getInstance(); + SubtypeLocaleUtils.init(context); + + EN_US_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.US.toString(), "qwerty"); + EN_GB_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.UK.toString(), "qwerty"); + FR_AZERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.FRENCH.toString(), "azerty"); + FR_CA_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.CANADA_FRENCH.toString(), "qwerty"); + FR_CH_SWISS = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "fr_CH", "swiss"); + FR_CH_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "fr_CH", "qwertz"); + FR_CH_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "fr_CH", "qwerty"); + ZZ_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); + } + + private static List<InputMethodSubtype> asList(final InputMethodSubtype ... subtypes) { + return Arrays.asList(subtypes); + } + + public void testOneSubtype() { + mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY)); + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); + assertEquals("one same English (US)", FORMAT_TYPE_NONE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("one same NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + + mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(FR_AZERTY)); + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); + assertEquals("one diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("one diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + } + + public void testTwoSubtypes() { + mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY, FR_AZERTY)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); + assertEquals("two same English (US)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("two same French)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); + assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); + assertEquals("two diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("two diff French", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); + assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + } + + public void testSameLanuageSubtypes() { + mLanguageOnSpacebarHelper.updateEnabledSubtypes( + asList(EN_US_QWERTY, EN_GB_QWERTY, FR_AZERTY, ZZ_QWERTY)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); + assertEquals("two same English (US)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("two same English (UK)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY)); + assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); + assertEquals("two diff English (US)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); + assertEquals("two diff English (UK)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY)); + assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + } + + public void testMultiSameLanuageSubtypes() { + mLanguageOnSpacebarHelper.updateEnabledSubtypes( + asList(FR_AZERTY, FR_CA_QWERTY, FR_CH_SWISS, FR_CH_QWERTY, FR_CH_QWERTZ)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); + assertEquals("multi same French", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); + assertEquals("multi same French (CA)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY)); + assertEquals("multi same French (CH)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS)); + assertEquals("multi same French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY)); + assertEquals("multi same French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ)); + + mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); + assertEquals("multi diff French", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); + assertEquals("multi diff French (CA)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY)); + assertEquals("multi diff French (CH)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS)); + assertEquals("multi diff French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY)); + assertEquals("multi diff French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ)); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java index 6e3e37add..a353e5a35 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java @@ -125,8 +125,9 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { } @Override - public void requestUpdatingShiftState() { - mState.onUpdateShiftState(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); + public void requestUpdatingShiftState(final int currentAutoCapsState, + final int currentRecapitalizeState) { + mState.onUpdateShiftState(currentAutoCapsState, currentRecapitalizeState); } @Override @@ -149,7 +150,7 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { } public void loadKeyboard() { - mState.onLoadKeyboard(); + mState.onLoadKeyboard(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); } public void saveKeyboardState() { @@ -157,11 +158,17 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { } public void onPressKey(final int code, final boolean isSinglePointer) { - mState.onPressKey(code, isSinglePointer, mAutoCapsState); + mState.onPressKey(code, isSinglePointer, mAutoCapsState, + RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); } public void onReleaseKey(final int code, final boolean withSliding) { - mState.onReleaseKey(code, withSliding); + onReleaseKey(code, withSliding, mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); + } + + public void onReleaseKey(final int code, final boolean withSliding, + final int currentAutoCapsState, final int currentRecapitalizeState) { + mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState); if (mLongPressTimeoutCode == code) { mLongPressTimeoutCode = 0; } @@ -176,10 +183,10 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { } else { mAutoCapsState = mAutoCapsMode; } - mState.onCodeInput(code, mAutoCapsState); + mState.onCodeInput(code, mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); } public void onFinishSlidingInput() { - mState.onFinishSlidingInput(); + mState.onFinishSlidingInput(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); } } diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java index 2eb448c82..29b169d80 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java @@ -22,16 +22,13 @@ import android.content.res.Resources; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.MediumTest; -import com.android.inputmethod.latin.utils.CollectionUtils; -import com.android.inputmethod.latin.utils.RunInLocale; - import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; @MediumTest -public class KeySpecParserSplitTests extends InstrumentationTestCase { +public class MoreKeySpecSplitTests extends InstrumentationTestCase { private static final Locale TEST_LOCALE = Locale.ENGLISH; final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); @@ -41,24 +38,20 @@ public class KeySpecParserSplitTests extends InstrumentationTestCase { final Instrumentation instrumentation = getInstrumentation(); final Context targetContext = instrumentation.getTargetContext(); - mTextsSet.setLanguage(TEST_LOCALE.getLanguage()); - new RunInLocale<Void>() { - @Override - protected Void job(final Resources res) { - mTextsSet.loadStringResources(targetContext); - return null; - } - }.runInLocale(targetContext.getResources(), TEST_LOCALE); + mTextsSet.setLocale(TEST_LOCALE, targetContext); final String[] testResourceNames = getAllResourceIdNames( com.android.inputmethod.latin.tests.R.string.class); - mTextsSet.loadStringResourcesInternal(instrumentation.getContext(), testResourceNames, + final Context testContext = instrumentation.getContext(); + final Resources testRes = testContext.getResources(); + final String testResPackageName = testRes.getResourcePackageName( // This dummy raw resource is needed to be able to load string resources from a test // APK successfully. com.android.inputmethod.latin.tests.R.raw.dummy_resource_for_testing); + mTextsSet.loadStringResourcesInternal(testRes, testResourceNames, testResPackageName); } private static String[] getAllResourceIdNames(final Class<?> resourceIdClass) { - final ArrayList<String> names = CollectionUtils.newArrayList(); + final ArrayList<String> names = new ArrayList<>(); for (final Field field : resourceIdClass.getFields()) { if (field.getType() == Integer.TYPE) { names.add(field.getName()); @@ -92,8 +85,8 @@ public class KeySpecParserSplitTests extends InstrumentationTestCase { private void assertTextArray(final String message, final String value, final String ... expectedArray) { - final String resolvedActual = KeySpecParser.resolveTextReference(value, mTextsSet); - final String[] actual = KeySpecParser.splitKeySpecs(resolvedActual); + final String resolvedActual = mTextsSet.resolveTextReference(value); + final String[] actual = MoreKeySpec.splitKeySpecs(resolvedActual); final String[] expected = (expectedArray.length == 0) ? null : expectedArray; assertArrayEquals(message, expected, actual); } @@ -116,6 +109,14 @@ public class KeySpecParserSplitTests extends InstrumentationTestCase { private static final String SURROGATE1 = PAIR1 + PAIR2; private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3; + public void testResolveNullText() { + assertNull("resolve null", mTextsSet.resolveTextReference(null)); + } + + public void testResolveEmptyText() { + assertNull("resolve empty text", mTextsSet.resolveTextReference("!text/empty_string")); + } + public void testSplitZero() { assertTextArray("Empty string", ""); assertTextArray("Empty entry", ","); @@ -352,16 +353,16 @@ public class KeySpecParserSplitTests extends InstrumentationTestCase { } public void testLabelReferece() { - assertTextArray("Label time am", "!text/label_time_am", "AM"); + assertTextArray("Label time am", "!text/keylabel_time_am", "AM"); - assertTextArray("More keys for am pm", "!text/more_keys_for_am_pm", + assertTextArray("More keys for am pm", "!text/morekeys_am_pm", "!fixedColumnOrder!2", "!hasLabels!", "AM", "PM"); - assertTextArray("Settings as more key", "!text/settings_as_more_key", + assertTextArray("Settings as more key", "!text/keyspec_settings", "!icon/settings_key|!code/key_settings"); assertTextArray("Indirect naviagte actions as more key", - "!text/indirect_navigate_actions_as_more_key", + "!text/keyspec_indirect_navigate_actions", "!fixedColumnOrder!2", "!hasLabels!", "Prev|!code/key_action_previous", "!hasLabels!", "Next|!code/key_action_next"); diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java new file mode 100644 index 000000000..6c0d74941 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2010 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.internal; + +import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.inputmethod.latin.Constants; + +import java.util.Arrays; +import java.util.Locale; + +@SmallTest +public final class MoreKeySpecTests extends KeySpecParserTestsBase { + @Override + protected void assertParser(final String message, final String moreKeySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIconId, + final int expectedCode) { + final String labelResolved = mTextsSet.resolveTextReference(moreKeySpec); + final MoreKeySpec spec = new MoreKeySpec( + labelResolved, false /* needsToUpperCase */, Locale.US); + assertEquals(message + " [label]", expectedLabel, spec.mLabel); + assertEquals(message + " [ouptputText]", expectedOutputText, spec.mOutputText); + assertEquals(message + " [icon]", + KeyboardIconsSet.getIconName(expectedIconId), + KeyboardIconsSet.getIconName(spec.mIconId)); + assertEquals(message + " [code]", + Constants.printableCode(expectedCode), + Constants.printableCode(spec.mCode)); + } + + // TODO: Move this method to {@link KeySpecParserBase}. + public void testEmptySpec() { + assertParserError("Null spec", null, + null, null, ICON_UNDEFINED, CODE_UNSPECIFIED); + assertParserError("Empty spec", "", + null, null, ICON_UNDEFINED, CODE_UNSPECIFIED); + } + + private static void assertArrayEquals(final String message, final Object[] expected, + final Object[] actual) { + if (expected == actual) { + return; + } + if (expected == null || actual == null) { + assertEquals(message, Arrays.toString(expected), Arrays.toString(actual)); + return; + } + if (expected.length != actual.length) { + assertEquals(message + " [length]", Arrays.toString(expected), Arrays.toString(actual)); + return; + } + for (int i = 0; i < expected.length; i++) { + assertEquals(message + " [" + i + "]", + Arrays.toString(expected), Arrays.toString(actual)); + } + } + + private static void assertInsertAdditionalMoreKeys(final String message, + final String[] moreKeys, final String[] additionalMoreKeys, final String[] expected) { + final String[] actual = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); + assertArrayEquals(message, expected, actual); + } + + public void testEmptyEntry() { + assertInsertAdditionalMoreKeys("null more keys and null additons", + null, + null, + null); + assertInsertAdditionalMoreKeys("null more keys and empty additons", + null, + new String[0], + null); + assertInsertAdditionalMoreKeys("empty more keys and null additons", + new String[0], + null, + null); + assertInsertAdditionalMoreKeys("empty more keys and empty additons", + new String[0], + new String[0], + null); + + assertInsertAdditionalMoreKeys("filter out empty more keys", + new String[] { null, "a", "", "b", null }, + null, + new String[] { "a", "b" }); + assertInsertAdditionalMoreKeys("filter out empty additons", + new String[] { "a", "%", "b", "%", "c", "%", "d" }, + new String[] { null, "A", "", "B", null }, + new String[] { "a", "A", "b", "B", "c", "d" }); + } + + public void testInsertAdditionalMoreKeys() { + // Escaped marker. + assertInsertAdditionalMoreKeys("escaped marker", + new String[] { "\\%", "%-)" }, + new String[] { "1", "2" }, + new String[] { "1", "2", "\\%", "%-)" }); + + // 0 more key. + assertInsertAdditionalMoreKeys("null & null", null, null, null); + assertInsertAdditionalMoreKeys("null & 1 additon", + null, + new String[] { "1" }, + new String[] { "1" }); + assertInsertAdditionalMoreKeys("null & 2 additons", + null, + new String[] { "1", "2" }, + new String[] { "1", "2" }); + + // 0 additional more key. + assertInsertAdditionalMoreKeys("1 more key & null", + new String[] { "A" }, + null, + new String[] { "A" }); + assertInsertAdditionalMoreKeys("2 more keys & null", + new String[] { "A", "B" }, + null, + new String[] { "A", "B" }); + + // No marker. + assertInsertAdditionalMoreKeys("1 more key & 1 addtional & no marker", + new String[] { "A" }, + new String[] { "1" }, + new String[] { "1", "A" }); + assertInsertAdditionalMoreKeys("1 more key & 2 addtionals & no marker", + new String[] { "A" }, + new String[] { "1", "2" }, + new String[] { "1", "2", "A" }); + assertInsertAdditionalMoreKeys("2 more keys & 1 addtional & no marker", + new String[] { "A", "B" }, + new String[] { "1" }, + new String[] { "1", "A", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 addtionals & no marker", + new String[] { "A", "B" }, + new String[] { "1", "2" }, + new String[] { "1", "2", "A", "B" }); + + // 1 marker. + assertInsertAdditionalMoreKeys("1 more key & 1 additon & marker at head", + new String[] { "%", "A" }, + new String[] { "1" }, + new String[] { "1", "A" }); + assertInsertAdditionalMoreKeys("1 more key & 1 additon & marker at tail", + new String[] { "A", "%" }, + new String[] { "1" }, + new String[] { "A", "1" }); + assertInsertAdditionalMoreKeys("2 more keys & 1 additon & marker at middle", + new String[] { "A", "%", "B" }, + new String[] { "1" }, + new String[] { "A", "1", "B" }); + + // 1 marker & excess additional more keys. + assertInsertAdditionalMoreKeys("1 more key & 2 additons & marker at head", + new String[] { "%", "A", "B" }, + new String[] { "1", "2" }, + new String[] { "1", "A", "B", "2" }); + assertInsertAdditionalMoreKeys("1 more key & 2 additons & marker at tail", + new String[] { "A", "B", "%" }, + new String[] { "1", "2" }, + new String[] { "A", "B", "1", "2" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & marker at middle", + new String[] { "A", "%", "B" }, + new String[] { "1", "2" }, + new String[] { "A", "1", "B", "2" }); + + // 2 markers. + assertInsertAdditionalMoreKeys("0 more key & 2 addtional & 2 markers", + new String[] { "%", "%" }, + new String[] { "1", "2" }, + new String[] { "1", "2" }); + assertInsertAdditionalMoreKeys("1 more key & 2 addtional & 2 markers at head", + new String[] { "%", "%", "A" }, + new String[] { "1", "2" }, + new String[] { "1", "2", "A" }); + assertInsertAdditionalMoreKeys("1 more key & 2 addtional & 2 markers at tail", + new String[] { "A", "%", "%" }, + new String[] { "1", "2" }, + new String[] { "A", "1", "2" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at middle", + new String[] { "A", "%", "%", "B" }, + new String[] { "1", "2" }, + new String[] { "A", "1", "2", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at head & middle", + new String[] { "%", "A", "%", "B" }, + new String[] { "1", "2" }, + new String[] { "1", "A", "2", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at head & tail", + new String[] { "%", "A", "B", "%" }, + new String[] { "1", "2" }, + new String[] { "1", "A", "B", "2" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 addtional & 2 markers at middle & tail", + new String[] { "A", "%", "B", "%" }, + new String[] { "1", "2" }, + new String[] { "A", "1", "B", "2" }); + + // 2 markers & excess additional more keys. + assertInsertAdditionalMoreKeys("0 more key & 2 additons & 2 markers", + new String[] { "%", "%" }, + new String[] { "1", "2", "3" }, + new String[] { "1", "2", "3" }); + assertInsertAdditionalMoreKeys("1 more key & 2 additons & 2 markers at head", + new String[] { "%", "%", "A" }, + new String[] { "1", "2", "3" }, + new String[] { "1", "2", "A", "3" }); + assertInsertAdditionalMoreKeys("1 more key & 2 additons & 2 markers at tail", + new String[] { "A", "%", "%" }, + new String[] { "1", "2", "3" }, + new String[] { "A", "1", "2", "3" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at middle", + new String[] { "A", "%", "%", "B" }, + new String[] { "1", "2", "3" }, + new String[] { "A", "1", "2", "B", "3" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at head & middle", + new String[] { "%", "A", "%", "B" }, + new String[] { "1", "2", "3" }, + new String[] { "1", "A", "2", "B", "3" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at head & tail", + new String[] { "%", "A", "B", "%" }, + new String[] { "1", "2", "3" }, + new String[] { "1", "A", "B", "2", "3" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & 2 markers at middle & tail", + new String[] { "A", "%", "B", "%" }, + new String[] { "1", "2", "3" }, + new String[] { "A", "1", "B", "2", "3" }); + + // 0 addtional more key and excess markers. + assertInsertAdditionalMoreKeys("0 more key & null & excess marker", + new String[] { "%" }, + null, + null); + assertInsertAdditionalMoreKeys("1 more key & null & excess marker at head", + new String[] { "%", "A" }, + null, + new String[] { "A" }); + assertInsertAdditionalMoreKeys("1 more key & null & excess marker at tail", + new String[] { "A", "%" }, + null, + new String[] { "A" }); + assertInsertAdditionalMoreKeys("2 more keys & null & excess marker at middle", + new String[] { "A", "%", "B" }, + null, + new String[] { "A", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & null & excess markers", + new String[] { "%", "A", "%", "B", "%" }, + null, + new String[] { "A", "B" }); + + // Excess markers. + assertInsertAdditionalMoreKeys("0 more key & 1 additon & excess marker", + new String[] { "%", "%" }, + new String[] { "1" }, + new String[] { "1" }); + assertInsertAdditionalMoreKeys("1 more key & 1 additon & excess marker at head", + new String[] { "%", "%", "A" }, + new String[] { "1" }, + new String[] { "1", "A" }); + assertInsertAdditionalMoreKeys("1 more key & 1 additon & excess marker at tail", + new String[] { "A", "%", "%" }, + new String[] { "1" }, + new String[] { "A", "1" }); + assertInsertAdditionalMoreKeys("2 more keys & 1 additon & excess marker at middle", + new String[] { "A", "%", "%", "B" }, + new String[] { "1" }, + new String[] { "A", "1", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 1 additon & excess markers", + new String[] { "%", "A", "%", "B", "%" }, + new String[] { "1" }, + new String[] { "1", "A", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 2 additons & excess markers", + new String[] { "%", "A", "%", "B", "%" }, + new String[] { "1", "2" }, + new String[] { "1", "A", "2", "B" }); + assertInsertAdditionalMoreKeys("2 more keys & 3 additons & excess markers", + new String[] { "%", "A", "%", "%", "B", "%" }, + new String[] { "1", "2", "3" }, + new String[] { "1", "A", "2", "3", "B" }); + } + + private static final String HAS_LABEL = "!hasLabel!"; + private static final String NEEDS_DIVIDER = "!needsDividers!"; + private static final String AUTO_COLUMN_ORDER = "!autoColumnOrder!"; + private static final String FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; + + private static void assertGetBooleanValue(final String message, final String key, + final String[] moreKeys, final String[] expected, final boolean expectedValue) { + final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); + final boolean actualValue = MoreKeySpec.getBooleanValue(actual, key); + assertEquals(message + " [value]", expectedValue, actualValue); + assertArrayEquals(message, expected, actual); + } + + public void testGetBooleanValue() { + assertGetBooleanValue("Has label", HAS_LABEL, + new String[] { HAS_LABEL, "a", "b", "c" }, + new String[] { null, "a", "b", "c" }, true); + // Upper case specification will not work. + assertGetBooleanValue("HAS LABEL", HAS_LABEL, + new String[] { HAS_LABEL.toUpperCase(Locale.ROOT), "a", "b", "c" }, + new String[] { "!HASLABEL!", "a", "b", "c" }, false); + + assertGetBooleanValue("No has label", HAS_LABEL, + new String[] { "a", "b", "c" }, + new String[] { "a", "b", "c" }, false); + assertGetBooleanValue("No has label with fixed clumn order", HAS_LABEL, + new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, + new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, false); + + // Upper case specification will not work. + assertGetBooleanValue("Multiple has label", HAS_LABEL, + new String[] { + "a", HAS_LABEL.toUpperCase(Locale.ROOT), "b", "c", HAS_LABEL, "d" }, + new String[] { + "a", "!HASLABEL!", "b", "c", null, "d" }, true); + // Upper case specification will not work. + assertGetBooleanValue("Multiple has label with needs dividers", HAS_LABEL, + new String[] { + "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(Locale.ROOT), "d" }, + new String[] { + "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true); + } + + private static void assertGetIntValue(final String message, final String key, + final int defaultValue, final String[] moreKeys, final String[] expected, + final int expectedValue) { + final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); + final int actualValue = MoreKeySpec.getIntValue(actual, key, defaultValue); + assertEquals(message + " [value]", expectedValue, actualValue); + assertArrayEquals(message, expected, actual); + } + + public void testGetIntValue() { + assertGetIntValue("Fixed column order 3", FIXED_COLUMN_ORDER, -1, + new String[] { FIXED_COLUMN_ORDER + "3", "a", "b", "c" }, + new String[] { null, "a", "b", "c" }, 3); + // Upper case specification will not work. + assertGetIntValue("FIXED COLUMN ORDER 3", FIXED_COLUMN_ORDER, -1, + new String[] { FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "3", "a", "b", "c" }, + new String[] { "!FIXEDCOLUMNORDER!3", "a", "b", "c" }, -1); + + assertGetIntValue("No fixed column order", FIXED_COLUMN_ORDER, -1, + new String[] { "a", "b", "c" }, + new String[] { "a", "b", "c" }, -1); + assertGetIntValue("No fixed column order with auto column order", FIXED_COLUMN_ORDER, -1, + new String[] { AUTO_COLUMN_ORDER + "5", "a", "b", "c" }, + new String[] { AUTO_COLUMN_ORDER + "5", "a", "b", "c" }, -1); + + assertGetIntValue("Multiple fixed column order 3,5", FIXED_COLUMN_ORDER, -1, + new String[] { FIXED_COLUMN_ORDER + "3", "a", FIXED_COLUMN_ORDER + "5", "b" }, + new String[] { null, "a", null, "b" }, 3); + // Upper case specification will not work. + assertGetIntValue("Multiple fixed column order 5,3 with has label", FIXED_COLUMN_ORDER, -1, + new String[] { + FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "5", HAS_LABEL, "a", + FIXED_COLUMN_ORDER + "3", "b" }, + new String[] { "!FIXEDCOLUMNORDER!5", HAS_LABEL, "a", null, "b" }, 3); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java index 279559cfe..7908b260e 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java @@ -27,7 +27,7 @@ public class PointerTrackerQueueTests extends AndroidTestCase { public final int mId; public boolean mIsModifier; - public boolean mIsInSlidingKeyInput; + public boolean mIsInDraggingFinger; public long mPhantomUpEventTime = NOT_HAPPENED; public Element(int id) { @@ -40,8 +40,8 @@ public class PointerTrackerQueueTests extends AndroidTestCase { } @Override - public boolean isInSlidingKeyInput() { - return mIsInSlidingKeyInput; + public boolean isInDraggingFinger() { + return mIsInDraggingFinger; } @Override @@ -297,19 +297,19 @@ public class PointerTrackerQueueTests extends AndroidTestCase { assertEquals(Element.NOT_HAPPENED, mElement4.mPhantomUpEventTime); } - public void testIsAnyInSlidingKeyInput() { + public void testIsAnyInDraggingFinger() { Element.sPhantomUpCount = 0; - assertFalse(mQueue.isAnyInSlidingKeyInput()); + assertFalse(mQueue.isAnyInDraggingFinger()); mQueue.add(mElement1); mQueue.add(mElement2); mQueue.add(mElement3); mQueue.add(mElement4); - assertFalse(mQueue.isAnyInSlidingKeyInput()); + assertFalse(mQueue.isAnyInDraggingFinger()); - mElement3.mIsInSlidingKeyInput = true; - assertTrue(mQueue.isAnyInSlidingKeyInput()); + mElement3.mIsInDraggingFinger = true; + assertTrue(mQueue.isAnyInDraggingFinger()); assertEquals(0, Element.sPhantomUpCount); assertEquals(4, mQueue.size()); diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java b/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java new file mode 100644 index 000000000..fa818654e --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java @@ -0,0 +1,352 @@ +/* + * 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.Symbols.RtlSymbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted.RtlSymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +public final class Arabic extends LayoutBase { + private static final String LAYOUT_NAME = "arabic"; + + public Arabic(final LayoutCustomizer customizer) { + super(customizer, ArabicSymbols.class, ArabicSymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class ArabicCustomizer extends LayoutCustomizer { + public ArabicCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey getAlphabetKey() { return ARABIC_ALPHABET_KEY; } + + @Override + public ExpectedKey getSymbolsKey() { return ARABIC_SYMBOLS_KEY; } + + @Override + public ExpectedKey getBackToSymbolsKey() { return ARABIC_BACK_TO_SYMBOLS_KEY; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { + return RtlSymbols.DOUBLE_ANGLE_QUOTES_LR_RTL; + } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { + return RtlSymbols.SINGLE_ANGLE_QUOTES_LR_RTL; + } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + if (isPhone) { + // U+060C: "،" ARABIC COMMA + return joinKeys(key("\u060C", SETTINGS_KEY)); + } + // U+060C: "،" ARABIC COMMA + // U+061F: "؟" ARABIC QUESTION MARK + // U+061B: "؛" ARABIC SEMICOLON + return joinKeys(key("\u060C", joinMoreKeys( + ":", "!", "\u061F", "\u061B", "-", "\"", "'", SETTINGS_KEY)), + "_"); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + if (isPhone) { + return super.getKeysRightToSpacebar(isPhone); + } + // U+060C: "،" ARABIC COMMA + // U+061F: "؟" ARABIC QUESTION MARK + // U+061B: "؛" ARABIC SEMICOLON + return joinKeys("/", key(".", getPunctuationMoreKeys(isPhone))); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return ARABIC_DIACRITICS; + } + + // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE + // U+200C: ZERO WIDTH NON-JOINER + // U+0628: "ب" ARABIC LETTER BEH + // U+062C: "ج" ARABIC LETTER JEEM + private static final ExpectedKey ARABIC_ALPHABET_KEY = key( + "\u0623\u200C\u0628\u200C\u062C", Constants.CODE_SWITCH_ALPHA_SYMBOL); + // U+0663: "٣" ARABIC-INDIC DIGIT THREE + // U+0662: "٢" ARABIC-INDIC DIGIT TWO + // U+0661: "١" ARABIC-INDIC DIGIT ONE + // U+061F: "؟" ARABIC QUESTION MARK + private static final ExpectedKey ARABIC_SYMBOLS_KEY = key( + "\u0663\u0662\u0661\u061F", Constants.CODE_SWITCH_ALPHA_SYMBOL); + private static final ExpectedKey ARABIC_BACK_TO_SYMBOLS_KEY = key( + "\u0663\u0662\u0661\u061F", Constants.CODE_SHIFT); + + private static final ExpectedKey[] ARABIC_DIACRITICS = { + // U+0655: "ٕ" ARABIC HAMZA BELOW + // U+0654: "ٔ" ARABIC HAMZA ABOVE + // U+0652: "ْ" ARABIC SUKUN + // U+064D: "ٍ" ARABIC KASRATAN + // U+064C: "ٌ" ARABIC DAMMATAN + // U+064B: "ً" ARABIC FATHATAN + // U+0651: "ّ" ARABIC SHADDA + // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF + // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF + // U+0653: "ٓ" ARABIC MADDAH ABOVE + // U+0650: "ِ" ARABIC KASRA + // U+064F: "ُ" ARABIC DAMMA + // U+064E: "َ" ARABIC FATHA + // U+0640: "ـ" ARABIC TATWEEL + moreKey(" \u0655", "\u0655"), moreKey(" \u0654", "\u0654"), + moreKey(" \u0652", "\u0652"), moreKey(" \u064D", "\u064D"), + moreKey(" \u064C", "\u064C"), moreKey(" \u064B", "\u064B"), + moreKey(" \u0651", "\u0651"), moreKey(" \u0656", "\u0656"), + moreKey(" \u0670", "\u0670"), moreKey(" \u0653", "\u0653"), + moreKey(" \u0650", "\u0650"), moreKey(" \u064F", "\u064F"), + moreKey(" \u064E", "\u064E"), moreKey("\u0640\u0640\u0640", "\u0640") + }; + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + if (isPhone) { + return ALPHABET_COMMON; + } else { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + // U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE + builder.insertKeysAtRow(3, 2, "\u0626"); + return builder.build(); + } + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0636: "ض" ARABIC LETTER DAD + // U+0661: "١" ARABIC-INDIC DIGIT ONE + key("\u0636", joinMoreKeys("1", "\u0661")), + // U+0635: "ص" ARABIC LETTER SAD + // U+0662: "٢" ARABIC-INDIC DIGIT TWO + key("\u0635", joinMoreKeys("2", "\u0662")), + // U+062B: "ث" ARABIC LETTER THEH + // U+0663: "٣" ARABIC-INDIC DIGIT THREE + key("\u062B", joinMoreKeys("3", "\u0663")), + // U+0642: "ق" ARABIC LETTER QAF + // U+0664: "٤" ARABIC-INDIC DIGIT FOUR + // U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE + key("\u0642", joinMoreKeys("4", "\u0664", "\u06A8")), + // U+0641: "ف" ARABIC LETTER FEH + // U+0665: "٥" ARABIC-INDIC DIGIT FIVE + // U+06A4: "ڤ" ARABIC LETTER VEH + // U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW + // U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW + key("\u0641", joinMoreKeys("5", "\u0665", "\u06A4", "\u06A2", "\u06A5")), + // U+063A: "غ" ARABIC LETTER GHAIN + // U+0666: "٦" ARABIC-INDIC DIGIT SIX + key("\u063A", joinMoreKeys("6", "\u0666")), + // U+0639: "ع" ARABIC LETTER AIN + // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN + key("\u0639", joinMoreKeys("7", "\u0667")), + // U+0647: "ه" ARABIC LETTER HEH + // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT + // U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM + // U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER + key("\u0647", joinMoreKeys("8", "\u0668", moreKey("\uFEEB", "\u0647\u200D"))), + // U+062E: "خ" ARABIC LETTER KHAH + // U+0669: "٩" ARABIC-INDIC DIGIT NINE + key("\u062E", joinMoreKeys("9", "\u0669")), + // U+062D: "ح" ARABIC LETTER HAH + // U+0660: "٠" ARABIC-INDIC DIGIT ZERO + key("\u062D", joinMoreKeys("0", "\u0660")), + // U+062C: "ج" ARABIC LETTER JEEM + // U+0686: "چ" ARABIC LETTER TCHEH + key("\u062C", moreKey("\u0686"))) + .setKeysOfRow(2, + // U+0634: "ش" ARABIC LETTER SHEEN + // U+069C: "ڜ" ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE + key("\u0634", moreKey("\u069C")), + // U+0633: "س" ARABIC LETTER SEEN + "\u0633", + // U+064A: "ي" ARABIC LETTER YEH + // U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE + // U+0649: "ى" ARABIC LETTER ALEF MAKSURA + key("\u064A", joinMoreKeys("\u0626", "\u0649")), + // U+0628: "ب" ARABIC LETTER BEH + // U+067E: "پ" ARABIC LETTER PEH + key("\u0628", moreKey("\u067E")), + // U+0644: "ل" ARABIC LETTER LAM + // U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + // U+0627: "ا" ARABIC LETTER ALEF + // U+FEF7: "ﻷ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE + // U+FEF9: "ﻹ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + // U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW + // U+FEF5: "ﻵ" ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + // U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE + key("\u0644", + moreKey("\uFEFB", "\u0644\u0627"), moreKey("\uFEF7", "\u0644\u0623"), + moreKey("\uFEF9", "\u0644\u0625"), moreKey("\uFEF5", "\u0644\u0622")), + // U+0627: "ا" ARABIC LETTER ALEF + // U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE + // U+0621: "ء" ARABIC LETTER HAMZA + // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE + // U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW + // U+0671: "ٱ" ARABIC LETTER ALEF WASLA + key("\u0627", joinMoreKeys("\u0622", "\u0621", "\u0623", "\u0625", "\u0671")), + // U+062A: "ت" ARABIC LETTER TEH + // U+0646: "ن" ARABIC LETTER NOON + // U+0645: "م" ARABIC LETTER MEEM + "\u062A", "\u0646", "\u0645", + // U+0643: "ك" ARABIC LETTER KAF + // U+06AF: "گ" ARABIC LETTER GAF + // U+06A9: "ک" ARABIC LETTER KEHEH + key("\u0643", joinMoreKeys("\u06AF", "\u06A9")), + // U+0637: "ط" ARABIC LETTER TAH + "\u0637") + .setKeysOfRow(3, + // U+0630: "ذ" ARABIC LETTER THAL + // U+0621: "ء" ARABIC LETTER HAMZA + // U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE + // U+0631: "ر" ARABIC LETTER REH + "\u0630", "\u0621", "\u0624", "\u0631", + // U+0649: "ى" ARABIC LETTER ALEF MAKSURA + // U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE + key("\u0649", moreKey("\u0626")), + // U+0629: "ة" ARABIC LETTER TEH MARBUTA + // U+0648: "و" ARABIC LETTER WAW + "\u0629", "\u0648", + // U+0632: "ز" ARABIC LETTER ZAIN + // U+0698: "ژ" ARABIC LETTER JEH + key("\u0632", moreKey("\u0698")), + // U+0638: "ظ" ARABIC LETTER ZAH + // U+062F: "د" ARABIC LETTER DAL + "\u0638", "\u062F") + .build(); + + private static class ArabicSymbols extends RtlSymbols { + public ArabicSymbols(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+0661: "١" ARABIC-INDIC DIGIT ONE + // 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 + .replaceKeyOfLabel("1", key("\u0661", + joinMoreKeys("1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B"))) + // U+0662: "٢" ARABIC-INDIC DIGIT TWO + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + .replaceKeyOfLabel("2", key("\u0662", joinMoreKeys("2", "\u00B2", "\u2154"))) + // U+0663: "٣" ARABIC-INDIC DIGIT THREE + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + .replaceKeyOfLabel("3", key("\u0663", + joinMoreKeys("3", "\u00B3", "\u00BE", "\u215C"))) + // U+0664: "٤" ARABIC-INDIC DIGIT FOUR + // U+2074: "⁴" SUPERSCRIPT FOUR + .replaceKeyOfLabel("4", key("\u0664", joinMoreKeys("4", "\u2074"))) + // U+0665: "٥" ARABIC-INDIC DIGIT FIVE + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + .replaceKeyOfLabel("5", key("\u0665", joinMoreKeys("5", "\u215D"))) + // U+0666: "٦" ARABIC-INDIC DIGIT SIX + .replaceKeyOfLabel("6", key("\u0666", moreKey("6"))) + // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + .replaceKeyOfLabel("7", key("\u0667", joinMoreKeys("7", "\u215E"))) + // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT + .replaceKeyOfLabel("8", key("\u0668", moreKey("8"))) + // U+0669: "٩" ARABIC-INDIC DIGIT NINE + .replaceKeyOfLabel("9", key("\u0669", moreKey("9"))) + // U+0660: "٠" ARABIC-INDIC DIGIT ZERO + // U+066B: "٫" ARABIC DECIMAL SEPARATOR + // U+066C: "٬" ARABIC THOUSANDS SEPARATOR + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + .replaceKeyOfLabel("0", key("\u0660", + joinMoreKeys("0", "\u066B", "\u066C", "\u207F", "\u2205"))) + // U+066A: "٪" ARABIC PERCENT SIGN + // U+2030: "‰" PER MILLE SIGN + .replaceKeyOfLabel("%", key("\u066A", joinMoreKeys("%", "\u2030"))) + // U+061B: "؛" ARABIC SEMICOLON + .replaceKeyOfLabel(";", key("\u061B", moreKey(";"))) + // U+061F: "؟" ARABIC QUESTION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + .replaceKeyOfLabel("?", key("\u061F", joinMoreKeys("?", "\u00BF"))) + // U+060C: "،" ARABIC COMMA + .replaceKeyOfLabel(",", "\u060C") + // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS + // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS + .replaceKeyOfLabel("(", key("(", ")", + moreKey("\uFD3E", "\uFD3F"), moreKey("<", ">"), moreKey("{", "}"), + moreKey("[", "]"))) + // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS + // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS + .replaceKeyOfLabel(")", key(")", "(", + moreKey("\uFD3F", "\uFD3E"), moreKey(">", "<"), moreKey("}", "{"), + moreKey("]", "["))) + // U+2605: "★" BLACK STAR + // U+066D: "٭" ARABIC FIVE POINTED STAR + .setMoreKeysOf("*", "\u2605", "\u066D") + .build(); + } + } + + private static class ArabicSymbolsShifted extends RtlSymbolsShifted { + public ArabicSymbolsShifted(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+2022: "•" BULLET + // U+266A: "♪" EIGHTH NOTE + .setMoreKeysOf("\u2022", "\u266A") + // U+060C: "،" ARABIC COMMA + .replaceKeyOfLabel(",", "\u060C") + .build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java b/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java new file mode 100644 index 000000000..eb64b832b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java @@ -0,0 +1,216 @@ +/* + * 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.latin.Constants; + +import java.util.Locale; + +/** + * The Armenian Phonetic alphabet keyboard. + */ +public final class ArmenianPhonetic extends LayoutBase { + private static final String LAYOUT_NAME = "armenian_phonetic"; + + public ArmenianPhonetic(final LayoutCustomizer customizer) { + super(customizer, ArmenianSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class ArmenianPhoneticCustomizer extends LayoutCustomizer { + public ArmenianPhoneticCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return ARMENIAN_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + if (isPhone) { + return EMPTY_KEYS; + } + // U+055C: "՜" ARMENIAN EXCLAMATION MARK + // U+00A1: "¡" INVERTED EXCLAMATION MARK + // U+055E: "՞" ARMENIAN QUESTION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + return joinKeys(key("!", joinMoreKeys("\u055C", "\u00A1")), + key("?", joinMoreKeys("\u055E", "\u00BF")), + SHIFT_KEY); + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + // U+002C: "," COMMA + // U+055D: "՝" ARMENIAN COMMA + return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY)) + : joinKeys(key("\u055D", SETTINGS_KEY), "_"); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + // U+0589: "։" ARMENIAN FULL STOP + // U+055D: "՝" ARMENIAN COMMA + final ExpectedKey fullStopKey = key("\u0589", getPunctuationMoreKeys(isPhone)); + return isPhone ? joinKeys(fullStopKey) : joinKeys("/", fullStopKey); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return ARMENIAN_PUNCTUATION_MORE_KEYS; + } + + // U+0531: "Ա" ARMENIAN CAPITAL LETTER AYB + // U+0532: "Բ" ARMENIAN CAPITAL LETTER BEN + // U+0533: "Գ" ARMENIAN CAPITAL LETTER GIM + private static final ExpectedKey ARMENIAN_ALPHABET_KEY = key( + "\u0531\u0532\u0533", Constants.CODE_SWITCH_ALPHA_SYMBOL); + + // U+055E: "՞" ARMENIAN QUESTION MARK + // U+055C: "՜" ARMENIAN EXCLAMATION MARK + // U+055A: "՚" ARMENIAN APOSTROPHE + // U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING + // U+055D: "՝" ARMENIAN COMMA + // U+055B: "՛" ARMENIAN EMPHASIS MARK + // U+058A: "֊" ARMENIAN HYPHEN + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+055F: "՟" ARMENIAN ABBREVIATION MARK + private static final ExpectedKey[] ARMENIAN_PUNCTUATION_MORE_KEYS = joinMoreKeys( + ",", "\u055E", "\u055C", ".", "\u055A", "\u0559", "?", "!", + "\u055D", "\u055B", "\u058A", "\u00BB", "\u00AB", "\u055F", ";", ":"); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + if (isPhone) { + // U+056D: "խ" ARMENIAN SMALL LETTER XEH + // U+0577: "շ" ARMENIAN SMALL LETTER SHA + builder.addKeysOnTheRightOfRow(3, "\u056D") + .addKeysOnTheRightOfRow(4, "\u0577"); + } else { + // U+056D: "խ" ARMENIAN SMALL LETTER XEH + // U+0577: "շ" ARMENIAN SMALL LETTER SHA + builder.addKeysOnTheRightOfRow(2, "\u056D") + .addKeysOnTheRightOfRow(3, "\u0577"); + } + return builder.build(); + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(4, DELETE_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0567: "է" ARMENIAN SMALL LETTER EH + key("\u0567", moreKey("1")), + // U+0569: "թ" ARMENIAN SMALL LETTER TO + key("\u0569", moreKey("2")), + // U+0583: "փ" ARMENIAN SMALL LETTER PIWR + key("\u0583", moreKey("3")), + // U+0571: "ձ" ARMENIAN SMALL LETTER JA + key("\u0571", moreKey("4")), + // U+057B: "ջ" ARMENIAN SMALL LETTER JHEH + key("\u057B", moreKey("5")), + // U+0580: "ր" ARMENIAN SMALL LETTER REH + key("\u0580", moreKey("6")), + // U+0579: "չ" ARMENIAN SMALL LETTER CHA + key("\u0579", moreKey("7")), + // U+0573: "ճ" ARMENIAN SMALL LETTER CHEH + key("\u0573", moreKey("8")), + // U+056A: "ժ" ARMENIAN SMALL LETTER ZHE + key("\u056A", moreKey("9")), + // U+056E: "ծ" ARMENIAN SMALL LETTER CA + key("\u056E", moreKey("0"))) + .setKeysOfRow(2, + // U+0584: "ք" ARMENIAN SMALL LETTER KEH + // U+0578: "ո" ARMENIAN SMALL LETTER VO + "\u0584", "\u0578", + // U+0565: "ե" ARMENIAN SMALL LETTER ECH + // U+0587: "և" ARMENIAN SMALL LIGATURE ECH YIWN + key("\u0565", moreKey("\u0587")), + // U+057C: "ռ" ARMENIAN SMALL LETTER RA + // U+057F: "տ" ARMENIAN SMALL LETTER TIWN + // U+0568: "ը" ARMENIAN SMALL LETTER ET + // U+0582: "ւ" ARMENIAN SMALL LETTER YIWN + // U+056B: "ի" ARMENIAN SMALL LETTER INI + // U+0585: "օ" ARMENIAN SMALL LETTER OH + // U+057A: "պ" ARMENIAN SMALL LETTER PEH + "\u057C", "\u057F", "\u0568", "\u0582", "\u056B", "\u0585", "\u057A") + .setKeysOfRow(3, + // U+0561: "ա" ARMENIAN SMALL LETTER AYB + // U+057D: "ս" ARMENIAN SMALL LETTER SEH + // U+0564: "դ" ARMENIAN SMALL LETTER DA + // U+0586: "ֆ" ARMENIAN SMALL LETTER FEH + // U+0563: "գ" ARMENIAN SMALL LETTER GIM + // U+0570: "հ" ARMENIAN SMALL LETTER HO + // U+0575: "յ" ARMENIAN SMALL LETTER YI + // U+056F: "կ" ARMENIAN SMALL LETTER KEN + // U+056C: "լ" ARMENIAN SMALL LETTER LIWN + "\u0561", "\u057D", "\u0564", "\u0586", "\u0563", "\u0570", "\u0575", "\u056F", + "\u056C") + .setKeysOfRow(4, + // U+0566: "զ" ARMENIAN SMALL LETTER ZA + // U+0572: "ղ" ARMENIAN SMALL LETTER GHAD + // U+0581: "ց" ARMENIAN SMALL LETTER CO + // U+057E: "վ" ARMENIAN SMALL LETTER VEW + // U+0562: "բ" ARMENIAN SMALL LETTER BEN + // U+0576: "ն" ARMENIAN SMALL LETTER NOW + // U+0574: "մ" ARMENIAN SMALL LETTER MEN + "\u0566", "\u0572", "\u0581", "\u057E", "\u0562", "\u0576", "\u0574") + .build(); + + private static final class ArmenianSymbols extends Symbols { + public ArmenianSymbols(final LayoutCustomizer customizer) { super(customizer); } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder( + super.getLayout(isPhone)); + // U+055C: "՜" ARMENIAN EXCLAMATION MARK + // U+00A1: "¡" INVERTED EXCLAMATION MARK + // U+055E: "՞" ARMENIAN QUESTION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + builder.setMoreKeysOf("!", "\u055C", "\u00A1") + .setMoreKeysOf("?", "\u055E", "\u00BF"); + return builder.build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Azerty.java b/tests/src/com/android/inputmethod/keyboard/layout/Azerty.java new file mode 100644 index 000000000..a0949637b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Azerty.java @@ -0,0 +1,77 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +/** + * The AZERTY alphabet keyboard. + */ +public final class Azerty extends LayoutBase { + public Azerty(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return "azerty"; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + customizer.setAccentedLetters(builder); + builder.replaceKeyOfLabel(ROW3_QUOTE, key("'", joinMoreKeys( + customizer.getSingleQuoteMoreKeys(), + customizer.getSingleAngleQuoteKeys()))); + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + final ExpectedKeyboardBuilder builder; + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED + || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { + builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone)); + } else { + builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + getCustomizer().setAccentedLetters(builder); + builder.replaceKeyOfLabel(ROW3_QUOTE, "?"); + } + builder.toUpperCase(getLocale()); + return builder.build(); + } + + private static final String ROW3_QUOTE = "ROW3_QUOUTE"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("a", additionalMoreKey("1")), + key("z", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("y", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0"))) + .setKeysOfRow(2, "q", "s", "d", "f", "g", "h", "j", "k", "l", "m") + .setKeysOfRow(3, "w", "x", "c", "v", "b", "n", ROW3_QUOTE) + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Bengali.java b/tests/src/com/android/inputmethod/keyboard/layout/Bengali.java new file mode 100644 index 000000000..79c7d08cc --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Bengali.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.latin.Constants; + +import java.util.Locale; + +/** + * The Bengali keyboard. + */ +public final class Bengali extends LayoutBase { + private static final String LAYOUT_NAME = "bengali"; + + public Bengali(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class BengaliCustomizer extends LayoutCustomizer { + public BengaliCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return BENGALI_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { return EMPTY_KEYS; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + // U+0995: "क" BENGALI LETTER KA + // U+0996: "ख" BENGALI LETTER KHA + // U+0997: "ग" BENGALI LETTER GA + private static final ExpectedKey BENGALI_ALPHABET_KEY = key( + "\u0995\u0996\u0997", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0994: "ঔ" BENGALI LETTER AU + // U+09E7: "১" BENGALI DIGIT ONE + key("\u0994", joinMoreKeys("\u09E7", "1")), + // U+0990: "ঐ" BENGALI LETTER AI + // U+09E8: "২" BENGALI DIGIT TWO + key("\u0990", joinMoreKeys("\u09E8", "2")), + // U+0986: "আ" BENGALI LETTER AA + // U+09E9: "৩" BENGALI DIGIT THREE + key("\u0986", joinMoreKeys("\u09E9", "3")), + // U+0988: "ঈ" BENGALI LETTER II + // U+09EA: "৪" BENGALI DIGIT FOUR + key("\u0988", joinMoreKeys("\u09EA", "4")), + // U+098A: "ঊ" BENGALI LETTER UU + // U+09EB: "৫" BENGALI DIGIT FIVE + key("\u098A", joinMoreKeys("\u09EB", "5")), + // U+09AC: "ব" BENGALI LETTER BA + // U+09AD: "ভ" BENGALI LETTER BHA + // U+09EC: "৬" BENGALI DIGIT SIX + key("\u09AC", joinMoreKeys("\u09AD", "\u09EC", "6")), + // U+09B9: "হ" BENGALI LETTER HA + // U+09ED: "৭" BENGALI DIGIT SEVEN + key("\u09B9", joinMoreKeys("\u09ED", "7")), + // U+0997: "গ" BENGALI LETTER GA + // U+0998: "ঘ" BENGALI LETTER GHA + // U+09EE: "৮" BENGALI DIGIT EIGHT + key("\u0997", joinMoreKeys("\u0998", "\u09EE", "8")), + // U+09A6: "দ" BENGALI LETTER DA + // U+09A7: "ধ" BENGALI LETTER DHA + // U+09EF: "৯" BENGALI DIGIT NINE + key("\u09A6", joinMoreKeys("\u09A7", "\u09EF", "9")), + // U+099C: "জ" BENGALI LETTER JA + // U+099D: "ঝ" BENGALI LETTER JHA + // U+099C/U+09CD/U+099E: + // "জ্ঞ" BENGALI LETTER JA/BENGALI SIGN VIRAMA/BENGALI LETTER NYA + // U+09E6: "০" BENGALI DIGIT ZERO + key("\u099C", joinMoreKeys("\u099D", "\u099C\u09CD\u099E", "\u09E6", "0")), + // U+09A1: "ড" BENGALI LETTER DDA + // U+09A1/U+09BC: "ড়" BENGALI LETTER DDA/BENGALI SIGN NUKTA + key("\u09A1", moreKey("\u09A1\u09BC"))) + .setKeysOfRow(2, + // U+0993: "ও" BENGALI LETTER O + // U+09CB: "ো" BENGALI VOWEL SIGN O + key("\u0993", moreKey("\u09CB")), + // U+098F: "এ" BENGALI LETTER E + // U+09C7: "ে" BENGALI VOWEL SIGN E + key("\u098F", moreKey("\u09C7")), + // U+0985: "অ" BENGALI LETTER A + // U+09CD: "্" BENGALI SIGN VIRAMA + key("\u0985", moreKey("\u09CD")), + // U+0987: "ই" BENGALI LETTER I + // U+09BF: "ি" BENGALI VOWEL SIGN I + key("\u0987", moreKey("\u09BF")), + // U+0989: "উ" BENGALI LETTER U + // U+09C1: "ু" BENGALI VOWEL SIGN U + key("\u0989", moreKey("\u09C1")), + // U+09AA: "প" BENGALI LETTER PA + // U+09AB: "ফ" BENGALI LETTER PHA + key("\u09AA", moreKey("\u09AB")), + // U+09B0: "র" BENGALI LETTER RA + // U+09C3: "ৃ" BENGALI VOWEL SIGN VOCALIC R + // U+098B: "ঋ" BENGALI LETTER VOCALIC R + // U+09A4/U+09CD/U+09B0: + // "ত্র" BENGALI LETTER TA/BENGALI SIGN VIRAMA/BENGALI LETTER RA + key("\u09B0", joinMoreKeys("\u09C3", "\u098B", "\u09A4\u09CD\u09B0")), + // U+0995: "ক" BENGALI LETTER KA + // U+0996: "খ" BENGALI LETTER KHA + key("\u0995", moreKey("\u0996")), + // U+09A4: "ত" BENGALI LETTER TA + // U+09CE: "ৎ" BENGALI LETTER KHANDA TA + // U+09A5: "থ" BENGALI LETTER THA + // U+09A4/U+09CD/U+09A4: + // "ত্ত" BENGALI LETTER TA/BENGALI SIGN VIRAMA/BENGALI LETTER TA + key("\u09A4", joinMoreKeys("\u09CE", "\u09A5", "\u09A4\u09CD\u09A4")), + // U+099A: "চ" BENGALI LETTER CA + // U+099B: "ছ" BENGALI LETTER CHA + key("\u099A", moreKey("\u099B")), + // U+099F: "ট" BENGALI LETTER TTA + // U+09A0: "ঠ" BENGALI LETTER TTHA + key("\u099F", moreKey("\u09A0"))) + .setKeysOfRow(3, + // U+0981: "ঁ" BENGALI SIGN CANDRABINDU + // U+0983: "ঃ" BENGALI SIGN VISARGA + // U+0982: "ং" BENGALI SIGN ANUSVARA + key("\u0981", joinMoreKeys("\u0983", "\u0982")), + // U+09A2: "ঢ" BENGALI LETTER DDHA + // U+09A2/U+09BC: "ঢ়" BENGALI LETTER DDHA/BENGALI SIGN NUKTA + key("\u09A2", moreKey("\u09A2\u09BC")), + // U+09AE: "ম" BENGALI LETTER MA + "\u09AE", + // U+09A8: "ন" BENGALI LETTER NA + // U+09A3: "ণ" BENGALI LETTER NNA + key("\u09A8", moreKey("\u09A3")), + // U+099E: "ঞ" BENGALI LETTER NYA + // U+0999: "ঙ" BENGALI LETTER NGA + // U+099E/U+09CD/U+099C: + // "ঞ্জ" BENGALI LETTER NYA/BENGALI SIGN VIRAMA/BENGALI LETTER JA + key("\u099E", joinMoreKeys("\u0999", "\u099E\u09CD\u099C")), + // U+09B2: "ল" BENGALI LETTER LA + "\u09B2", + // U+09B7: "ষ" BENGALI LETTER SSA + // U+0995/U+09CD/U+09B7: + // "ক্ষ" BENGALI LETTER KA/BENGALI SIGN VIRAMA/BENGALI LETTER SSA + key("\u09B7", moreKey("\u0995\u09CD\u09B7")), + // U+09B8: "স" BENGALI LETTER SA + // U+09B6: "শ" BENGALI LETTER SHA + key("\u09B8", moreKey("\u09B6")), + // U+09DF: "য়" BENGALI LETTER YYA + // U+09AF: "য" BENGALI LETTER YA + key("\u09DF", moreKey("\u09AF")), + // U+0964: "।" DEVANAGARI DANDA + // U+0965: "॥" DEVANAGARI DOUBLE DANDA + key("\u0964", moreKey("\u0965"))) + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Bulgarian.java b/tests/src/com/android/inputmethod/keyboard/layout/Bulgarian.java new file mode 100644 index 000000000..3282e44ae --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Bulgarian.java @@ -0,0 +1,105 @@ +/* + * 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.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +public final class Bulgarian extends LayoutBase { + private static final String LAYOUT_NAME = "bulgarian"; + + public Bulgarian(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class BulgarianCustomizer extends LayoutCustomizer { + private final EastSlavicCustomizer mEastSlavicCustomizer; + + public BulgarianCustomizer(final Locale locale) { + super(locale); + mEastSlavicCustomizer = new EastSlavicCustomizer(locale); + } + + @Override + public ExpectedKey getAlphabetKey() { + return mEastSlavicCustomizer.getAlphabetKey(); + } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+044F: "я" CYRILLIC SMALL LETTER YA + key("\u044F", moreKey("1")), + // U+0432: "в" CYRILLIC SMALL LETTER VE + key("\u0432", moreKey("2")), + // U+0435: "е" CYRILLIC SMALL LETTER IE + key("\u0435", moreKey("3")), + // U+0440: "р" CYRILLIC SMALL LETTER ER + key("\u0440", moreKey("4")), + // U+0442: "т" CYRILLIC SMALL LETTER TE + key("\u0442", moreKey("5")), + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + key("\u044A", moreKey("6")), + // U+0443: "у" CYRILLIC SMALL LETTER U + key("\u0443", moreKey("7")), + // U+0438: "и" CYRILLIC SMALL LETTER I + // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE + key("\u0438", joinMoreKeys("8", "\u045D")), + // U+043E: "о" CYRILLIC SMALL LETTER O + key("\u043E", moreKey("9")), + // U+043F: "п" CYRILLIC SMALL LETTER PE + key("\u043F", moreKey("0")), + // U+0447: "ч" CYRILLIC SMALL LETTER CHE + "\u0447") + .setKeysOfRow(2, + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+0441: "с" CYRILLIC SMALL LETTER ES + // U+0434: "д" CYRILLIC SMALL LETTER DE + // U+0444: "ф" CYRILLIC SMALL LETTER EF + // U+0433: "г" CYRILLIC SMALL LETTER GHE + // U+0445: "х" CYRILLIC SMALL LETTER HA + // U+0439: "й" CYRILLIC SMALL LETTER SHORT I + // U+043A: "к" CYRILLIC SMALL LETTER KA + // U+043B: "л" CYRILLIC SMALL LETTER EL + // U+0448: "ш" CYRILLIC SMALL LETTER SHA + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + "\u0430", "\u0441", "\u0434", "\u0444", "\u0433", "\u0445", "\u0439", "\u043A", + "\u043B", "\u0448", "\u0449") + .setKeysOfRow(3, + // U+0437: "з" CYRILLIC SMALL LETTER ZE + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+0446: "ц" CYRILLIC SMALL LETTER TSE + // U+0436: "ж" CYRILLIC SMALL LETTER ZHE + // U+0431: "б" CYRILLIC SMALL LETTER BE + // U+043D: "н" CYRILLIC SMALL LETTER EN + // U+043C: "м" CYRILLIC SMALL LETTER EM + // U+044E: "ю" CYRILLIC SMALL LETTER YU + "\u0437", "\u044C", "\u0446", "\u0436", "\u0431", "\u043D", "\u043C", "\u044E") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/BulgarianBds.java b/tests/src/com/android/inputmethod/keyboard/layout/BulgarianBds.java new file mode 100644 index 000000000..20a5f7dac --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/BulgarianBds.java @@ -0,0 +1,97 @@ +/* + * 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.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +public final class BulgarianBds extends LayoutBase { + private static final String LAYOUT_NAME = "bulgarian_bds"; + + public BulgarianBds(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class BulgarianBdsCustomizer extends EastSlavicCustomizer { + public BulgarianBdsCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0443: "у" CYRILLIC SMALL LETTER U + key("\u0443", moreKey("1")), + // U+0435: "е" CYRILLIC SMALL LETTER IE + key("\u0435", moreKey("2")), + // U+0438: "и" CYRILLIC SMALL LETTER I + // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE + key("\u0438", joinMoreKeys("3", "\u045D")), + // U+0448: "ш" CYRILLIC SMALL LETTER SHA + key("\u0448", moreKey("4")), + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + key("\u0449", moreKey("5")), + // U+043A: "к" CYRILLIC SMALL LETTER KA + key("\u043A", moreKey("6")), + // U+0441: "с" CYRILLIC SMALL LETTER ES + key("\u0441", moreKey("7")), + // U+0434: "д" CYRILLIC SMALL LETTER DE + key("\u0434", moreKey("8")), + // U+0437: "з" CYRILLIC SMALL LETTER ZE + key("\u0437", moreKey("9")), + // U+0446: "ц" CYRILLIC SMALL LETTER TSE + key("\u0446", moreKey("0")), + // U+0431: "б" CYRILLIC SMALL LETTER BE + "\u0431") + .setKeysOfRow(2, + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044F: "я" CYRILLIC SMALL LETTER YA + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+043E: "о" CYRILLIC SMALL LETTER O + // U+0436: "ж" CYRILLIC SMALL LETTER ZHE + // U+0433: "г" CYRILLIC SMALL LETTER GHE + // U+0442: "т" CYRILLIC SMALL LETTER TE + // U+043D: "н" CYRILLIC SMALL LETTER EN + // U+0432: "в" CYRILLIC SMALL LETTER VE + // U+043C: "м" CYRILLIC SMALL LETTER EM + // U+0447: "ч" CYRILLIC SMALL LETTER CHE + "\u044C", "\u044F", "\u0430", "\u043E", "\u0436", "\u0433", "\u0442", "\u043D", + "\u0432", "\u043C", "\u0447") + .setKeysOfRow(3, + // U+044E: "ю" CYRILLIC SMALL LETTER YU + // U+0439: "й" CYRILLIC SMALL LETTER SHORT I + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + // U+044D: "э" CYRILLIC SMALL LETTER E + // U+0444: "ф" CYRILLIC SMALL LETTER EF + // U+0445: "х" CYRILLIC SMALL LETTER HA + // U+043F: "п" CYRILLIC SMALL LETTER PE + // U+0440: "р" CYRILLIC SMALL LETTER ER + // U+043B: "л" CYRILLIC SMALL LETTER EL + "\u044E", "\u0439", "\u044A", "\u044D", "\u0444", "\u0445", "\u043F", "\u0440", + "\u043B") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Colemak.java b/tests/src/com/android/inputmethod/keyboard/layout/Colemak.java new file mode 100644 index 000000000..a4a9701cd --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Colemak.java @@ -0,0 +1,76 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +/** + * The Colemak alphabet keyboard. + */ +public final class Colemak extends LayoutBase { + private static final String LAYOUT_NAME = "colemak"; + + public Colemak(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + getCustomizer().setAccentedLetters(builder); + builder.replaceKeyOfLabel(ROW1_10, key(";", additionalMoreKey("0"), moreKey(":"))); + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + final ExpectedKeyboardBuilder builder; + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED + || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { + builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone)); + } else { + builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + getCustomizer().setAccentedLetters(builder); + builder.replaceKeyOfLabel(ROW1_10, key(":", additionalMoreKey("0"))); + } + builder.toUpperCase(getLocale()); + return builder.build(); + } + + private static final String ROW1_10 = "ROW1_10"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("f", additionalMoreKey("3")), + key("p", additionalMoreKey("4")), + key("g", additionalMoreKey("5")), + key("j", additionalMoreKey("6")), + key("l", additionalMoreKey("7")), + key("u", additionalMoreKey("8")), + key("y", additionalMoreKey("9")), + ROW1_10) + .setKeysOfRow(2, "a", "r", "s", "t", "d", "h", "n", "e", "i", "o") + .setKeysOfRow(3, "z", "x", "c", "v", "b", "k", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/DevanagariLetterConstants.java b/tests/src/com/android/inputmethod/keyboard/layout/DevanagariLetterConstants.java new file mode 100644 index 000000000..bcf06f085 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/DevanagariLetterConstants.java @@ -0,0 +1,75 @@ +/* + * 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 android.os.Build; + +/** + * This class offers label strings of Devanagari letters that need the dotted circle to draw + * its glyph. + */ +class DevanagariLetterConstants { + private static final boolean NEEDS_DOTTED_CIRCLE = + Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN; + // U+25CC: "◌" DOTTED CIRCLE + private static final String DOTTED_CIRCLE = NEEDS_DOTTED_CIRCLE ? "\u25CC" : ""; + + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + static final String SIGN_CANDRABINDU = DOTTED_CIRCLE + "\u0901"; + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + static final String SIGN_ANUSVARA = DOTTED_CIRCLE + "\u0902"; + // U+0903: "ः" DEVANAGARI SIGN VISARGA + static final String SIGN_VISARGA = DOTTED_CIRCLE + "\u0903"; + // U+093C: "़" DEVANAGARI SIGN NUKTA + static final String SIGN_NUKTA = DOTTED_CIRCLE + "\u093C"; + // U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA + static final String SIGN_AVAGRAHA = DOTTED_CIRCLE + "\u093D"; + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + static final String VOWEL_SIGN_AA = DOTTED_CIRCLE + "\u093E"; + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + static final String VOWEL_SIGN_I = DOTTED_CIRCLE + "\u093F"; + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + static final String VOWEL_SIGN_II = DOTTED_CIRCLE + "\u0940"; + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + static final String VOWEL_SIGN_U = DOTTED_CIRCLE + "\u0941"; + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + static final String VOWEL_SIGN_UU = DOTTED_CIRCLE + "\u0942"; + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + static final String VOWEL_SIGN_VOCALIC_R = DOTTED_CIRCLE + "\u0943"; + // U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR + static final String VOWEL_SIGN_VOCALIC_RR = DOTTED_CIRCLE + "\u0944"; + // U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E + static final String VOWEL_SIGN_CANDRA_E = DOTTED_CIRCLE + "\u0945"; + // U+0947: "े" DEVANAGARI VOWEL SIGN E + static final String VOWEL_SIGN_E = DOTTED_CIRCLE + "\u0947"; + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + static final String VOWEL_SIGN_AI = DOTTED_CIRCLE + "\u0948"; + // U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O + static final String VOWEL_SIGN_CANDRA_O = DOTTED_CIRCLE + "\u0949"; + // U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O + static final String VOWEL_SIGN_SHORT_O = DOTTED_CIRCLE + "\u094A"; + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + static final String VOWEL_SIGN_O = DOTTED_CIRCLE + "\u094B"; + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + static final String VOWEL_SIGN_AU = DOTTED_CIRCLE + "\u094C"; + // U+094D: "्" DEVANAGARI SIGN VIRAMA + static final String SIGN_VIRAMA = DOTTED_CIRCLE + "\u094D"; + // U+0970: "॰" DEVANAGARI ABBREVIATION SIGN + static final String ABBREVIATION_SIGN = DOTTED_CIRCLE + "\u0970"; + // U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP + static final String LETTER_GLOTTAL_STOP = DOTTED_CIRCLE + "\u097D"; +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java new file mode 100644 index 000000000..e75cfd0ff --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java @@ -0,0 +1,122 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey; + +import java.util.Locale; + +/** + * The QWERTY alphabet keyboard. + */ +public final class Dvorak extends LayoutBase { + private static final String LAYOUT_NAME = "dvorak"; + + public Dvorak(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class DvorakCustomizer extends LayoutCustomizer { + public DvorakCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return isPhone ? joinKeys(SHIFT_KEY): joinKeys(SHIFT_KEY, key("q")); + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : joinKeys(key("z"), SHIFT_KEY); + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + return isPhone ? joinKeys(key("q", SETTINGS_KEY)) : + joinKeys(SETTINGS_KEY, key("_", moreKey("-"))); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + final ExpectedAdditionalMoreKey[] punctuationMoreKeys = + convertToAdditionalMoreKeys(getPunctuationMoreKeys(isPhone)); + return isPhone + ? joinKeys(key("z", punctuationMoreKeys)) + : joinKeys("/", key("?", moreKey("!"))); + } + + private static ExpectedAdditionalMoreKey[] convertToAdditionalMoreKeys( + final ExpectedKey ... moreKeys) { + final ExpectedAdditionalMoreKey[] additionalMoreKeys = + new ExpectedAdditionalMoreKey[moreKeys.length]; + for (int index = 0; index < moreKeys.length; index++) { + additionalMoreKeys[index] = ExpectedAdditionalMoreKey.newInstance(moreKeys[index]); + } + return additionalMoreKeys; + } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_SYMBOLS + || elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) { + return super.getLayout(isPhone, elementId); + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder( + getCommonAlphabetLayout(isPhone)); + if (elementId == KeyboardId.ELEMENT_ALPHABET + || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + builder.addKeysOnTheLeftOfRow(1, + key("'", joinMoreKeys(additionalMoreKey("1"), "!", "\"")), + key(",", joinMoreKeys(additionalMoreKey("2"), "?", "<")), + key(".", joinMoreKeys(additionalMoreKey("3"), ">"))); + } else { + builder.addKeysOnTheLeftOfRow(1, + key("\"", additionalMoreKey("1")), + key("<", additionalMoreKey("2")), + key(">", additionalMoreKey("3"))); + } + convertCommonLayoutToKeyboard(builder, isPhone); + getCustomizer().setAccentedLetters(builder); + if (elementId != KeyboardId.ELEMENT_ALPHABET) { + builder.toUpperCase(getLocale()); + builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY); + } + return builder.build(); + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("p", additionalMoreKey("4")), + key("y", additionalMoreKey("5")), + key("f", additionalMoreKey("6")), + key("g", additionalMoreKey("7")), + key("c", additionalMoreKey("8")), + key("r", additionalMoreKey("9")), + key("l", additionalMoreKey("0"))) + .setKeysOfRow(2, "a", "o", "e", "u", "i", "d", "h", "t", "n", "s") + .setKeysOfRow(3, "j", "k", "x", "b", "m", "w", "v") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/EastSlavic.java b/tests/src/com/android/inputmethod/keyboard/layout/EastSlavic.java new file mode 100644 index 000000000..7fcc974c2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/EastSlavic.java @@ -0,0 +1,110 @@ +/* + * 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.latin.Constants; + +import java.util.Locale; + +public final class EastSlavic extends LayoutBase { + private static final String LAYOUT_NAME = "east_slavic"; + + public EastSlavic(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class EastSlavicCustomizer extends LayoutCustomizer { + public EastSlavicCustomizer(final Locale locale) { + super(locale); + } + + @Override + public final ExpectedKey getAlphabetKey() { return EAST_SLAVIC_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + private static final ExpectedKey EAST_SLAVIC_ALPHABET_KEY = key( + "\u0410\u0411\u0412", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + public static final String ROW1_9 = "ROW1_9"; + public static final String ROW2_2 = "ROW2_2"; + public static final String ROW2_11 = "ROW2_11"; + public static final String ROW3_5 = "ROW3_5"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0443: "у" CYRILLIC SMALL LETTER U + key("\u0439", additionalMoreKey("1")), + // U+0446: "ц" CYRILLIC SMALL LETTER TSE + key("\u0446", additionalMoreKey("2")), + // U+0439: "й" CYRILLIC SMALL LETTER SHORT I + key("\u0443", additionalMoreKey("3")), + // U+043A: "к" CYRILLIC SMALL LETTER KA + key("\u043A", additionalMoreKey("4")), + // U+0435: "е" CYRILLIC SMALL LETTER IE + key("\u0435", additionalMoreKey("5")), + // U+043D: "н" CYRILLIC SMALL LETTER EN + key("\u043D", additionalMoreKey("6")), + // U+0433: "г" CYRILLIC SMALL LETTER GHE + key("\u0433", additionalMoreKey("7")), + // U+0448: "ш" CYRILLIC SMALL LETTER SHA + key("\u0448", additionalMoreKey("8")), + key(ROW1_9, additionalMoreKey("9")), + // U+0437: "з" CYRILLIC SMALL LETTER ZE + key("\u0437", additionalMoreKey("0")), + // U+0445: "х" CYRILLIC SMALL LETTER HA + "\u0445") + .setKeysOfRow(2, + // U+0444: "ф" CYRILLIC SMALL LETTER EF + // U+0432: "в" CYRILLIC SMALL LETTER VE + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+043F: "п" CYRILLIC SMALL LETTER PE + // U+0440: "р" CYRILLIC SMALL LETTER ER + // U+043E: "о" CYRILLIC SMALL LETTER O + // U+043B: "л" CYRILLIC SMALL LETTER EL + // U+0434: "д" CYRILLIC SMALL LETTER DE + // U+0436: "ж" CYRILLIC SMALL LETTER ZHE + "\u0444", ROW2_2, "\u0432", "\u0430", "\u043F", "\u0440", "\u043E", "\u043B", + "\u0434", "\u0436", ROW2_11) + .setKeysOfRow(3, + // U+044F: "я" CYRILLIC SMALL LETTER YA + // U+0447: "ч" CYRILLIC SMALL LETTER CHE + // U+0441: "с" CYRILLIC SMALL LETTER ES + // U+043C: "м" CYRILLIC SMALL LETTER EM + // U+0442: "т" CYRILLIC SMALL LETTER TE + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+0431: "б" CYRILLIC SMALL LETTER BE + // U+044E: "ю" CYRILLIC SMALL LETTER YU + "\u044F", "\u0447", "\u0441", "\u043C", ROW3_5, "\u0442", "\u044C", "\u0431", + "\u044E") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java new file mode 100644 index 000000000..a513740e7 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java @@ -0,0 +1,364 @@ +/* + * 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.Symbols.RtlSymbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted.RtlSymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +public final class Farsi extends LayoutBase { + private static final String LAYOUT_NAME = "farsi"; + + public Farsi(final LayoutCustomizer customizer) { + super(customizer, FarsiSymbols.class, FarsiSymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class FarsiCustomizer extends LayoutCustomizer { + public FarsiCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey getAlphabetKey() { return FARSI_ALPHABET_KEY; } + + @Override + public ExpectedKey getSymbolsKey() { return FARSI_SYMBOLS_KEY; } + + @Override + public ExpectedKey getBackToSymbolsKey() { return FARSI_BACK_TO_SYMBOLS_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_RIAL; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + if (isPhone) { + // U+060C: "،" ARABIC COMMA + return joinKeys(key("\u060C", SETTINGS_KEY)); + } + // U+060C: "،" ARABIC COMMA + // U+061F: "؟" ARABIC QUESTION MARK + // U+061B: "؛" ARABIC SEMICOLON + return joinKeys(key("\u060C", joinMoreKeys( + ":", "!", "\u061F", "\u061B", "-", RtlSymbols.DOUBLE_ANGLE_QUOTES_LR_RTL, + SETTINGS_KEY)), + "_"); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + if (isPhone) { + return super.getKeysRightToSpacebar(isPhone); + } + return joinKeys("/", key(".", getPunctuationMoreKeys(isPhone))); + } + + @Override + public ExpectedKey[] getSpaceKeys(final boolean isPhone) { + return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY)); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return FARSI_DIACRITICS; + } + + // U+0627: "ا" ARABIC LETTER ALEF + // U+200C: ZERO WIDTH NON-JOINER + // U+0628: "ب" ARABIC LETTER BEH + // U+067E: "پ" ARABIC LETTER PEH + private static final ExpectedKey FARSI_ALPHABET_KEY = key( + "\u0627\u200C\u0628\u200C\u067E", Constants.CODE_SWITCH_ALPHA_SYMBOL); + // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE + // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO + // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE + // U+061F: "؟" ARABIC QUESTION MARK + private static final ExpectedKey FARSI_SYMBOLS_KEY = key( + "\u06F3\u06F2\u06F1\u061F", Constants.CODE_SWITCH_ALPHA_SYMBOL); + private static final ExpectedKey FARSI_BACK_TO_SYMBOLS_KEY = key( + "\u06F3\u06F2\u06F1\u061F", Constants.CODE_SHIFT); + // U+FDFC: "﷼" RIAL SIGN + private static final ExpectedKey CURRENCY_RIAL = key("\uFDFC", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + private static final ExpectedKey[] FARSI_DIACRITICS = { + // U+0655: "ٕ" ARABIC HAMZA BELOW + // U+0652: "ْ" ARABIC SUKUN + // U+0651: "ّ" ARABIC SHADDA + // U+064C: "ٌ" ARABIC DAMMATAN + // U+064D: "ٍ" ARABIC KASRATAN + // U+064B: "ً" ARABIC FATHATAN + // U+0654: "ٔ" ARABIC HAMZA ABOVE + // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF + // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF + // U+0653: "ٓ" ARABIC MADDAH ABOVE + // U+064F: "ُ" ARABIC DAMMA + // U+0650: "ِ" ARABIC KASRA + // U+064E: "َ" ARABIC FATHA + // U+0640: "ـ" ARABIC TATWEEL + moreKey(" \u0655", "\u0655"), moreKey(" \u0652", "\u0652"), + moreKey(" \u0651", "\u0651"), moreKey(" \u064C", "\u064C"), + moreKey(" \u064D", "\u064D"), moreKey(" \u064B", "\u064B"), + moreKey(" \u0654", "\u0654"), moreKey(" \u0656", "\u0656"), + moreKey(" \u0670", "\u0670"), moreKey(" \u0653", "\u0653"), + moreKey(" \u064F", "\u064F"), moreKey(" \u0650", "\u0650"), + moreKey(" \u064E", "\u064E"), moreKey("\u0640\u0640\u0640", "\u0640") + }; + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + if (isPhone) { + return ALPHABET_COMMON; + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + // U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE + builder.insertKeysAtRow(3, 10, "\u0622"); + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0636: "ض" ARABIC LETTER DAD + // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE + key("\u0636", joinMoreKeys("\u06F1", "1")), + // U+0635: "ص" ARABIC LETTER SAD + // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO + key("\u0635", joinMoreKeys("\u06F2", "2")), + // U+062B: "ث" ARABIC LETTER THEH + // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE + key("\u062B", joinMoreKeys("\u06F3", "3")), + // U+0642: "ق" ARABIC LETTER QAF + // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR + key("\u0642", joinMoreKeys("\u06F4", "4")), + // U+0641: "ف" ARABIC LETTER FEH + // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE + key("\u0641", joinMoreKeys("\u06F5", "5")), + // U+063A: "غ" ARABIC LETTER GHAIN + // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX + key("\u063A", joinMoreKeys("\u06F6", "6")), + // U+0639: "ع" ARABIC LETTER AIN + // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN + key("\u0639", joinMoreKeys("\u06F7", "7")), + // U+0647: "ه" ARABIC LETTER HEH + // U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM + // U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER + // U+0647/U+0654: ARABIC LETTER HEH + ARABIC HAMZA ABOVE + // U+0629: "ة" ARABIC LETTER TEH MARBUTA + // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT + key("\u0647", joinMoreKeys(moreKey("\uFEEB", "\u0647\u200D"), "\u0647\u0654", + "\u0629", "\u06F8", "8")), + // U+062E: "خ" ARABIC LETTER KHAH + // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE + key("\u062E", joinMoreKeys("\u06F9", "9")), + // U+062D: "ح" ARABIC LETTER HAH + // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO + key("\u062D", joinMoreKeys("\u06F0", "0")), + // U+062C: "ج" ARABIC LETTER JEEM + "\u062C") + .setKeysOfRow(2, + // U+0634: "ش" ARABIC LETTER SHEEN + // U+0633: "س" ARABIC LETTER SEEN + "\u0634", "\u0633", + // U+06CC: "ی" ARABIC LETTER FARSI YEH + // U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE + // U+064A: "ي" ARABIC LETTER YEH + // U+FBE8: "ﯨ" ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM + // U+0649: "ى" ARABIC LETTER ALEF MAKSURA + key("\u06CC", joinMoreKeys("\u0626", "\u064A", moreKey("\uFBE8", "\u0649"))), + // U+0628: "ب" ARABIC LETTER BEH + // U+0644: "ل" ARABIC LETTER LAM + "\u0628", "\u0644", + // U+0627: "ا" ARABIC LETTER ALEF + // U+0671: "ٱ" ARABIC LETTER ALEF WASLA + // U+0621: "ء" ARABIC LETTER HAMZA + // U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE + // U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE + // U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW + key("\u0627", joinMoreKeys("\u0671", "\u0621", "\u0622", "\u0623", "\u0625")), + // U+062A: "ت" ARABIC LETTER TEH + // U+0629: "ة": ARABIC LETTER TEH MARBUTA + key("\u062A", moreKey("\u0629")), + // U+0646: "ن" ARABIC LETTER NOON + // U+0645: "م" ARABIC LETTER MEEM + "\u0646", "\u0645", + // U+06A9: "ک" ARABIC LETTER KEHEH + // U+0643: "ك" ARABIC LETTER KAF + key("\u06A9", moreKey("\u0643")), + // U+06AF: "گ" ARABIC LETTER GAF + "\u06AF") + .setKeysOfRow(3, + // U+0638: "ظ" ARABIC LETTER ZAH + // U+0637: "ط" ARABIC LETTER TAH + // U+0698: "ژ" ARABIC LETTER JEH + // U+0632: "ز" ARABIC LETTER ZAIN + // U+0631: "ر" ARABIC LETTER REH + // U+0630: "ذ" ARABIC LETTER THAL + // U+062F: "د" ARABIC LETTER DAL + // U+067E: "پ" ARABIC LETTER PEH + "\u0638", "\u0637", "\u0698", "\u0632", "\u0631", "\u0630", "\u062F", "\u067E", + // U+0648: "و" ARABIC LETTER WAW + // U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE + key("\u0648", moreKey("\u0624")), + // U+0686: "چ" ARABIC LETTER TCHEH + "\u0686") + .build(); + + private static class FarsiSymbols extends RtlSymbols { + public FarsiSymbols(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE + // 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 + .replaceKeyOfLabel("1", key("\u06F1", + joinMoreKeys("1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B"))) + // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + .replaceKeyOfLabel("2", key("\u06F2", joinMoreKeys("2", "\u00B2", "\u2154"))) + // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + .replaceKeyOfLabel("3", key("\u06F3", + joinMoreKeys("3", "\u00B3", "\u00BE", "\u215C"))) + // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR + // U+2074: "⁴" SUPERSCRIPT FOUR + .replaceKeyOfLabel("4", key("\u06F4", joinMoreKeys("4", "\u2074"))) + // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + .replaceKeyOfLabel("5", key("\u06F5", joinMoreKeys("5", "\u215D"))) + // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX + .replaceKeyOfLabel("6", key("\u06F6", moreKey("6"))) + // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + .replaceKeyOfLabel("7", key("\u06F7", joinMoreKeys("7", "\u215E"))) + // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT + .replaceKeyOfLabel("8", key("\u06F8", moreKey("8"))) + // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE + .replaceKeyOfLabel("9", key("\u06F9", moreKey("9"))) + // U+066C: "٬" ARABIC THOUSANDS SEPARATOR + .replaceKeyOfLabel("@", key("\u066C", moreKey("@"))) + // U+066B: "٫" ARABIC DECIMAL SEPARATOR + .replaceKeyOfLabel("#", key("\u066B", moreKey("#"))) + // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO + // U+066B: "٫" ARABIC DECIMAL SEPARATOR + // U+066C: "٬" ARABIC THOUSANDS SEPARATOR + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + .replaceKeyOfLabel("0", key("\u06F0", + joinMoreKeys("0", "\u066B", "\u066C", "\u207F", "\u2205"))) + // U+066A: "٪" ARABIC PERCENT SIGN + // U+2030: "‰" PER MILLE SIGN + .replaceKeyOfLabel("%", key("\u066A", joinMoreKeys("%", "\u2030"))) + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + .replaceKeyOfLabel("\"", key("\u00AB", "\u00BB", joinMoreKeys( + DOUBLE_QUOTES_9LR, DOUBLE_ANGLE_QUOTES_LR_RTL))) + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2265: "≥" GREATER-THAN EQUAL TO + .replaceKeyOfLabel("'", key("\u00BB", "\u00AB", joinMoreKeys( + SINGLE_QUOTES_9LR, SINGLE_ANGLE_QUOTES_LR_RTL))) + // U+061B: "؛" ARABIC SEMICOLON + .replaceKeyOfLabel(";", key("\u061B", moreKey(";"))) + // U+061F: "؟" ARABIC QUESTION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + .replaceKeyOfLabel("?", key("\u061F", joinMoreKeys("?", "\u00BF"))) + // U+060C: "،" ARABIC COMMA + .replaceKeyOfLabel(",", "\u060C") + // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS + // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS + .replaceKeyOfLabel("(", key("(", ")", + moreKey("\uFD3E", "\uFD3F"), moreKey("<", ">"), moreKey("{", "}"), + moreKey("[", "]"))) + // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS + // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS + .replaceKeyOfLabel(")", key(")", "(", + moreKey("\uFD3F", "\uFD3E"), moreKey(">", "<"), moreKey("}", "{"), + moreKey("]", "["))) + // U+2605: "★" BLACK STAR + // U+066D: "٭" ARABIC FIVE POINTED STAR + .setMoreKeysOf("*", "\u2605", "\u066D") + .build(); + } + } + + private static class FarsiSymbolsShifted extends RtlSymbolsShifted { + public FarsiSymbolsShifted(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+2022: "•" BULLET + // U+266A: "♪" EIGHTH NOTE + .setMoreKeysOf("\u2022", "\u266A") + // U+060C: "،" ARABIC COMMA + .replaceKeyOfLabel(",", "\u060C") + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + .replaceKeyOfLabel("<", key("\u00AB", "\u00BB", + moreKey("\u2039", "\u203A"), moreKey("\u2264", "\u2265"), + moreKey("<", ">"))) + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2265: "≥" GREATER-THAN EQUAL TO + .replaceKeyOfLabel(">", key("\u00BB", "\u00AB", + moreKey("\u203A", "\u2039"), moreKey("\u2265", "\u2264"), + moreKey(">", "<"))) + .build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Georgian.java b/tests/src/com/android/inputmethod/keyboard/layout/Georgian.java new file mode 100644 index 000000000..6f20dfcd1 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Georgian.java @@ -0,0 +1,163 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Georgian alphabet keyboard. + */ +public final class Georgian extends LayoutBase { + private static final String LAYOUT_NAME = "georgian"; + + public Georgian(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class GeorgianCustomizer extends LayoutCustomizer { + public GeorgianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return GEORGIAN_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + // U+10D0: "ა" GEORGIAN LETTER AN + // U+10D1: "ბ" GEORGIAN LETTER BAN + // U+10D2: "გ" GEORGIAN LETTER GAN + private static final ExpectedKey GEORGIAN_ALPHABET_KEY = key( + "\u10D0\u10D1\u10D2", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + return ALPHABET_COMMON; + } + + @Override + public ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, + final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+10E5: "ქ" GEORGIAN LETTER GHAN + key("\u10E5", moreKey("1")), + // U+10EC: "წ" GEORGIAN LETTER CIL + key("\u10EC", moreKey("2")), + // U+10D4: "ე" GEORGIAN LETTER EN + // U+10F1: "ჱ" GEORGIAN LETTER HE + key("\u10D4", joinMoreKeys("3", "\u10F1")), + // U+10E0: "რ" GEORGIAN LETTER RAE + key("\u10E0", moreKey("4")), + // U+10E2: "ტ" GEORGIAN LETTER TAR + key("\u10E2", moreKey("5")), + // U+10E7: "ყ" GEORGIAN LETTER QAR + // U+10F8: "ჸ" GEORGIAN LETTER ELIFI + key("\u10E7", joinMoreKeys("6", "\u10F8")), + // U+10E3: "უ" GEORGIAN LETTER UN + key("\u10E3", moreKey("7")), + // U+10D8: "ი" GEORGIAN LETTER IN + // U+10F2: "ჲ" GEORGIAN LETTER HIE + key("\u10D8", joinMoreKeys("8", "\u10F2")), + // U+10DD: "ო" GEORGIAN LETTER ON + key("\u10DD", moreKey("9")), + // U+10DE: "პ" GEORGIAN LETTER PAR + key("\u10DE", moreKey("0"))) + .setKeysOfRow(2, + // U+10D0: "ა" GEORGIAN LETTER AN + // U+10FA: "ჺ" GEORGIAN LETTER AIN + key("\u10D0", moreKey("\u10FA")), + // U+10E1: "ს" GEORGIAN LETTER SAN + // U+10D3: "დ" GEORGIAN LETTER DON + "\u10E1", "\u10D3", + // U+10E4: "ფ" GEORGIAN LETTER PHAR + // U+10F6: "ჶ" GEORGIAN LETTER FI + key("\u10E4", moreKey("\u10F6")), + // U+10D2: "გ" GEORGIAN LETTER GAN + // U+10F9: "ჹ" GEORGIAN LETTER TURNED GAN + key("\u10D2", moreKey("\u10F9")), + // U+10F0: "ჰ" GEORGIAN LETTER HAE + // U+10F5: "ჵ" GEORGIAN LETTER HOE + key("\u10F0", moreKey("\u10F5")), + // U+10EF: "ჯ" GEORGIAN LETTER JHAN + // U+10F7: "ჷ" GEORGIAN LETTER YN + key("\u10EF", moreKey("\u10F7")), + // U+10D9: "კ" GEORGIAN LETTER KAN + // U+10DA: "ლ" GEORGIAN LETTER LAS + "\u10D9", "\u10DA") + .setKeysOfRow(3, + // U+10D6: "ზ" GEORGIAN LETTER ZEN + "\u10D6", + // U+10EE: "ხ" GEORGIAN LETTER XAN + // U+10F4: "ჴ" GEORGIAN LETTER HAR + key("\u10EE", moreKey("\u10F4")), + // U+10EA: "ც" GEORGIAN LETTER CAN + "\u10EA", + // U+10D5: "ვ" GEORGIAN LETTER VIN + // U+10F3: "ჳ" GEORGIAN LETTER WE + key("\u10D5", moreKey("\u10F3")), + // U+10D1: "ბ" GEORGIAN LETTER BAN + "\u10D1", + // U+10DC: "ნ" GEORGIAN LETTER NAR + // U+10FC: "ჼ" MODIFIER LETTER GEORGIAN NAR + key("\u10DC", moreKey("\u10FC")), + // U+10DB: "მ" GEORGIAN LETTER MAN + "\u10DB") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("Q", moreKey("1")), + // U+10ED: "ჭ" GEORGIAN LETTER CHAR + key("\u10ED", moreKey("2")), + key("E", moreKey("3")), + // U+10E6: "ღ" GEORGIAN LETTER GHAN + key("\u10E6", moreKey("4")), + // U+10D7: "თ" GEORGIAN LETTER TAN + key("\u10D7", moreKey("5")), + key("Y", moreKey("6")), + key("U", moreKey("7")), + key("I", moreKey("8")), + key("O", moreKey("9")), + key("P", moreKey("0"))) + .setKeysOfRow(2, + // U+10E8: "შ" GEORGIAN LETTER SHIN + // U+10DF: "ჟ" GEORGIAN LETTER ZHAR + "A", "\u10E8", "D", "F", "G", "H", "\u10DF", "K", "L") + .setKeysOfRow(3, + // U+10EB: "ძ" GEORGIAN LETTER JIL + // U+10E9: "ჩ" GEORGIAN LETTER CHIN + "\u10EB", "X", "\u10E9", "V", "B", "N", "M") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Greek.java b/tests/src/com/android/inputmethod/keyboard/layout/Greek.java new file mode 100644 index 000000000..475052c75 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Greek.java @@ -0,0 +1,139 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Greek alphabet keyboard. + */ +public final class Greek extends LayoutBase { + private static final String LAYOUT_NAME = "greek"; + + public Greek(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class GreekCustomizer extends EuroCustomizer { + public GreekCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return GREEK_ALPHABET_KEY; } + + // U+0391: "Α" GREEK CAPITAL LETTER ALPHA + // U+0392: "Β" GREEK CAPITAL LETTER BETA + // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA + private static final ExpectedKey GREEK_ALPHABET_KEY = key( + "\u0391\u0392\u0393", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + builder.replaceKeyOfLabel(ROW1_1, ROW1_1_SEMICOLON); + builder.replaceKeyOfLabel(ROW1_2, ROW1_2_FINAL_SIGMA); + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + builder.toUpperCase(getLocale()); + if (elementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED + || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED) { + builder.replaceKeyOfLabel(ROW1_1, ROW1_1_COLON); + } else { + builder.replaceKeyOfLabel(ROW1_1, ROW1_1_SEMICOLON); + } + builder.replaceKeyOfLabel(ROW1_2, ROW1_2_FINAL_SIGMA); + return builder.build(); + } + + private static final String ROW1_1 = "ROW1_1"; + private static final ExpectedKey ROW1_1_SEMICOLON = key(";", joinMoreKeys("1", ":")); + private static final ExpectedKey ROW1_1_COLON = key(":", joinMoreKeys("1", ";")); + + private static final String ROW1_2 = "ROW2_2"; + // U+03C2: "ς" GREEK SMALL LETTER FINAL SIGMA + private static final ExpectedKey ROW1_2_FINAL_SIGMA = key("\u03C2", moreKey("2")); + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key(ROW1_1, moreKey("1")), + key(ROW1_2, moreKey("2")), + // U+03B5: "ε" GREEK SMALL LETTER EPSILON + // U+03AD: "έ" GREEK SMALL LETTER EPSILON WITH TONOS + key("\u03B5", joinMoreKeys("\u03AD", "3")), + // U+03C1: "ρ" GREEK SMALL LETTER RHO + key("\u03C1", moreKey("4")), + // U+03C4: "τ" GREEK SMALL LETTER TAU + key("\u03C4", moreKey("5")), + // U+03C5: "υ" GREEK SMALL LETTER UPSILON + // U+03CD: "ύ" GREEK SMALL LETTER UPSILON WITH TONOS + // U+03CB: "ϋ" GREEK SMALL LETTER UPSILON WITH DIALYTIKA + // U+03B0: "ΰ" GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS + key("\u03C5", joinMoreKeys("\u03CD", "6", "\u03CB", "\u03B0")), + // U+03B8: "θ" GREEK SMALL LETTER THETA + key("\u03B8", moreKey("7")), + // U+03B9: "ι" GREEK SMALL LETTER IOTA + // U+03AF: "ί" GREEK SMALL LETTER IOTA WITH TONOS + // U+03CA: "ϊ" GREEK SMALL LETTER IOTA WITH DIALYTIKA + // U+0390: "ΐ" GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS + key("\u03B9", joinMoreKeys("\u03AF", "8", "\u03CA", "\u0390")), + // U+03BF: "ο" GREEK SMALL LETTER OMICRON + // U+03CC: "ό" GREEK SMALL LETTER OMICRON WITH TONOS + key("\u03BF", joinMoreKeys("\u03CC", "9")), + // U+03C0: "π" GREEK SMALL LETTER PI + key("\u03C0", moreKey("0"))) + .setKeysOfRow(2, + // U+03B1: "α" GREEK SMALL LETTER ALPHA + // U+03AC: "ά" GREEK SMALL LETTER ALPHA WITH TONOS + key("\u03B1", moreKey("\u03AC")), + // U+03C3: "σ" GREEK SMALL LETTER SIGMA + // U+03B4: "δ" GREEK SMALL LETTER DELTA + // U+03C6: "φ" GREEK SMALL LETTER PHI + // U+03B3: "γ" GREEK SMALL LETTER GAMMA + "\u03C3", "\u03B4", "\u03C6", "\u03B3", + // U+03B7: "η" GREEK SMALL LETTER ETA + // U+03AE: "ή" GREEK SMALL LETTER ETA WITH TONOS + key("\u03B7", moreKey("\u03AE")), + // U+03BE: "ξ" GREEK SMALL LETTER XI + // U+03BA: "κ" GREEK SMALL LETTER KAPPA + // U+03BB: "λ" GREEK SMALL LETTER LAMDA + "\u03BE", "\u03BA", "\u03BB") + .setKeysOfRow(3, + // U+03B6: "ζ" GREEK SMALL LETTER ZETA + // U+03C7: "χ" GREEK SMALL LETTER CHI + // U+03C8: "ψ" GREEK SMALL LETTER PSI + "\u03B6", "\u03C7", "\u03C8", + // U+03C9: "ω" GREEK SMALL LETTER OMEGA + // U+03CE: "ώ" GREEK SMALL LETTER OMEGA WITH TONOS + key("\u03C9", moreKey("\u03CE")), + // U+03B2: "β" GREEK SMALL LETTER BETA + // U+03BD: "ν" GREEK SMALL LETTER NU + // U+03BC: "μ" GREEK SMALL LETTER MU + "\u03B2", "\u03BD", "\u03BC") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.java b/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.java new file mode 100644 index 000000000..552f0d3d5 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Hebrew.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.Symbols.RtlSymbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted.RtlSymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +public final class Hebrew extends LayoutBase { + private static final String LAYOUT_NAME = "hebrew"; + + public Hebrew(final LayoutCustomizer customizer) { + super(customizer, HebrewSymbols.class, RtlSymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class HebrewCustomizer extends LayoutCustomizer { + public HebrewCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey getAlphabetKey() { return HEBREW_ALPHABET_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_NEW_SHEQEL; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_LR9; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_LR9; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { + return RtlSymbols.DOUBLE_ANGLE_QUOTES_LR_RTL; + } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { + return RtlSymbols.SINGLE_ANGLE_QUOTES_LR_RTL; + } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? RTL_PHONE_PUNCTUATION_MORE_KEYS + : RTL_TABLET_PUNCTUATION_MORE_KEYS; + } + + // U+05D0: "א" HEBREW LETTER ALEF + // U+05D1: "ב" HEBREW LETTER BET + // U+05D2: "ג" HEBREW LETTER GIMEL + private static final ExpectedKey HEBREW_ALPHABET_KEY = key( + "\u05D0\u05D1\u05D2", Constants.CODE_SWITCH_ALPHA_SYMBOL); + // U+20AA: "₪" NEW SHEQEL SIGN + private static final ExpectedKey CURRENCY_NEW_SHEQEL = key("\u20AA", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + private static final ExpectedKey[] RTL_PHONE_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "?", "!", "#", key(")", "("), key("(", ")"), "/", ";", + "'", "@", ":", "-", "\"", "+", "%", "&"); + // Punctuation more keys for tablet form factor. + private static final ExpectedKey[] RTL_TABLET_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "'", "#", key(")", "("), key("(", ")"), "/", ";", + "@", ":", "-", "\"", "+", "%", "&"); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("'", joinMoreKeys("1", "\"")), + key("-", joinMoreKeys("2", "_")), + // U+05E7: "ק" HEBREW LETTER QOF + key("\u05E7", moreKey("3")), + // U+05E8: "ר" HEBREW LETTER RESH + key("\u05E8", moreKey("4")), + // U+05D0: "א" HEBREW LETTER ALEF + key("\u05D0", moreKey("5")), + // U+05D8: "ט" HEBREW LETTER TET + key("\u05D8", moreKey("6")), + // U+05D5: "ו" HEBREW LETTER VAV + key("\u05D5", moreKey("7")), + // U+05DF: "ן" HEBREW LETTER FINAL NUN + key("\u05DF", moreKey("8")), + // U+05DD: "ם" HEBREW LETTER FINAL MEM + key("\u05DD", moreKey("9")), + // U+05E4: "פ" HEBREW LETTER PE + key("\u05E4", moreKey("0"))) + .setKeysOfRow(2, + // U+05E9: "ש" HEBREW LETTER SHIN + // U+05D3: "ד" HEBREW LETTER DALET + "\u05E9", "\u05D3", + // U+05D2: "ג" HEBREW LETTER GIMEL + // U+05D2 U+05F3: "ג׳" HEBREW LETTER GIMEL + HEBREW PUNCTUATION GERESH + key("\u05D2", moreKey("\u05D2\u05F3")), + // U+05DB: "כ" HEBREW LETTER KAF + // U+05E2: "ע" HEBREW LETTER AYIN + "\u05DB", "\u05E2", + // U+05D9: "י" HEBREW LETTER YOD + // U+05F2 U+05B7: "ײַ" HEBREW LIGATURE YIDDISH DOUBLE YOD + HEBREW POINT PATAH + key("\u05D9", moreKey("\u05F2\u05B7")), + // U+05D7: "ח" HEBREW LETTER HET + // U+05D7 U+05F3: "ח׳" HEBREW LETTER HET + HEBREW PUNCTUATION GERESH + key("\u05D7", moreKey("\u05D7\u05F3")), + // U+05DC: "ל" HEBREW LETTER LAMED + // U+05DA: "ך" HEBREW LETTER FINAL KAF + // U+05E3: "ף" HEBREW LETTER FINAL PE + "\u05DC", "\u05DA", "\u05E3") + .setKeysOfRow(3, + // U+05D6: "ז" HEBREW LETTER ZAYIN + // U+05D6 U+05F3: "ז׳" HEBREW LETTER ZAYIN + HEBREW PUNCTUATION GERESH + key("\u05D6", moreKey("\u05D6\u05F3")), + // U+05E1: "ס" HEBREW LETTER SAMEKH + // U+05D1: "ב" HEBREW LETTER BET + // U+05D4: "ה" HEBREW LETTER HE + // U+05E0: "נ" HEBREW LETTER NUN + // U+05DE: "מ" HEBREW LETTER MEM + "\u05E1", "\u05D1", "\u05D4", "\u05E0", "\u05DE", + // U+05E6: "צ" HEBREW LETTER TSADI + // U+05E6 U+05F3: "צ׳" HEBREW LETTER TSADI + HEBREW PUNCTUATION GERESH + key("\u05E6", moreKey("\u05E6\u05F3")), + // U+05EA: "ת" HEBREW LETTER TAV + // U+05EA U+05F3: "ת׳" HEBREW LETTER TAV + HEBREW PUNCTUATION GERESH + key("\u05EA", moreKey("\u05EA\u05F3")), + // U+05E5: "ץ" HEBREW LETTER FINAL TSADI + // U+05E5 U+05F3: "ץ׳" HEBREW LETTER FINAL TSADI + HEBREW PUNCTUATION GERESH + key("\u05E5", moreKey("\u05E5\u05F3"))) + .build(); + + private static class HebrewSymbols extends RtlSymbols { + public HebrewSymbols(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+00B1: "±" PLUS-MINUS SIGN + // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN + .setMoreKeysOf("+", "\u00B1", "\uFB29") + // U+2605: "★" BLACK STAR + .setMoreKeysOf("*", "\u2605") + .build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Hindi.java b/tests/src/com/android/inputmethod/keyboard/layout/Hindi.java new file mode 100644 index 000000000..b12b8be64 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Hindi.java @@ -0,0 +1,378 @@ +/* + * 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 static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*; + +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Hindi keyboard. + */ +public final class Hindi extends LayoutBase { + private static final String LAYOUT_NAME = "hindi"; + + public Hindi(final LayoutCustomizer customizer) { + super(customizer, HindiSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class HindiCustomizer extends LayoutCustomizer { + public HindiCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return HINDI_ALPHABET_KEY; } + + @Override + public ExpectedKey getSymbolsKey() { return HINDI_SYMBOLS_KEY; } + + @Override + public ExpectedKey getBackToSymbolsKey() { return HINDI_BACK_TO_SYMBOLS_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_RUPEE; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + // U+0915: "क" DEVANAGARI LETTER KA + // U+0916: "ख" DEVANAGARI LETTER KHA + // U+0917: "ग" DEVANAGARI LETTER GA + private static final ExpectedKey HINDI_ALPHABET_KEY = key( + "\u0915\u0916\u0917", Constants.CODE_SWITCH_ALPHA_SYMBOL); + // U+0967: "१" DEVANAGARI DIGIT ONE + // U+0968: "२" DEVANAGARI DIGIT TWO + // U+0969: "३" DEVANAGARI DIGIT THREE + private static final String HINDI_SYMBOLS_LABEL = "?\u0967\u0968\u0969"; + private static final ExpectedKey HINDI_SYMBOLS_KEY = key(HINDI_SYMBOLS_LABEL, + Constants.CODE_SWITCH_ALPHA_SYMBOL); + private static final ExpectedKey HINDI_BACK_TO_SYMBOLS_KEY = key(HINDI_SYMBOLS_LABEL, + Constants.CODE_SHIFT); + + // U+20B9: "₹" INDIAN RUPEE SIGN + private static final ExpectedKey CURRENCY_RUPEE = key("\u20B9", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + // U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA + // U+0967: "१" DEVANAGARI DIGIT ONE + key(VOWEL_SIGN_AU, "\u094C", joinMoreKeys( + moreKey(VOWEL_SIGN_AU + "\u0902", "\u094C\u0902"), + "\u0967", "1")), + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + // U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA + // U+0968: "२" DEVANAGARI DIGIT TWO + key(VOWEL_SIGN_AI, "\u0948", joinMoreKeys( + moreKey(VOWEL_SIGN_AI + "\u0902", "\u0948\u0902"), + "\u0968", "2")), + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + // U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA + // U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU + // U+0969: "३" DEVANAGARI DIGIT THREE + key(VOWEL_SIGN_AA, "\u093E", joinMoreKeys( + moreKey(VOWEL_SIGN_AA + "\u0902", "\u093E\u0902"), + moreKey(VOWEL_SIGN_AA + "\u0901", "\u093E\u0901"), + "\u0969", "3")), + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + // U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA + // U+096A: "४" DEVANAGARI DIGIT FOUR + key(VOWEL_SIGN_II, "\u0940", joinMoreKeys( + moreKey(VOWEL_SIGN_II + "\u0902", "\u0940\u0902"), + "\u096A", "4")), + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + // U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA + // U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU + // U+096B: "५" DEVANAGARI DIGIT FIVE + key(VOWEL_SIGN_UU, "\u0942", joinMoreKeys( + moreKey(VOWEL_SIGN_UU + "\u0902", "\u0942\u0902"), + moreKey(VOWEL_SIGN_UU + "\u0901", "\u0942\u0901"), + "\u096B", "5")), + // U+092C: "ब" DEVANAGARI LETTER BA + // U+092C/U+0952: "ब॒" DEVANAGARI LETTER BA/DEVANAGARI STRESS SIGN ANUDATTA + // U+096C: "६" DEVANAGARI DIGIT SIX + key("\u092C", joinMoreKeys("\u092C\u0952", "\u096C", "6")), + // U+0939: "ह" DEVANAGARI LETTER HA + // U+096D: "७" DEVANAGARI DIGIT SEVEN + key("\u0939", joinMoreKeys("\u096D", "7")), + // U+0917: "ग" DEVANAGARI LETTER GA + // U+091C/U+094D/U+091E: + // "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA + // U+0917/U+093C: "ग़" DEVANAGARI LETTER GA/DEVANAGARI SIGN NUKTA + // U+0917/U+0952: "ग॒" DEVANAGARI LETTER GA/DEVANAGARI STRESS SIGN ANUDATTA + // U+096E: "८" DEVANAGARI DIGIT EIGHT + key("\u0917", joinMoreKeys("\u091C\u094D\u091E", "\u0917\u093C", "\u0917\u0952", + "\u096E", "8")), + // U+0926: "द" DEVANAGARI LETTER DA + // U+096F: "९" DEVANAGARI DIGIT NINE + key("\u0926", joinMoreKeys("\u096F", "9")), + // U+091C: "ज" DEVANAGARI LETTER JA + // U+091C/U+0952: "ज॒" DEVANAGARI LETTER JA/DEVANAGARI STRESS SIGN ANUDATTA + // U+091C/U+094D/U+091E: + // "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA + // U+091C/U+093C: "ज़" DEVANAGARI LETTER JA/DEVANAGARI SIGN NUKTA + // U+0966: "०" DEVANAGARI DIGIT ZERO + key("\u091C", joinMoreKeys("\u091C\u0952", "\u091C\u094D\u091E", "\u091C\u093C", + "\u0966", "0")), + // U+0921: "ड" DEVANAGARI LETTER DDA + // U+0921/U+0952: "ड॒" DEVANAGARI LETTER DDA/DEVANAGARI STRESS SIGN ANUDATTA + // U+0921/U+093C: "ड़" DEVANAGARI LETTER DDA/DEVANAGARI SIGN NUKTA + key("\u0921", joinMoreKeys("\u0921\u0952", "\u0921\u093C"))) + .setKeysOfRow(2, + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + // U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA + // U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O + // U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O + key(VOWEL_SIGN_O, "\u094B", joinMoreKeys( + moreKey(VOWEL_SIGN_O + "\u0902", "\u094B\u0902"), + moreKey(VOWEL_SIGN_CANDRA_O, "\u0949"), + moreKey(VOWEL_SIGN_SHORT_O, "\u094A"))), + // U+0947: "े" DEVANAGARI VOWEL SIGN E + // U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA + key(VOWEL_SIGN_E, "\u0947", + moreKey(VOWEL_SIGN_E + "\u0902", "\u0947\u0902")), + // U+094D: "्" DEVANAGARI SIGN VIRAMA + key(SIGN_VIRAMA, "\u094D"), + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + // U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA + key(VOWEL_SIGN_I, "\u093F", + moreKey("\u093F" + SIGN_ANUSVARA, "\u093F\u0902")), + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + // U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA + // U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU + key(VOWEL_SIGN_U, "\u0941", joinMoreKeys( + moreKey(VOWEL_SIGN_U + "\u0902", "\u0941\u0902"), + moreKey(VOWEL_SIGN_U + "\u0901", "\u0941\u0901"))), + // U+092A: "प" DEVANAGARI LETTER PA + "\u092A", + // U+0930: "र" DEVANAGARI LETTER RA + // U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R + // U+0930/U+093C: "ऱ" DEVANAGARI LETTER RA/DEVANAGARI SIGN NUKTA + // U+0960: "ॠ" DEVANAGARI LETTER VOCALIC RR + key("\u0930", joinMoreKeys("\u090B", "\u0930\u093C", "\u0960")), + // U+0915: "क" DEVANAGARI LETTER KA + // U+0915/U+093C: "क़" DEVANAGARI LETTER KA/DEVANAGARI SIGN NUKTA + key("\u0915", moreKey("\u0915\u093C")), + // U+0924: "त" DEVANAGARI LETTER TA + // U+0924/U+094D/U+0930: + // "त्र" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0924", moreKey("\u0924\u094D\u0930")), + // U+091A: "च" DEVANAGARI LETTER CA + // U+091F: "ट" DEVANAGARI LETTER TTA + "\u091A","\u091F") + .setKeysOfRow(3, + // U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O + key(VOWEL_SIGN_CANDRA_O, "\u0949"), + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + key(SIGN_ANUSVARA, "\u0902"), + // U+092E: "म" DEVANAGARI LETTER MA + // U+0950: "ॐ" DEVANAGARI OM + key("\u092E", moreKey("\u0950")), + // U+0928: "न" DEVANAGARI LETTER NA + // U+091E: "ञ" DEVANAGARI LETTER NYA + // U+0919: "ङ" DEVANAGARI LETTER NGA + // U+0928/U+093C: "ऩ" DEVANAGARI LETTER NA/DEVANAGARI SIGN NUKTA + key("\u0928", joinMoreKeys("\u091E", "\u0919", "\u0928\u093C")), + // U+0935: "व" DEVANAGARI LETTER VA + "\u0935", + // U+0932: "ल" DEVANAGARI LETTER LA + // U+090C: "ऌ" DEVANAGARI LETTER VOCALIC L + // U+0961: "ॡ" DEVANAGARI LETTER VOCALIC LL + key("\u0932", joinMoreKeys("\u090C", "\u0961")), + // U+0938: "स" DEVANAGARI LETTER SA + "\u0938", + // U+092F: "य" DEVANAGARI LETTER YA + // U+095F: "य़" DEVANAGARI LETTER YYA + key("\u092F", moreKey("\u095F")), + // U+093C: "़" DEVANAGARI SIGN NUKTA + // U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP + // U+0970: "॰" DEVANAGARI ABBREVIATION SIGN + // U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA + key(SIGN_NUKTA, "\u093C", joinMoreKeys( + moreKey(LETTER_GLOTTAL_STOP, "\u097D"), + moreKey(ABBREVIATION_SIGN, "\u0970"), + moreKey(SIGN_AVAGRAHA, "\u093D")))) + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0914: "औ" DEVANAGARI LETTER AU + // U+0912/U+0902: "ऒं" DEVANAGARI LETTER SHORT O//DEVANAGARI SIGN ANUSVARA + key("\u0914", moreKey("\u0912\u0902")), + // U+0910: "ऐ" DEVANAGARI LETTER AI + // U+0910/U+0902: "ऐं" DEVANAGARI LETTER AI/DEVANAGARI SIGN ANUSVARA + key("\u0910", moreKey("\u0910\u0902")), + // U+0906: "आ" DEVANAGARI LETTER AA + // U+0906/U+0902: "आं" DEVANAGARI LETTER AA/DEVANAGARI SIGN ANUSVARA + // U+0906/U+0901: "आँ" DEVANAGARI LETTER AA/DEVANAGARI SIGN CANDRABINDU + key("\u0906", joinMoreKeys("\u0906\u0902", "\u0906\u0901")), + // U+0908: "ई" DEVANAGARI LETTER II + // U+0908/U+0902: "ईं" DEVANAGARI LETTER II/DEVANAGARI SIGN ANUSVARA + key("\u0908", moreKey("\u0908\u0902")), + // U+090A: "ऊ" DEVANAGARI LETTER UU + // U+090A/U+0902: "ऊं" DEVANAGARI LETTER UU/DEVANAGARI SIGN ANUSVARA + // U+090A/U+0901: "ऊँ" DEVANAGARI LETTER UU/DEVANAGARI SIGN CANDRABINDU + key("\u090A", joinMoreKeys("\u090A\u0902", "\u090A\u0901")), + // U+092D: "भ" DEVANAGARI LETTER BHA + // U+0903: "ः" DEVANAGARI SIGN VISARGA + // U+0918: "घ" DEVANAGARI LETTER GHA + "\u092D", key(SIGN_VISARGA, "\u0903"), "\u0918", + // U+0927: "ध" DEVANAGARI LETTER DHA + // U+0915/U+094D/U+0937: + // "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA + // U+0936/U+094D/U+0930: + // "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0927", joinMoreKeys("\u0915\u094D\u0937", "\u0936\u094D\u0930")), + // U+091D: "झ" DEVANAGARI LETTER JHA + // U+0922: "ढ" DEVANAGARI LETTER DDHA + "\u091D", "\u0922") + .setKeysOfRow(2, + // U+0913: "ओ" DEVANAGARI LETTER O + // U+0913/U+0902: "ओं" DEVANAGARI LETTER O/DEVANAGARI SIGN ANUSVARA + // U+0911: "ऑ" DEVANAGARI LETTER CANDRA O + // U+0912: "ऒ" DEVANAGARI LETTER SHORT O + key("\u0913", joinMoreKeys("\u0913\u0902", "\u0911", "\u0912")), + // U+090F: "ए" DEVANAGARI LETTER E + // U+090F/U+0902: "एं" DEVANAGARI LETTER E/DEVANAGARI SIGN ANUSVARA + // U+090F/U+0901: "एँ" DEVANAGARI LETTER E/DEVANAGARI SIGN CANDRABINDU + // U+090D: "ऍ" DEVANAGARI LETTER CANDRA E + // U+090E: "ऎ" DEVANAGARI LETTER SHORT E + key("\u090F", joinMoreKeys("\u090F\u0902", "\u090F\u0901", "\u090D", "\u090E")), + // U+0905: "अ" DEVANAGARI LETTER A + // U+0905/U+0902: "अं" DEVANAGARI LETTER A/DEVANAGARI SIGN ANUSVARA + // U+0905/U+0901: "अँ" DEVANAGARI LETTER A/DEVANAGARI SIGN CANDRABINDU + key("\u0905", joinMoreKeys("\u0905\u0902", "\u0905\u0901")), + // U+0907: "इ" DEVANAGARI LETTER I + // U+0907/U+0902: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN ANUSVARA + // U+0907/U+0901: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN CANDRABINDU + key("\u0907", joinMoreKeys("\u0907\u0902", "\u0907\u0901")), + // U+0909: "उ" DEVANAGARI LETTER U + // U+0909/U+0902: "उं" DEVANAGARI LETTER U/DEVANAGARI SIGN ANUSVARA + // U+0909/U+0901: "उँ" DEVANAGARI LETTER U/DEVANAGARI SIGN CANDRABINDU + key("\u0909", joinMoreKeys("\u0909\u0902", "\u0909\u0901")), + // U+092B: "फ" DEVANAGARI LETTER PHA + // U+092B/U+093C: "फ़" DEVANAGARI LETTER PHA/DEVANAGARI SIGN NUKTA + key("\u092B", moreKey("\u092B\u093C")), + // U+0931: "ऱ" DEVANAGARI LETTER RRA + // U+094D/U+0930: "्र" DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + // U+0930/U+094D: "र्" DEVANAGARI LETTER RA/DEVANAGARI SIGN VIRAMA + key("\u0931", joinMoreKeys("\u094D\u0930", "\u0930\u094D")), + // U+0916: "ख" DEVANAGARI LETTER KHA + // U+0916/U+093C: "ख़" DEVANAGARI LETTER KHA/DEVANAGARI SIGN NUKTA + key("\u0916", moreKey("\u0916\u093C")), + // U+0925: "थ" DEVANAGARI LETTER THA + // U+091B: "छ" DEVANAGARI LETTER CHA + // U+0920: "ठ" DEVANAGARI LETTER TTHA + "\u0925", "\u091B", "\u0920") + .setKeysOfRow(3, + // U+0911: "ऑ" DEVANAGARI LETTER CANDRA O + "\u0911", + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + // U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E + key(SIGN_CANDRABINDU, "\u0901", moreKey(VOWEL_SIGN_CANDRA_E, "\u0945")), + // U+0923: "ण" DEVANAGARI LETTER NNA + // U+0929: "ऩ" DEVANAGARI LETTER NNNA + "\u0923", "\u0929", + // U+0933: "ळ" DEVANAGARI LETTER LLA + // U+0934: "ऴ" DEVANAGARI LETTER LLLA + key("\u0933", moreKey("\u0934")), + // U+0936: "श" DEVANAGARI LETTER SHA + // U+0937: "ष" DEVANAGARI LETTER SSA + "\u0936", "\u0937", + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + // U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR + key(VOWEL_SIGN_VOCALIC_R, "\u0943", moreKey(VOWEL_SIGN_VOCALIC_RR, "\u0944")), + // U+091E: "ञ" DEVANAGARI LETTER NYA + "\u091E") + .build(); + + static class HindiSymbols extends Symbols { + public HindiSymbols(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + // U+0967: "१" DEVANAGARI DIGIT ONE + // 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 + .replaceKeyOfLabel("1", key("\u0967", + joinMoreKeys("1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B"))) + // U+0968: "२" DEVANAGARI DIGIT TWO + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + .replaceKeyOfLabel("2", key("\u0968", joinMoreKeys("2", "\u00B2", "\u2154"))) + // U+0969: "३" DEVANAGARI DIGIT THREE + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + .replaceKeyOfLabel("3", key("\u0969", + joinMoreKeys("3", "\u00B3", "\u00BE","\u215C"))) + // U+096A: "४" DEVANAGARI DIGIT FOUR + // U+2074: "⁴" SUPERSCRIPT FOUR + .replaceKeyOfLabel("4", key("\u096A", joinMoreKeys("4", "\u2074"))) + // U+096B: "५" DEVANAGARI DIGIT FIVE + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + .replaceKeyOfLabel("5", key("\u096B", joinMoreKeys("5", "\u215D"))) + // U+096C: "६" DEVANAGARI DIGIT SIX + .replaceKeyOfLabel("6", key("\u096C", moreKey("6"))) + // U+096D: "७" DEVANAGARI DIGIT SEVEN + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + .replaceKeyOfLabel("7", key("\u096D", joinMoreKeys("7", "\u215E"))) + // U+096E: "८" DEVANAGARI DIGIT EIGHT + .replaceKeyOfLabel("8", key("\u096E", moreKey("8"))) + // U+096F: "९" DEVANAGARI DIGIT NINE + .replaceKeyOfLabel("9", key("\u096F", moreKey("9"))) + // U+0966: "०" DEVANAGARI DIGIT ZERO + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + .replaceKeyOfLabel("0", key("\u0966", joinMoreKeys("0", "\u207F", "\u2205"))) + .build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java new file mode 100644 index 000000000..a7f682340 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java @@ -0,0 +1,199 @@ +/* + * 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 static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*; + +import com.android.inputmethod.keyboard.layout.Hindi.HindiCustomizer; +import com.android.inputmethod.keyboard.layout.Hindi.HindiSymbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * The Hindi Compact keyboard. + */ +public final class HindiCompact extends LayoutBase { + private static final String LAYOUT_NAME = "hindi_compact"; + + public HindiCompact(final LayoutCustomizer customizer) { + super(customizer, HindiSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class HindiCompactCustomizer extends HindiCustomizer { + public HindiCompactCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + // U+0964: "।" DEVANAGARI DANDA + final ExpectedKey periodKey = key("\u0964", getPunctuationMoreKeys(isPhone)); + return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? HINDI_PHONE_PUNCTUATION_MORE_KEYS : HINDI_TABLET_PUNCTUATION_MORE_KEYS; + } + + // Punctuation more keys for phone form factor. + private static final ExpectedKey[] HINDI_PHONE_PUNCTUATION_MORE_KEYS = joinKeys( + ",", ".", "?", "!", "#", ")", "(", "/", ";", + "'", "@", ":", "-", "\"", "+", "%", "&"); + // Punctuation more keys for tablet form factor. + private static final ExpectedKey[] HINDI_TABLET_PUNCTUATION_MORE_KEYS = joinKeys( + ",", ".", "'", "#", ")", "(", "/", ";", + "@", ":", "-", "\"", "+", "%", "&"); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0914: "औ" DEVANAGARI LETTER AU + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + // U+0967: "१" DEVANAGARI DIGIT ONE + key("\u0914", joinMoreKeys(moreKey(VOWEL_SIGN_AU, "\u094C"), "\u0967", "1")), + // U+0910: "ऐ" DEVANAGARI LETTER AI + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + // U+0968: "२" DEVANAGARI DIGIT TWO + key("\u0910", joinMoreKeys(moreKey(VOWEL_SIGN_AI, "\u0948"), "\u0968", "2")), + // U+0906: "आ" DEVANAGARI LETTER AA + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + // U+0969: "३" DEVANAGARI DIGIT THREE + key("\u0906", joinMoreKeys(moreKey(VOWEL_SIGN_AA, "\u093E"), "\u0969", "3")), + // U+0908: "ई" DEVANAGARI LETTER II + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + // U+096A: "४" DEVANAGARI DIGIT FOUR + key("\u0908", joinMoreKeys(moreKey(VOWEL_SIGN_II, "\u0940"), "\u096A", "4")), + // U+090A: "ऊ" DEVANAGARI LETTER UU + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + // U+096B: "५" DEVANAGARI DIGIT FIVE + key("\u090A", joinMoreKeys(moreKey(VOWEL_SIGN_UU, "\u0942"), "\u096B", "5")), + // U+092C: "ब" DEVANAGARI LETTER BA + // U+092D: "भ" DEVANAGARI LETTER BHA + // U+096C: "६" DEVANAGARI DIGIT SIX + key("\u092C", joinMoreKeys("\u092D", "\u096C", "6")), + // U+0939: "ह" DEVANAGARI LETTER HA + // U+096D: "७" DEVANAGARI DIGIT SEVEN + key("\u0939", joinMoreKeys("\u096D", "7")), + // U+0917: "ग" DEVANAGARI LETTER GA + // U+0918: "घ" DEVANAGARI LETTER GHA + // U+096E: "८" DEVANAGARI DIGIT EIGHT + key("\u0917", joinMoreKeys("\u0918", "\u096E", "8")), + // U+0926: "द" DEVANAGARI LETTER DA + // U+0927: "ध" DEVANAGARI LETTER DHA + // U+096F: "९" DEVANAGARI DIGIT NINE + key("\u0926", joinMoreKeys("\u0927", "\u096F", "9")), + // U+091C: "ज" DEVANAGARI LETTER JA + // U+091D: "झ" DEVANAGARI LETTER JHA + // U+091C/U+094D/U+091E: + // "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA + // U+0966: "०" DEVANAGARI DIGIT ZERO + key("\u091C", joinMoreKeys("\u091D", "\u091C\u094D\u091E", "\u0966", "0")), + // U+0921: "ड" DEVANAGARI LETTER DDA + // U+0922: "ढ" DEVANAGARI LETTER DDHA + key("\u0921", moreKey("\u0922"))) + .setKeysOfRow(2, + // U+0913: "ओ" DEVANAGARI LETTER O + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + key("\u0913", moreKey(VOWEL_SIGN_O, "\u094B")), + // U+090F: "ए" DEVANAGARI LETTER E + // U+0947: "े" DEVANAGARI VOWEL SIGN E + key("\u090F", moreKey(VOWEL_SIGN_E, "\u0947")), + // U+0905: "अ" DEVANAGARI LETTER A + // U+094D: "्" DEVANAGARI SIGN VIRAMA + key("\u0905", moreKey(SIGN_VIRAMA, "\u094D")), + // U+0907: "इ" DEVANAGARI LETTER I + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + key("\u0907", moreKey(VOWEL_SIGN_I, "\u093F")), + // U+0909: "उ" DEVANAGARI LETTER U + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + key("\u0909", moreKey(VOWEL_SIGN_U, "\u0941")), + // U+092A: "प" DEVANAGARI LETTER PA + // U+092B: "फ" DEVANAGARI LETTER PHA + key("\u092A", moreKey("\u092B")), + // U+0930: "र" DEVANAGARI LETTER RA + // U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + key("\u0930", joinMoreKeys("\u090B", moreKey(VOWEL_SIGN_VOCALIC_R, "\u0943"))), + // U+0915: "क" DEVANAGARI LETTER KA + // U+0916: "ख" DEVANAGARI LETTER KHA + key("\u0915", moreKey("\u0916")), + // U+0924: "त" DEVANAGARI LETTER TA + // U+0925: "थ" DEVANAGARI LETTER THA + // U+0924/U+094D/U+0930: + // "त्र" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0924", joinMoreKeys("\u0925", "\u0924\u094D\u0930")), + // U+091A: "च" DEVANAGARI LETTER CA + // U+091B: "छ" DEVANAGARI LETTER CHA + key("\u091A", moreKey("\u091B")), + // U+091F: "ट" DEVANAGARI LETTER TTA + // U+0920: "ठ" DEVANAGARI LETTER TTHA + key("\u091F", moreKey("\u0920"))) + .setKeysOfRow(3, + // U+0911: "ऑ" DEVANAGARI LETTER CANDRA O + // U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O + key("\u0911", moreKey(VOWEL_SIGN_CANDRA_O, "\u0949")), + // U+090D: "ऍ" DEVANAGARI LETTER CANDRA E + // U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E + key("\u090D", moreKey(VOWEL_SIGN_CANDRA_E, "\u0945")), + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + // U+0903: "ः" DEVANAGARI SIGN VISARGA + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + // U+093C: "़" DEVANAGARI SIGN NUKTA + key(SIGN_ANUSVARA, "\u0902", joinMoreKeys( + moreKey(SIGN_VISARGA, "\u0903"), + moreKey(SIGN_CANDRABINDU, "\u0901"), + moreKey(SIGN_NUKTA, "\u093C"))), + // U+092E: "म" DEVANAGARI LETTER MA + // U+0950: "ॐ" DEVANAGARI OM + key("\u092E", moreKey("\u0950")), + // U+0928: "न" DEVANAGARI LETTER NA + // U+0923: "ण" DEVANAGARI LETTER NNA + // U+091E: "ञ" DEVANAGARI LETTER NYA + // U+0919: "ङ" DEVANAGARI LETTER NGA + key("\u0928", joinMoreKeys("\u0923", "\u091E", "\u0919")), + // U+0935: "व" DEVANAGARI LETTER VA + // U+0932: "ल" DEVANAGARI LETTER LA + "\u0935", "\u0932", + // U+0938: "स" DEVANAGARI LETTER SA + // U+0936: "श" DEVANAGARI LETTER SHA + // U+0937: "ष" DEVANAGARI LETTER SSA + // U+0936/U+094D/U+0930: + // "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0938", joinMoreKeys("\u0936", "\u0937", "\u0936\u094D\u0930")), + // U+092F: "य" DEVANAGARI LETTER YA + // U+0915/U+094D/U+0937: + // "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA + "\u092F", "\u0915\u094D\u0937") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Khmer.java b/tests/src/com/android/inputmethod/keyboard/layout/Khmer.java new file mode 100644 index 000000000..143ccf6eb --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Khmer.java @@ -0,0 +1,281 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Khmer alphabet keyboard. + */ +public final class Khmer extends LayoutBase { + private static final String LAYOUT_NAME = "khmer"; + + public Khmer(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class KhmerCustomizer extends LayoutCustomizer { + public KhmerCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return KHMER_ALPHABET_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_DOLLAR_WITH_RIEL; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { return EMPTY_KEYS; } + + // U+1780: "ក" KHMER LETTER KA + // U+1781: "ខ" KHMER LETTER KHA + // U+1782: "គ" KHMER LETTER KO + private static final ExpectedKey KHMER_ALPHABET_KEY = key( + "\u1780\u1781\u1782", Constants.CODE_SWITCH_ALPHA_SYMBOL); + + // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL + private static final ExpectedKey CURRENCY_DOLLAR_WITH_RIEL = key(Symbols.DOLLAR_SIGN, + moreKey("\u17DB"), Symbols.CENT_SIGN, Symbols.POUND_SIGN, Symbols.EURO_SIGN, + Symbols.YEN_SIGN, Symbols.PESO_SIGN); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + if (isPhone) { + return ALPHABET_COMMON; + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + builder.addKeysOnTheRightOfRow(4, (Object[])EXCLAMATION_AND_QUESTION_MARKS); + return builder.build(); + } + + @Override + public ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, + final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(4, DELETE_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+17E1: "១" KHMER DIGIT ONE + // U+17F1: "៱" KHMER SYMBOL LEK ATTAK MUOY + key("\u17E1", joinMoreKeys("1", "\u17F1")), + // U+17E2: "២" KHMER DIGIT TWO + // U+17F2: "៲" KHMER SYMBOL LEK ATTAK PII + key("\u17E2", joinMoreKeys("2", "\u17F2")), + // U+17E3: "៣" KHMER DIGIT THREE + // U+17F3: "៳" KHMER SYMBOL LEK ATTAK BEI + key("\u17E3", joinMoreKeys("3", "\u17F3")), + // U+17E4: "៤" KHMER DIGIT FOUR + // U+17F4: "៴" KHMER SYMBOL LEK ATTAK BUON + key("\u17E4", joinMoreKeys("4", "\u17F4")), + // U+17E5: "៥" KHMER DIGIT FIVE + // U+17F5: "៵" KHMER SYMBOL LEK ATTAK PRAM + key("\u17E5", joinMoreKeys("5", "\u17F5")), + // U+17E6: "៦" KHMER DIGIT SIX + // U+17F6: "៶" KHMER SYMBOL LEK ATTAK PRAM-MUOY + key("\u17E6", joinMoreKeys("6", "\u17F6")), + // U+17E7: "៧" KHMER DIGIT SEVEN + // U+17F7: "៷" KHMER SYMBOL LEK ATTAK PRAM-PII + key("\u17E7", joinMoreKeys("7", "\u17F7")), + // U+17E8: "៨" KHMER DIGIT EIGHT + // U+17F8: "៸" KHMER SYMBOL LEK ATTAK PRAM-BEI + key("\u17E8", joinMoreKeys("8", "\u17F8")), + // U+17E9: "៩" KHMER DIGIT NINE + // U+17F9: "៹" KHMER SYMBOL LEK ATTAK PRAM-BUON + key("\u17E9", joinMoreKeys("9", "\u17F9")), + // U+17E0: "០" KHMER DIGIT ZERO + // U+17F0: "៰" KHMER SYMBOL LEK ATTAK SON + key("\u17E0", joinMoreKeys("0", "\u17F0")), + // U+17A5: "ឥ" KHMER INDEPENDENT VOWEL QI + // U+17A6: "ឦ" KHMER INDEPENDENT VOWEL QII + key("\u17A5", moreKey("\u17A6")), + // U+17B2: "ឲ" KHMER INDEPENDENT VOWEL QOO TYPE TWO + // U+17B1: "ឱ" KHMER INDEPENDENT VOWEL QOO TYPE ONE + key("\u17B2", moreKey("\u17B1"))) + .setKeysOfRow(2, + // U+1786: "ឆ" KHMER LETTER CHA + // U+17B9: "ឹ" KHMER VOWEL SIGN Y + // U+17C1: "េ" KHMER VOWEL SIGN E + // U+179A: "រ" KHMER LETTER RO + // U+178F: "ត" KHMER LETTER TA + // U+1799: "យ" KHMER LETTER YO + // U+17BB: "ុ" KHMER VOWEL SIGN U + // U+17B7: "ិ" KHMER VOWEL SIGN I + // U+17C4: "ោ" KHMER VOWEL SIGN OO + // U+1795: "ផ" KHMER LETTER PHA + // U+17C0: "ៀ" KHMER VOWEL SIGN IE + "\u1786", "\u17B9", "\u17C1", "\u179A", "\u178F", "\u1799", "\u17BB", "\u17B7", + "\u17C4", "\u1795", "\u17C0", + // U+17AA: "ឪ" KHMER INDEPENDENT VOWEL QUUV + // U+17A7: "ឧ" KHMER INDEPENDENT VOWEL QU + // U+17B1: "ឱ" KHMER INDEPENDENT VOWEL QOO TYPE ONE + // U+17B3: "ឳ" KHMER INDEPENDENT VOWEL QAU + // U+17A9: "ឩ" KHMER INDEPENDENT VOWEL QUU + // U+17A8: "ឨ" KHMER INDEPENDENT VOWEL QUK + key("\u17AA", joinMoreKeys("\u17A7", "\u17B1", "\u17B3", "\u17A9", "\u17A8"))) + .setKeysOfRow(3, + // U+17B6: "ា" KHMER VOWEL SIGN AA + // U+179F: "ស" KHMER LETTER SA + // U+178A: "ដ" KHMER LETTER DA + // U+1790: "ថ" KHMER LETTER THA + // U+1784: "ង" KHMER LETTER NGO + // U+17A0: "ហ" KHMER LETTER HA + // U+17D2: "្" KHMER SIGN COENG + // U+1780: "ក" KHMER LETTER KA + // U+179B: "ល" KHMER LETTER LO + // U+17BE: "ើ" KHMER VOWEL SIGN OE + // U+17CB: "់" KHMER SIGN BANTOC + "\u17B6", "\u179F", "\u178A", "\u1790", "\u1784", "\u17A0", "\u17D2", "\u1780", + "\u179B", "\u17BE", "\u17CB", + // U+17AE: "ឮ" KHMER INDEPENDENT VOWEL LYY + // U+17AD: "ឭ" KHMER INDEPENDENT VOWEL LY + // U+17B0: "ឰ" KHMER INDEPENDENT VOWEL QAI + key("\u17AE", joinMoreKeys("\u17AD", "\u17B0"))) + .setKeysOfRow(4, + // U+178B: "ឋ" KHMER LETTER TTHA + // U+1781: "ខ" KHMER LETTER KHA + // U+1785: "ច" KHMER LETTER CA + // U+179C: "វ" KHMER LETTER VO + // U+1794: "ប" KHMER LETTER BA + // U+1793: "ន" KHMER LETTER NO + // U+1798: "ម" KHMER LETTER MO + // U+17BB/U+17C6: "ុំ" KHMER VOWEL SIGN U/KHMER SIGN NIKAHIT + // U+17D4: "។" KHMER SIGN KHAN + // U+17CA: "៊" KHMER SIGN TRIISAP + "\u178B", "\u1781", "\u1785", "\u179C", "\u1794", "\u1793", "\u1798", + "\u17BB\u17C6", "\u17D4", "\u17CA") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("!", ZWJ_KEY), + // U+17D7: "ៗ" KHMER SIGN LEK TOO + key("\u17D7", ZWNJ_KEY), + // U+17D1: "៑" KHMER SIGN VIRIAM + key("\"", moreKey("\u17D1")), + // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL + key("\u17DB", joinMoreKeys(Symbols.DOLLAR_SIGN, Symbols.EURO_SIGN)), + // U+17D6: "៖" KHMER SIGN CAMNUC PII KUUH + key("%", moreKey("\u17D6")), + // U+17CD: "៍" KHMER SIGN TOANDAKHIAT + // U+17D9: "៙" KHMER SIGN PHNAEK MUAN + key("\u17CD", moreKey("\u17D9")), + // U+17D0: "័" KHMER SIGN SAMYOK SANNYA + // U+17DA: "៚" KHMER SIGN KOOMUUT + key("\u17D0", moreKey("\u17DA")), + // U+17CF: "៏" KHMER SIGN AHSDA + key("\u17CF", moreKey("*")), + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + key("(", joinMoreKeys("{", "\u00AB")), + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + key(")", joinMoreKeys("}", "\u00BB")), + // U+17CC: "៌" KHMER SIGN ROBAT + // U+00D7: "×" MULTIPLICATION SIGN + key("\u17CC", moreKey("\u00D7")), + // U+17CE: "៎" KHMER SIGN KAKABAT + "\u17CE") + .setKeysOfRow(2, + // U+1788: "ឈ" KHMER LETTER CHO + // U+17DC: "ៜ" KHMER SIGN AVAKRAHASANYA + key("\u1788", moreKey("\u17DC")), + // U+17BA: "ឺ" KHMER VOWEL SIGN YY + // U+17DD: "៝" KHMER SIGN ATTHACAN + key("\u17BA", moreKey("\u17DD")), + // U+17C2: "ែ" KHMER VOWEL SIGN AE + "\u17C2", + // U+17AC: "ឬ" KHMER INDEPENDENT VOWEL RYY + // U+17AB: "ឫ" KHMER INDEPENDENT VOWEL RY + key("\u17AC", moreKey("\u17AB")), + // U+1791: "ទ" KHMER LETTER TO + // U+17BD: "ួ" KHMER VOWEL SIGN UA + // U+17BC: "ូ" KHMER VOWEL SIGN UU + // U+17B8: "ី" KHMER VOWEL SIGN II + // U+17C5: "ៅ" KHMER VOWEL SIGN AU + // U+1797: "ភ" KHMER LETTER PHO + // U+17BF: "ឿ" KHMER VOWEL SIGN YA + // U+17B0: "ឰ" KHMER INDEPENDENT VOWEL QAI + "\u1791", "\u17BD", "\u17BC", "\u17B8", "\u17C5", "\u1797", "\u17BF", "\u17B0") + .setKeysOfRow(3, + // U+17B6/U+17C6: "ាំ" KHMER VOWEL SIGN AA/KHMER SIGN NIKAHIT + // U+17C3: "ៃ" KHMER VOWEL SIGN AI + // U+178C: "ឌ" KHMER LETTER DO + // U+1792: "ធ" KHMER LETTER THO + // U+17A2: "អ" KHMER LETTER QAE + "\u17B6\u17C6", "\u17C3", "\u178C", "\u1792", "\u17A2", + // U+17C7: "ះ" KHMER SIGN REAHMUK + // U+17C8: "ៈ" KHMER SIGN YUUKALEAPINTU + key("\u17C7", moreKey("\u17C8")), + // U+1789: "ញ" KHMER LETTER NYO + "\u1789", + // U+1782: "គ" KHMER LETTER KO + // U+179D: "ឝ" KHMER LETTER SHA + key("\u1782", moreKey("\u179D")), + // U+17A1: "ឡ" KHMER LETTER LA + // U+17C4/U+17C7: "ោះ" KHMER VOWEL SIGN OO/KHMER SIGN REAHMUK + // U+17C9: "៉" KHMER SIGN MUUSIKATOAN + // U+17AF: "ឯ" KHMER INDEPENDENT VOWEL QE + "\u17A1", "\u17C4\u17C7", "\u17C9", "\u17AF") + .setKeysOfRow(4, + // U+178D: "ឍ" KHMER LETTER TTHO + // U+1783: "ឃ" KHMER LETTER KHO + // U+1787: "ជ" KHMER LETTER CO + // U+17C1/U+17C7: "េះ" KHMER VOWEL SIGN E/KHMER SIGN REAHMUK + "\u178D", "\u1783", "\u1787", "\u17C1\u17C7", + // U+1796: "ព" KHMER LETTER PO + // U+179E: "ឞ" KHMER LETTER SSO + key("\u1796", moreKey("\u179E")), + // U+178E: "ណ" KHMER LETTER NNO + // U+17C6: "ំ" KHMER SIGN NIKAHIT + // U+17BB/U+17C7: "ុះ" KHMER VOWEL SIGN U/KHMER SIGN REAHMUK + // U+17D5: "៕" KHMER SIGN BARIYOOSAN + "\u178E", "\u17C6", "\u17BB\u17C7", "\u17D5", "?") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Lao.java b/tests/src/com/android/inputmethod/keyboard/layout/Lao.java new file mode 100644 index 000000000..e7be9982a --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Lao.java @@ -0,0 +1,237 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Khmer alphabet keyboard. + */ +public final class Lao extends LayoutBase { + private static final String LAYOUT_NAME = "lao"; + + public Lao(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class LaoCustomizer extends LayoutCustomizer { + public LaoCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return LAO_ALPHABET_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_KIP; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { return EMPTY_KEYS; } + + // U+0E81: "ກ" LAO LETTER KO + // U+0E82: "ຂ" LAO LETTER KHO SUNG + // U+0E84: "ຄ" LAO LETTER KHO TAM + private static final ExpectedKey LAO_ALPHABET_KEY = key( + "\u0E81\u0E82\u0E84", Constants.CODE_SWITCH_ALPHA_SYMBOL); + + // U+20AD: "₭" KIP SIGN + private static final ExpectedKey CURRENCY_KIP = key("\u20AD", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + if (isPhone) { + return ALPHABET_COMMON; + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + builder.addKeysOnTheRightOfRow(4, (Object[])EXCLAMATION_AND_QUESTION_MARKS); + return builder.build(); + } + + @Override + public ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, + final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(4, DELETE_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0EA2: "ຢ" LAO LETTER YO + // U+0ED1: "໑" LAO DIGIT ONE + key("\u0EA2", joinMoreKeys("1", "\u0ED1")), + // U+0E9F: "ຟ" LAO LETTER FO SUNG + // U+0ED2: "໒" LAO DIGIT TWO + key("\u0E9F", joinMoreKeys("2", "\u0ED2")), + // U+0EC2: "ໂ" LAO VOWEL SIGN O + // U+0ED3: "໓" LAO DIGIT THREE + key("\u0EC2", joinMoreKeys("3", "\u0ED3")), + // U+0E96: "ຖ" LAO LETTER THO SUNG + // U+0ED4: "໔" LAO DIGIT FOUR + key("\u0E96", joinMoreKeys("4", "\u0ED4")), + // U+0EB8: "ຸ" LAO VOWEL SIGN U + // U+0EB9: "ູ" LAO VOWEL SIGN UU + "\u0EB8", "\u0EB9", + // U+0E84: "ຄ" LAO LETTER KHO TAM + // U+0ED5: "໕" LAO DIGIT FIVE + key("\u0E84", joinMoreKeys("5", "\u0ED5")), + // U+0E95: "ຕ" LAO LETTER TO + // U+0ED6: "໖" LAO DIGIT SIX + key("\u0E95", joinMoreKeys("6", "\u0ED6")), + // U+0E88: "ຈ" LAO LETTER CO + // U+0ED7: "໗" LAO DIGIT SEVEN + key("\u0E88", joinMoreKeys("7", "\u0ED7")), + // U+0E82: "ຂ" LAO LETTER KHO SUNG + // U+0ED8: "໘" LAO DIGIT EIGHT + key("\u0E82", joinMoreKeys("8", "\u0ED8")), + // U+0E8A: "ຊ" LAO LETTER SO TAM + // U+0ED9: "໙" LAO DIGIT NINE + key("\u0E8A", joinMoreKeys("9", "\u0ED9")), + // U+0ECD: "ໍ" LAO NIGGAHITA + "\u0ECD") + .setKeysOfRow(2, + // U+0EBB: "ົ" LAO VOWEL SIGN MAI KON + "\u0EBB", + // U+0EC4: "ໄ" LAO VOWEL SIGN AI + // U+0ED0: "໐" LAO DIGIT ZERO + key("\u0EC4", joinMoreKeys("0", "\u0ED0")), + // U+0EB3: "ຳ" LAO VOWEL SIGN AM + // U+0E9E: "ພ" LAO LETTER PHO TAM + // U+0EB0: "ະ" LAO VOWEL SIGN A + // U+0EB4: "ິ" LAO VOWEL SIGN I + // U+0EB5: "ີ" LAO VOWEL SIGN II + // U+0EAE: "ຮ" LAO LETTER HO TAM + // U+0E99: "ນ" LAO LETTER NO + // U+0E8D: "ຍ" LAO LETTER NYO + // U+0E9A: "ບ" LAO LETTER BO + // U+0EA5: "ລ" LAO LETTER LO LOOT + "\u0EB3", "\u0E9E", "\u0EB0", "\u0EB4", "\u0EB5", "\u0EAE", "\u0E99", "\u0E8D", + "\u0E9A", "\u0EA5") + .setKeysOfRow(3, + // U+0EB1: "ັ" LAO VOWEL SIGN MAI KAN + // U+0EAB: "ຫ" LAO LETTER HO SUNG + // U+0E81: "ກ" LAO LETTER KO + // U+0E94: "ດ" LAO LETTER DO + // U+0EC0: "ເ" LAO VOWEL SIGN E + // U+0EC9: "້" LAO TONE MAI THO + // U+0EC8: "່" LAO TONE MAI EK + // U+0EB2: "າ" LAO VOWEL SIGN AA + // U+0EAA: "ສ" LAO LETTER SO SUNG + // U+0EA7: "ວ" LAO LETTER WO + // U+0E87: "ງ" LAO LETTER NGO + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + "\u0EB1", "\u0EAB", "\u0E81", "\u0E94", "\u0EC0", "\u0EC9", "\u0EC8", "\u0EB2", + "\u0EAA", "\u0EA7", "\u0E87", "\u201C") + .setKeysOfRow(4, + // U+0E9C: "ຜ" LAO LETTER PHO SUNG + // U+0E9B: "ປ" LAO LETTER PO + // U+0EC1: "ແ" LAO VOWEL SIGN EI + // U+0EAD: "ອ" LAO LETTER O + // U+0EB6: "ຶ" LAO VOWEL SIGN Y + // U+0EB7: "ື" LAO VOWEL SIGN YY + // U+0E97: "ທ" LAO LETTER THO TAM + // U+0EA1: "ມ" LAO LETTER MO + // U+0EC3: "ໃ" LAO VOWEL SIGN AY + // U+0E9D: "ຝ" LAO LETTER FO TAM + "\u0E9C", "\u0E9B", "\u0EC1", "\u0EAD", "\u0EB6", "\u0EB7", "\u0E97", "\u0EA1", + "\u0EC3", "\u0E9D") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0ED1: "໑" LAO DIGIT ONE + // U+0ED2: "໒" LAO DIGIT TWO + // U+0ED3: "໓" LAO DIGIT THREE + // U+0ED4: "໔" LAO DIGIT FOUR + // U+0ECC: "໌" LAO CANCELLATION MARK + // U+0EBC: "ຼ" LAO SEMIVOWEL SIGN LO + // U+0ED5: "໕" LAO DIGIT FIVE + // U+0ED6: "໖" LAO DIGIT SIX + // U+0ED7: "໗" LAO DIGIT SEVEN + // U+0ED8: "໘" LAO DIGIT EIGHT + // U+0ED9: "໙" LAO DIGIT NINE + // U+0ECD/U+0EC8: "ໍ່" LAO NIGGAHITA/LAO TONE MAI EK + "\u0ED1", "\u0ED2", "\u0ED3", "\u0ED4", "\u0ECC", "\u0EBC", "\u0ED5", "\u0ED6", + "\u0ED7", "\u0ED8", "\u0ED9", "\u0ECD\u0EC8") + .setKeysOfRow(2, + // U+0EBB/U+0EC9: "" LAO VOWEL SIGN MAI KON/LAO TONE MAI THO + // U+0ED0: "໐" LAO DIGIT ZERO + // U+0EB3/U+0EC9: "ຳ້" LAO VOWEL SIGN AM/LAO TONE MAI THO + // U+0EB4/U+0EC9: "ິ້" LAO VOWEL SIGN I/LAO TONE MAI THO + // U+0EB5/U+0EC9: "ີ້" LAO VOWEL SIGN II/LAO TONE MAI THO + // U+0EA3: "ຣ" LAO LETTER LO LING + // U+0EDC: "ໜ" LAO HO NO + // U+0EBD: "ຽ" LAO SEMIVOWEL SIGN NYO + // U+0EAB/U+0EBC: "" LAO LETTER HO SUNG/LAO SEMIVOWEL SIGN LO + // U+201D: "”" RIGHT DOUBLE QUOTATION MARK + "\u0EBB\u0EC9", "\u0ED0", "\u0EB3\u0EC9", "_", "+", "\u0EB4\u0EC9", + "\u0EB5\u0EC9", "\u0EA3", "\u0EDC", "\u0EBD", "\u0EAB\u0EBC", "\u201D") + .setKeysOfRow(3, + // U+0EB1/U+0EC9: "ັ້" LAO VOWEL SIGN MAI KAN/LAO TONE MAI THO + // U+0ECA: "໊" LAO TONE MAI TI + // U+0ECB: "໋" LAO TONE MAI CATAWA + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + "\u0EB1\u0EC9", ";", ".", ",", ":", "\u0ECA", "\u0ECB", "!", "?", "%", "=", + "\u201C") + .setKeysOfRow(4, + // U+20AD: "₭" KIP SIGN + // U+0EAF: "ຯ" LAO ELLIPSIS + // U+0EB6/U+0EC9: "ຶ້" LAO VOWEL SIGN Y/LAO TONE MAI THO + // U+0EB7/U+0EC9: "ື້" LAO VOWEL SIGN YY/LAO TONE MAI THO + // U+0EC6: "ໆ" LAO KO LA + // U+0EDD: "ໝ" LAO HO MO + "\u20AD", "(", "\u0EAF", "@", "\u0EB6\u0EC9", "\u0EB7\u0EC9", "\u0EC6", + "\u0EDD", "$", ")") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java new file mode 100644 index 000000000..c5223720c --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java @@ -0,0 +1,371 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The base class of keyboard layout. + */ +public abstract class LayoutBase extends AbstractLayoutBase { + /** + * This class is used to customize common keyboard layout to language specific layout. + */ + public static class LayoutCustomizer { + private final Locale mLocale; + + // Empty keys definition to remove keys by adding this. + protected static final ExpectedKey[] EMPTY_KEYS = joinKeys(); + + public LayoutCustomizer(final Locale locale) { + mLocale = locale; + } + + public final Locale getLocale() { + return mLocale; + } + + /** + * Set accented letters to common layout. + * @param builder the {@link ExpectedKeyboardBuilder} object that contains common keyboard + * layout. + * @return the {@link ExpectedKeyboardBuilder} object that contains accented letters as + * "more keys". + */ + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder; + } + + /** + * Get the function key to switch to alphabet layout. + * @return the {@link ExpectedKey} of the alphabet key. + */ + public ExpectedKey getAlphabetKey() { return ALPHABET_KEY; } + + /** + * Get the function key to switch to symbols layout. + * @return the {@link ExpectedKey} of the symbols key. + */ + public ExpectedKey getSymbolsKey() { return SYMBOLS_KEY; } + + /** + * Get the function key to switch to symbols shift layout. + * @param isPhone true if requesting phone's key. + * @return the {@link ExpectedKey} of the symbols shift key. + */ + public ExpectedKey getSymbolsShiftKey(boolean isPhone) { + return isPhone ? SYMBOLS_SHIFT_KEY : TABLET_SYMBOLS_SHIFT_KEY; + } + + /** + * Get the function key to switch from symbols shift to symbols layout. + * @return the {@link ExpectedKey} of the back to symbols key. + */ + public ExpectedKey getBackToSymbolsKey() { return BACK_TO_SYMBOLS_KEY; } + + /** + * Get the currency key. + * @return the {@link ExpectedKey} of the currency key. + */ + public ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_DOLLAR; } + + /** + * Get other currencies keys. + * @return the array of {@link ExpectedKey} that represents other currency keys. + */ + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_THAN_DOLLAR; + } + + /** + * Get "more keys" of double quotation mark. + * @return the array of {@link ExpectedKey} of more double quotation marks in natural order. + */ + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_9LR; } + + /** + * Get "more keys" of single quotation mark. + * @return the array of {@link ExpectedKey} of more single quotation marks in natural order. + */ + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_9LR; } + + /** + * Get double angle quotation marks in natural order. + * @return the array of {@link ExpectedKey} of double angle quotation marks in natural + * order. + */ + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_LR; } + + /** + * Get single angle quotation marks in natural order. + * @return the array of {@link ExpectedKey} of single angle quotation marks in natural + * order. + */ + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_LR; } + + /** + * Get the left shift keys. + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that should be placed at left edge of the + * keyboard. + */ + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return joinKeys(SHIFT_KEY); + } + + /** + * Get the right shift keys. + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that should be placed at right edge of the + * keyboard. + */ + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : joinKeys(EXCLAMATION_AND_QUESTION_MARKS, SHIFT_KEY); + } + + /** + * Get the space keys. + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that should be placed at the center of the + * keyboard. + */ + public ExpectedKey[] getSpaceKeys(final boolean isPhone) { + return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY); + } + + /** + * Get the keys left to the spacebar. + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that should be placed at left of the spacebar. + */ + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + // U+002C: "," COMMA + return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY)) + : joinKeys(key("\u002C", SETTINGS_KEY), "_"); + } + + /** + * Get the keys right to the spacebar. + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that should be placed at right of the spacebar. + */ + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + final ExpectedKey periodKey = key(".", getPunctuationMoreKeys(isPhone)); + return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey); + } + + /** + * Get "more keys" for the punctuation key (usually the period key). + * @param isPhone true if requesting phone's keys. + * @return the array of {@link ExpectedKey} that are "more keys" of the punctuation key. + */ + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? PHONE_PUNCTUATION_MORE_KEYS : TABLET_PUNCTUATION_MORE_KEYS; + } + } + + /** + * The layout customize class for countries that use Euro. + */ + public static class EuroCustomizer extends LayoutCustomizer { + public EuroCustomizer(final Locale locale) { + super(locale); + } + + @Override + public final ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_EURO; } + + @Override + public final ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_THAN_EURO; + } + } + + private final LayoutCustomizer mCustomizer; + private final Symbols mSymbols; + private final SymbolsShifted mSymbolsShifted; + + LayoutBase(final LayoutCustomizer customizer, final Class<? extends Symbols> symbolsClass, + final Class<? extends SymbolsShifted> symbolsShiftedClass) { + mCustomizer = customizer; + try { + mSymbols = symbolsClass.getDeclaredConstructor(LayoutCustomizer.class) + .newInstance(customizer); + mSymbolsShifted = symbolsShiftedClass.getDeclaredConstructor(LayoutCustomizer.class) + .newInstance(customizer); + } catch (final Exception e) { + throw new RuntimeException("Unknown Symbols/SymbolsShifted class", e); + } + } + + /** + * The layout name. + * @return the name of this layout. + */ + public abstract String getName(); + + /** + * The locale of this layout. + * @return the locale of this layout. + */ + public final Locale getLocale() { return mCustomizer.getLocale(); } + + /** + * The layout customizer for this layout. + * @return the layout customizer; + */ + public final LayoutCustomizer getCustomizer() { return mCustomizer; } + + // Icon id. + private static final int ICON_SHIFT = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_SHIFT_KEY); + private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED); + private static final int ICON_ZWNJ = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_ZWNJ_KEY); + private static final int ICON_ZWJ = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_ZWJ_KEY); + + // Functional key. + static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK); + static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT, + Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY); + static final ExpectedKey SHIFTED_SHIFT_KEY = key(ICON_SHIFTED_SHIFT, + Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY); + static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL); + static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL); + static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT); + static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT); + static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT); + + // U+00A1: "¡" INVERTED EXCLAMATION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + static final ExpectedKey[] EXCLAMATION_AND_QUESTION_MARKS = joinKeys( + key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF"))); + // U+200C: ZERO WIDTH NON-JOINER + // U+200D: ZERO WIDTH JOINER + static final ExpectedKey ZWNJ_KEY = key(ICON_ZWNJ, "\u200C"); + static final ExpectedKey ZWJ_KEY = key(ICON_ZWJ, "\u200D"); + + // Punctuation more keys for phone form factor. + public static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "?", "!", "#", ")", "(", "/", ";", + "'", "@", ":", "-", "\"", "+", "%", "&"); + // Punctuation more keys for tablet form factor. + public static final ExpectedKey[] TABLET_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "'", "#", ")", "(", "/", ";", + "@", ":", "-", "\"", "+", "%", "&"); + + /** + * Helper method to create alphabet layout adding special function keys. + * @param builder the {@link ExpectedKeyboardBuilder} object that contains common keyboard + * layout + * @param isPhone true if requesting phone's layout. + * @return the {@link ExpectedKeyboardBuilder} object that is customized and have special keys. + */ + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(4, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(4, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(3, DELETE_KEY) + .addKeysOnTheLeftOfRow(4, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .addKeysOnTheLeftOfRow(4, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(4, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(3, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(3, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + /** + * Get common alphabet layout. This layout doesn't contain any special keys. + * + * A keyboard layout is an array of rows, and a row consists of an array of + * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s. + * + * @param isPhone true if requesting phone's layout. + * @return the common alphabet keyboard layout. + */ + abstract ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone); + + /** + * Get common alphabet shifted layout. This layout doesn't contain any special keys. + * + * A keyboard layout is an array of rows, and a row consists of an array of + * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s. + * + * @param isPhone true if requesting phone's layout. + * @param elementId the element id of the requesting shifted mode. + * @return the common alphabet shifted keyboard layout. + */ + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder( + getCommonAlphabetLayout(isPhone)); + getCustomizer().setAccentedLetters(builder); + builder.toUpperCase(getLocale()); + return builder.build(); + } + + /** + * Get the complete expected keyboard layout. + * + * A keyboard layout is an array of rows, and a row consists of an array of + * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s. + * + * @param isPhone true if requesting phone's layout. + * @param elementId the element id of the requesting keyboard mode. + * @return the keyboard layout of the <code>elementId</code>. + */ + public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_SYMBOLS) { + return mSymbols.getLayout(isPhone); + } + if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) { + return mSymbolsShifted.getLayout(isPhone); + } + final ExpectedKeyboardBuilder builder; + if (elementId == KeyboardId.ELEMENT_ALPHABET) { + builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone)); + getCustomizer().setAccentedLetters(builder); + } else { + final ExpectedKey[][] commonLayout = getCommonAlphabetShiftLayout(isPhone, elementId); + if (commonLayout == null) { + return null; + } + builder = new ExpectedKeyboardBuilder(commonLayout); + } + convertCommonLayoutToKeyboard(builder, isPhone); + if (elementId != KeyboardId.ELEMENT_ALPHABET) { + builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY); + } + return builder.build(); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Marathi.java b/tests/src/com/android/inputmethod/keyboard/layout/Marathi.java new file mode 100644 index 000000000..00cf838f9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Marathi.java @@ -0,0 +1,179 @@ +/* + * 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 static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*; + +import com.android.inputmethod.keyboard.layout.Hindi.HindiCustomizer; +import com.android.inputmethod.keyboard.layout.Hindi.HindiSymbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * The Marathi keyboard. + */ +public final class Marathi extends LayoutBase { + private static final String LAYOUT_NAME = "marathi"; + + public Marathi(final LayoutCustomizer customizer) { + super(customizer, HindiSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class MarathiCustomizer extends HindiCustomizer { + public MarathiCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return EMPTY_KEYS; + } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + return null; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + // U+0914: "औ" DEVANAGARI LETTER AU + // U+0967: "१" DEVANAGARI DIGIT ONE + key(VOWEL_SIGN_AU, "\u094C", joinMoreKeys("\u0914", "\u0967", "1")), + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + // U+0910: "ऐ" DEVANAGARI LETTER AI + // U+0968: "२" DEVANAGARI DIGIT TWO + key(VOWEL_SIGN_AI, "\u0948", joinMoreKeys("\u0910", "\u0968", "2")), + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + // U+0906: "आ" DEVANAGARI LETTER AA + // U+0969: "३" DEVANAGARI DIGIT THREE + key(VOWEL_SIGN_AA, "\u093E", joinMoreKeys("\u0906", "\u0969", "3")), + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + // U+0908: "ई" DEVANAGARI LETTER II + // U+096A: "४" DEVANAGARI DIGIT FOUR + key(VOWEL_SIGN_II, "\u0940", joinMoreKeys("\u0908", "\u096A", "4")), + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + // U+090A: "ऊ" DEVANAGARI LETTER UU + // U+096B: "५" DEVANAGARI DIGIT FIVE + key(VOWEL_SIGN_UU, "\u0942", joinMoreKeys("\u090A", "\u096B", "5")), + // U+092C: "ब" DEVANAGARI LETTER BA + // U+092D: "भ" DEVANAGARI LETTER BHA + // U+096C: "६" DEVANAGARI DIGIT SIX + key("\u092C", joinMoreKeys("\u092D", "\u096C", "6")), + // U+0939: "ह" DEVANAGARI LETTER HA + // U+096D: "७" DEVANAGARI DIGIT SEVEN + key("\u0939", joinMoreKeys("\u096D", "7")), + // U+0917: "ग" DEVANAGARI LETTER GA + // U+0918: "घ" DEVANAGARI LETTER GHA + // U+096E: "८" DEVANAGARI DIGIT EIGHT + key("\u0917", joinMoreKeys("\u0918", "\u096E", "8")), + // U+0926: "द" DEVANAGARI LETTER DA + // U+0927: "ध" DEVANAGARI LETTER DHA + // U+096F: "९" DEVANAGARI DIGIT NINE + key("\u0926", joinMoreKeys("\u0927", "\u096F", "9")), + // U+091C: "ज" DEVANAGARI LETTER JA + // U+091D: "झ" DEVANAGARI LETTER JHA + // U+091C/U+094D/U+091E: + // "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA + // U+0966: "०" DEVANAGARI DIGIT ZERO + key("\u091C", joinMoreKeys("\u091D", "\u091C\u094D\u091E", "\u0966", "0")), + // U+0921: "ड" DEVANAGARI LETTER DDA + // U+0922: "ढ" DEVANAGARI LETTER DDHA + key("\u0921", moreKey("\u0922"))) + .setKeysOfRow(2, + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + // U+0913: "ओ" DEVANAGARI LETTER O + key(VOWEL_SIGN_O, "\u094B", moreKey("\u0913")), + // U+0947: "े" DEVANAGARI VOWEL SIGN E + // U+090F: "ए" DEVANAGARI LETTER SHORT E + key(VOWEL_SIGN_E, "\u0947", moreKey("\u090F")), + // U+094D: "्" DEVANAGARI SIGN VIRAMA + // U+0905: "अ" DEVANAGARI LETTER A + key(SIGN_VIRAMA, "\u094D", moreKey("\u0905")), + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + // U+0907: "इ" DEVANAGARI LETTER I + key(VOWEL_SIGN_I, "\u093F", moreKey("\u0907")), + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + // U+0909: "उ" DEVANAGARI LETTER U + key(VOWEL_SIGN_U, "\u0941", moreKey("\u0909")), + // U+092A: "प" DEVANAGARI LETTER PA + // U+092B: "फ" DEVANAGARI LETTER PHA + key("\u092A", moreKey("\u092B")), + // U+0930: "र" DEVANAGARI LETTER RA + // U+0931: "ऱ" DEVANAGARI LETTER RRA + // U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + key("\u0930", joinMoreKeys( + "\u0931", "\u090B", moreKey(VOWEL_SIGN_VOCALIC_R, "\u0943"))), + // U+0915: "क" DEVANAGARI LETTER KA + // U+0916: "ख" DEVANAGARI LETTER KHA + key("\u0915", moreKey("\u0916")), + // U+0924: "त" DEVANAGARI LETTER TA + // U+0925: "थ" DEVANAGARI LETTER THA + // U+0924/U+094D/U+0930: + // "त्र" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0924", joinMoreKeys("\u0925", "\u0924\u094D\u0930")), + // U+091A: "च" DEVANAGARI LETTER CA + // U+091B: "छ" DEVANAGARI LETTER CHA + key("\u091A", moreKey("\u091B")), + // U+091F: "ट" DEVANAGARI LETTER TTA + // U+0920: "ठ" DEVANAGARI LETTER TTHA + key("\u091F", moreKey("\u0920"))) + .setKeysOfRow(3, + // U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O + // U+0911: "ऑ" DEVANAGARI LETTER CANDRA O + key(VOWEL_SIGN_CANDRA_O, "\u0949", moreKey("\u0911")), + // U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E + // U+090D: "ऍ" DEVANAGARI LETTER CANDRA E + key(VOWEL_SIGN_CANDRA_E, "\u0945", moreKey("\u090D")), + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + // U+0903: "ः" DEVANAGARI SIGN VISARGA + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + key(SIGN_ANUSVARA, "\u0902", joinMoreKeys( + moreKey(SIGN_VISARGA, "\u0903"), moreKey(SIGN_CANDRABINDU, "\u0901"))), + // U+092E: "म" DEVANAGARI LETTER MA + "\u092E", + // U+0928: "न" DEVANAGARI LETTER NA + // U+0923: "ण" DEVANAGARI LETTER NNA + // U+091E: "ञ" DEVANAGARI LETTER NYA + // U+0919: "ङ" DEVANAGARI LETTER NGA + key("\u0928", joinMoreKeys("\u0923", "\u091E", "\u0919")), + // U+0935: "व" DEVANAGARI LETTER VA + "\u0935", + // U+0932: "ल" DEVANAGARI LETTER LA + // U+0933: "ळ" DEVANAGARI LETTER LLA + key("\u0932", moreKey("\u0933")), + // U+0938: "स" DEVANAGARI LETTER SA + // U+0936: "श" DEVANAGARI LETTER SHA + // U+0937: "ष" DEVANAGARI LETTER SSA + // U+0936/U+094D/U+0930: + // "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key("\u0938", joinMoreKeys("\u0936", "\u0937", "\u0936\u094D\u0930")), + // U+092F: "य" DEVANAGARI LETTER YA + "\u092F", + // U+0915/U+094D/U+0937: + // "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA + "\u0915\u094D\u0937") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Mongolian.java b/tests/src/com/android/inputmethod/keyboard/layout/Mongolian.java new file mode 100644 index 000000000..3c6c05841 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Mongolian.java @@ -0,0 +1,114 @@ +/* + * 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.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +public final class Mongolian extends LayoutBase { + private static final String LAYOUT_NAME = "mongolian"; + + public Mongolian(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class MongolianMNCustomizer extends EastSlavicCustomizer { + public MongolianMNCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_TUGRIK; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + // U+20AE: "₮" TUGRIK SIGN + private static final ExpectedKey CURRENCY_TUGRIK = key("\u20AE", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0444: "ф" CYRILLIC SMALL LETTER EF + key("\u0444", moreKey("1")), + // U+0446: "ц" CYRILLIC SMALL LETTER TSE + key("\u0446", moreKey("2")), + // U+0443: "у" CYRILLIC SMALL LETTER U + key("\u0443", moreKey("3")), + // U+0436: "ж" CYRILLIC SMALL LETTER ZHE + key("\u0436", moreKey("4")), + // U+044D: "э" CYRILLIC SMALL LETTER E + key("\u044D", moreKey("5")), + // U+043D: "н" CYRILLIC SMALL LETTER EN + key("\u043D", moreKey("6")), + // U+0433: "г" CYRILLIC SMALL LETTER GHE + key("\u0433", moreKey("7")), + // U+0448: "ш" CYRILLIC SMALL LETTER SHA + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + key("\u0448", joinMoreKeys("8", "\u0449")), + // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U + key("\u04AF", moreKey("9")), + // U+0437: "з" CYRILLIC SMALL LETTER ZE + key("\u0437", moreKey("0")), + // U+043A: "к" CYRILLIC SMALL LETTER KA + "\u043A") + .setKeysOfRow(2, + // U+0439: "й" CYRILLIC SMALL LETTER SHORT I + // U+044B: "ы" CYRILLIC SMALL LETTER YERU + // U+0431: "б" CYRILLIC SMALL LETTER BE + // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+0445: "х" CYRILLIC SMALL LETTER HA + // U+0440: "р" CYRILLIC SMALL LETTER ER + // U+043E: "о" CYRILLIC SMALL LETTER O + // U+043B: "л" CYRILLIC SMALL LETTER EL + // U+0434: "д" CYRILLIC SMALL LETTER DE + // U+043F: "п" CYRILLIC SMALL LETTER PE + "\u0439", "\u044B", "\u0431", "\u04E9", "\u0430", "\u0445", "\u0440", "\u043E", + "\u043B", "\u0434", "\u043F") + .setKeysOfRow(3, + // U+044F: "я" CYRILLIC SMALL LETTER YA + // U+0447: "ч" CYRILLIC SMALL LETTER CHE + "\u044F", "\u0447", + // U+0451: "ё" CYRILLIC SMALL LETTER IO + // U+0435: "е" CYRILLIC SMALL LETTER IE + key("\u0451", moreKey("\u0435")), + // U+0441: "с" CYRILLIC SMALL LETTER ES + // U+043C: "м" CYRILLIC SMALL LETTER EM + // U+0438: "и" CYRILLIC SMALL LETTER I + // U+0442: "т" CYRILLIC SMALL LETTER TE + "\u0441", "\u043C", "\u0438", "\u0442", + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + key("\u044C", moreKey("\u044A")), + // U+0432: "в" CYRILLIC SMALL LETTER VE + // U+044E: "ю" CYRILLIC SMALL LETTER YU + key("\u0432", moreKey("\u044E"))) + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java b/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java new file mode 100644 index 000000000..1b571acc6 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java @@ -0,0 +1,258 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Myanmar alphabet keyboard. + */ +public final class Myanmar extends LayoutBase { + private static final String LAYOUT_NAME = "myanmar"; + + public Myanmar(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class MyanmarCustomizer extends LayoutCustomizer { + public MyanmarCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return MYANMAR_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + // U+002C: "," COMMA + // U+104A: "၊" MYANMAR SIGN LITTLE SECTION + return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY)) + : joinKeys(key("\u104A", moreKey(","), SETTINGS_KEY), "_"); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + // U+104B: "။" MYANMAR SIGN SECTION + final ExpectedKey periodKey = key("\u104B", getPunctuationMoreKeys(isPhone)); + return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? MYANMAR_PHONE_PUNCTUATION_MORE_KEYS + : MYANMAR_TABLET_PUNCTUATION_MORE_KEYS; + } + + // U+1000: "က" MYANMAR LETTER KA + // U+1001: "ခ" MYANMAR LETTER KHA + // U+1002: "ဂ" MYANMAR LETTER GA + private static final ExpectedKey MYANMAR_ALPHABET_KEY = key( + "\u1000\u1001\u1002", Constants.CODE_SWITCH_ALPHA_SYMBOL); + + // U+104A: "၊" MYANMAR SIGN LITTLE SECTION + // Punctuation more keys for phone form factor. + private static final ExpectedKey[] MYANMAR_PHONE_PUNCTUATION_MORE_KEYS = joinKeys( + "\u104A", ".", "?", "!", "#", ")", "(", "/", ";", + "...", "'", "@", ":", "-", "\"", "+", "%", "&"); + // Punctuation more keys for tablet form factor. + private static final ExpectedKey[] MYANMAR_TABLET_PUNCTUATION_MORE_KEYS = joinKeys( + ".", "'", "#", ")", "(", "/", ";", "@", + "...", ":", "-", "\"", "+", "%", "&"); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + @Override + public ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, + final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(4, DELETE_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+1041: "၁" MYANMAR DIGIT ONE + key("\u1041", moreKey("1")), + // U+1042: "၂" MYANMAR DIGIT TWO + key("\u1042", moreKey("2")), + // U+1043: "၃" MYANMAR DIGIT THREE + key("\u1043", moreKey("3")), + // U+1044: "၄" MYANMAR DIGIT FOUR + key("\u1044", moreKey("4")), + // U+1045: "၅" MYANMAR DIGIT FIVE + key("\u1045", moreKey("5")), + // U+1046: "၆" MYANMAR DIGIT SIX + key("\u1046", moreKey("6")), + // U+1047: "၇" MYANMAR DIGIT SEVEN + key("\u1047", moreKey("7")), + // U+1048: "၈" MYANMAR DIGIT EIGHT + key("\u1048", moreKey("8")), + // U+1049: "၉" MYANMAR DIGIT NINE + key("\u1049", moreKey("9")), + // U+1040: "၀" MYANMAR DIGIT ZERO + key("\u1040", moreKey("0"))) + .setKeysOfRow(2, + // U+1006: "ဆ" MYANMAR LETTER CHA + // U+1010: "တ" MYANMAR LETTER TA + // U+1014: "န" MYANMAR LETTER NA + // U+1019: "မ" MYANMAR LETTER MA + // U+1021: "အ" MYANMAR LETTER A + // U+1015: "ပ" MYANMAR LETTER PA + // U+1000: "က" MYANMAR LETTER KA + // U+1004: "င" MYANMAR LETTER NGA + // U+101E: "သ" MYANMAR LETTER SA + // U+1005: "စ" MYANMAR LETTER CA + "\u1006", "\u1010", "\u1014", "\u1019", "\u1021", "\u1015", "\u1000", "\u1004", + "\u101E", "\u1005") + .setKeysOfRow(3, + // U+1031: "ေ" MYANMAR VOWEL SIGN E + // U+103B: "ျ" MYANMAR CONSONANT SIGN MEDIAL YA + // U+103C: "ြ" MYANMAR CONSONANT SIGN MEDIAL RA + "\u1031", "\u103B", "\u103C", + // U+103D: "ွ" MYANMAR CONSONANT SIGN MEDIAL WA + // U+103E: "ှ" MYANMAR CONSONANT SIGN MEDIAL HA + // U+103D/U+103E: + // "ွှ" MYANMAR CONSONANT SIGN MEDIAL WA/MYANMAR CONSONANT SIGN MEDIAL HA + key("\u103D", joinMoreKeys("\u103E", "\u103D\u103E")), + // U+102D: "ိ" MYANMAR VOWEL SIGN I + // U+102E: "ီ" MYANMAR VOWEL SIGN II + key("\u102D", moreKey("\u102E")), + // U+102F: "ု" MYANMAR VOWEL SIGN U + // U+1030: "ူ" MYANMAR VOWEL SIGN UU + key("\u102F", moreKey("\u1030")), + // U+102C: "ာ" MYANMAR VOWEL SIGN AA + "\u102C", + // U+103A: "်" MYANMAR SIGN ASAT + // U+1032: "ဲ" MYANMAR VOWEL SIGN AI + key("\u103A", moreKey("\u1032")), + // U+1037: "့" MYANMAR SIGN DOT BELOW + // U+1036: "ံ" MYANMAR SIGN ANUSVARA + key("\u1037", moreKey("\u1036")), + // U+1038: "း" MYANMAR SIGN VISARGA + "\u1038") + .setKeysOfRow(4, + // U+1016: "ဖ" MYANMAR LETTER PHA + // U+1011: "ထ" MYANMAR LETTER THA + // U+1001: "ခ" MYANMAR LETTER KHA + // U+101C: "လ" MYANMAR LETTER LA + // U+1018: "ဘ" MYANMAR LETTER BHA + "\u1016", "\u1011", "\u1001", "\u101C", "\u1018", + // U+100A: "ည" MYANMAR LETTER NNYA + // U+1009: "ဉ" MYANMAR LETTER NYA + key("\u100A", moreKey("\u1009")), + // U+101B: "ရ" MYANMAR LETTER RA + // U+101D: "ဝ" MYANMAR LETTER WA + "\u101B", "\u101D") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+1027: "ဧ" MYANMAR LETTER E + // U+104F: "၏" MYANMAR SYMBOL GENITIVE + // U+1024: "ဤ" MYANMAR LETTER II + // U+1023: "ဣ" MYANMAR LETTER I + // U+104E: "၎" MYANMAR SYMBOL AFOREMENTIONED + // U+1000/U+103B/U+1015/U+103A: "ကျပ်" MYANMAR LETTER KA + // /MYANMAR CONSONANT SIGN MEDIAL YA/MYANMAR LETTER PA/MYANMAR SIGN ASAT + // U+1029: "ဩ" MYANMAR LETTER O + // U+102A: "ဪ" MYANMAR LETTER AU + // U+104D: "၍" MYANMAR SYMBOL COMPLETED + // U+104C: "၌" MYANMAR SYMBOL LOCATIVE + "\u1027", "\u104F", "\u1024", "\u1023", "\u104E", "\u1000\u103B\u1015\u103A", + "\u1029", "\u102A", "\u104D", "\u104C") + .setKeysOfRow(2, + // U+1017: "ဗ" MYANMAR LETTER BA + // U+1012: "ဒ" MYANMAR LETTER DA + // U+1013: "ဓ" MYANMAR LETTER DHA + // U+1003: "ဃ" MYANMAR LETTER GHA + // U+100E: "ဎ" MYANMAR LETTER DDHA + // U+103F: "ဿ" MYANMAR LETTER GREAT SA + // U+100F: "ဏ" MYANMAR LETTER NNA + // U+1008: "ဈ" MYANMAR LETTER JHA + // U+1007: "ဇ" MYANMAR LETTER JA + // U+1002: "ဂ" MYANMAR LETTER GA + "\u1017", "\u1012", "\u1013", "\u1003", "\u100E", "\u103F", "\u100F", "\u1008", + "\u1007", "\u1002") + .setKeysOfRow(3, + // U+101A: "ယ" MYANMAR LETTER YA + // U+1039: "္" MYANMAR SIGN VIRAMA + // U+1004/U+103A/U+1039: "င်္င" MYANMAR LETTER NGA + // /MYANMAR SIGN ASAT/MYANMAR SIGN VIRAMA + // U+103E: "ှ" MYANMAR CONSONANT SIGN MEDIAL HA + // U+102E: "ီ" MYANMAR VOWEL SIGN II + // U+1030: "ူ" MYANMAR VOWEL SIGN UU + // U+102B: "ါ" MYANMAR VOWEL SIGN TALL AA + // U+1032: "ဲ" MYANMAR VOWEL SIGN AI + // U+1036: "ံ" MYANMAR SIGN ANUSVARA + // U+101F: "ဟ" MYANMAR LETTER HA + "\u101A", "\u1039", "\u1004\u103A\u1039", "\u103E", "\u102E", "\u1030", + "\u102B", "\u1032", "\u1036", "\u101F") + .setKeysOfRow(4, + // U+1025: "ဥ" MYANMAR LETTER U + // U+1026: "ဦ" MYANMAR LETTER UU + // U+100C: "ဌ" MYANMAR LETTER TTHA + // U+100B: "ဋ" MYANMAR LETTER TTA + // U+100D: "ဍ" MYANMAR LETTER DDA + // U+1020: "ဠ" MYANMAR LETTER LLA + // U+100B/U+1039/U+100C: "ဋ္ဌ" MYANMAR LETTER TTA + // /MYANMAR SIGN VIRAMA/MYANMAR LETTER TTHA + "\u1025", "\u1026", "\u100C", "\u100B", "\u100D", "\u1020", + "\u100B\u1039\u100C", + // U+100F/U+1039/U+100D: "ဏ္ဍ" MYANMAR LETTER NNA + // /MYANMAR SIGN VIRAMA/MYANMAR LETTER DDA + // U+100F/U+1039/U+100C: "ဏ္ဌ" MYANMAR LETTER NNA + // /MYANMAR SIGN VIRAMA/MYANMAR LETTER TTHA + key("\u100F\u1039\u100D", moreKey("\u100F\u1039\u100C"))) + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java b/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java new file mode 100644 index 000000000..7933d078c --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.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 static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*; + +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.layout.Hindi.HindiCustomizer; +import com.android.inputmethod.keyboard.layout.Hindi.HindiSymbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * The nepali_romanized layout + */ +public final class NepaliRomanized extends LayoutBase { + private static final String LAYOUT_NAME = "nepali_romanized"; + + public NepaliRomanized(final LayoutCustomizer customizer) { + super(customizer, HindiSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class NepaliRomanizedCustomizer extends HindiCustomizer { + public NepaliRomanizedCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_NEPALI; } + + @Override + public ExpectedKey[] getSpaceKeys(final boolean isPhone) { + return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY)); + } + + // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN + private static final ExpectedKey CURRENCY_NEPALI = key("\u0930\u0941\u002E", + Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN, Symbols.EURO_SIGN, Symbols.POUND_SIGN, + Symbols.YEN_SIGN, Symbols.PESO_SIGN); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + return ALPHABET_SHIFTED_COMMON; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+091F: "ट" DEVANAGARI LETTER TTA + // U+0967: "१" DEVANAGARI DIGIT ONE + // U+093C: "़" DEVANAGARI SIGN NUKTA + key("\u091F", joinMoreKeys("\u0967", "1", key(SIGN_NUKTA, "\u093C"))), + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + // U+0968: "२" DEVANAGARI DIGIT TWO + key(VOWEL_SIGN_AU, "\u094C", joinMoreKeys("\u0968", "2")), + // U+0947: "े" DEVANAGARI VOWEL SIGN E + // U+0969: "३" DEVANAGARI DIGIT THREE + key(VOWEL_SIGN_E, "\u0947", joinMoreKeys("\u0969", "3")), + // U+0930: "र" DEVANAGARI LETTER RA + // U+096A: "४" DEVANAGARI DIGIT FOUR + key("\u0930", joinMoreKeys("\u096A", "4")), + // U+0924: "त" DEVANAGARI LETTER TA + // U+096B: "५" DEVANAGARI DIGIT FIVE + key("\u0924", joinMoreKeys("\u096B", "5")), + // U+092F: "य" DEVANAGARI LETTER YA + // U+096C: "६" DEVANAGARI DIGIT SIX + key("\u092F", joinMoreKeys("\u096C", "6")), + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + // U+096D: "७" DEVANAGARI DIGIT SEVEN + key(VOWEL_SIGN_U, "\u0941", joinMoreKeys("\u096D", "7")), + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + // U+096E: "८" DEVANAGARI DIGIT EIGHT + key(VOWEL_SIGN_I, "\u093F", joinMoreKeys("\u096E", "8")), + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + // U+096F: "९" DEVANAGARI DIGIT NINE + key(VOWEL_SIGN_O, "\u094B", joinMoreKeys("\u096F", "9")), + // U+092A: "प" DEVANAGARI LETTER PA + // U+0966: "०" DEVANAGARI DIGIT ZERO + key("\u092A", joinMoreKeys("\u0966", "0")), + // U+0907: "इ" DEVANAGARI LETTER I + "\u0907") + .setKeysOfRow(2, + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + key(VOWEL_SIGN_AA, "\u093E"), + // U+0938: "स" DEVANAGARI LETTER SA + // U+0926: "द" DEVANAGARI LETTER DA + // U+0909: "उ" DEVANAGARI LETTER U + // U+0917: "ग" DEVANAGARI LETTER GA + // U+0939: "ह" DEVANAGARI LETTER HA + // U+091C: "ज" DEVANAGARI LETTER JA + // U+0915: "क" DEVANAGARI LETTER KA + // U+0932: "ल" DEVANAGARI LETTER LA + // U+090F: "ए" DEVANAGARI LETTER E + // U+0950: "ॐ" DEVANAGARI OM + "\u0938", "\u0926", "\u0909", "\u0917", "\u0939", "\u091C", "\u0915", "\u0932", + "\u090F", "\u0950") + .setKeysOfRow(3, + // U+0937: "ष" DEVANAGARI LETTER SSA + // U+0921: "ड" DEVANAGARI LETTER DDA + // U+091A: "च" DEVANAGARI LETTER CA + // U+0935: "व" DEVANAGARI LETTER VA + // U+092C: "ब" DEVANAGARI LETTER BHA + // U+0928: "न" DEVANAGARI LETTER NA + // U+092E: "म" DEVANAGARI LETTER MA + "\u0937", "\u0921", "\u091A", "\u0935", "\u092C", "\u0928", "\u092E", + // U+0964: "।" DEVANAGARI DANDA + // U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA + key("\u0964", moreKey("\u093D")), + // U+094D: "्" DEVANAGARI SIGN VIRAMA + key(SIGN_VIRAMA, "\u094D")) + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0920: "ठ" DEVANAGARI LETTER TTHA + // U+0914: "औ" DEVANAGARI LETTER AU + "\u0920", "\u0914", + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + key(VOWEL_SIGN_AI, "\u0948"), + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + key(VOWEL_SIGN_VOCALIC_R, "\u0943"), + // U+0925: "थ" DEVANAGARI LETTER THA + // U+091E: "ञ" DEVANAGARI LETTER NYA + "\u0925", "\u091E", + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + key(VOWEL_SIGN_UU, "\u0942"), + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + key(VOWEL_SIGN_II, "\u0940"), + // U+0913: "ओ" DEVANAGARI LETTER O + // U+092B: "फ" DEVANAGARI LETTER PHA + // U+0908: "ई" DEVANAGARI LETTER II + "\u0913", "\u092B", "\u0908") + .setKeysOfRow(2, + // U+0906: "आ" DEVANAGARI LETTER AA + // U+0936: "श" DEVANAGARI LETTER SHA + // U+0927: "ध" DEVANAGARI LETTER DHA + // U+090A: "ऊ" DEVANAGARI LETTER UU + // U+0918: "घ" DEVANAGARI LETTER GHA + // U+0905: "अ" DEVANAGARI LETTER A + // U+091D: "झ" DEVANAGARI LETTER JHA + // U+0916: "ख" DEVANAGARI LETTER KHA + // U+0965: "॥" DEVANAGARI DOUBLE DANDA + // U+0910: "ऐ" DEVANAGARI LETTER AI + // U+0903: "ः" DEVANAGARI SIGN VISARGA + "\u0906", "\u0936", "\u0927", "\u090A", "\u0918", "\u0905", "\u091D", "\u0916", + "\u0965", "\u0910", key(SIGN_VISARGA, "\u0903")) + .setKeysOfRow(3, + // U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R + // U+0922: "ढ" DEVANAGARI LETTER DDHA + // U+091B: "छ" DEVANAGARI LETTER CHA + "\u090B", "\u0922", "\u091B", + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + key(SIGN_CANDRABINDU, "\u0901"), + // U+092D: "भ" DEVANAGARI LETTER BHA + // U+0923: "ण" DEVANAGARI LETTER NNA + "\u092D", "\u0923", + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + key(SIGN_ANUSVARA, "\u0902"), + // U+0919: "ङ" DEVANAGARI LETTER NGA + "\u0919", + // U+094D: "्" DEVANAGARI SIGN VIRAMA + key(SIGN_VIRAMA, "\u094D")) + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/NepaliTraditional.java b/tests/src/com/android/inputmethod/keyboard/layout/NepaliTraditional.java new file mode 100644 index 000000000..4d6cdedbf --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/NepaliTraditional.java @@ -0,0 +1,269 @@ +/* + * 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 static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*; + +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.layout.Hindi.HindiSymbols; +import com.android.inputmethod.keyboard.layout.NepaliRomanized.NepaliRomanizedCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * The nepali_traditional keyboard. + */ +public final class NepaliTraditional extends LayoutBase { + private static final String LAYOUT_NAME = "nepali_traditional"; + + public NepaliTraditional(final LayoutCustomizer customizer) { + super(customizer, HindiSymbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class NepaliTraditionalCustomizer extends NepaliRomanizedCustomizer { + public NepaliTraditionalCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { return EMPTY_KEYS; } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + if (isPhone) { + // U+094D: "्" DEVANAGARI SIGN VIRAMA + return joinKeys(key(SIGN_VIRAMA, "\u094D", PHONE_PUNCTUATION_MORE_KEYS)); + } + return super.getKeysRightToSpacebar(isPhone); + } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + if (isPhone) { + builder.addKeysOnTheRightOfRow(3, + // U+0947: "े" DEVANAGARI VOWEL SIGN E + // U+0903: "ः" DEVANAGARI SIGN VISARGA + // U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA + key(VOWEL_SIGN_E, "\u0947", joinMoreKeys( + moreKey(SIGN_VISARGA, "\u0903"), "\u093D")), + // U+0964: "।" DEVANAGARI DANDA + "\u0964", + // U+0930: "र" DEVANAGARI LETTER RA + // U+0930/U+0941: "रु" DEVANAGARI LETTER RA/DEVANAGARI VOWEL SIGN U + key("\u0930", moreKey("\u0930\u0941"))); + } else { + builder.addKeysOnTheRightOfRow(3, + // U+0903: "ः" DEVANAGARI SIGN VISARGA + // U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA + key(SIGN_VISARGA, "\u0903", moreKey("\u093D")), + // U+0947: "े" DEVANAGARI VOWEL SIGN E + key(VOWEL_SIGN_E, "\u0947"), + // U+0964: "।" DEVANAGARI DANDA + "\u0964", + // U+0930: "र" DEVANAGARI LETTER RA + key("\u0930", moreKey("!")), + // U+094D: "्" DEVANAGARI SIGN VIRAMA + key(SIGN_VIRAMA, "\u094D", moreKey("?"))); + } + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder( + ALPHABET_SHIFTED_COMMON); + if (isPhone) { + builder.addKeysOnTheRightOfRow(3, + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + key(SIGN_ANUSVARA, "\u0902"), + // U+0919: "ङ" DEVANAGARI LETTER NGA + "\u0919", + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + // U+0936/U+094D/U+0930: + // "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key(VOWEL_SIGN_AI, "\u0948", moreKey("\u0936\u094D\u0930"))); + } else { + builder.addKeysOnTheRightOfRow(3, + // U+0902: "ं" DEVANAGARI SIGN ANUSVARA + key(SIGN_ANUSVARA, "\u0902"), + // U+0919: "ङ" DEVANAGARI LETTER NGA + "\u0919", + // U+0948: "ै" DEVANAGARI VOWEL SIGN AI + // U+0936/U+094D/U+0930: + // "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA + key(VOWEL_SIGN_AI, "\u0948", moreKey("\u0936\u094D\u0930")), + // U+0930/U+0941: "रु" DEVANAGARI LETTER RA/DEVANAGARI VOWEL SIGN U + key("\u0930\u0941", moreKey("!")), + "?"); + } + return builder.build(); + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+091F: "ट" DEVANAGARI LETTER TTA + // U+0967: "१" DEVANAGARI DIGIT ONE + key("\u091F", joinMoreKeys("\u0967", "1")), + // U+0927: "ध" DEVANAGARI LETTER DHA + // U+0968: "२" DEVANAGARI DIGIT TWO + key("\u0927", joinMoreKeys("\u0968", "2")), + // U+092D: "भ" DEVANAGARI LETTER BHA + // U+0969: "३" DEVANAGARI DIGIT THREE + key("\u092D", joinMoreKeys("\u0969", "3")), + // U+091A: "च" DEVANAGARI LETTER CA + // U+096A: "४" DEVANAGARI DIGIT FOUR + key("\u091A", joinMoreKeys("\u096A", "4")), + // U+0924: "त" DEVANAGARI LETTER TA + // U+096B: "५" DEVANAGARI DIGIT FIVE + key("\u0924", joinMoreKeys("\u096B", "5")), + // U+0925: "थ" DEVANAGARI LETTER THA + // U+096C: "६" DEVANAGARI DIGIT SIX + key("\u0925", joinMoreKeys("\u096C", "6")), + // U+0917: "ग" DEVANAGARI LETTER G + // U+096D: "७" DEVANAGARI DIGIT SEVEN + key("\u0917", joinMoreKeys("\u096D", "7")), + // U+0937: "ष" DEVANAGARI LETTER SSA + // U+096E: "८" DEVANAGARI DIGIT EIGHT + key("\u0937", joinMoreKeys("\u096E", "8")), + // U+092F: "य" DEVANAGARI LETTER YA + // U+096F: "९" DEVANAGARI DIGIT NINE + key("\u092F", joinMoreKeys("\u096F", "9")), + // U+0909: "उ" DEVANAGARI LETTER U + // U+0966: "०" DEVANAGARI DIGIT ZERO + key("\u0909", joinMoreKeys("\u0966", "0")), + // U+0907: "इ" DEVANAGARI LETTER I + // U+0914: "औ" DEVANAGARI LETTER AU + key("\u0907", moreKey("\u0914"))) + .setKeysOfRow(2, + // U+092C: "ब" DEVANAGARI LETTER BA + // U+0915: "क" DEVANAGARI LETTER KA + // U+092E: "म" DEVANAGARI LETTER MA + "\u092C", "\u0915", "\u092E", + // U+093E: "ा" DEVANAGARI VOWEL SIGN AA + key(VOWEL_SIGN_AA, "\u093E"), + // U+0928: "न" DEVANAGARI LETTER NA + // U+091C: "ज" DEVANAGARI LETTER JA + // U+0935: "व" DEVANAGARI LETTER VA + // U+092A: "प" DEVANAGARI LETTER PA + "\u0928", "\u091C", "\u0935", "\u092A", + // U+093F: "ि" DEVANAGARI VOWEL SIGN I + key(VOWEL_SIGN_I, "\u093F"), + // U+0938: "स" DEVANAGARI LETTER SA + "\u0938", + // U+0941: "ु" DEVANAGARI VOWEL SIGN U + key(VOWEL_SIGN_U, "\u0941")) + .setKeysOfRow(3, + // U+0936: "श" DEVANAGARI LETTER SHA + // U+0939: "ह" DEVANAGARI LETTER HA + // U+0905: "अ" DEVANAGARI LETTER A + // U+0916: "ख" DEVANAGARI LETTER KHA + // U+0926: "द" DEVANAGARI LETTER DA + // U+0932: "ल" DEVANAGARI LETTER LA + "\u0936", "\u0939", "\u0905", "\u0916", "\u0926", "\u0932") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0924/U+094D/U+0924: + // "त्त" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TA + // U+091E: "ञ" DEVANAGARI LETTER NYA + // U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN + // VIRAMA/DEVANAGARI LETTER NYA + // U+0965: "॥" DEVANAGARI DOUBLE DANDA + key("\u0924\u094D\u0924", + joinMoreKeys("\u091E", "\u091C\u094D\u091E", "\u0965")), + // U+0921/U+094D/U+0922: + // "ड्ढ" DEVANAGARI LETTER DDA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DDHA + // U+0908: "ई" DEVANAGARI LETTER II + key("\u0921\u094D\u0922", moreKey("\u0908")), + // U+0910: "ऐ" DEVANAGARI LETTER AI + // U+0918: "घ" DEVANAGARI LETTER GHA + key("\u0910", moreKey("\u0918")), + // U+0926/U+094D/U+0935: + // "द्व" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER VA + // U+0926/U+094D/U+0927: + // "द्ध" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DHA + key("\u0926\u094D\u0935", moreKey("\u0926\u094D\u0927")), + // U+091F/U+094D/U+091F: + // "ट्ट" DEVANAGARI LETTER TTA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTA + // U+091B: "छ" DEVANAGARI LETTER CHA + key("\u091F\u094D\u091F", moreKey("\u091B")), + // U+0920/U+094D/U+0920: + // "ठ्ठ" DEVANAGARI LETTER TTHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTHA + // U+091F: "ट" DEVANAGARI LETTER TTA + key("\u0920\u094D\u0920", moreKey("\u091F")), + // U+090A: "ऊ" DEVANAGARI LETTER UU + // U+0920: "ठ" DEVANAGARI LETTER TTHA + key("\u090A", moreKey("\u0920")), + // U+0915/U+094D/U+0937: + // "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA + // U+0921: "ड" DEVANAGARI LETTER DDA + key("\u0915\u094D\u0937", moreKey("\u0921")), + // U+0907: "इ" DEVANAGARI LETTER I + // U+0922: "ढ" DEVANAGARI LETTER DDHA + key("\u0907", moreKey("\u0922")), + // U+090F: "ए" DEVANAGARI LETTER E + // U+0923: "ण" DEVANAGARI LETTER NNA + key("\u090F", moreKey("\u0923")), + // U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R + // U+0913: "ओ" DEVANAGARI LETTER O + key(VOWEL_SIGN_VOCALIC_R, "\u0943", moreKey("\u0913"))) + .setKeysOfRow(2, + // U+0906: "आ" DEVANAGARI LETTER AA + // U+0919/U+094D: "ङ्" DEVANAGARI LETTER NGA/DEVANAGARI SIGN VIRAMA + // U+0921/U+094D/U+0921: + // "ड्ड" DEVANAGARI LETTER DDA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DDA + "\u0906", "\u0919\u094D", "\u0921\u094D\u0921", + // U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU + key(SIGN_CANDRABINDU, "\u0901"), + // U+0926/U+094D/U+0926: + // "द्द" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DA + // U+091D: "झ" DEVANAGARI LETTER JHA + "\u0926\u094D\u0926", "\u091D", + // U+094B: "ो" DEVANAGARI VOWEL SIGN O + key(VOWEL_SIGN_O, "\u094B"), + // U+092B: "फ" DEVANAGARI LETTER PHA + "\u092B", + // U+0940: "ी" DEVANAGARI VOWEL SIGN II + key(VOWEL_SIGN_II, "\u0940"), + // U+091F/U+094D/U+0920: + // "ट्ठ" DEVANAGARI LETTER TTA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTHA + "\u091F\u094D\u0920", + // U+0942: "ू" DEVANAGARI VOWEL SIGN UU + key(VOWEL_SIGN_UU, "\u0942")) + .setKeysOfRow(3, + // U+0915/U+094D: "क्" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA + // U+0939/U+094D/U+092E: + // "ह्म" DEVANAGARI LETTER HA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER MA + // U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R + // U+0950: "ॐ" DEVANAGARI OM + "\u0915\u094D", "\u0939\u094D\u092E", "\u090B", "\u0950", + // U+094C: "ौ" DEVANAGARI VOWEL SIGN AU + key(VOWEL_SIGN_AU, "\u094C"), + // U+0926/U+094D/U+092F: + // "द्य" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER YA + "\u0926\u094D\u092F") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Nordic.java b/tests/src/com/android/inputmethod/keyboard/layout/Nordic.java new file mode 100644 index 000000000..c791c404d --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Nordic.java @@ -0,0 +1,58 @@ +/* + * 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; + +/** + * The Nordic alphabet keyboard. + */ +public final class Nordic extends LayoutBase { + private static final String LAYOUT_NAME = "nordic"; + + public Nordic(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + public static final String ROW1_11 = "ROW1_11"; + public static final String ROW2_10 = "ROW2_10"; + public static final String ROW2_11 = "ROW2_11"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("y", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0")), + ROW1_11) + .setKeysOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l", ROW2_10, ROW2_11) + .setKeysOfRow(3, "z", "x", "c", "v", "b", "n", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/PcQwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/PcQwerty.java new file mode 100644 index 000000000..9da6dcc44 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/PcQwerty.java @@ -0,0 +1,227 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * The PC QWERTY alphabet keyboard. + */ +public final class PcQwerty extends LayoutBase { + private static final String LAYOUT_NAME = "pcqwerty"; + + public PcQwerty(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class PcQwertyCustomizer extends LayoutCustomizer { + public PcQwertyCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) { + return joinKeys(SHIFT_KEY); + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return joinKeys(SHIFT_KEY); + } + + @Override + public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) { + return joinKeys(SETTINGS_KEY); + } + + @Override + public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) { + return isPhone ? joinKeys(key(ENTER_KEY, EMOJI_KEY)) : joinKeys(EMOJI_KEY); + } + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + customizer.setAccentedLetters(builder); + builder.replaceKeyOfLabel(ROW1_1, key("`", moreKey("~"))) + .replaceKeyOfLabel(ROW2_11, key("[", moreKey("{"))) + .replaceKeyOfLabel(ROW2_12, key("]", moreKey("}"))) + .replaceKeyOfLabel(ROW2_13, key("\\", moreKey("|"))) + .replaceKeyOfLabel(ROW3_10, key(";", moreKey(":"))) + .replaceKeyOfLabel(ROW3_11, key("'", joinMoreKeys(additionalMoreKey("\""), + customizer.getDoubleQuoteMoreKeys(), + customizer.getSingleQuoteMoreKeys()))) + .setAdditionalMoreKeysPositionOf("'", 4) + .replaceKeyOfLabel(ROW4_8, key(",", moreKey("<"))) + .replaceKeyOfLabel(ROW4_9, key(".", moreKey(">"))) + // U+00BF: "¿" INVERTED QUESTION MARK + .replaceKeyOfLabel(ROW4_10, key("/", joinMoreKeys("?", "\u00BF"))); + if (isPhone) { + // U+221E: "∞" INFINITY + // U+2260: "≠" NOT EQUAL TO + // U+2248: "≈" ALMOST EQUAL TO + builder.replaceKeyOfLabel(ROW1_13, key("=", + joinMoreKeys("\u221E", "\u2260", "\u2248", "+"))); + } else { + // U+221E: "∞" INFINITY + // U+2260: "≠" NOT EQUAL TO + // U+2248: "≈" ALMOST EQUAL TO + builder.replaceKeyOfLabel(ROW1_13, key("=", + joinMoreKeys("+", "\u221E", "\u2260", "\u2248"))); + } + return builder.build(); + } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) { + final ExpectedKeyboardBuilder builder; + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED + || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { + builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone)); + } else { + builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + final LayoutCustomizer customizer = getCustomizer(); + customizer.setAccentedLetters(builder); + builder.setKeysOfRow(1, + "~", + // U+00A1: "¡" INVERTED EXCLAMATION MARK + key("!", moreKey("\u00A1")), + "@", "#", + customizer.getCurrencyKey(), + // U+2030: "‰" PER MILLE SIGN + key("%", moreKey("\u2030")), + "^", "&", + // U+2020: "†" DAGGER + // U+2021: "‡" DOUBLE DAGGER + // U+2605: "★" BLACK STAR + key("*", joinMoreKeys("\u2020", "\u2021", "\u2605")), + "(", ")", "_", + // U+00B1: "±" PLUS-MINUS SIGN + // U+00D7: "×" MULTIPLICATION SIGN + // U+00F7: "÷" DIVISION SIGN + // U+221A: "√" SQUARE ROOT + key("+", joinMoreKeys("\u00B1", "\u00D7", "\u00F7", "\u221A"))) + .replaceKeyOfLabel(ROW2_11, key("{")) + .replaceKeyOfLabel(ROW2_12, key("}")) + .replaceKeyOfLabel(ROW2_13, key("|")) + .replaceKeyOfLabel(ROW3_10, key(":")) + .replaceKeyOfLabel(ROW3_11, key("\"", joinMoreKeys( + customizer.getDoubleQuoteMoreKeys(), + customizer.getSingleQuoteMoreKeys()))) + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + .replaceKeyOfLabel(ROW4_8, key("<", joinMoreKeys("\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 + .replaceKeyOfLabel(ROW4_9, key(">", joinMoreKeys("\u203A", "\u2265", "\u00BB"))) + // U+00BF: "¿" INVERTED QUESTION MARK + .replaceKeyOfLabel(ROW4_10, key("?", moreKey("\u00BF"))); + } + builder.toUpperCase(getLocale()); + return builder.build(); + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(3, DELETE_KEY); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheLeftOfRow(2, TAB_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_SYMBOLS + || elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) { + return null; + } + return super.getLayout(isPhone, elementId); + } + + private static final String ROW1_1 = "ROW1_1"; + private static final String ROW1_13 = "ROW1_13"; + private static final String ROW2_11 = "ROW2_11"; + private static final String ROW2_12 = "ROW2_12"; + private static final String ROW2_13 = "ROW2_13"; + private static final String ROW3_10 = "ROW3_10"; + private static final String ROW3_11 = "ROW3_11"; + private static final String ROW4_8 = "ROW4_8"; + private static final String ROW4_9 = "ROW4_9"; + private static final String ROW4_10 = "ROW4_10"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + ROW1_1, + // U+00A1: "¡" INVERTED EXCLAMATION MARK + // 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 + key("1", joinMoreKeys( + "!", "\u00A1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B")), + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + key("2", joinMoreKeys("@", "\u00B2", "\u2154")), + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + key("3", joinMoreKeys("#", "\u00B3", "\u00BE", "\u215C")), + // U+2074: "⁴" SUPERSCRIPT FOUR + key("4", joinMoreKeys("$", "\u2074")), + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + key("5", joinMoreKeys("%", "\u215D")), + key("6", moreKey("^")), + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + key("7", joinMoreKeys("&", "\u215E")), + key("8", moreKey("*")), + key("9", moreKey("(")), + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + key("0", joinMoreKeys(")", "\u207F", "\u2205")), + // U+2013: "–" EN DASH + // U+2014: "—" EM DASH + // U+00B7: "·" MIDDLE DOT + key("-", joinMoreKeys("_", "\u2013", "\u2014", "\u00B7")), + ROW1_13) + .setKeysOfRow(2, "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", + ROW2_11, ROW2_12, ROW2_13) + .setKeysOfRow(3, "a", "s", "d", "f", "g", "h", "j", "k", "l", ROW3_10, ROW3_11) + .setKeysOfRow(4, "z", "x", "c", "v", "b", "n", "m", ROW4_8, ROW4_9, ROW4_10) + .build(); +} 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..d790a1e53 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * The QWERTY alphabet keyboard. + */ +public final class Qwerty extends LayoutBase { + private static final String LAYOUT_NAME = "qwerty"; + + public Qwerty(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("y", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0"))) + .setKeysOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l") + .setKeysOfRow(3, "z", "x", "c", "v", "b", "n", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Qwertz.java b/tests/src/com/android/inputmethod/keyboard/layout/Qwertz.java new file mode 100644 index 000000000..26ba6cffb --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwertz.java @@ -0,0 +1,50 @@ +/* + * 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; + +public final class Qwertz extends LayoutBase { + private static final String LAYOUT_NAME = "qwertz"; + + public Qwertz(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("z", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0"))) + .setKeysOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l") + .setKeysOfRow(3, "y", "x", "c", "v", "b", "n", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Sinhala.java b/tests/src/com/android/inputmethod/keyboard/layout/Sinhala.java new file mode 100644 index 000000000..5c0ffb4f9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Sinhala.java @@ -0,0 +1,177 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Sinhala keyboard. + */ +public final class Sinhala extends LayoutBase { + private static final String LAYOUT_NAME = "sinhala"; + + public Sinhala(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class SinhalaCustomizer extends LayoutCustomizer { + public SinhalaCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return SINHALA_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + // U+0D85: "අ" SINHALA LETTER AYANNA + // U+0D86: "ආ" SINHALA LETTER AAYANNA + private static final ExpectedKey SINHALA_ALPHABET_KEY = key( + "\u0D85,\u0D86", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone) { return ALPHABET_COMMON; } + + @Override + ExpectedKey[][] getCommonAlphabetShiftLayout(boolean isPhone, final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return ALPHABET_COMMON; + } + return ALPHABET_SHIFTED_COMMON; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0DD4: "ු" SINHALA VOWEL SIGN KETTI PAA-PILLA + key("\u0DD4", moreKey("1")), + // U+0D85: "අ" SINHALA LETTER AYANNA + key("\u0D85", moreKey("2")), + // U+0DD0: "ැ" SINHALA VOWEL SIGN KETTI AEDA-PILLA + key("\u0DD0", moreKey("3")), + // U+0DBB: "ර" SINHALA LETTER RAYANNA + key("\u0DBB", moreKey("4")), + // U+0D91: "එ" SINHALA LETTER EYANNA + key("\u0D91", moreKey("5")), + // U+0DC4: "හ" SINHALA LETTER HAYANNA + key("\u0DC4", moreKey("6")), + // U+0DB8: "ම" SINHALA LETTER MAYANNA + key("\u0DB8", moreKey("7")), + // U+0DC3: "ස" SINHALA LETTER DANTAJA SAYANNA + key("\u0DC3", moreKey("8")), + // U+0DAF: "ද" SINHALA LETTER ALPAPRAANA DAYANNA + // U+0DB3: "ඳ" SINHALA LETTER SANYAKA DAYANNA + key("\u0DAF", joinMoreKeys("9", "\u0DB3")), + // U+0DA0: "ච" SINHALA LETTER ALPAPRAANA CAYANNA + key("\u0DA0", moreKey("0")), + // U+0DA4: "ඤ" SINHALA LETTER TAALUJA NAASIKYAYA + // U+0DF4: "෴" SINHALA PUNCTUATION KUNDDALIYA + key("\u0DA4", moreKey("\u0DF4"))) + .setKeysOfRow(2, + // U+0DCA: "්" SINHALA SIGN AL-LAKUNA + // U+0DD2: "ි" SINHALA VOWEL SIGN KETTI IS-PILLA + // U+0DCF: "ා" SINHALA VOWEL SIGN AELA-PILLA + // U+0DD9: "ෙ" SINHALA VOWEL SIGN KOMBUVA + // U+0DA7: "ට" SINHALA LETTER ALPAPRAANA TTAYANNA + // U+0DBA: "ය" SINHALA LETTER YAYANNA + // U+0DC0: "ව" SINHALA LETTER VAYANNA + // U+0DB1: "න" SINHALA LETTER DANTAJA NAYANNA + // U+0D9A: "ක" SINHALA LETTER ALPAPRAANA KAYANNA + // U+0DAD: "ත" SINHALA LETTER ALPAPRAANA TAYANNA + // U+0D8F: "ඏ" SINHALA LETTER ILUYANNA + "\u0DCA", "\u0DD2", "\u0DCF", "\u0DD9", "\u0DA7", "\u0DBA", "\u0DC0", "\u0DB1", + "\u0D9A", "\u0DAD", "\u0D8F") + .setKeysOfRow(3, + // U+0D82: "ං" SINHALA SIGN ANUSVARAYA + // U+0D83: "ඃ" SINHALA SIGN VISARGAYA + key("\u0D82", moreKey("\u0D83")), + // U+0DA2: "ජ" SINHALA LETTER ALPAPRAANA JAYANNA + // U+0DA6: "ඦ" SINHALA LETTER SANYAKA JAYANNA + key("\u0DA2", moreKey("\u0DA6")), + // U+0DA9: "ඩ" SINHALA LETTER ALPAPRAANA DDAYANNA + // U+0DAC: "ඬ" SINHALA LETTER SANYAKA DDAYANNA + key("\u0DA9", moreKey("\u0DAC")), + // U+0D89: "ඉ" SINHALA LETTER IYANNA + // U+0DB6: "බ" SINHALA LETTER ALPAPRAANA BAYANNA + // U+0DB4: "ප" SINHALA LETTER ALPAPRAANA PAYANNA + // U+0DBD: "ල" SINHALA LETTER DANTAJA LAYANNA + "\u0D89", "\u0DB6", "\u0DB4", "\u0DBD", + // U+0D9C: "ග" SINHALA LETTER ALPAPRAANA GAYANNA + // U+0D9F: "ඟ" SINHALA LETTER SANYAKA GAYANNA + key("\u0D9C", moreKey("\u0D9F")), + // U+0DF3: "ෳ" SINHALA VOWEL SIGN DIGA GAYANUKITTA + "\u0DF3") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0DD6: "ූ" SINHALA VOWEL SIGN DIGA PAA-PILLA + // U+0D8B: "උ" SINHALA LETTER UYANNA + // U+0DD1: "ෑ" SINHALA VOWEL SIGN DIGA AEDA-PILLA + // U+0D8D: "ඍ" SINHALA LETTER IRUYANNA + // U+0D94: "ඔ" SINHALA LETTER OYANNA + // U+0DC1: "ශ" SINHALA LETTER TAALUJA SAYANNA + // U+0DB9: "ඹ" SINHALA LETTER AMBA BAYANNA + // U+0DC2: "ෂ" SINHALA LETTER MUURDHAJA SAYANNA + // U+0DB0: "ධ" SINHALA LETTER MAHAAPRAANA DAYANNA + // U+0DA1: "ඡ" SINHALA LETTER MAHAAPRAANA CAYANNA + "\u0DD6", "\u0D8B", "\u0DD1", "\u0D8D", "\u0D94", "\u0DC1", "\u0DB9", "\u0DC2", + "\u0DB0", "\u0DA1", + // U+0DA5: "ඥ" SINHALA LETTER TAALUJA SANYOOGA NAAKSIKYAYA + // U+0DF4: "෴" SINHALA PUNCTUATION KUNDDALIYA + key("\u0DA5", moreKey("\u0DF4"))) + .setKeysOfRow(2, + // U+0DDF: "ෟ" SINHALA VOWEL SIGN GAYANUKITTA + // U+0DD3: "ී" SINHALA VOWEL SIGN DIGA IS-PILLA + // U+0DD8: "ෘ" SINHALA VOWEL SIGN GAETTA-PILLA + // U+0DC6: "ෆ" SINHALA LETTER FAYANNA + // U+0DA8: "ඨ" SINHALA LETTER MAHAAPRAANA TTAYANNA + // U+0DCA/U+200D/U+0DBA: + // "්ය" SINHALA SIGN AL-LAKUNA/ZERO WIDTH JOINER/SINHALA LETTER YAYANNA + // U+0DC5/U+0DD4: + // "ළු" SINHALA LETTER MUURDHAJA LAYANNA/SINHALA VOWEL SIGN KETTI PAA-PILLA + // U+0DAB: "ණ" SINHALA LETTER MUURDHAJA NAYANNA + // U+0D9B: "ඛ" SINHALA LETTER MAHAAPRAANA KAYANNA + // U+0DAE: "ථ" SINHALA LETTER MAHAAPRAANA TAYANNA + // U+0DCA/U+200D/U+0DBB: + // "්ර" SINHALA SIGN AL-LAKUNA/ZERO WIDTH JOINER/SINHALA LETTER RAYANNA + "\u0DDF", "\u0DD3", "\u0DD8", "\u0DC6", "\u0DA8", "\u0DCA\u200D\u0DBA", + "\u0DC5\u0DD4", "\u0DAB", "\u0D9B", "\u0DAE", "\u0DCA\u200D\u0DBB") + .setKeysOfRow(3, + // U+0D9E: "ඞ" SINHALA LETTER KANTAJA NAASIKYAYA + // U+0DA3: "ඣ" SINHALA LETTER MAHAAPRAANA JAYANNA + // U+0DAA: "ඪ" SINHALA LETTER MAHAAPRAANA DDAYANNA + // U+0D8A: "ඊ" SINHALA LETTER IIYANNA + // U+0DB7: "භ" SINHALA LETTER MAHAAPRAANA BAYANNA + // U+0DB5: "ඵ" SINHALA LETTER MAHAAPRAANA PAYANNA + // U+0DC5: "ළ" SINHALA LETTER MUURDHAJA LAYANNA + // U+0D9D: "ඝ" SINHALA LETTER MAHAAPRAANA GAYANNA + // U+0DBB/U+0DCA/U+200D: + // "ර්" SINHALA LETTER RAYANNA/SINHALA SIGN AL-LAKUNA/ZERO WIDTH JOINER + "\u0d9E", "\u0DA3", "\u0DAA", "\u0D8A", "\u0DB7", "\u0DB5", "\u0DC5", "\u0D9D", + "\u0DBB\u0DCA\u200D") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/SouthSlavic.java b/tests/src/com/android/inputmethod/keyboard/layout/SouthSlavic.java new file mode 100644 index 000000000..be8b435d4 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/SouthSlavic.java @@ -0,0 +1,110 @@ +/* + * 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.latin.Constants; + +import java.util.Locale; + +public final class SouthSlavic extends LayoutBase { + private static final String LAYOUT_NAME = "south_slavic"; + + public SouthSlavic(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class SouthSlavicLayoutCustomizer extends LayoutCustomizer { + public SouthSlavicLayoutCustomizer(final Locale locale) { + super(locale); + } + + @Override + public final ExpectedKey getAlphabetKey() { return SOUTH_SLAVIC_ALPHABET_KEY; } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { + return isPhone ? EMPTY_KEYS : EXCLAMATION_AND_QUESTION_MARKS; + } + + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + private static final ExpectedKey SOUTH_SLAVIC_ALPHABET_KEY = key( + "\u0410\u0411\u0412", Constants.CODE_SWITCH_ALPHA_SYMBOL); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + public static final String ROW1_6 = "ROW1_6"; + public static final String ROW2_11 = "ROW2_11"; + public static final String ROW3_1 = "ROW3_1"; + public static final String ROW3_8 = "ROW3_8"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0459: "љ" CYRILLIC SMALL LETTER LJE + key("\u0459", additionalMoreKey("1")), + // U+045A: "њ" CYRILLIC SMALL LETTER NJE + key("\u045A", additionalMoreKey("2")), + // U+0435: "е" CYRILLIC SMALL LETTER IE + key("\u0435", additionalMoreKey("3")), + // U+0440: "р" CYRILLIC SMALL LETTER ER + key("\u0440", additionalMoreKey("4")), + // U+0442: "т" CYRILLIC SMALL LETTER TE + key("\u0442", additionalMoreKey("5")), + key(ROW1_6, additionalMoreKey("6")), + // U+0443: "у" CYRILLIC SMALL LETTER U + key("\u0443", additionalMoreKey("7")), + // U+0438: "и" CYRILLIC SMALL LETTER I + key("\u0438", additionalMoreKey("8")), + // U+043E: "о" CYRILLIC SMALL LETTER O + key("\u043E", additionalMoreKey("9")), + // U+043F: "п" CYRILLIC SMALL LETTER PE + key("\u043F", additionalMoreKey("0")), + // U+0448: "ш" CYRILLIC SMALL LETTER SHA + "\u0448") + .setKeysOfRow(2, + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+0441: "с" CYRILLIC SMALL LETTER ES + // U+0434: "д" CYRILLIC SMALL LETTER DE + // U+0444: "ф" CYRILLIC SMALL LETTER EF + // U+0433: "г" CYRILLIC SMALL LETTER GHE + // U+0445: "х" CYRILLIC SMALL LETTER HA + // U+0458: "ј" CYRILLIC SMALL LETTER JE + // U+043A: "к" CYRILLIC SMALL LETTER KA + // U+043B: "л" CYRILLIC SMALL LETTER EL + // U+0447: "ч" CYRILLIC SMALL LETTER CHE + "\u0430", "\u0441", "\u0434", "\u0444", "\u0433", "\u0445", "\u0458", "\u043A", + "\u043B", "\u0447", ROW2_11) + .setKeysOfRow(3, + // U+045F: "џ" CYRILLIC SMALL LETTER DZHE + // U+0446: "ц" CYRILLIC SMALL LETTER TSE + // U+0432: "в" CYRILLIC SMALL LETTER VE + // U+0431: "б" CYRILLIC SMALL LETTER BE + // U+043D: "н" CYRILLIC SMALL LETTER EN + // U+043C: "м" CYRILLIC SMALL LETTER EM + // U+0436: "ж" CYRILLIC SMALL LETTER ZHE + ROW3_1, "\u045F", "\u0446", "\u0432", "\u0431", "\u043D", "\u043C", ROW3_8, + "\u0436") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Spanish.java b/tests/src/com/android/inputmethod/keyboard/layout/Spanish.java new file mode 100644 index 000000000..225b9f604 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Spanish.java @@ -0,0 +1,52 @@ +/* + * 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; + +public final class Spanish extends LayoutBase { + private static final String LAYOUT_NAME = "spanish"; + + public Spanish(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + public static final String ROW2_10 = "ROW2_10"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("y", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0"))) + .setKeysOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l", ROW2_10) + .setKeysOfRow(3, "z", "x", "c", "v", "b", "n", "m") + .build(); +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Swiss.java b/tests/src/com/android/inputmethod/keyboard/layout/Swiss.java new file mode 100644 index 000000000..01a602054 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Swiss.java @@ -0,0 +1,55 @@ +/* + * 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; + +public final class Swiss extends LayoutBase { + private static final String LAYOUT_NAME = "swiss"; + + public Swiss(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; } + + public static final String ROW1_11 = "ROW1_11"; + public static final String ROW2_10 = "ROW2_10"; + public static final String ROW2_11 = "ROW2_11"; + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + key("q", additionalMoreKey("1")), + key("w", additionalMoreKey("2")), + key("e", additionalMoreKey("3")), + key("r", additionalMoreKey("4")), + key("t", additionalMoreKey("5")), + key("z", additionalMoreKey("6")), + key("u", additionalMoreKey("7")), + key("i", additionalMoreKey("8")), + key("o", additionalMoreKey("9")), + key("p", additionalMoreKey("0")), + ROW1_11) + .setKeysOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l", ROW2_10, ROW2_11) + .setKeysOfRow(3, "y", "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..5f3e4b196 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java @@ -0,0 +1,205 @@ +/* + * 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.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +/** + * The symbols keyboard layout. + */ +public class Symbols extends AbstractLayoutBase { + private final LayoutCustomizer mCustomizer; + + public Symbols(final LayoutCustomizer customizer) { + mCustomizer = customizer; + } + + public ExpectedKey[][] getLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(SYMBOLS_COMMON); + final LayoutCustomizer customizer = mCustomizer; + builder.replaceKeyOfLabel(CURRENCY, customizer.getCurrencyKey()); + builder.replaceKeyOfLabel(DOUBLE_QUOTE, key("\"", joinMoreKeys( + customizer.getDoubleQuoteMoreKeys(), customizer.getDoubleAngleQuoteKeys()))); + builder.replaceKeyOfLabel(SINGLE_QUOTE, key("'", joinMoreKeys( + customizer.getSingleQuoteMoreKeys(), customizer.getSingleAngleQuoteKeys()))); + if (isPhone) { + builder.addKeysOnTheLeftOfRow(3, customizer.getSymbolsShiftKey(isPhone)) + .addKeysOnTheRightOfRow(3, DELETE_KEY) + .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey()) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)); + } else { + // Tablet symbols keyboard has extra two keys at the left edge of the 3rd row. + builder.addKeysOnTheLeftOfRow(3, (Object[])joinKeys("\\", "=")); + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .addKeysOnTheLeftOfRow(3, customizer.getSymbolsShiftKey(isPhone)) + .addKeysOnTheRightOfRow(3, customizer.getSymbolsShiftKey(isPhone)) + .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey()) + .addKeysOnTheRightOfRow(4, EMOJI_KEY); + } + return builder.build(); + } + + // Variations of the "currency" key on the 2nd row. + public static final String CURRENCY = "CURRENCY"; + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+00A5: "¥" YEN SIGN + // U+20AC: "€" EURO SIGN + // U+20B1: "₱" PESO SIGN + public static final ExpectedKey DOLLAR_SIGN = key("$"); + public static final ExpectedKey CENT_SIGN = key("\u00A2"); + public static final ExpectedKey POUND_SIGN = key("\u00A3"); + public static final ExpectedKey YEN_SIGN = key("\u00A5"); + public static final ExpectedKey EURO_SIGN = key("\u20AC"); + public static final ExpectedKey PESO_SIGN = key("\u20B1"); + public static final ExpectedKey CURRENCY_DOLLAR = key("$", + CENT_SIGN, POUND_SIGN, EURO_SIGN, YEN_SIGN, PESO_SIGN); + public static final ExpectedKey CURRENCY_EURO = key("\u20AC", + CENT_SIGN, POUND_SIGN, DOLLAR_SIGN, YEN_SIGN, PESO_SIGN); + public static final ExpectedKey[] CURRENCY_GENERIC_MORE_KEYS = joinMoreKeys( + Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN, Symbols.EURO_SIGN, Symbols.POUND_SIGN, + Symbols.YEN_SIGN, Symbols.PESO_SIGN); + + // Variations of the "double quote" key's "more keys" on the 3rd row. + public static final String DOUBLE_QUOTE = "DOUBLE_QUOTE"; + // U+201C: "“" LEFT DOUBLE QUOTATION MARK + // U+201D: "”" RIGHT DOUBLE QUOTATION MARK + // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK + private static final ExpectedKey DQUOTE_LEFT = key("\u201C"); + private static final ExpectedKey DQUOTE_RIGHT = key("\u201D"); + private static final ExpectedKey DQUOTE_LOW9 = key("\u201E"); + public static ExpectedKey[] DOUBLE_QUOTES_9LR = { DQUOTE_LOW9, DQUOTE_LEFT, DQUOTE_RIGHT }; + public static ExpectedKey[] DOUBLE_QUOTES_R9L = { DQUOTE_RIGHT, DQUOTE_LOW9, DQUOTE_LEFT }; + public static ExpectedKey[] DOUBLE_QUOTES_L9R = { DQUOTE_LEFT, DQUOTE_LOW9, DQUOTE_RIGHT }; + public static ExpectedKey[] DOUBLE_QUOTES_LR9 = { DQUOTE_LEFT, DQUOTE_RIGHT, DQUOTE_LOW9 }; + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + private static final ExpectedKey DAQUOTE_LEFT = key("\u00AB"); + private static final ExpectedKey DAQUOTE_RIGHT = key("\u00BB"); + public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_LR = { DAQUOTE_LEFT, DAQUOTE_RIGHT }; + public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_RL = { DAQUOTE_RIGHT, DAQUOTE_LEFT }; + + // Variations of the "single quote" key's "more keys" on the 3rd row. + public static final String SINGLE_QUOTE = "SINGLE_QUOTE"; + // U+2018: "‘" LEFT SINGLE QUOTATION MARK + // U+2019: "’" RIGHT SINGLE QUOTATION MARK + // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK + private static final ExpectedKey SQUOTE_LEFT = key("\u2018"); + private static final ExpectedKey SQUOTE_RIGHT = key("\u2019"); + private static final ExpectedKey SQUOTE_LOW9 = key("\u201A"); + public static ExpectedKey[] SINGLE_QUOTES_9LR = { SQUOTE_LOW9, SQUOTE_LEFT, SQUOTE_RIGHT }; + public static ExpectedKey[] SINGLE_QUOTES_R9L = { SQUOTE_RIGHT, SQUOTE_LOW9, SQUOTE_LEFT }; + public static ExpectedKey[] SINGLE_QUOTES_L9R = { SQUOTE_LEFT, SQUOTE_LOW9, SQUOTE_RIGHT }; + public static ExpectedKey[] SINGLE_QUOTES_LR9 = { SQUOTE_LEFT, SQUOTE_RIGHT, SQUOTE_LOW9 }; + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + private static final ExpectedKey SAQUOTE_LEFT = key("\u2039"); + private static final ExpectedKey SAQUOTE_RIGHT = key("\u203A"); + public static ExpectedKey[] SINGLE_ANGLE_QUOTES_LR = { SAQUOTE_LEFT, SAQUOTE_RIGHT }; + public static ExpectedKey[] SINGLE_ANGLE_QUOTES_RL = { SAQUOTE_RIGHT, SAQUOTE_LEFT }; + + // Common symbols keyboard layout. + private static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // 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 + key("1", joinMoreKeys("\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B")), + // U+00B2: "²" SUPERSCRIPT TWO + // U+2154: "⅔" VULGAR FRACTION TWO THIRDS + key("2", joinMoreKeys("\u00B2", "\u2154")), + // U+00B3: "³" SUPERSCRIPT THREE + // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS + // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS + key("3", joinMoreKeys("\u00B3", "\u00BE", "\u215C")), + // U+2074: "⁴" SUPERSCRIPT FOUR + key("4", moreKey("\u2074")), + // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS + key("5", moreKey("\u215D")), + "6", + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + key("7", moreKey("\u215E")), + "8", "9", + // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N + // U+2205: "∅" EMPTY SET + key("0", joinMoreKeys("\u207F", "\u2205"))) + .setKeysOfRow(2, + key("@"), key("#"), key(CURRENCY), + // U+2030: "‰" PER MILLE SIGN + key("%", moreKey("\u2030")), + "&", + // U+2013: "–" EN DASH + // U+2014: "—" EM DASH + // U+00B7: "·" MIDDLE DOT + key("-", joinMoreKeys("_", "\u2013", "\u2014", "\u00B7")), + // U+00B1: "±" PLUS-MINUS SIGN + key("+", moreKey("\u00B1")), + key("(", joinMoreKeys("<", "{", "[")), + key(")", joinMoreKeys(">", "}", "]"))) + .setKeysOfRow(3, + // U+2020: "†" DAGGER + // U+2021: "‡" DOUBLE DAGGER + // U+2605: "★" BLACK STAR + key("*", joinMoreKeys("\u2020", "\u2021", "\u2605")), + key(DOUBLE_QUOTE), key(SINGLE_QUOTE), key(":"), key(";"), + // U+00A1: "¡" INVERTED EXCLAMATION MARK + key("!", moreKey("\u00A1")), + // U+00BF: "¿" INVERTED QUESTION MARK + key("?", moreKey("\u00BF"))) + .setKeysOfRow(4, + key(","), key("_"), SPACE_KEY, key("/"), + // U+2026: "…" HORIZONTAL ELLIPSIS + key(".", moreKey("\u2026"))) + .build(); + + public static class RtlSymbols extends Symbols { + public RtlSymbols(final LayoutCustomizer customizer) { + super(customizer); + } + + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + private static final ExpectedKey DAQUOTE_LEFT_RTL = key("\u00AB", "\u00BB"); + private static final ExpectedKey DAQUOTE_RIGHT_RTL = key("\u00BB", "\u00AB"); + public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_LR_RTL = { + DAQUOTE_LEFT_RTL, DAQUOTE_RIGHT_RTL + }; + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + private static final ExpectedKey SAQUOTE_LEFT_RTL = key("\u2039", "\u203A"); + private static final ExpectedKey SAQUOTE_RIGHT_RTL = key("\u203A", "\u2039"); + public static ExpectedKey[] SINGLE_ANGLE_QUOTES_LR_RTL = { + SAQUOTE_LEFT_RTL, SAQUOTE_RIGHT_RTL + }; + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + .replaceKeyOfLabel("(", key("(", ")", + moreKey("<", ">"), moreKey("{", "}"), moreKey("[", "]"))) + .replaceKeyOfLabel(")", key(")", "(", + moreKey(">", "<"), moreKey("}", "{"), moreKey("]", "["))) + .build(); + } + } +} 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..3265e10e1 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java @@ -0,0 +1,161 @@ +/* + * 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.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase; + +/** + * The symbols shifted keyboard layout. + */ +public class SymbolsShifted extends AbstractLayoutBase { + private final LayoutCustomizer mCustomizer; + + public SymbolsShifted(final LayoutCustomizer customizer) { + mCustomizer = customizer; + } + + public ExpectedKey[][] getLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(SYMBOLS_SHIFTED_COMMON); + final LayoutCustomizer customizer = mCustomizer; + builder.replaceKeyOfLabel(OTHER_CURRENCIES, (Object[])customizer.getOtherCurrencyKeys()); + if (isPhone) { + builder.addKeysOnTheLeftOfRow(3, customizer.getBackToSymbolsKey()) + .addKeysOnTheRightOfRow(3, DELETE_KEY) + .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey()) + .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY)); + } else { + // Tablet symbols shifted keyboard has extra two keys at the right edge of the 3rd row. + // U+00BF: "¿" INVERTED QUESTION MARK + // U+00A1: "¡" INVERTED EXCLAMATION MARK + builder.addKeysOnTheRightOfRow(3, (Object[])joinKeys("\u00A1", "\u00BF")); + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(2, ENTER_KEY) + .addKeysOnTheLeftOfRow(3, customizer.getBackToSymbolsKey()) + .addKeysOnTheRightOfRow(3, customizer.getBackToSymbolsKey()) + .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey()) + .addKeysOnTheRightOfRow(4, EMOJI_KEY); + } + return builder.build(); + } + + // Variations of the "other currencies" keys on the 2rd row. + public static final String OTHER_CURRENCIES = "OTHER_CURRENCY"; + public static final ExpectedKey[] CURRENCIES_OTHER_THAN_DOLLAR = { + Symbols.POUND_SIGN, Symbols.CENT_SIGN, Symbols.EURO_SIGN, Symbols.YEN_SIGN + }; + public static final ExpectedKey[] CURRENCIES_OTHER_THAN_EURO = { + Symbols.POUND_SIGN, Symbols.YEN_SIGN, key(Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN), + Symbols.CENT_SIGN + }; + public static final ExpectedKey[] CURRENCIES_OTHER_GENERIC = { + Symbols.POUND_SIGN, Symbols.EURO_SIGN, key(Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN), + Symbols.CENT_SIGN + }; + + // Common symbols shifted keyboard layout. + private static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0060: "`" GRAVE ACCENT + "~", "\u0060", "|", + // 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 + key("\u2022", joinMoreKeys("\u266A", "\u2665", "\u2660", "\u2666", "\u2663")), + // U+221A: "√" SQUARE ROOT + "\u221A", + // U+03C0: "π" GREEK SMALL LETTER PI + // U+03A0: "Π" GREEK CAPITAL LETTER PI + key("\u03C0", moreKey("\u03A0")), + // U+00F7: "÷" DIVISION SIGN + // U+00D7: "×" MULTIPLICATION SIGN + "\u00F7", "\u00D7", + // U+00B6: "¶" PILCROW SIGN + // U+00A7: "§" SECTION SIGN + key("\u00B6", moreKey("\u00A7")), + // U+2206: "∆" INCREMENT + "\u2206") + .setKeysOfRow(2, + OTHER_CURRENCIES, + // U+2191: "↑" UPWARDS ARROW + // U+2193: "↓" DOWNWARDS ARROW + // U+2190: "←" LEFTWARDS ARROW + // U+2192: "→" RIGHTWARDS ARROW + key("^", joinMoreKeys("\u2191", "\u2193", "\u2190", "\u2192")), + // U+00B0: "°" DEGREE SIGN + // U+2032: "′" PRIME + // U+2033: "″" DOUBLE PRIME + key("\u00B0", joinMoreKeys("\u2032", "\u2033")), + // U+2260: "≠" NOT EQUAL TO + // U+2248: "≈" ALMOST EQUAL TO + // U+221E: "∞" INFINITY + key("=", joinMoreKeys("\u2260", "\u2248", "\u221E")), + "{", "}") + .setKeysOfRow(3, + // U+00A9: "©" COPYRIGHT SIGN + // U+00AE: "®" REGISTERED SIGN + // U+2122: "™" TRADE MARK SIGN + // U+2105: "℅" CARE OF + "\\", "\u00A9", "\u00AE", "\u2122", "\u2105", "[", "]") + .setKeysOfRow(4, + ",", + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + key("<", joinMoreKeys("\u2039", "\u2264", "\u00AB")), + SPACE_KEY, + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2265: "≥" GREATER-THAN EQUAL TO + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + key(">", joinMoreKeys("\u203A", "\u2265", "\u00BB")), + // U+2026: "…" HORIZONTAL ELLIPSIS + key(".", moreKey("\u2026"))) + .build(); + + public static class RtlSymbolsShifted extends SymbolsShifted { + public RtlSymbolsShifted(final LayoutCustomizer customizer) { + super(customizer); + } + + @Override + public ExpectedKey[][] getLayout(final boolean isPhone) { + return new ExpectedKeyboardBuilder(super.getLayout(isPhone)) + .replaceKeyOfLabel("{", key("{", "}")) + .replaceKeyOfLabel("}", key("}", "{")) + .replaceKeyOfLabel("[", key("[", "]")) + .replaceKeyOfLabel("]", key("]", "[")) + // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK + // U+2264: "≤" LESS-THAN OR EQUAL TO + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + .replaceKeyOfLabel("<", key("<", ">", + moreKey("\u2039", "\u203A"), moreKey("\u2264", "\u2265"), + moreKey("\u00AB", "\u00BB"))) + // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + // U+2265: "≥" GREATER-THAN EQUAL TO + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + .replaceKeyOfLabel(">", key(">", "<", + moreKey("\u203A", "\u2039"), moreKey("\u2265", "\u2264"), + moreKey("\u00BB", "\u00AB"))) + .build(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Thai.java b/tests/src/com/android/inputmethod/keyboard/layout/Thai.java new file mode 100644 index 000000000..af4abea93 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/Thai.java @@ -0,0 +1,266 @@ +/* + * 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.KeyboardId; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.Constants; + +import java.util.Locale; + +/** + * The Thai alphabet keyboard. + */ +public final class Thai extends LayoutBase { + private static final String LAYOUT_NAME = "thai"; + + public Thai(final LayoutCustomizer customizer) { + super(customizer, Symbols.class, SymbolsShifted.class); + } + + @Override + public String getName() { return LAYOUT_NAME; } + + public static class ThaiCustomizer extends LayoutCustomizer { + public ThaiCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getAlphabetKey() { return THAI_ALPHABET_KEY; } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_BAHT; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getRightShiftKeys(final boolean isPhone) { return EMPTY_KEYS; } + + // U+0E01: "ก" THAI CHARACTER KO KAI + // U+0E02: "ข" THAI CHARACTER KHO KHAI + // U+0E04: "ค" THAI CHARACTER KHO KHWAI + private static final ExpectedKey THAI_ALPHABET_KEY = key( + "\u0E01\u0E02\u0E04", Constants.CODE_SWITCH_ALPHA_SYMBOL); + + // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT + private static final ExpectedKey CURRENCY_BAHT = key("\u0E3F", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } + + @Override + ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(ALPHABET_COMMON); + if (isPhone) { + // U+0E03: "ฃ" THAI CHARACTER KHO KHUAT + builder.addKeysOnTheRightOfRow(3, "\u0E03"); + } else { + // U+0E03: "ฃ" THAI CHARACTER KHO KHUAT + builder.addKeysOnTheRightOfRow(2, "\u0E03") + .addKeysOnTheRightOfRow(4, (Object[])EXCLAMATION_AND_QUESTION_MARKS); + } + return builder.build(); + } + + @Override + public ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, + final int elementId) { + if (elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + return getCommonAlphabetLayout(isPhone); + } + final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder( + ALPHABET_SHIFTED_COMMON); + if (isPhone) { + // U+0E05: "ฅ" THAI CHARACTER KHO KHON + builder.addKeysOnTheRightOfRow(3, "\u0E05"); + } else { + // U+0E05: "ฅ" THAI CHARACTER KHO KHON + builder.addKeysOnTheRightOfRow(2, "\u0E05"); + } + return builder.build(); + } + + // Helper method to create alphabet layout by adding special function keys. + @Override + ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, + final boolean isPhone) { + final LayoutCustomizer customizer = getCustomizer(); + builder.setKeysOfRow(5, (Object[])customizer.getSpaceKeys(isPhone)); + builder.addKeysOnTheLeftOfRow(5, (Object[])customizer.getKeysLeftToSpacebar(isPhone)); + builder.addKeysOnTheRightOfRow(5, (Object[])customizer.getKeysRightToSpacebar(isPhone)); + if (isPhone) { + builder.addKeysOnTheRightOfRow(4, DELETE_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, key(ENTER_KEY, EMOJI_KEY)); + } else { + builder.addKeysOnTheRightOfRow(1, DELETE_KEY) + .addKeysOnTheRightOfRow(3, ENTER_KEY) + .addKeysOnTheLeftOfRow(5, customizer.getSymbolsKey()) + .addKeysOnTheRightOfRow(5, EMOJI_KEY); + } + builder.addKeysOnTheLeftOfRow(4, (Object[])customizer.getLeftShiftKeys(isPhone)) + .addKeysOnTheRightOfRow(4, (Object[])customizer.getRightShiftKeys(isPhone)); + return builder; + } + + private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO + "\u0E45", + // U+0E51: "๑" THAI DIGIT ONE + key("/", joinMoreKeys("1", "\u0E51")), + // U+0E52: "๒" THAI DIGIT TWO + key("_", joinMoreKeys("2", "\u0E52")), + // U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO + // U+0E53: "๓" THAI DIGIT THREE + key("\u0E20", joinMoreKeys("3", "\u0E53")), + // U+0E16: "ถ" THAI CHARACTER THO THUNG + // U+0E54: "๔" THAI DIGIT FOUR + key("\u0E16", joinMoreKeys("4", "\u0E54")), + // U+0E38: " ุ" THAI CHARACTER SARA U + key(" \u0E38", "\u0E38"), + // U+0E36: " ึ" THAI CHARACTER SARA UE + key(" \u0E36", "\u0E36"), + // U+0E04: "ค" THAI CHARACTER KHO KHWAI + // U+0E55: "๕" THAI DIGIT FIVE + key("\u0E04", joinMoreKeys("5", "\u0E55")), + // U+0E15: "ต" THAI CHARACTER TO TAO + // U+0E56: "๖" THAI DIGIT SIX + key("\u0E15", joinMoreKeys("6", "\u0E56")), + // U+0E08: "จ" THAI CHARACTER CHO CHAN + // U+0E57: "๗" THAI DIGIT SEVEN + key("\u0E08", joinMoreKeys("7", "\u0E57")), + // U+0E02: "ข" THAI CHARACTER KHO KHAI + // U+0E58: "๘" THAI DIGIT EIGHT + key("\u0E02", joinMoreKeys("8", "\u0E58")), + // U+0E0A: "ช" THAI CHARACTER CHO CHANG + // U+0E59: "๙" THAI DIGIT NINE + key("\u0E0A", joinMoreKeys("9", "\u0E59"))) + .setKeysOfRow(2, + // U+0E46: "ๆ" THAI CHARACTER MAIYAMOK + // U+0E50: "๐" THAI DIGIT ZERO + key("\u0E46", joinMoreKeys("0", "\u0E50")), + // U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI + // U+0E33: "ำ" THAI CHARACTER SARA AM + // U+0E1E: "พ" THAI CHARACTER PHO PHAN + // U+0E30: "ะ" THAI CHARACTER SARA A + "\u0E44", "\u0E33", "\u0E1E", "\u0E30", + // U+0E31: " ั" THAI CHARACTER MAI HAN-AKAT + key(" \u0E31", "\u0E31"), + // U+0E35: " ี" HAI CHARACTER SARA II + key(" \u0E35", "\u0E35"), + // U+0E23: "ร" THAI CHARACTER RO RUA + // U+0E19: "น" THAI CHARACTER NO NU + // U+0E22: "ย" THAI CHARACTER YO YAK + // U+0E1A: "บ" THAI CHARACTER BO BAIMAI + // U+0E25: "ล" THAI CHARACTER LO LING + "\u0E23", "\u0E19", "\u0E22", "\u0E1A", "\u0E25") + .setKeysOfRow(3, + // U+0E1F: "ฟ" THAI CHARACTER FO FAN + // U+0E2B: "ห" THAI CHARACTER HO HIP + // U+0E01: "ก" THAI CHARACTER KO KAI + // U+0E14: "ด" THAI CHARACTER DO DEK + // U+0E40: "เ" THAI CHARACTER SARA E + "\u0E1F", "\u0E2B", "\u0E01", "\u0E14", "\u0E40", + // U+0E49: " ้" THAI CHARACTER MAI THO + key(" \u0E49", "\u0E49"), + // U+0E48: " ่" THAI CHARACTER MAI EK + key(" \u0E48", "\u0E48"), + // U+0E32: "า" THAI CHARACTER SARA AA + // U+0E2A: "ส" THAI CHARACTER SO SUA + // U+0E27: "ว" THAI CHARACTER WO WAEN + // U+0E07: "ง" THAI CHARACTER NGO NGU + "\u0E32", "\u0E2A", "\u0E27", "\u0E07") + .setKeysOfRow(4, + // U+0E1C: "ผ" THAI CHARACTER PHO PHUNG + // U+0E1B: "ป" THAI CHARACTER PO PLA + // U+0E41: "แ" THAI CHARACTER SARA AE + // U+0E2D: "อ" THAI CHARACTER O ANG + "\u0E1C", "\u0E1B", "\u0E41", "\u0E2D", + // U+0E34: " ิ" THAI CHARACTER SARA I + key(" \u0E34", "\u0E34"), + // U+0E37: " ื" THAI CHARACTER SARA UEE + key(" \u0E37", "\u0E37"), + // U+0E17: "ท" THAI CHARACTER THO THAHAN + // U+0E21: "ม" THAI CHARACTER MO MA + // U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN + // U+0E1D: "ฝ" THAI CHARACTER FO FA + "\u0E17", "\u0E21", "\u0E43", "\u0E1D") + .build(); + + private static final ExpectedKey[][] ALPHABET_SHIFTED_COMMON = new ExpectedKeyboardBuilder() + .setKeysOfRow(1, + // U+0E51: "๑" THAI DIGIT ONE + // U+0E52: "๒" THAI DIGIT TWO + // U+0E53: "๓" THAI DIGIT THREE + // U+0E54: "๔" THAI DIGIT FOUR + // U+0E39: " ู" THAI CHARACTER SARA UU + "+", "\u0E51", "\u0E52", "\u0E53", "\u0E54", + key(" \u0E39", "\u0E39"), + // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT + // U+0E55: "๕" THAI DIGIT FIVE + // U+0E56: "๖" THAI DIGIT SIX + // U+0E57: "๗" THAI DIGIT SEVEN + // U+0E58: "๘" THAI DIGIT EIGHT + // U+0E59: "๙" THAI DIGIT NINE + "\u0E3F", "\u0E55", "\u0E56", "\u0E57", "\u0E58", "\u0E59") + .setKeysOfRow(2, + // U+0E50: "๐" THAI DIGIT ZERO + // U+0E0E: "ฎ" THAI CHARACTER DO CHADA + // U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO + // U+0E18: "ธ" THAI CHARACTER THO THONG + "\u0E50", "\"", "\u0E0E", "\u0E11", "\u0E18", + // U+0E4D: " ํ" THAI CHARACTER THANTHAKHAT + key(" \u0E4D", "\u0E4D"), + // U+0E4A: " ๊" THAI CHARACTER MAI TRI + key(" \u0E4A", "\u0E4A"), + // U+0E13: "ณ" THAI CHARACTER NO NEN + // U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI + // U+0E0D: "ญ" THAI CHARACTER YO YING + // U+0E10: "ฐ" THAI CHARACTER THO THAN + "\u0E13", "\u0E2F", "\u0E0D", "\u0E10", ",") + .setKeysOfRow(3, + // U+0E24: "ฤ" THAI CHARACTER RU + // U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG + // U+0E0F: "ฏ" THAI CHARACTER TO PATAK + // U+0E42: "โ" THAI CHARACTER SARA O + // U+0E0C: "ฌ" THAI CHARACTER CHO CHOE + "\u0E24", "\u0E06", "\u0E0F", "\u0E42", "\u0E0C", + // U+0E47: " ็" THAI CHARACTER MAITAIKHU + key(" \u0E47", "\u0E47"), + // U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA + key(" \u0E4B", "\u0E4B"), + // U+0E29: "ษ" THAI CHARACTER SO RUSI + // U+0E28: "ศ" THAI CHARACTER SO SALA + // U+0E0B: "ซ" THAI CHARACTER SO SO + "\u0E29", "\u0E28", "\u0E0B", ".") + .setKeysOfRow(4, + // U+0E09: "ฉ" THAI CHARACTER CHO CHING + // U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK + "(", ")", "\u0E09", "\u0E2E", + // U+0E3A: " ฺ" THAI CHARACTER PHINTHU + key(" \u0E3A", "\u0E3A"), + // U+0E4C: " ์" THAI CHARACTER THANTHAKHAT + key(" \u0E4C", "\u0E4C"), + // U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO + // U+0E2C: "ฬ" THAI CHARACTER LO CHULA + // U+0E26: "ฦ" THAI CHARACTER LU + "?", "\u0E12", "\u0E2C", "\u0E26") + .build(); +} 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..6e721047c --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.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 java.util.Arrays; + +/** + * This class builds a keyboard that is a two dimensional array of elements <code>E</code>. + * + * A keyboard consists of an array of rows, and a row consists of an 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 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 an empty builder. + */ + AbstractKeyboardBuilder() { + mRows = newArrayOfArray(0); + } + + /** + * 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; + } + + /** + * Return the number of rows. + * @return the number of rows being constructed. + */ + int getRowCount() { + return mRows.length; + } + + /** + * 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 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 RuntimeException if <code>row</code> is illegal. + */ + void setRowAt(final int row, final E[] elements) { + final int rowIndex = row - 1; + if (rowIndex < 0) { + throw new RuntimeException("Illegal row number: " + row); + } + final E[][] newRows = (rowIndex < mRows.length) ? mRows + : Arrays.copyOf(mRows, rowIndex + 1); + newRows[rowIndex] = elements; + mRows = newRows; + } + + /** + * 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 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 (columnIndex < 0) { + throw new RuntimeException("Illegal column number: " + column); + } + if (insert) { + if (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; + } + final E[] newElements = (columnIndex < elements.length) ? elements + : Arrays.copyOf(elements, columnIndex + 1); + newElements[columnIndex] = element; + setRowAt(row, newElements); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java new file mode 100644 index 000000000..9e0039d84 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java @@ -0,0 +1,136 @@ +/* + * 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.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey; +import com.android.inputmethod.latin.Constants; + +/** + * Base class to create an expected keyboard for unit test. + */ +public abstract class AbstractLayoutBase { + // Those helper methods have a lower case name to be readable when defining expected keyboard + // layouts. + + // Helper method to create an {@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 an {@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 an {@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 an {@link ExpectedKey} object that has the icon and the output text. + public static ExpectedKey key(final int iconId, final String outputText, + final ExpectedKey ... moreKeys) { + return ExpectedKey.newInstance(iconId, outputText, moreKeys); + } + + // Helper method to create an {@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 an {@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 an {@link ExpectedAdditionalMoreKey} object for an + // "additional more key" that has the label. + // The additional more keys can be defined independently from other more keys. The position of + // the additional more keys in the long press popup keyboard can be controlled by specifying + // special marker "%" in the usual more keys definitions. + public static ExpectedAdditionalMoreKey additionalMoreKey(final String label) { + return ExpectedAdditionalMoreKey.newInstance(label); + } + + // Helper method to create an {@link ExpectedKey} object for a "more key" that has the label. + public static ExpectedKey moreKey(final String label) { + return ExpectedKey.newInstance(label); + } + + // Helper method to create an {@link ExpectedKey} object for a "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 an {@link ExpectedKey} object for a "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); + } + + // Helper method to create an {@link ExpectedKey} object for a "more key" that has the icon + // and the output text. + public static ExpectedKey moreKey(final int iconId, final String outputText) { + return ExpectedKey.newInstance(iconId, outputText); + } + + // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey}, + // {@link ExpectedKey} array, and {@link String}. + public static ExpectedKey[] joinMoreKeys(final Object ... moreKeys) { + return joinKeys(moreKeys); + } + + // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey}, + // {@link ExpectedKey} array, and {@link String}. + public static ExpectedKey[] joinKeys(final Object ... keys) { + return ExpectedKeyboardBuilder.joinKeys(keys); + } + + // Icon ids. + private static final int ICON_DELETE = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_DELETE_KEY); + private static final int ICON_SPACE = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_SPACE_KEY); + private static final int ICON_TAB = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_TAB_KEY); + private static final int ICON_SHORTCUT = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_SHORTCUT_KEY); + private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_SETTINGS_KEY); + private static final int ICON_LANGUAGE_SWITCH = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_LANGUAGE_SWITCH_KEY); + private static final int ICON_ENTER = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_ENTER_KEY); + private static final int ICON_EMOJI = KeyboardIconsSet.getIconId( + KeyboardIconsSet.NAME_EMOJI_KEY); + + // Functional keys. + public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE); + public static final ExpectedKey TAB_KEY = key(ICON_TAB, Constants.CODE_TAB); + public static final ExpectedKey SHORTCUT_KEY = key(ICON_SHORTCUT, Constants.CODE_SHORTCUT); + public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS); + public static final ExpectedKey LANGUAGE_SWITCH_KEY = key( + ICON_LANGUAGE_SWITCH, Constants.CODE_LANGUAGE_SWITCH); + public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER); + public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI); + public static final ExpectedKey SPACE_KEY = key(ICON_SPACE, Constants.CODE_SPACE); +} 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..56149189f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java @@ -0,0 +1,184 @@ +/* + * 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.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class builds an actual keyboard for unit test. + * + * An actual keyboard is an array of rows, and a row consists of an array of {@link Key}s. + * Each row may have different number of {@link Key}s. + */ +public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> { + private static ArrayList<Key> filterOutSpacer(final List<Key> keys) { + final ArrayList<Key> filteredKeys = new ArrayList<>(); + for (final Key key : keys) { + if (key.isSpacer()) { + continue; + } + filteredKeys.add(key); + } + return filteredKeys; + } + + /** + * Create the keyboard that consists of the array of rows of the actual keyboard's keys. + * @param sortedKeys keys list of the actual keyboard that is sorted from top-left to + * bottom-right. + * @return the actual keyboard grouped with rows. + */ + public static Key[][] buildKeyboard(final List<Key> sortedKeys) { + // Filter out spacer to prepare to create rows. + final ArrayList<Key> filteredSortedKeys = filterOutSpacer(sortedKeys); + + // Grouping keys into rows. + final ArrayList<ArrayList<Key>> rows = new ArrayList<>(); + ArrayList<Key> elements = new ArrayList<>(); + int lastY = filteredSortedKeys.get(0).getY(); + for (final Key key : filteredSortedKeys) { + if (lastY != key.getY()) { + // A new row is starting. + lastY = key.getY(); + rows.add(elements); + elements = new ArrayList<>(); + } + 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(); + + 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(); + } + + @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..0e1c71cd1 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java @@ -0,0 +1,363 @@ +/* + * 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.ArrayList; +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 an output text and may have "more keys". + static ExpectedKey newInstance(final int iconId, final String outputText, + final ExpectedKey... moreKeys) { + return newInstance(ExpectedKeyVisual.newInstance(iconId), + ExpectedKeyOutput.newInstance(outputText), 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); + } + // The more keys are the extra keys that the main keyboard key may have in its long press + // popup keyboard. + // The additional more keys can be defined independently from other more keys. + // The position of the additional more keys in the long press popup keyboard can be + // controlled by specifying special marker "%" in the usual more keys definitions. + final ArrayList<ExpectedKey> moreKeysList = new ArrayList<>(); + final ArrayList<ExpectedAdditionalMoreKey> additionalMoreKeys = new ArrayList<>(); + int firstAdditionalMoreKeyIndex = -1; + for (int index = 0; index < moreKeys.length; index++) { + final ExpectedKey moreKey = moreKeys[index]; + if (moreKey instanceof ExpectedAdditionalMoreKey) { + additionalMoreKeys.add((ExpectedAdditionalMoreKey) moreKey); + if (firstAdditionalMoreKeyIndex < 0) { + firstAdditionalMoreKeyIndex = index; + } + } else { + moreKeysList.add(moreKey); + } + } + if (additionalMoreKeys.isEmpty()) { + return new ExpectedKeyWithMoreKeys(visual, output, moreKeys); + } + final ExpectedKey[] moreKeysArray = moreKeysList.toArray( + new ExpectedKey[moreKeysList.size()]); + final ExpectedAdditionalMoreKey[] additionalMoreKeysArray = additionalMoreKeys.toArray( + new ExpectedAdditionalMoreKey[additionalMoreKeys.size()]); + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + visual, output, moreKeysArray, firstAdditionalMoreKeyIndex, + additionalMoreKeysArray); + } + + 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; + } + + public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { + return newInstance(mVisual, mOutput, moreKeys); + } + + public ExpectedKey setAdditionalMoreKeys( + final ExpectedAdditionalMoreKey... additionalMoreKeys) { + if (additionalMoreKeys.length == 0) { + return this; + } + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + mVisual, mOutput, EMPTY_KEYS, 0 /* additionalMoreKeysIndex */, additionalMoreKeys); + } + + public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { + if (additionalMoreKeysIndex == 0) { + return this; + } + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + mVisual, mOutput, EMPTY_KEYS, additionalMoreKeysIndex); + } + + 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 "additional more key". + * + * The additional more keys can be defined independently from other more keys. The position of + * the additional more keys in the long press popup keyboard can be controlled by specifying + * special marker "%" in the usual more keys definitions. + */ + public static class ExpectedAdditionalMoreKey extends ExpectedKey { + public static ExpectedAdditionalMoreKey newInstance(final String label) { + return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label), + ExpectedKeyOutput.newInstance(label)); + } + + public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) { + return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput()); + } + + ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { + super(visual, output); + } + + @Override + public ExpectedAdditionalMoreKey toUpperCase(final Locale locale) { + final ExpectedKey upperCaseKey = super.toUpperCase(locale); + return new ExpectedAdditionalMoreKey( + upperCaseKey.getVisual(), upperCaseKey.getOutput()); + } + } + + /** + * This class represents an expected key that has "more keys". + */ + private static 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 ExpectedKey setAdditionalMoreKeys( + final ExpectedAdditionalMoreKey... additionalMoreKeys) { + if (additionalMoreKeys.length == 0) { + return this; + } + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual(), getOutput(), mMoreKeys, 0 /* additionalMoreKeysIndex */, + additionalMoreKeys); + } + + @Override + public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { + if (additionalMoreKeysIndex == 0) { + return this; + } + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual(), getOutput(), mMoreKeys, additionalMoreKeysIndex); + } + + @Override + public boolean equalsTo(final Key key) { + if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) { + final MoreKeySpec[] moreKeySpecs = key.getMoreKeys(); + final ExpectedKey[] moreKeys = getMoreKeys(); + // This key should have at least one "more key". + if (moreKeySpecs == null || moreKeySpecs.length != moreKeys.length) { + return false; + } + for (int index = 0; index < moreKeySpecs.length; index++) { + if (!moreKeys[index].equalsTo(moreKeySpecs[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(getMoreKeys()); + } + } + + /** + * This class represents an expected key that has "more keys" and "additional more keys". + */ + private static final class ExpectedKeyWithMoreKeysAndAdditionalMoreKeys + extends ExpectedKeyWithMoreKeys { + private final ExpectedAdditionalMoreKey[] mAdditionalMoreKeys; + private final int mAdditionalMoreKeysIndex; + + ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual, + final ExpectedKeyOutput output, final ExpectedKey[] moreKeys, + final int additionalMoreKeysIndex, + final ExpectedAdditionalMoreKey... additionalMoreKeys) { + super(visual, output, moreKeys); + mAdditionalMoreKeysIndex = additionalMoreKeysIndex; + mAdditionalMoreKeys = additionalMoreKeys; + } + + @Override + public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex, + mAdditionalMoreKeys); + } + + @Override + public ExpectedKey setAdditionalMoreKeys( + final ExpectedAdditionalMoreKey... additionalMoreKeys) { + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex, + additionalMoreKeys); + } + + @Override + public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex, + mAdditionalMoreKeys); + } + + @Override + public ExpectedKey toUpperCase(final Locale locale) { + final ExpectedKey[] moreKeys = super.getMoreKeys(); + final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[moreKeys.length]; + for (int i = 0; i < moreKeys.length; i++) { + upperCaseMoreKeys[i] = moreKeys[i].toUpperCase(locale); + } + final ExpectedAdditionalMoreKey[] upperCaseAdditionalMoreKeys = + new ExpectedAdditionalMoreKey[mAdditionalMoreKeys.length]; + for (int i = 0; i < mAdditionalMoreKeys.length; i++) { + upperCaseAdditionalMoreKeys[i] = mAdditionalMoreKeys[i].toUpperCase(locale); + } + return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( + getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), + upperCaseMoreKeys, mAdditionalMoreKeysIndex, upperCaseAdditionalMoreKeys); + } + + @Override + public ExpectedKey[] getMoreKeys() { + final ExpectedKey[] moreKeys = super.getMoreKeys(); + final ExpectedKey[] edittedMoreKeys = Arrays.copyOf( + moreKeys, moreKeys.length + mAdditionalMoreKeys.length); + System.arraycopy(edittedMoreKeys, mAdditionalMoreKeysIndex, + edittedMoreKeys, mAdditionalMoreKeysIndex + mAdditionalMoreKeys.length, + moreKeys.length - mAdditionalMoreKeysIndex); + System.arraycopy(mAdditionalMoreKeys, 0, edittedMoreKeys, mAdditionalMoreKeysIndex, + mAdditionalMoreKeys.length); + return edittedMoreKeys; + } + } +} 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..9b7de88ea --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java @@ -0,0 +1,345 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Locale; + +/** + * This class builds an expected keyboard for unit test. + * + * An expected keyboard is an array of rows, and a row consists of an array of {@link ExpectedKey}s. + * Each row may have different number of {@link ExpectedKey}s. While building an expected keyboard, + * an {@link ExpectedKey} can be specified by a row number and a column number, both numbers starts + * from 1. + */ +public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> { + public ExpectedKeyboardBuilder() { + super(); + } + + 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. + private interface ReplaceJob { + // Returns a {@link ExpectedKey} objects to replace. + ExpectedKey[] replacingKeys(final ExpectedKey oldKey); + // Return true if replacing should be stopped at first occurrence. + boolean stopAtFirstOccurrence(); + } + + private static ExpectedKey[] replaceKeyAt(final ExpectedKey[] keys, final int columnIndex, + final ExpectedKey[] replacingKeys) { + // Optimization for replacing a key with another key. + if (replacingKeys.length == 1) { + keys[columnIndex] = replacingKeys[0]; + return keys; + } + final int newLength = keys.length - 1 + replacingKeys.length; + // Remove the key at columnIndex. + final ExpectedKey[] newKeys = Arrays.copyOf(keys, newLength); + System.arraycopy(keys, columnIndex + 1, newKeys, columnIndex + replacingKeys.length, + keys.length - 1 - columnIndex); + // Insert replacing keys at columnIndex. + System.arraycopy(replacingKeys, 0, newKeys, columnIndex, replacingKeys.length); + return newKeys; + + } + + // Replace key(s) that has the specified visual. + private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) { + int replacedCount = 0; + final int rowCount = getRowCount(); + for (int row = 1; row <= rowCount; row++) { + ExpectedKey[] keys = getRowAt(row); + for (int columnIndex = 0; columnIndex < keys.length; /* nothing */) { + final ExpectedKey currentKey = keys[columnIndex]; + if (!currentKey.getVisual().equalsTo(visual)) { + columnIndex++; + continue; + } + final ExpectedKey[] replacingKeys = job.replacingKeys(currentKey); + keys = replaceKeyAt(keys, columnIndex, replacingKeys); + columnIndex += replacingKeys.length; + setRowAt(row, keys); + replacedCount++; + if (job.stopAtFirstOccurrence()) { + return; + } + } + } + if (replacedCount == 0) { + throw new RuntimeException( + "Can't find key that has visual: " + visual + " in\n" + this); + } + } + + // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey}, + // {@link ExpectedKey} array, and {@link String}. + static ExpectedKey[] joinKeys(final Object ... keys) { + final ArrayList<ExpectedKey> list = new ArrayList<>(); + for (final Object key : keys) { + if (key instanceof ExpectedKey) { + list.add((ExpectedKey)key); + } else if (key instanceof ExpectedKey[]) { + list.addAll(Arrays.asList((ExpectedKey[])key)); + } else if (key instanceof String) { + list.add(ExpectedKey.newInstance((String)key)); + } else { + throw new RuntimeException("Unknown expected key type: " + key); + } + } + return list.toArray(new ExpectedKey[list.size()]); + } + + /** + * Set the row with specified keys. + * @param row the row number to set keys. + * @param keys the keys to be set at <code>row</code>. Each key can be {@link ExpectedKey}, + * {@link ExpectedKey} array, and {@link String}. + * @return this builder. + */ + public ExpectedKeyboardBuilder setKeysOfRow(final int row, final Object ... keys) { + setRowAt(row, joinKeys(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 "more key" to be set. Each "more key" can be + * {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final Object ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(label), joinKeys(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. Each "more key" can be + * {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final Object ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), joinKeys(moreKeys)); + return this; + } + + private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) { + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) { + return new ExpectedKey[] { oldKey.setMoreKeys(moreKeys) }; + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + } + + /** + * Set the "additional more keys position" of the key that has the specified label. + * @param label the label of the key to set the "additional more keys". + * @param additionalMoreKeysPosition the position in the "more keys" where + * "additional more keys" will be merged. The position starts from 1. + * @return this builder. + */ + public ExpectedKeyboardBuilder setAdditionalMoreKeysPositionOf(final String label, + final int additionalMoreKeysPosition) { + final int additionalMoreKeysIndex = additionalMoreKeysPosition - 1; + if (additionalMoreKeysIndex < 0) { + throw new RuntimeException("Illegal additional more keys position: " + + additionalMoreKeysPosition); + } + final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label); + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) { + return new ExpectedKey[] { + oldKey.setAdditionalMoreKeysIndex(additionalMoreKeysIndex) + }; + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + return this; + } + + /** + * 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>. Each key can be + * {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}. + * @return this builder. + * @throws RuntimeException if <code>row</code> or <code>column</code> is illegal. + */ + public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column, + final Object ... keys) { + final ExpectedKey[] expectedKeys = joinKeys(keys); + for (int index = 0; index < keys.length; index++) { + setElementAt(row, column + index, expectedKeys[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. Each key can be + * {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}. + * @return this builder. + * @throws RuntimeException if <code>row</code> is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row, + final Object ... keys) { + final ExpectedKey[] expectedKeys = joinKeys(keys); + // Keys should be inserted from the last to preserve the order. + for (int index = keys.length - 1; index >= 0; index--) { + setElementAt(row, 1, expectedKeys[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. Each key can be + * {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}. + * @return this builder. + * @throws RuntimeException if <code>row</code> is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row, + final Object ... 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 keys. + * @param label the label of the key to set <code>newKeys</code>. + * @param newKeys the keys to be set. Each key can be {@link ExpectedKey}, {@link ExpectedKey} + * array, and {@link String}. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, + final Object ... newKeys) { + final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label); + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) { + return joinKeys(newKeys); + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + return this; + } + + /** + * Replace the all specified keys with the new keys. + * @param key the key to be replaced by <code>newKeys</code>. + * @param newKeys the keys to be set. Each key can be {@link ExpectedKey}, {@link ExpectedKey} + * array, and {@link String}. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeysOfAll(final ExpectedKey key, + final Object ... newKeys) { + replaceKeyOf(key.getVisual(), new ReplaceJob() { + @Override + public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) { + return joinKeys(newKeys); + } + @Override + public boolean stopAtFirstOccurrence() { + return false; + } + }); + return this; + } + + /** + * Convert all keys of this keyboard builder to upper case keys. + * @param locale the locale used to convert cases. + * @return this builder + */ + public ExpectedKeyboardBuilder toUpperCase(final Locale locale) { + final int rowCount = getRowCount(); + for (int row = 1; row <= rowCount; row++) { + final ExpectedKey[] lowerCaseKeys = getRowAt(row); + final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length]; + for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) { + upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale); + } + setRowAt(row, upperCaseKeys); + } + return this; + } + + @Override + public String toString() { + return toString(build()); + } + + /** + * 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/tests/EnglishCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java new file mode 100644 index 000000000..3e82f65bf --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java @@ -0,0 +1,76 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class EnglishCustomizer extends LayoutCustomizer { + EnglishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // 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", "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0113") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FB", "\u00FC", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + .setMoreKeysOf("i", "\u00ED", "\u00EE", "\u00EF", "\u012B", "\u00EC") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // 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+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", + "\u00F3", "\u00F4", "\u00F6", "\u00F2", "\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"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java new file mode 100644 index 000000000..ab90267d0 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java @@ -0,0 +1,89 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class FrenchCustomizer extends LayoutCustomizer { + FrenchCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // 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 + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setAdditionalMoreKeysPositionOf("a", 3) + .setMoreKeysOf("a", + "\u00E0", "\u00E2", "\u00E6", "\u00E1", "\u00E4", "\u00E3", "\u00E5", + "\u0101", "\u00AA") + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setAdditionalMoreKeysPositionOf("e", 5) + .setMoreKeysOf("e", + "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", "\u0113") + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FF") + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setAdditionalMoreKeysPositionOf("u", 3) + .setMoreKeysOf("u", "\u00F9", "\u00FB", "\u00FC", "\u00FA", "\u016B") + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setAdditionalMoreKeysPositionOf("i", 2) + .setMoreKeysOf("i", "\u00EE", "\u00EF", "\u00EC", "\u00ED", "\u012F", "\u012B") + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+0153: "œ" LATIN SMALL LIGATURE OE + // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setAdditionalMoreKeysPositionOf("o", 3) + .setMoreKeysOf("o", + "\u00F4", "\u0153", "\u00F6", "\u00F2", "\u00F3", "\u00F5", "\u00F8", + "\u014D", "\u00BA") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D") + .setAdditionalMoreKeysPositionOf("c", 2); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/GermanCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/GermanCustomizer.java new file mode 100644 index 000000000..6d38937aa --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/GermanCustomizer.java @@ -0,0 +1,89 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class GermanCustomizer extends LayoutCustomizer { + public GermanCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + .setMoreKeysOf("e", "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0117") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // 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", "\u00FC", "\u00FB", "\u00F9", "\u00FA", "\u016B") + .setAdditionalMoreKeysPositionOf("u", 2) + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F6", "\u00F4", "\u00F2", "\u00F3", "\u00F5", "\u0153", "\u00F8", + "\u014D") + .setAdditionalMoreKeysPositionOf("o", 2) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // 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", + "\u00E4", "\u00E2", "\u00E0", "\u00E1", "\u00E6", "\u00E3", "\u00E5", + "\u0101") + .setAdditionalMoreKeysPositionOf("a", 2) + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u00DF", "\u015B", "\u0161") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/ItalianCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/ItalianCustomizer.java new file mode 100644 index 000000000..735070946 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/ItalianCustomizer.java @@ -0,0 +1,77 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class ItalianCustomizer extends LayoutCustomizer { + public ItalianCustomizer(final Locale locale) { super(locale); } + + @Override + public 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+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0119", "\u0117", "\u0113") + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00F9", "\u00FA", "\u00FB", "\u00FC", "\u016B") + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u012F", "\u012B") + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F2", "\u00F3", "\u00F4", "\u00F6", "\u00F5", "\u0153", "\u00F8", + "\u014D", "\u00BA") + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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 + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5", + "\u0101", "\u00AA"); + } +} 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..a22ed60ac --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java @@ -0,0 +1,191 @@ +/* + * 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.KeyboardTheme; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ActualKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; + +import java.util.Arrays; + +/** + * Base class for keyboard layout unit test. + */ +abstract class LayoutTestsBase extends KeyboardLayoutSetTestsBase { + private LayoutBase mLayout; + private InputMethodSubtype mSubtype; + private String mLogTag; + private KeyboardLayoutSet mKeyboardLayoutSet; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mLayout = getLayout(); + mSubtype = getSubtype(mLayout.getLocale(), mLayout.getName()); + mLogTag = SubtypeLocaleUtils.getSubtypeNameForLogging(mSubtype) + "/" + + (isPhone() ? "phone" : "tablet"); + // TODO: Test with language switch key enabled and disabled. + mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */, + true /* voiceInputKeyEnabled */, true /* languageSwitchKeyEnabled */); + } + + @Override + protected int getKeyboardThemeForTests() { + return KeyboardTheme.THEME_ID_KLP; + } + + // Those helper methods have a lower case name to be readable when defining expected keyboard + // layouts. + + // Helper method to create an {@link ExpectedKey} object that has the label. + static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) { + return AbstractLayoutBase.key(label, moreKeys); + } + + // Helper method to create an {@link ExpectedKey} object that has the label and the output text. + static ExpectedKey key(final String label, final String outputText, + final ExpectedKey ... moreKeys) { + return AbstractLayoutBase.key(label, outputText, moreKeys); + } + + // Helper method to create an {@link ExpectedKey} object that has new "more keys". + static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) { + return AbstractLayoutBase.key(key, moreKeys); + } + + // Helper method to create an {@link ExpectedAdditionalMoreKey} object for an + // "additional more key" that has the label. + public static ExpectedAdditionalMoreKey additionalMoreKey(final String label) { + return AbstractLayoutBase.additionalMoreKey(label); + } + + // Helper method to create an {@link ExpectedKey} object for a "more key" that has the label. + static ExpectedKey moreKey(final String label) { + return AbstractLayoutBase.moreKey(label); + } + + // Helper method to create an {@link ExpectedKey} object for a "more key" that has the label + // and the output text. + static ExpectedKey moreKey(final String label, final String outputText) { + return AbstractLayoutBase.moreKey(label, outputText); + } + + // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey}, + // {@link ExpectedKey} array, and {@link String}. + static ExpectedKey[] joinMoreKeys(final Object ... moreKeys) { + return AbstractLayoutBase.joinKeys(moreKeys); + } + + // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey}, + // {@link ExpectedKey} array, and {@link String}. + static ExpectedKey[] joinKeys(final Object ... keys) { + return AbstractLayoutBase.joinKeys(keys); + } + + // Keyboard layout for testing subtype. + abstract LayoutBase getLayout(); + + ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder; + } + + // TODO: Add phone, phone symbols, number, number password layout tests. + + public final void testAlphabet() { + doKeyboardTests(KeyboardId.ELEMENT_ALPHABET); + } + + public final void testAlphabetAutomaticShifted() { + doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED); + } + + public final void testAlphabetManualShifted() { + doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED); + } + + public final void testAlphabetShiftLocked() { + doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED); + } + + public final void testAlphabetShiftLockShifted() { + doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED); + } + + public final void testSymbols() { + doKeyboardTests(KeyboardId.ELEMENT_SYMBOLS); + } + + public final void testSymbolsShifted() { + doKeyboardTests(KeyboardId.ELEMENT_SYMBOLS_SHIFTED); + } + + // Comparing expected keyboard and actual keyboard. + private void doKeyboardTests(final int elementId) { + final ExpectedKey[][] expectedKeyboard = mLayout.getLayout(isPhone(), elementId); + // 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.getSortedKeys()); + + // 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=" + ExpectedKeyboardBuilder.toString(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/NoLanguageCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/NoLanguageCustomizer.java new file mode 100644 index 000000000..9edbcab69 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/NoLanguageCustomizer.java @@ -0,0 +1,159 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class NoLanguageCustomizer extends LayoutCustomizer { + NoLanguageCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX + .setMoreKeysOf("w", "\u0175") + // 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 + // U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + .setMoreKeysOf("e", + "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113", "\u0115", "\u0117", + "\u0119", "\u011B") + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + .setMoreKeysOf("r", "\u0155", "\u0157", "\u0159") + // U+00FE: "þ" LATIN SMALL LETTER THORN + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE + .setMoreKeysOf("t", "\u00FE", "\u0163", "\u0165", "\u0167") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("y", "\u00FD", "\u0177", "\u00FF", "\u0133") + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + .setMoreKeysOf("u", + "\u00F9", "\u00FA", "\u00FB", "\u00FC", "\u0169", "\u016B", "\u016D", + "\u016F", "\u0171", "\u0173") + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+012D: "ĭ" LATIN SMALL LETTER I WITH BREVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("i", + "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u0129", "\u012B", "\u012D", + "\u012F", "\u0131", "\u0133") + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+014F: "ŏ" LATIN SMALL LETTER O WITH BREVE + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F2", "\u00F3", "\u00F4", "\u00F5", "\u00F6", "\u00F8", "\u014D", + "\u014F", "\u0151", "\u0153", "\u00BA") + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E0", "\u00E1", "\u00E2", "\u00E3", "\u00E4", "\u00E5", "\u00E6", + "\u0101", "\u0103", "\u0105", "\u00AA") + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+017F: "ſ" LATIN SMALL LETTER LONG S + .setMoreKeysOf("s", "\u00DF", "\u015B", "\u015D", "\u015F", "\u0161", "\u017F") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE + // U+00F0: "ð" LATIN SMALL LETTER ETH + .setMoreKeysOf("d", "\u010F", "\u0111", "\u00F0") + // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + .setMoreKeysOf("g", "\u011D", "\u011F", "\u0121", "\u0123") + // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX + .setMoreKeysOf("h", "\u0125") + // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX + .setMoreKeysOf("j", "\u0135") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + // U+0138: "ĸ" LATIN SMALL LETTER KRA + .setMoreKeysOf("k", "\u0137", "\u0138") + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u013A", "\u013C", "\u013E", "\u0140", "\u0142") + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + .setMoreKeysOf("z", "\u017A", "\u017C", "\u017E") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX + // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u0109", "\u010B", "\u010D") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+0148: "ň" LATIN SMALL LETTER N WITH CARON + // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + // U+014B: "ŋ" LATIN SMALL LETTER ENG + .setMoreKeysOf("n", "\u00F1", "\u0144", "\u0146", "\u0148", "\u0149", "\u014B"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/PortugueseCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/PortugueseCustomizer.java new file mode 100644 index 000000000..629e8cb8b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/PortugueseCustomizer.java @@ -0,0 +1,80 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class PortugueseCustomizer extends LayoutCustomizer { + PortugueseCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + .setMoreKeysOf("e", + "\u00E9", "\u00EA", "\u00E8", "\u0119", "\u0117", "\u0113", "\u00EB") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00F9", "\u00FB", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EE", "\u00EC", "\u00EF", "\u012F", "\u012B") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F3", "\u00F5", "\u00F4", "\u00F2", "\u00F6", "\u0153", "\u00F8", + "\u014D", "\u00BA") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E1", "\u00E3", "\u00E0", "\u00E2", "\u00E4", "\u00E5", "\u00E6", + "\u00AA") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u00E7", "\u010D", "\u0107"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/SpanishCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/SpanishCustomizer.java new file mode 100644 index 000000000..8974ad6ec --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/SpanishCustomizer.java @@ -0,0 +1,104 @@ +/* + * 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 com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +class SpanishCustomizer extends LayoutCustomizer { + SpanishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? PHONE_PUNCTUATION_MORE_KEYS + : LayoutBase.TABLET_PUNCTUATION_MORE_KEYS; + } + + // Punctuation more keys for phone form factor. + private static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = AbstractLayoutBase.joinKeys( + // U+00A1: "¡" INVERTED EXCLAMATION MARK + // U+00BF: "¿" INVERTED QUESTION MARK + ",", "?", "!", "#", ")", "(", "/", ";", "\u00A1", + "'", "@", ":", "-", "\"", "+", "%", "&", "\u00BF"); + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00E8", "\u00EB", "\u00EA", "\u0119", "\u0117", "\u0113") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00F9", "\u00FB", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EF", "\u00EC", "\u00EE", "\u012F", "\u012B") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F3", "\u00F2", "\u00F6", "\u00F4", "\u00F5", "\u00F8", "\u0153", + "\u014D", "\u00BA") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E1", "\u00E0", "\u00E4", "\u00E2", "\u00E3", "\u00E5", "\u0105", + "\u00E6", "\u0101", "\u00AA") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + .replaceKeyOfLabel(Spanish.ROW2_10, "\u00F1") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAfrikaans.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAfrikaans.java new file mode 100644 index 000000000..cd2259888 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAfrikaans.java @@ -0,0 +1,99 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * af: TestsAfrikaans/qwerty + */ +@SmallTest +public final class TestsAfrikaans extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("af"); + private static final LayoutBase LAYOUT = new Qwerty(new AfrikaansCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class AfrikaansCustomizer extends LayoutCustomizer { + AfrikaansCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", "\u0113") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FB", "\u00FC", "\u00F9", "\u016B") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("y", "\u00FD", "\u0133") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("i", + "\u00ED", "\u00EC", "\u00EF", "\u00EE", "\u012F", "\u012B", "\u0133") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // 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+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F4", "\u00F6", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D") + // 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+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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", + "\u00E1", "\u00E2", "\u00E4", "\u00E0", "\u00E6", "\u00E3", "\u00E5", + "\u0101") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArabic.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArabic.java new file mode 100644 index 000000000..fd7670827 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArabic.java @@ -0,0 +1,37 @@ +/* + * 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.Arabic; +import com.android.inputmethod.keyboard.layout.Arabic.ArabicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * ar: Arabic/arabic + */ +@SmallTest +public class TestsArabic extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ar"); + private static final LayoutBase LAYOUT = new Arabic(new ArabicCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArmenianAMPhonetic.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArmenianAMPhonetic.java new file mode 100644 index 000000000..327e9438f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsArmenianAMPhonetic.java @@ -0,0 +1,38 @@ +/* + * 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.ArmenianPhonetic; +import com.android.inputmethod.keyboard.layout.ArmenianPhonetic.ArmenianPhoneticCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * hy_AM: Armenian (Armenia) Phonetic/armenian_phonetic + */ +@SmallTest +public final class TestsArmenianAMPhonetic extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("hy", "AM"); + private static final LayoutBase LAYOUT = new ArmenianPhonetic( + new ArmenianPhoneticCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAzerbaijaniAZ.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAzerbaijaniAZ.java new file mode 100644 index 000000000..f5317e269 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAzerbaijaniAZ.java @@ -0,0 +1,88 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * az_AZ: Azerbaijani (Azerbaijan)/qwerty + */ +@SmallTest +public final class TestsAzerbaijaniAZ extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("az", "AZ"); + private static final LayoutBase LAYOUT = new Qwerty(new AzerbaijaniAZCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static final class AzerbaijaniAZCustomizer extends LayoutCustomizer { + public AzerbaijaniAZCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0259: "ə" LATIN SMALL LETTER SCHWA + .setMoreKeysOf("e", "\u0259") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // 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", "\u00FC", "\u00FB", "\u00F9", "\u00FA", "\u016B") + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", + "\u0131", "\u00EE", "\u00EF", "\u00EC", "\u00ED", "\u012F", "\u012B") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F6", "\u00F4", "\u0153", "\u00F2", "\u00F3", "\u00F5", "\u00F8", + "\u014D") + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + .setMoreKeysOf("a", "\u00E2") + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u015F", "\u00DF", "\u015B", "\u0161") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D") + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u011F"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBasqueES.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBasqueES.java new file mode 100644 index 000000000..bef18c5d5 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBasqueES.java @@ -0,0 +1,52 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * eu_ES: Basque (Spain)/spanish + */ +@SmallTest +public class TestsBasqueES extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("eu", "ES"); + private static final LayoutBase LAYOUT = new Spanish(new BasqueESCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class BasqueESCustomizer extends EuroCustomizer { + private final SpanishCustomizer mSpanishCustomizer; + + public BasqueESCustomizer(final Locale locale) { + super(locale); + mSpanishCustomizer = new SpanishCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mSpanishCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBelarusianBY.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBelarusianBY.java new file mode 100644 index 000000000..c5238d54f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBelarusianBY.java @@ -0,0 +1,73 @@ +/* + * 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.EastSlavic; +import com.android.inputmethod.keyboard.layout.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * be_BY: Belarusian (Belarus)/east_slavic + */ +@SmallTest +public final class TestsBelarusianBY extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("be", "BY"); + private static final LayoutBase LAYOUT = new EastSlavic(new BelarusianBYCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class BelarusianBYCustomizer extends EastSlavicCustomizer { + public BelarusianBYCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { + return Symbols.DOUBLE_QUOTES_R9L; + } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { + return Symbols.SINGLE_QUOTES_R9L; + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0451: "ё" CYRILLIC SMALL LETTER IO + .setMoreKeysOf("\u0435", "\u0451") + // U+045E: "ў" CYRILLIC SMALL LETTER SHORT U + .replaceKeyOfLabel(EastSlavic.ROW1_9, key("\u045E", additionalMoreKey("9"))) + // U+044B: "ы" CYRILLIC SMALL LETTER YERU + .replaceKeyOfLabel(EastSlavic.ROW2_2, "\u044B") + // U+044D: "э" CYRILLIC SMALL LETTER E + .replaceKeyOfLabel(EastSlavic.ROW2_11, "\u044D") + // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + .replaceKeyOfLabel(EastSlavic.ROW3_5, "\u0456") + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + .setMoreKeysOf("\u044C", "\u044A"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliIN.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliIN.java new file mode 100644 index 000000000..d64263207 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliIN.java @@ -0,0 +1,50 @@ +/* + * 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.Bengali; +import com.android.inputmethod.keyboard.layout.Bengali.BengaliCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * bn_IN: Bengali (India)/bengali + */ +@SmallTest +public final class TestsBengaliIN extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("bn", "IN"); + private static final LayoutBase LAYOUT = new Bengali(new BengaliINCustomzier(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class BengaliINCustomzier extends BengaliCustomizer { + public BengaliINCustomzier(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_RUPEE; } + + // U+20B9: "₹" INDIAN RUPEE SIGN + private static final ExpectedKey CURRENCY_RUPEE = key("\u20B9", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarian.java new file mode 100644 index 000000000..ded8d7243 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarian.java @@ -0,0 +1,37 @@ +/* + * 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.Bulgarian; +import com.android.inputmethod.keyboard.layout.Bulgarian.BulgarianCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * bg: TestsBulgarian/bulgarian + */ +@SmallTest +public final class TestsBulgarian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("bg"); + private static final LayoutBase LAYOUT = new Bulgarian(new BulgarianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarianBds.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarianBds.java new file mode 100644 index 000000000..22b2011ee --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBulgarianBds.java @@ -0,0 +1,37 @@ +/* + * 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.BulgarianBds; +import com.android.inputmethod.keyboard.layout.BulgarianBds.BulgarianBdsCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * bg: Bulgarian/bulgarian_bds + */ +@SmallTest +public final class TestsBulgarianBds extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("bg"); + private static final LayoutBase LAYOUT = new BulgarianBds(new BulgarianBdsCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCatalan.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCatalan.java new file mode 100644 index 000000000..151a0a627 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCatalan.java @@ -0,0 +1,122 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * ca: Catalan/spanish + */ +@SmallTest +public class TestsCatalan extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ca"); + private static final LayoutBase LAYOUT = new Spanish(new CatalanCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class CatalanCustomizer extends EuroCustomizer { + public CatalanCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? PHONE_PUNCTUATION_MORE_KEYS + : TABLET_PUNCTUATION_MORE_KEYS; + } + + // U+00B7: "·" MIDDLE DOT + private static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "?", "!", "\u00B7", "#", ")", "(", "/", ";", + "'", "@", ":", "-", "\"", "+", "%", "&"); + + private static final ExpectedKey[] TABLET_PUNCTUATION_MORE_KEYS = joinKeys( + ",", "'", "\u00B7", "#", ")", "(", "/", ";", + "@", ":", "-", "\"", "+", "%", "&"); + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E8", "\u00E9", "\u00EB", "\u00EA", "\u0119", "\u0117", "\u0113") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00F9", "\u00FB", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EF", "\u00EC", "\u00EE", "\u012F", "\u012B") + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F2", "\u00F3", "\u00F6", "\u00F4", "\u00F5", "\u00F8", "\u0153", + "\u014D", "\u00BA") + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E0", "\u00E1", "\u00E4", "\u00E2", "\u00E3", "\u00E5", "\u0105", + "\u00E6", "\u0101", "\u00AA") + // U+00B7: "·" MIDDLE DOT + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "l\u00B7l", "\u0142") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + .replaceKeyOfLabel(Spanish.ROW2_10, "\u00E7") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCroatian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCroatian.java new file mode 100644 index 000000000..8575ef219 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCroatian.java @@ -0,0 +1,78 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwertz; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * hr: Croatian/qwertz + */ +@SmallTest +public final class TestsCroatian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("hr"); + private static final LayoutBase LAYOUT = new Qwertz(new CroatianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class CroatianCustomizer extends LayoutCustomizer { + public CroatianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + .setMoreKeysOf("z", "\u017E", "\u017A", "\u017C") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + .setMoreKeysOf("s", "\u0161", "\u015B", "\u00DF") + // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE + .setMoreKeysOf("d", "\u0111") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + .setMoreKeysOf("c", "\u010D", "\u0107", "\u00E7") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCzech.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCzech.java new file mode 100644 index 000000000..f4794707f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsCzech.java @@ -0,0 +1,133 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwertz; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * cs: Czech/qwertz + */ +@SmallTest +public final class TestsCzech extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("cs"); + private static final LayoutBase LAYOUT = new Qwertz(new CzechCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class CzechCustomizer extends LayoutCustomizer { + public CzechCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u011B", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", + "\u0113") + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + .setMoreKeysOf("r", "\u0159") + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + .setMoreKeysOf("t", "\u0165") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + .setMoreKeysOf("z", "\u017E", "\u017A", "\u017C") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u016F", "\u00FB", "\u00FC", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EE", "\u00EF", "\u00EC", "\u012F", "\u012B") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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", + "\u00E1", "\u00E0", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5", + "\u0101") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u010F") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u00E7", "\u0107") + // U+0148: "ň" LATIN SMALL LETTER N WITH CARON + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u0148", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDanish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDanish.java new file mode 100644 index 000000000..85c63a128 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDanish.java @@ -0,0 +1,109 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Nordic; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * da: Danish/nordic + */ +@SmallTest +public final class TestsDanish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("da"); + private static final LayoutBase LAYOUT = new Nordic(new DanishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class DanishCustomizer extends EuroCustomizer { + public DanishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + .setMoreKeysOf("e", "\u00E9", "\u00EB") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00FB", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + .setMoreKeysOf("i", "\u00ED", "\u00EF") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", "\u00F3", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u014D") + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + .replaceKeyOfLabel(Nordic.ROW1_11, "\u00E5") + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW2_10, key("\u00E6", moreKey("\u00E4"))) + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW2_11, key("\u00F8", moreKey("\u00F6"))) + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", "\u00E1", "\u00E4", "\u00E0", "\u00E2", "\u00E3", "\u0101") + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u00DF", "\u015B", "\u0161") + // U+00F0: "ð" LATIN SMALL LETTER ETH + .setMoreKeysOf("d", "\u00F0") + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u0142") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutch.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutch.java new file mode 100644 index 000000000..1730f66be --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutch.java @@ -0,0 +1,106 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * nl: Dutch/qwerty + */ +@SmallTest +public final class TestsDutch extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("nl"); + private static final LayoutBase LAYOUT = new Qwerty(new DutchCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + static class DutchCustomizer extends EuroCustomizer { + public DutchCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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", + "\u00E1", "\u00E4", "\u00E2", "\u00E0", "\u00E6", "\u00E3", "\u00E5", + "\u0101") + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00EB", "\u00EA", "\u00E8", "\u0119", "\u0117", "\u0113") + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("y", "\u0133") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00FB", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("i", + "\u00ED", "\u00EF", "\u00EC", "\u00EE", "\u012F", "\u012B", "\u0133") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutchBE.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutchBE.java new file mode 100644 index 000000000..31adf7a8d --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutchBE.java @@ -0,0 +1,37 @@ +/* + * 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.Azerty; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.tests.TestsDutch.DutchCustomizer; + +import java.util.Locale; + +/** + * nl_BE: Dutch (Belgium)/azerty + */ +@SmallTest +public final class TestsDutchBE extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("nl", "BE"); + private static final LayoutBase LAYOUT = new Azerty(new DutchCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java new file mode 100644 index 000000000..a05269312 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java @@ -0,0 +1,52 @@ +/* + * 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.Dvorak; +import com.android.inputmethod.keyboard.layout.Dvorak.DvorakCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * en_US: English (United States)/dvorak + */ +@SmallTest +public class TestsEnglishDvorak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("en", "US"); + private static final LayoutBase LAYOUT = new Dvorak(new EnglishDvorakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class EnglishDvorakCustomizer extends DvorakCustomizer { + private final EnglishCustomizer mEnglishCustomizer; + + EnglishDvorakCustomizer(final Locale locale) { + super(locale); + mEnglishCustomizer = new EnglishCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mEnglishCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishIN.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishIN.java new file mode 100644 index 000000000..c80b25024 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishIN.java @@ -0,0 +1,55 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/* + * en_IN: English (India)/qwerty + */ +@SmallTest +public final class TestsEnglishIN extends TestsEnglishUS { + private static final Locale LOCALE = new Locale("en", "IN"); + private static final LayoutBase LAYOUT = new Qwerty(new EnglishINCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class EnglishINCustomizer extends EnglishCustomizer { + public EnglishINCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_RUPEE; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + // U+20B9: "₹" INDIAN RUPEE SIGN + private static final ExpectedKey CURRENCY_RUPEE = key("\u20B9", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUK.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUK.java new file mode 100644 index 000000000..c0dcbdc06 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUK.java @@ -0,0 +1,57 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/* + * en_GB: English (Great Britain)/qwerty + */ +@SmallTest +public final class TestsEnglishUK extends TestsEnglishUS { + private static final Locale LOCALE = new Locale("en", "GB"); + private static final LayoutBase LAYOUT = new Qwerty(new EnglishUKCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class EnglishUKCustomizer extends EnglishCustomizer { + public EnglishUKCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_POUND; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { return CURRENCIES_OTHER_THAN_POUND; } + + private static final ExpectedKey CURRENCY_POUND = key(Symbols.POUND_SIGN, + Symbols.CENT_SIGN, Symbols.DOLLAR_SIGN, Symbols.EURO_SIGN, Symbols.YEN_SIGN, + Symbols.PESO_SIGN); + + private static final ExpectedKey[] CURRENCIES_OTHER_THAN_POUND = { + Symbols.EURO_SIGN, Symbols.YEN_SIGN, key(Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN), + Symbols.CENT_SIGN + }; + } +} 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..6ea8f6000 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * en_US: English (United States)/qwerty + */ +@SmallTest +public class TestsEnglishUS extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("en", "US"); + private static final LayoutBase LAYOUT = new Qwerty(new EnglishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEsperanto.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEsperanto.java new file mode 100644 index 000000000..6a44187c9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEsperanto.java @@ -0,0 +1,181 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * eo: Esperanto/spanish + */ +@SmallTest +public class TestsEsperanto extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("eo"); + private static final LayoutBase LAYOUT = new Spanish(new EsperantoCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class EsperantoCustomizer extends LayoutCustomizer { + public EsperantoCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX + .replaceKeyOfLabel("q", key("\u015D", joinMoreKeys( + additionalMoreKey("1"), "q"))) + // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX + // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX + .replaceKeyOfLabel("w", key("\u011D", joinMoreKeys( + additionalMoreKey("2"), "w", "\u0175"))) + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u011B", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", + "\u0113") + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + .setMoreKeysOf("r", "\u0159", "\u0155", "\u0157") + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE + .setMoreKeysOf("t", "\u0165", "\u021B", "\u0163", "\u0167") + // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + // U+00FE: "þ" LATIN SMALL LETTER THORN + .replaceKeyOfLabel("y", key("\u016D", joinMoreKeys( + additionalMoreKey("6"), "y", "\u00FD", "\u0177", "\u00FF", "\u00FE"))) + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // 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+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + // U+00B5: "µ" MICRO SIGN + .setMoreKeysOf("u", + "\u00FA", "\u016F", "\u00FB", "\u00FC", "\u00F9", "\u016B", "\u0169", + "\u0171", "\u0173", "\u00B5") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + // U+0133: "ij" LATIN SMALL LIGATURE IJ + .setMoreKeysOf("i", + "\u00ED", "\u00EE", "\u00EF", "\u0129", "\u00EC", "\u012F", "\u012B", + "\u0131", "\u0133") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00BA: "º" MASCULINE ORDINAL INDICATOR + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D", "\u0151", "\u00BA") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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 + // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00AA: "ª" FEMININE ORDINAL INDICATOR + .setMoreKeysOf("a", + "\u00E1", "\u00E0", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5", + "\u0101", "\u0103", "\u0105", "\u00AA") + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + .setMoreKeysOf("s", "\u00DF", "\u0161", "\u015B", "\u0219", "\u015F") + // U+00F0: "ð" LATIN SMALL LETTER ETH + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE + .setMoreKeysOf("d", "\u00F0", "\u010F", "\u0111") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+0148: "ň" LATIN SMALL LETTER N WITH CARON + // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + // U+014B: "ŋ" LATIN SMALL LETTER ENG + .setMoreKeysOf("n", "\u00F1", "\u0144", "\u0146", "\u0148", "\u0149", "\u014B") + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + .setMoreKeysOf("g", "\u011F", "\u0121", "\u0123") + // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX + // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE + .setMoreKeysOf("h", "\u0125", "\u0127") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + // U+0138: "ĸ" LATIN SMALL LETTER KRA + .setMoreKeysOf("k", "\u0137", "\u0138") + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u013A", "\u013C", "\u013E", "\u0140", "\u0142") + // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX + .replaceKeyOfLabel(Spanish.ROW2_10, "\u0135") + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + .setMoreKeysOf("z", "\u017A", "\u017C", "\u017E") + // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX + .replaceKeyOfLabel("x", key("\u0109", moreKey("x"))) + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE + .setMoreKeysOf("c", "\u0107", "\u010D", "\u00E7", "\u010B") + // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX + .setMoreKeysOf("v", "w", "\u0175"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEstonianEE.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEstonianEE.java new file mode 100644 index 000000000..865e9ea17 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEstonianEE.java @@ -0,0 +1,157 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Nordic; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * et_EE: Estonian (Estonia)/nordic + */ +@SmallTest +public final class TestsEstonianEE extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("et", "EE"); + private static final LayoutBase LAYOUT = new Nordic(new EstonianEECustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class EstonianEECustomizer extends EuroCustomizer { + public EstonianEECustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // 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+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + .setMoreKeysOf("e", + "\u0113", "\u00E8", "\u0117", "\u00E9", "\u00EA", "\u00EB", "\u0119", + "\u011B") + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + .setMoreKeysOf("r", "\u0157", "\u0159", "\u0155") + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + .setMoreKeysOf("t", "\u0163", "\u0165") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + .setMoreKeysOf("u", + "\u00FC", "\u016B", "\u0173", "\u00F9", "\u00FA", "\u00FB", "\u016F", + "\u0171") + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + .setMoreKeysOf("i", + "\u012B", "\u00EC", "\u012F", "\u00ED", "\u00EE", "\u00EF", "\u0131") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + .setMoreKeysOf("o", + "\u00F6", "\u00F5", "\u00F2", "\u00F3", "\u00F4", "\u0153", "\u0151", + "\u00F8") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW1_11, "\u00FC") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + .replaceKeyOfLabel(Nordic.ROW2_10, key("\u00F6", moreKey("\u00F5"))) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW2_11, "\u00E4") + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + .setMoreKeysOf("a", + "\u00E4", "\u0101", "\u00E0", "\u00E1", "\u00E2", "\u00E3", "\u00E5", + "\u00E6", "\u0105") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B", "\u015F") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u010F") + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u0123", "\u011F") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + .setMoreKeysOf("k", "\u0137") + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + .setMoreKeysOf("l", "\u013C", "\u0142", "\u013A", "\u013E") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + .setMoreKeysOf("z", "\u017E", "\u017C", "\u017A") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u00E7", "\u0107") + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u0146", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFinnish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFinnish.java new file mode 100644 index 000000000..ff32da117 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFinnish.java @@ -0,0 +1,81 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Nordic; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * fi: Finnish/nordic + */ +@SmallTest +public final class TestsFinnish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fi"); + private static final LayoutBase LAYOUT = new Nordic(new FinnishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class FinnishCustomizer extends EuroCustomizer { + public FinnishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + .setMoreKeysOf("u", "\u00FC") + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F8", "\u00F4", "\u00F2", "\u00F3", "\u00F5", "\u0153", "\u014D") + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + .replaceKeyOfLabel(Nordic.ROW1_11, "\u00E5") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + .replaceKeyOfLabel(Nordic.ROW2_10, key("\u00F6", moreKey("\u00F8"))) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E6: "æ" LATIN SMALL LETTER AE + .replaceKeyOfLabel(Nordic.ROW2_11, key("\u00E4", moreKey("\u00E6"))) + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", "\u00E6", "\u00E0", "\u00E1", "\u00E2", "\u00E3", "\u0101") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + .setMoreKeysOf("z", "\u017E", "\u017A", "\u017C"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrench.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrench.java new file mode 100644 index 000000000..7ced1fb7b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrench.java @@ -0,0 +1,55 @@ +/* + * 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.Azerty; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * fr: French/azerty + */ +@SmallTest +public final class TestsFrench extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fr"); + private static final LayoutBase LAYOUT = new Azerty(new FrenchEuroCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + static final class FrenchEuroCustomizer extends FrenchCustomizer { + private final EuroCustomizer mEuroCustomizer; + + public FrenchEuroCustomizer(final Locale locale) { + super(locale); + mEuroCustomizer = new EuroCustomizer(locale); + } + + @Override + public final ExpectedKey getCurrencyKey() { return mEuroCustomizer.getCurrencyKey(); } + + @Override + public final ExpectedKey[] getOtherCurrencyKeys() { + return mEuroCustomizer.getOtherCurrencyKeys(); + } + } +}
\ No newline at end of file diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCA.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCA.java new file mode 100644 index 000000000..9b3cd1ee2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCA.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * fr_CA: French (Canada)/qwerty + */ +@SmallTest +public final class TestsFrenchCA extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fr", "CA"); + private static final LayoutBase LAYOUT = new Qwerty(new FrenchCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCH.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCH.java new file mode 100644 index 000000000..2598aa3bf --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCH.java @@ -0,0 +1,56 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Swiss; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * fr_CH: French (Switzerland)/swiss + */ +@SmallTest +public final class TestsFrenchCH extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fr", "CH"); + private static final LayoutBase LAYOUT = new Swiss(new FrenchCHCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class FrenchCHCustomizer extends FrenchCustomizer { + public FrenchCHCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + super.setAccentedLetters(builder); + return builder + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + .replaceKeyOfLabel(Swiss.ROW1_11, key("\u00E8", moreKey("\u00FC"))) + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + .replaceKeyOfLabel(Swiss.ROW2_10, key("\u00E9", moreKey("\u00F6"))) + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + .replaceKeyOfLabel(Swiss.ROW2_11, key("\u00E0", moreKey("\u00E4"))); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchDvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchDvorak.java new file mode 100644 index 000000000..33d1445a4 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchDvorak.java @@ -0,0 +1,62 @@ +/* + * 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.Dvorak; +import com.android.inputmethod.keyboard.layout.Dvorak.DvorakCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; +import com.android.inputmethod.keyboard.layout.tests.TestsFrench.FrenchEuroCustomizer; + +import java.util.Locale; + +/** + * fr: French/dvorak + */ +@SmallTest +public final class TestsFrenchDvorak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fr"); + private static final LayoutBase LAYOUT = new Dvorak(new FrenchDvorakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class FrenchDvorakCustomizer extends DvorakCustomizer { + private final FrenchEuroCustomizer mFrenchEuroCustomizer; + + public FrenchDvorakCustomizer(final Locale locale) { + super(locale); + mFrenchEuroCustomizer = new FrenchEuroCustomizer(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return mFrenchEuroCustomizer.getCurrencyKey(); } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return mFrenchEuroCustomizer.getOtherCurrencyKeys(); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mFrenchEuroCustomizer.setAccentedLetters(builder); + } + } +}
\ No newline at end of file diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchQwertz.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchQwertz.java new file mode 100644 index 000000000..6ab28704a --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchQwertz.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwertz; +import com.android.inputmethod.keyboard.layout.tests.TestsFrench.FrenchEuroCustomizer; + +import java.util.Locale; + +/** + * fr: French/qwertz + */ +@SmallTest +public final class TestsFrenchQwertz extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fr"); + private static final LayoutBase LAYOUT = new Qwertz(new FrenchEuroCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGalicianES.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGalicianES.java new file mode 100644 index 000000000..1472828a4 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGalicianES.java @@ -0,0 +1,52 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * gl_ES: Galician (Spain)/spanish + */ +@SmallTest +public class TestsGalicianES extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("gl", "ES"); + private static final LayoutBase LAYOUT = new Spanish(new GalicianESCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class GalicianESCustomizer extends EuroCustomizer { + private final SpanishCustomizer mSpanishCustomizer; + + public GalicianESCustomizer(final Locale locale) { + super(locale); + mSpanishCustomizer = new SpanishCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mSpanishCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGeorgianGE.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGeorgianGE.java new file mode 100644 index 000000000..f25942fb5 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGeorgianGE.java @@ -0,0 +1,37 @@ +/* + * 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.Georgian; +import com.android.inputmethod.keyboard.layout.Georgian.GeorgianCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * ka_GE: Georgian (Georgia)/georgian + */ +@SmallTest +public final class TestsGeorgianGE extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ka", "GE"); + private static final LayoutBase LAYOUT = new Georgian(new GeorgianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGerman.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGerman.java new file mode 100644 index 000000000..6f7571197 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGerman.java @@ -0,0 +1,55 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwertz; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * de: German/qwertz + */ +@SmallTest +public final class TestsGerman extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("de"); + private static final LayoutBase LAYOUT = new Qwertz(new GermanEuroCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + static class GermanEuroCustomizer extends GermanCustomizer { + final EuroCustomizer mEuroCustomizer; + + public GermanEuroCustomizer(final Locale locale) { + super(locale); + mEuroCustomizer = new EuroCustomizer(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return mEuroCustomizer.getCurrencyKey(); } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return mEuroCustomizer.getOtherCurrencyKeys(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanCH.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanCH.java new file mode 100644 index 000000000..7deb00bb4 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanCH.java @@ -0,0 +1,56 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Swiss; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * de_CH: German (Switzerland)/swiss + */ +@SmallTest +public final class TestsGermanCH extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("de", "CH"); + private static final LayoutBase LAYOUT = new Swiss(new GermanCHCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class GermanCHCustomizer extends GermanCustomizer { + public GermanCHCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + super.setAccentedLetters(builder); + return builder + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + .replaceKeyOfLabel(Swiss.ROW1_11, key("\u00FC", moreKey("\u00E8"))) + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + .replaceKeyOfLabel(Swiss.ROW2_10, key("\u00F6", moreKey("\u00E9"))) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + .replaceKeyOfLabel(Swiss.ROW2_11, key("\u00E4", moreKey("\u00E0"))); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanDvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanDvorak.java new file mode 100644 index 000000000..b28d5cfcf --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanDvorak.java @@ -0,0 +1,75 @@ +/* + * 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.Dvorak; +import com.android.inputmethod.keyboard.layout.Dvorak.DvorakCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * de: German/dvorak + */ +@SmallTest +public final class TestsGermanDvorak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("de"); + private static final LayoutBase LAYOUT = new Dvorak(new GermanDvorakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + static class GermanDvorakCustomizer extends DvorakCustomizer { + final GermanCustomizer mGermanCustomizer; + + public GermanDvorakCustomizer(final Locale locale) { + super(locale); + mGermanCustomizer = new GermanCustomizer(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_EURO; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_THAN_EURO; + } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mGermanCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanQwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanQwerty.java new file mode 100644 index 000000000..19ae5a3f5 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanQwerty.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.tests.TestsGerman.GermanEuroCustomizer; + +import java.util.Locale; + +/** + * de: German/qwerty + */ +@SmallTest +public final class TestsGermanQwerty extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("de"); + private static final LayoutBase LAYOUT = new Qwerty(new GermanEuroCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGreek.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGreek.java new file mode 100644 index 000000000..4acb119ac --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGreek.java @@ -0,0 +1,37 @@ +/* + * 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.Greek; +import com.android.inputmethod.keyboard.layout.Greek.GreekCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * el: Greek/greek + */ +@SmallTest +public class TestsGreek extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("el"); + private static final LayoutBase LAYOUT = new Greek(new GreekCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java new file mode 100644 index 000000000..c0243a870 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHebrew.java @@ -0,0 +1,37 @@ +/* + * 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.Hebrew; +import com.android.inputmethod.keyboard.layout.Hebrew.HebrewCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * iw: Hebrew/hebrew + */ +@SmallTest +public class TestsHebrew extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("iw"); + private static final LayoutBase LAYOUT = new Hebrew(new HebrewCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindi.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindi.java new file mode 100644 index 000000000..84053b5be --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindi.java @@ -0,0 +1,37 @@ +/* + * 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.Hindi; +import com.android.inputmethod.keyboard.layout.Hindi.HindiCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * hi: Hindi/hindi + */ +@SmallTest +public final class TestsHindi extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("hi"); + private static final LayoutBase LAYOUT = new Hindi(new HindiCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java new file mode 100644 index 000000000..2e676df26 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java @@ -0,0 +1,37 @@ +/* + * 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.HindiCompact; +import com.android.inputmethod.keyboard.layout.HindiCompact.HindiCompactCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * hi: Hindi/hindi_compact + */ +@SmallTest +public final class TestsHindiCompact extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("hi"); + private static final LayoutBase LAYOUT = new HindiCompact(new HindiCompactCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHungarian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHungarian.java new file mode 100644 index 000000000..efc95dcf9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHungarian.java @@ -0,0 +1,107 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwertz; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * hu: Hungarian/qwertz + */ +@SmallTest +public final class TestsHungarian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("hu"); + private static final LayoutBase LAYOUT = new Qwertz(new HungarianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class HungarianCustomizer extends LayoutCustomizer { + public HungarianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", "\u0113") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u0171", "\u00FB", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EE", "\u00EF", "\u00EC", "\u012F", "\u012B") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u0151", "\u00F4", "\u00F2", "\u00F5", "\u0153", + "\u00F8", "\u014D") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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", + "\u00E1", "\u00E0", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5", + "\u0101"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIcelandic.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIcelandic.java new file mode 100644 index 000000000..62b111e6a --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIcelandic.java @@ -0,0 +1,106 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * is: Icelandic/qwerty + */ +@SmallTest +public final class TestsIcelandic extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("is"); + private static final LayoutBase LAYOUT = new Qwerty(new IcelandicCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class IcelandicCustomizer extends LayoutCustomizer { + public IcelandicCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00EB", "\u00E8", "\u00EA", "\u0119", "\u0117", "\u0113") + // U+00FE: "þ" LATIN SMALL LETTER THORN + .setMoreKeysOf("t", "\u00FE") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FA", "\u00FC", "\u00FB", "\u00F9", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00ED", "\u00EF", "\u00EE", "\u00EC", "\u012F", "\u012B") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", + "\u00E1", "\u00E4", "\u00E6", "\u00E5", "\u00E0", "\u00E2", "\u00E3", + "\u0101") + // U+00F0: "ð" LATIN SMALL LETTER ETH + .setMoreKeysOf("d", "\u00F0"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIndonesian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIndonesian.java new file mode 100644 index 000000000..9b23bfe2b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIndonesian.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * in: Indonesian/qwerty # "id" is the official language code of Indonesian. + */ +@SmallTest +public final class TestsIndonesian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("in"); + private static final LayoutBase LAYOUT = new Qwerty(new LayoutCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalian.java new file mode 100644 index 000000000..f3c610c8b --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalian.java @@ -0,0 +1,52 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * it: Italian/qwerty + */ +@SmallTest +public final class TestsItalian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("it"); + private static final LayoutBase LAYOUT = new Qwerty(new ItalianITCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class ItalianITCustomizer extends EuroCustomizer { + private final ItalianCustomizer mItalianCustomizer; + + public ItalianITCustomizer(final Locale locale) { + super(locale); + mItalianCustomizer = new ItalianCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mItalianCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalianCH.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalianCH.java new file mode 100644 index 000000000..d32f9e957 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalianCH.java @@ -0,0 +1,56 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Swiss; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * it_CH: Italian (Switzerland)/swiss + */ +@SmallTest +public final class TestsItalianCH extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("it", "CH"); + private static final LayoutBase LAYOUT = new Swiss(new ItalianCHCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class ItalianCHCustomizer extends ItalianCustomizer { + public ItalianCHCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + super.setAccentedLetters(builder); + return builder + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + .replaceKeyOfLabel(Swiss.ROW1_11, key("\u00FC", moreKey("\u00E8"))) + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + .replaceKeyOfLabel(Swiss.ROW2_10, key("\u00F6", moreKey("\u00E9"))) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + .replaceKeyOfLabel(Swiss.ROW2_11, key("\u00E4", moreKey("\u00E0"))); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKazakh.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKazakh.java new file mode 100644 index 000000000..d255a0fa9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKazakh.java @@ -0,0 +1,82 @@ +/* + * 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.EastSlavic; +import com.android.inputmethod.keyboard.layout.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * kk: Kazakh/east_slavic + */ +@SmallTest +public final class TestsKazakh extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("kk"); + private static final LayoutBase LAYOUT = new EastSlavic(new KazakhCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class KazakhCustomizer extends EastSlavicCustomizer { + public KazakhCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0443: "у" CYRILLIC SMALL LETTER U + // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U + // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE + .setMoreKeysOf("\u0443", "\u04AF", "\u04B1") + // U+043A: "к" CYRILLIC SMALL LETTER KA + // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER + .setMoreKeysOf("\u043A", "\u049B") + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0451: "ё" CYRILLIC SMALL LETTER IO + .setMoreKeysOf("\u0435", "\u0451") + // U+043D: "н" CYRILLIC SMALL LETTER EN + // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER + .setMoreKeysOf("\u043D", "\u04A3") + // U+0433: "г" CYRILLIC SMALL LETTER GHE + // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE + .setMoreKeysOf("\u0433", "\u0493") + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + .replaceKeyOfLabel(EastSlavic.ROW1_9, key("\u0449", additionalMoreKey("9"))) + // U+044B: "ы" CYRILLIC SMALL LETTER YERU + // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + .replaceKeyOfLabel(EastSlavic.ROW2_2, key("\u044B", moreKey("\u0456"))) + // U+0430: "а" CYRILLIC SMALL LETTER A + // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA + .setMoreKeysOf("\u0430", "\u04D9") + // U+043E: "о" CYRILLIC SMALL LETTER O + // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O + .setMoreKeysOf("\u043E", "\u04E9") + // U+044D: "э" CYRILLIC SMALL LETTER E + // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA + .replaceKeyOfLabel(EastSlavic.ROW2_11, key("\u044D", moreKey("\u04BB"))) + // U+0438: "и" CYRILLIC SMALL LETTER I + .replaceKeyOfLabel(EastSlavic.ROW3_5, "\u0438") + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + .setMoreKeysOf("\u044C", "\u044A"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKhmerKH.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKhmerKH.java new file mode 100644 index 000000000..df2f40d86 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKhmerKH.java @@ -0,0 +1,37 @@ +/* + * 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.Khmer; +import com.android.inputmethod.keyboard.layout.Khmer.KhmerCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * km_KH: Khmer (Cambodia)/khmer + */ +@SmallTest +public final class TestsKhmerKH extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("km", "KH"); + private static final LayoutBase LAYOUT = new Khmer(new KhmerCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKyrgyz.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKyrgyz.java new file mode 100644 index 000000000..9797b4ba9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsKyrgyz.java @@ -0,0 +1,70 @@ +/* + * 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.EastSlavic; +import com.android.inputmethod.keyboard.layout.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * ky: Kyrgyz/east_slavic + */ +@SmallTest +public final class TestsKyrgyz extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ky"); + private static final LayoutBase LAYOUT = new EastSlavic(new KyrgyzCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class KyrgyzCustomizer extends EastSlavicCustomizer { + public KyrgyzCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0443: "у" CYRILLIC SMALL LETTER U + // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U + .setMoreKeysOf("\u0443", "\u04AF") + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0451: "ё" CYRILLIC SMALL LETTER IO + .setMoreKeysOf("\u0435", "\u0451") + // U+043D: "н" CYRILLIC SMALL LETTER EN + // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER + .setMoreKeysOf("\u043D", "\u04A3") + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + .replaceKeyOfLabel(EastSlavic.ROW1_9, key("\u0449", additionalMoreKey("9"))) + // U+044B: "ы" CYRILLIC SMALL LETTER YERU + .replaceKeyOfLabel(EastSlavic.ROW2_2, "\u044B") + // U+043E: "о" CYRILLIC SMALL LETTER O + // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O + .setMoreKeysOf("\u043E", "\u04E9") + // U+044D: "э" CYRILLIC SMALL LETTER E + .replaceKeyOfLabel(EastSlavic.ROW2_11, "\u044D") + // U+0438: "и" CYRILLIC SMALL LETTER I + .replaceKeyOfLabel(EastSlavic.ROW3_5, "\u0438") + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + .setMoreKeysOf("\u044C", "\u044A"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLaoLA.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLaoLA.java new file mode 100644 index 000000000..34ad1fb7f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLaoLA.java @@ -0,0 +1,37 @@ +/* + * 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.Lao; +import com.android.inputmethod.keyboard.layout.Lao.LaoCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * lo_LA: Lao (Laos)/lao + */ +@SmallTest +public final class TestsLaoLA extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("lo", "LA"); + private static final LayoutBase LAYOUT = new Lao(new LaoCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLatvian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLatvian.java new file mode 100644 index 000000000..dc1736c6d --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLatvian.java @@ -0,0 +1,148 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * lv: Latvian/qwerty + */ +@SmallTest +public final class TestsLatvian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("lv"); + private static final LayoutBase LAYOUT = new Qwerty(new LatvianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class LatvianCustomizer extends LayoutCustomizer { + public LatvianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // 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+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + .setMoreKeysOf("e", + "\u0113", "\u0117", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0119", + "\u011B") + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + .setMoreKeysOf("r", "\u0157", "\u0159", "\u0155") + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + .setMoreKeysOf("t", "\u0163", "\u0165") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + .setMoreKeysOf("u", + "\u016B", "\u0173", "\u00F9", "\u00FA", "\u00FB", "\u00FC", "\u016F", + "\u0171") + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + .setMoreKeysOf("i", + "\u012B", "\u012F", "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u0131") + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + .setMoreKeysOf("o", + "\u00F2", "\u00F3", "\u00F4", "\u00F5", "\u00F6", "\u0153", "\u0151", + "\u00F8") + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + .setMoreKeysOf("a", + "\u0101", "\u00E0", "\u00E1", "\u00E2", "\u00E3", "\u00E4", "\u00E5", + "\u00E6", "\u0105") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B", "\u015F") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u010F") + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u0123", "\u011F") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + .setMoreKeysOf("k", "\u0137") + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + .setMoreKeysOf("l", "\u013C", "\u0142", "\u013A", "\u013E") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + .setMoreKeysOf("z", "\u017E", "\u017C", "\u017A") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u00E7", "\u0107") + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u0146", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLithuanian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLithuanian.java new file mode 100644 index 000000000..55ac37a37 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLithuanian.java @@ -0,0 +1,149 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * lt: Lithuanian/qwerty + */ +@SmallTest +public final class TestsLithuanian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("lt"); + private static final LayoutBase LAYOUT = new Qwerty(new LithuanianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class LithuanianCustomizer extends LayoutCustomizer { + public LithuanianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + // 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+011B: "ě" LATIN SMALL LETTER E WITH CARON + .setMoreKeysOf("e", + "\u0117", "\u0119", "\u0113", "\u00E8", "\u00E9", "\u00EA", "\u00EB", + "\u011B") + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + .setMoreKeysOf("r", "\u0157", "\u0159", "\u0155") + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + .setMoreKeysOf("t", "\u0163", "\u0165") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + .setMoreKeysOf("u", + "\u016B", "\u0173", "\u00FC", "\u016B", "\u00F9", "\u00FA", "\u00FB", + "\u016F", "\u0171") + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + .setMoreKeysOf("i", + "\u012F", "\u012B", "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u0131") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + .setMoreKeysOf("o", + "\u00F6", "\u00F5", "\u00F2", "\u00F3", "\u00F4", "\u0153", "\u0151", + "\u00F8") + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + .setMoreKeysOf("a", + "\u0105", "\u00E4", "\u0101", "\u00E0", "\u00E1", "\u00E2", "\u00E3", + "\u00E5", "\u00E6") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B", "\u015F") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u010F") + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u0123", "\u011F") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + .setMoreKeysOf("k", "\u0137") + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + .setMoreKeysOf("l", "\u013C", "\u0142", "\u013A", "\u013E") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + .setMoreKeysOf("z", "\u017E", "\u017C", "\u017A") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u00E7", "\u0107") + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u0146", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMacedonian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMacedonian.java new file mode 100644 index 000000000..1d7d85650 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMacedonian.java @@ -0,0 +1,69 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.SouthSlavic; +import com.android.inputmethod.keyboard.layout.SouthSlavic.SouthSlavicLayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * mk: Macedonian/south_slavic + */ +@SmallTest +public final class TestsMacedonian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("mk"); + private static final LayoutBase LAYOUT = new SouthSlavic(new MacedonianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class MacedonianCustomizer extends SouthSlavicLayoutCustomizer { + public MacedonianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE + .setMoreKeysOf("\u0435", "\u0450") + // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE + .replaceKeyOfLabel(SouthSlavic.ROW1_6, key("\u0455", additionalMoreKey("6"))) + // U+0438: "и" CYRILLIC SMALL LETTER I + // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE + .setMoreKeysOf("\u0438", "\u045D") + // U+045C: "ќ" CYRILLIC SMALL LETTER KJE + .replaceKeyOfLabel(SouthSlavic.ROW2_11, "\u045C") + // U+0437: "з" CYRILLIC SMALL LETTER ZE + .replaceKeyOfLabel(SouthSlavic.ROW3_1, "\u0437") + // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE + .replaceKeyOfLabel(SouthSlavic.ROW3_8, "\u0453"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMalayMY.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMalayMY.java new file mode 100644 index 000000000..9792af9d0 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMalayMY.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * ms_MY: Malay (Malaysia)/qwerty + */ +@SmallTest +public final class TestsMalayMY extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ms", "MY"); + private static final LayoutBase LAYOUT = new Qwerty(new LayoutCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java new file mode 100644 index 000000000..b937629b0 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Marathi; +import com.android.inputmethod.keyboard.layout.Marathi.MarathiCustomizer; + +import java.util.Locale; + +/** + * mr_IN: Marathi (India)/marathi + */ +@SmallTest +public final class TestsMarathiIN extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("mr", "IN"); + private static final LayoutBase LAYOUT = new Marathi(new MarathiCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMongolianMN.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMongolianMN.java new file mode 100644 index 000000000..e28e962f9 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMongolianMN.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Mongolian; +import com.android.inputmethod.keyboard.layout.Mongolian.MongolianMNCustomizer; + +import java.util.Locale; + +/** + * mn_MN: Mongolian (Mongolia)/mongolian + */ +@SmallTest +public final class TestsMongolianMN extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("mn", "MN"); + private static final LayoutBase LAYOUT = new Mongolian(new MongolianMNCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java new file mode 100644 index 000000000..e6d3b3b92 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Myanmar; +import com.android.inputmethod.keyboard.layout.Myanmar.MyanmarCustomizer; + +import java.util.Locale; + +/** + * my_MM: Myanmar (Myanmar)/myanmar + */ +@SmallTest +public final class TestsMyanmarMM extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("my", "MM"); + private static final LayoutBase LAYOUT = new Myanmar(new MyanmarCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliRomanized.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliRomanized.java new file mode 100644 index 000000000..971976aec --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliRomanized.java @@ -0,0 +1,38 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.NepaliRomanized; +import com.android.inputmethod.keyboard.layout.NepaliRomanized.NepaliRomanizedCustomizer; + +import java.util.Locale; + +/** + * ne_NP: Nepali (Nepal) Romanized/nepali_romanized + */ +@SmallTest +public final class TestsNepaliRomanized extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ne", "NP"); + private static final LayoutBase LAYOUT = new NepaliRomanized( + new NepaliRomanizedCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliTraditional.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliTraditional.java new file mode 100644 index 000000000..724c4304f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNepaliTraditional.java @@ -0,0 +1,38 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.NepaliTraditional; +import com.android.inputmethod.keyboard.layout.NepaliTraditional.NepaliTraditionalCustomizer; + +import java.util.Locale; + +/** + * ne_NP: Nepali (Nepal) Traditional/nepali_traditional + */ +@SmallTest +public final class TestsNepaliTraditional extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ne", "NP"); + private static final LayoutBase LAYOUT = new NepaliTraditional( + new NepaliTraditionalCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguage.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguage.java new file mode 100644 index 000000000..3ed63153a --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguage.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * zz: Alphabet/qwerty + */ +@SmallTest +public final class TestsNoLanguage extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("zz"); + private static final LayoutBase LAYOUT = new Qwerty(new NoLanguageCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageColemak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageColemak.java new file mode 100644 index 000000000..8d627e3b4 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageColemak.java @@ -0,0 +1,52 @@ +/* + * 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.Colemak; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * zz: Alphabet/colemak + */ +@SmallTest +public final class TestsNoLanguageColemak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("zz"); + private static final LayoutBase LAYOUT = new Colemak(new NoLanguageColemakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class NoLanguageColemakCustomizer extends LayoutCustomizer { + private final NoLanguageCustomizer mNoLanguageCustomizer; + + public NoLanguageColemakCustomizer(final Locale locale) { + super(locale); + mNoLanguageCustomizer = new NoLanguageCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mNoLanguageCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageDvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageDvorak.java new file mode 100644 index 000000000..9bf47ed42 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguageDvorak.java @@ -0,0 +1,52 @@ +/* + * 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.Dvorak; +import com.android.inputmethod.keyboard.layout.Dvorak.DvorakCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * zz: Alphabet/dvorak + */ +@SmallTest +public final class TestsNoLanguageDvorak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("zz"); + private static final LayoutBase LAYOUT = new Dvorak(new NoLanguageDvorakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class NoLanguageDvorakCustomizer extends DvorakCustomizer { + private final NoLanguageCustomizer mNoLanguageCustomizer; + + public NoLanguageDvorakCustomizer(final Locale locale) { + super(locale); + mNoLanguageCustomizer = new NoLanguageCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mNoLanguageCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguagePcQwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguagePcQwerty.java new file mode 100644 index 000000000..cd8d43ca8 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguagePcQwerty.java @@ -0,0 +1,52 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.PcQwerty; +import com.android.inputmethod.keyboard.layout.PcQwerty.PcQwertyCustomizer; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * zz: Alphabet/pcqwerty + */ +@SmallTest +public final class TestsNoLanguagePcQwerty extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("zz"); + private static final LayoutBase LAYOUT = new PcQwerty(new NoLanguagePcQwertyCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class NoLanguagePcQwertyCustomizer extends PcQwertyCustomizer { + private final NoLanguageCustomizer mNoLanguageCustomizer; + + public NoLanguagePcQwertyCustomizer(final Locale locale) { + super(locale); + mNoLanguageCustomizer = new NoLanguageCustomizer(locale); + } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return mNoLanguageCustomizer.setAccentedLetters(builder); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNorwegian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNorwegian.java new file mode 100644 index 000000000..5d220dfa1 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNorwegian.java @@ -0,0 +1,94 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Nordic; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * nb: Norwegian Bokmål/nordic + */ +@SmallTest +public final class TestsNorwegian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("nb"); + private static final LayoutBase LAYOUT = new Nordic(new NorwegianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class NorwegianCustomizer extends LayoutCustomizer { + public NorwegianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0119", "\u0117", "\u0113") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // 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", "\u00FC", "\u00FB", "\u00F9", "\u00FA", "\u016B") + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F4", "\u00F2", "\u00F3", "\u00F6", "\u00F5", "\u0153", "\u014D") + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + .replaceKeyOfLabel(Nordic.ROW1_11, "\u00E5") + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW2_10, key("\u00F8", moreKey("\u00F6"))) + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + .replaceKeyOfLabel(Nordic.ROW2_11, key("\u00E6", moreKey("\u00E4"))) + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", "\u00E0", "\u00E4", "\u00E1", "\u00E2", "\u00E3", "\u0101"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPersian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPersian.java new file mode 100644 index 000000000..b7d75c9f0 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPersian.java @@ -0,0 +1,37 @@ +/* + * 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.Farsi; +import com.android.inputmethod.keyboard.layout.Farsi.FarsiCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; + +import java.util.Locale; + +/** + * fa: Persian/farsi + */ +@SmallTest +public class TestsPersian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("fa"); + private static final LayoutBase LAYOUT = new Farsi(new FarsiCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPolish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPolish.java new file mode 100644 index 000000000..04f88c3fc --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPolish.java @@ -0,0 +1,104 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * pl: Polish/qwerty + */ +@SmallTest +public final class TestsPolish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("pl"); + private static final LayoutBase LAYOUT = new Qwerty(new PolishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class PolishCustomizer extends LayoutCustomizer { + public PolishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + // 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+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + .setMoreKeysOf("e", + "\u0119", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0117", "\u0113") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F3", "\u00F6", "\u00F4", "\u00F2", "\u00F5", "\u0153", "\u00F8", + "\u014D") + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // 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", + "\u0105", "\u00E1", "\u00E0", "\u00E2", "\u00E4", "\u00E6", "\u00E3", + "\u00E5", "\u0101") + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u015B", "\u00DF", "\u0161") + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u0142") + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + .setMoreKeysOf("z", "\u017C", "\u017A", "\u017E") + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u0107", "\u00E7", "\u010D") + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + .setMoreKeysOf("n", "\u0144", "\u00F1"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortugueseBR.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortugueseBR.java new file mode 100644 index 000000000..8a984a765 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortugueseBR.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * pt_BR: Portuguese (Brazil)/qwerty + */ +@SmallTest +public class TestsPortugueseBR extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("pt", "BR"); + private static final LayoutBase LAYOUT = new Qwerty(new PortugueseCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortuguesePT.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortuguesePT.java new file mode 100644 index 000000000..e15e811db --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortuguesePT.java @@ -0,0 +1,55 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * pt_PT: Portuguese (Portugal)/qwerty + */ +@SmallTest +public final class TestsPortuguesePT extends TestsPortugueseBR { + private static final Locale LOCALE = new Locale("pt", "PT"); + private static final LayoutBase LAYOUT = new Qwerty(new PortuguesePTCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class PortuguesePTCustomizer extends PortugueseCustomizer { + private final EuroCustomizer mEuroCustomizer; + + public PortuguesePTCustomizer(final Locale locale) { + super(locale); + mEuroCustomizer = new EuroCustomizer(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return mEuroCustomizer.getCurrencyKey(); } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return mEuroCustomizer.getOtherCurrencyKeys(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRomanian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRomanian.java new file mode 100644 index 000000000..0207f1c22 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRomanian.java @@ -0,0 +1,81 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * ro: Romanian/qwerty + */ +@SmallTest +public final class TestsRomanian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ro"); + private static final LayoutBase LAYOUT = new Qwerty(new RomanianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class RomanianCustomizer extends LayoutCustomizer { + public RomanianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_L9R; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_L9R; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW + .setMoreKeysOf("t", "\u021B") + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", "\u00EE", "\u00EF", "\u00EC", "\u00ED", "\u012F", "\u012B") + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + .setMoreKeysOf("a", + "\u00E2", "\u00E3", "\u0103", "\u00E0", "\u00E1", "\u00E4", "\u00E6", + "\u00E5", "\u0101") + // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u0219", "\u00DF", "\u015B", "\u0161"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRussian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRussian.java new file mode 100644 index 000000000..9919207ed --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRussian.java @@ -0,0 +1,69 @@ +/* + * 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.EastSlavic; +import com.android.inputmethod.keyboard.layout.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * ru: Russian/east_slavic + */ +@SmallTest +public final class TestsRussian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("ru"); + private static final LayoutBase LAYOUT = new EastSlavic(new RussianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class RussianCustomizer extends EastSlavicCustomizer { + public RussianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0451: "ё" CYRILLIC SMALL LETTER IO + .setMoreKeysOf("\u0435", "\u0451") + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + .replaceKeyOfLabel(EastSlavic.ROW1_9, key("\u0449", additionalMoreKey("9"))) + // U+044B: "ы" CYRILLIC SMALL LETTER YERU + .replaceKeyOfLabel(EastSlavic.ROW2_2, "\u044B") + // U+044D: "э" CYRILLIC SMALL LETTER E + .replaceKeyOfLabel(EastSlavic.ROW2_11, "\u044D") + // U+0438: "и" CYRILLIC SMALL LETTER I + .replaceKeyOfLabel(EastSlavic.ROW3_5, "\u0438") + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + .setMoreKeysOf("\u044C", "\u044A"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSerbian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSerbian.java new file mode 100644 index 000000000..41f1690f3 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSerbian.java @@ -0,0 +1,75 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.SouthSlavic; +import com.android.inputmethod.keyboard.layout.SouthSlavic.SouthSlavicLayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * sr: Serbian/south_slavic + */ +@SmallTest +public final class TestsSerbian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("sr"); + private static final LayoutBase LAYOUT = new SouthSlavic(new SerbianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SerbianCustomizer extends SouthSlavicLayoutCustomizer { + public SerbianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0435: "е" CYRILLIC SMALL LETTER IE + // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE + .setMoreKeysOf("\u0435", "\u0450") + // U+0437: "з" CYRILLIC SMALL LETTER ZE + .replaceKeyOfLabel(SouthSlavic.ROW1_6, key("\u0437", additionalMoreKey("6"))) + // U+0438: "и" CYRILLIC SMALL LETTER I + // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE + .setMoreKeysOf("\u0438", "\u045D") + // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE + .replaceKeyOfLabel(SouthSlavic.ROW2_11, "\u045B") + // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE + .replaceKeyOfLabel(SouthSlavic.ROW3_1, "\u0455") + // U+0452: "ђ" CYRILLIC SMALL LETTER DJE + .replaceKeyOfLabel(SouthSlavic.ROW3_8, "\u0452"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSinhalaLK.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSinhalaLK.java new file mode 100644 index 000000000..1cea49760 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSinhalaLK.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Sinhala; +import com.android.inputmethod.keyboard.layout.Sinhala.SinhalaCustomizer; + +import java.util.Locale; + +/** + * si_LK: Sinhala (Sri Lanka)/sinhala + */ +@SmallTest +public final class TestsSinhalaLK extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("si", "LK"); + private static final LayoutBase LAYOUT = new Sinhala(new SinhalaCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovak.java new file mode 100644 index 000000000..bdaf0cad1 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovak.java @@ -0,0 +1,155 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * sk: Slovak/qwerty + */ +@SmallTest +public final class TestsSlovak extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("sk"); + private static final LayoutBase LAYOUT = new Qwerty(new SlovakCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SlovakCustomizer extends EuroCustomizer { + public SlovakCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+011B: "ě" LATIN SMALL LETTER E WITH CARON + // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON + // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + .setMoreKeysOf("e", + "\u00E9", "\u011B", "\u0113", "\u0117", "\u00E8", "\u00EA", "\u00EB", + "\u0119") + // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA + .setMoreKeysOf("r", "\u0155", "\u0159", "\u0157") + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA + .setMoreKeysOf("t", "\u0165", "\u0163") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE + .setMoreKeysOf("u", + "\u00FA", "\u016F", "\u00FC", "\u016B", "\u0173", "\u00F9", "\u00FB", + "\u0171") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + .setMoreKeysOf("i", + "\u00ED", "\u012B", "\u012F", "\u00EC", "\u00EE", "\u00EF", "\u0131") + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + .setMoreKeysOf("o", + "\u00F4", "\u00F3", "\u00F6", "\u00F2", "\u00F5", "\u0153", "\u0151", + "\u00F8") + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + // U+00E6: "æ" LATIN SMALL LETTER AE + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + .setMoreKeysOf("a", + "\u00E1", "\u00E4", "\u0101", "\u00E0", "\u00E2", "\u00E3", "\u00E5", + "\u00E6", "\u0105") + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + .setMoreKeysOf("s", "\u0161", "\u00DF", "\u015B", "\u015F") + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u010F") + // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u0123", "\u011F") + // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA + .setMoreKeysOf("k", "\u0137") + // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON + // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE + // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u013E", "\u013A", "\u013C", "\u0142") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + .setMoreKeysOf("z", "\u017E", "\u017C", "\u017A") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u00E7", "\u0107") + // U+0148: "ň" LATIN SMALL LETTER N WITH CARON + // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + .setMoreKeysOf("n", "\u0148", "\u0146", "\u00F1", "\u0144"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovenian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovenian.java new file mode 100644 index 000000000..cdb1beeba --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovenian.java @@ -0,0 +1,70 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * sl: Slovenian/qwerty + */ +@SmallTest +public final class TestsSlovenian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("sl"); + private static final LayoutBase LAYOUT = new Qwerty(new SlovenianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SlovenianCustomizer extends EuroCustomizer { + public SlovenianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u0161") + // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE + .setMoreKeysOf("d", "\u0111") + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + .setMoreKeysOf("z", "\u017E") + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + .setMoreKeysOf("c", "\u010D", "\u0107"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish.java new file mode 100644 index 000000000..12e8676ae --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish.java @@ -0,0 +1,55 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * es: Spanish/spanish + */ +@SmallTest +public class TestsSpanish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("es"); + private static final LayoutBase LAYOUT = new Spanish(new SpanishESCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SpanishESCustomizer extends SpanishCustomizer { + private final EuroCustomizer mEuroCustomizer; + + public SpanishESCustomizer(final Locale locale) { + super(locale); + mEuroCustomizer = new EuroCustomizer(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return mEuroCustomizer.getCurrencyKey(); } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return mEuroCustomizer.getOtherCurrencyKeys(); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java new file mode 100644 index 000000000..75aad136f --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Spanish; + +import java.util.Locale; + +/** + * es_419: Spanish (Latin America)/spanish + */ +@SmallTest +public class TestsSpanish419 extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("es", "419"); + private static final LayoutBase LAYOUT = new Spanish(new SpanishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanishUS.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanishUS.java new file mode 100644 index 000000000..c3ac0a0c0 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanishUS.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Spanish; + +import java.util.Locale; + +/** + * es_US: Spanish (United States)/spanish + */ +@SmallTest +public class TestsSpanishUS extends TestsSpanish { + private static final Locale LOCALE = new Locale("es", "US"); + private static final LayoutBase LAYOUT = new Spanish(new SpanishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwahili.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwahili.java new file mode 100644 index 000000000..13b974194 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwahili.java @@ -0,0 +1,93 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * sw: Swahili/qwerty + */ +@SmallTest +public final class TestsSwahili extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("sw"); + private static final LayoutBase LAYOUT = new Qwerty(new SwahiliCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SwahiliCustomizer extends LayoutCustomizer { + public SwahiliCustomizer(final Locale locale) { super(locale); } + + @Override + public 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", "\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", "\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", "\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", + "\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") + .setMoreKeysOf("g", "g'") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + .setMoreKeysOf("c", "\u00E7") + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + .setMoreKeysOf("n", "\u00F1"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwedish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwedish.java new file mode 100644 index 000000000..9b58914a2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwedish.java @@ -0,0 +1,124 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Nordic; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * sv: Swedish/nordic + */ +@SmallTest +public final class TestsSwedish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("sv"); + private static final LayoutBase LAYOUT = new Nordic(new SwedishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class SwedishCustomizer extends EuroCustomizer { + public SwedishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_RL; } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS + // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK + .setMoreKeysOf("e", "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0119") + // U+0159: "ř" LATIN SMALL LETTER R WITH CARON + .setMoreKeysOf("r", "\u0159") + // U+0165: "ť" LATIN SMALL LETTER T WITH CARON + // U+00FE: "þ" LATIN SMALL LETTER THORN + .setMoreKeysOf("t", "\u0165", "\u00FE") + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS + .setMoreKeysOf("y", "\u00FD", "\u00FF") + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON + .setMoreKeysOf("u", "\u00FC", "\u00FA", "\u00F9", "\u00FB", "\u016B") + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + .setMoreKeysOf("i", "\u00ED", "\u00EC", "\u00EE", "\u00EF") + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", "\u00F3", "\u00F2", "\u00F4", "\u00F5", "\u014D") + // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE + .replaceKeyOfLabel(Nordic.ROW1_11, "\u00E5") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+0153: "œ" LATIN SMALL LIGATURE OE + .replaceKeyOfLabel(Nordic.ROW2_10, + key("\u00F6", joinMoreKeys("\u00F8", "\u0153"))) + // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS + // U+00E6: "æ" LATIN SMALL LETTER AE + .replaceKeyOfLabel(Nordic.ROW2_11, key("\u00E4", moreKey("\u00E6"))) + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + .setMoreKeysOf("a", "\u00E1", "\u00E0", "\u00E2", "\u0105", "\u00E3") + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + .setMoreKeysOf("s", "\u015B", "\u0161", "\u015F", "\u00DF") + // U+00F0: "ð" LATIN SMALL LETTER ETH + // U+010F: "ď" LATIN SMALL LETTER D WITH CARON + .setMoreKeysOf("d", "\u00F0", "\u010F") + // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE + .setMoreKeysOf("l", "\u0142") + // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE + // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON + // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE + .setMoreKeysOf("z", "\u017A", "\u017E", "\u017C") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D") + // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + // U+0148: "ň" LATIN SMALL LETTER N WITH CARON + .setMoreKeysOf("n", "\u0144", "\u00F1", "\u0148"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTagalog.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTagalog.java new file mode 100644 index 000000000..38d5364e5 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTagalog.java @@ -0,0 +1,50 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Spanish; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; + +import java.util.Locale; + +/** + * tl: Tagalog/spanish + */ +@SmallTest +public class TestsTagalog extends TestsSpanish { + private static final Locale LOCALE = new Locale("tl"); + private static final LayoutBase LAYOUT = new Spanish(new TagalogCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class TagalogCustomizer extends SpanishCustomizer { + + public TagalogCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) { + return isPhone ? LayoutBase.PHONE_PUNCTUATION_MORE_KEYS + : LayoutBase.TABLET_PUNCTUATION_MORE_KEYS; + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsThai.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsThai.java new file mode 100644 index 000000000..3c8727290 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsThai.java @@ -0,0 +1,37 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Thai; +import com.android.inputmethod.keyboard.layout.Thai.ThaiCustomizer; + +import java.util.Locale; + +/** + * th: Thai/thai + */ +@SmallTest +public final class TestsThai extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("th"); + private static final LayoutBase LAYOUT = new Thai(new ThaiCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTurkish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTurkish.java new file mode 100644 index 000000000..b35f8850a --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTurkish.java @@ -0,0 +1,86 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.EuroCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * tr: Turkish/qwerty + */ +@SmallTest +public final class TestsTurkish extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("tr"); + private static final LayoutBase LAYOUT = new Qwerty(new TurkishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class TurkishCustomizer extends EuroCustomizer { + public TurkishCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS + // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX + // 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", "\u00FC", "\u00FB", "\u00F9", "\u00FA", "\u016B") + // U+0131: "ı" LATIN SMALL LETTER DOTLESS I + // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX + // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK + // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON + .setMoreKeysOf("i", + "\u0131", "\u00EE", "\u00EF", "\u00EC", "\u00ED", "\u012F", "\u012B") + // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+0153: "œ" LATIN SMALL LIGATURE OE + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE + // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON + .setMoreKeysOf("o", + "\u00F6", "\u00F4", "\u0153", "\u00F2", "\u00F3", "\u00F5", "\u00F8", + "\u014D") + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + .setMoreKeysOf("a", "\u00E2") + // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA + // U+00DF: "ß" LATIN SMALL LETTER SHARP S + // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE + // U+0161: "š" LATIN SMALL LETTER S WITH CARON + .setMoreKeysOf("s", "\u015F", "\u00DF", "\u015B", "\u0161") + // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE + .setMoreKeysOf("g", "\u011F") + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE + // U+010D: "č" LATIN SMALL LETTER C WITH CARON + .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsUkrainian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsUkrainian.java new file mode 100644 index 000000000..a6bcacc9e --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsUkrainian.java @@ -0,0 +1,83 @@ +/* + * 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.EastSlavic; +import com.android.inputmethod.keyboard.layout.EastSlavic.EastSlavicCustomizer; +import com.android.inputmethod.keyboard.layout.LayoutBase; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * uk: Ukrainian/east_slavic + */ +@SmallTest +public final class TestsUkrainian extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("uk"); + private static final LayoutBase LAYOUT = new EastSlavic(new UkrainianCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class UkrainianCustomizer extends EastSlavicCustomizer { + public UkrainianCustomizer(final Locale locale) { super(locale); } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_HRYVNIA; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + @Override + public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_R9L; } + + @Override + public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_R9L; } + + // U+20B4: "₴" HRYVNIA SIGN + private static final ExpectedKey CURRENCY_HRYVNIA = key("\u20B4", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+0433: "г" CYRILLIC SMALL LETTER GHE + // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN + .setMoreKeysOf("\u0433", "\u0491") + // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA + .replaceKeyOfLabel(EastSlavic.ROW1_9, key("\u0449", additionalMoreKey("9"))) + // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + // U+0457: "ї" CYRILLIC SMALL LETTER YI + .replaceKeyOfLabel(EastSlavic.ROW2_2, key("\u0456", moreKey("\u0457"))) + // U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE + .replaceKeyOfLabel(EastSlavic.ROW2_11, "\u0454") + // U+0438: "и" CYRILLIC SMALL LETTER I + .replaceKeyOfLabel(EastSlavic.ROW3_5, "\u0438") + // U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN + .setMoreKeysOf("\u044C", "\u044A"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsVietnamese.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsVietnamese.java new file mode 100644 index 000000000..83d86ac4d --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsVietnamese.java @@ -0,0 +1,148 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.LayoutBase.LayoutCustomizer; +import com.android.inputmethod.keyboard.layout.Qwerty; +import com.android.inputmethod.keyboard.layout.Symbols; +import com.android.inputmethod.keyboard.layout.SymbolsShifted; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKey; +import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder; + +import java.util.Locale; + +/** + * vi: Vietnamese/qwerty + */ +@SmallTest +public final class TestsVietnamese extends LayoutTestsBase { + private static final Locale LOCALE = new Locale("vi"); + private static final LayoutBase LAYOUT = new Qwerty(new VietnameseCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } + + private static class VietnameseCustomizer extends LayoutCustomizer { + public VietnameseCustomizer(final Locale locale) { + super(locale); + } + + @Override + public ExpectedKey getCurrencyKey() { return CURRENCY_DONG; } + + @Override + public ExpectedKey[] getOtherCurrencyKeys() { + return SymbolsShifted.CURRENCIES_OTHER_GENERIC; + } + + // U+20AB: "₫" DONG SIGN + private static final ExpectedKey CURRENCY_DONG = key("\u20AB", + Symbols.CURRENCY_GENERIC_MORE_KEYS); + + @Override + public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) { + return builder + // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE + // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE + // U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE + // U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE + // U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW + // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX + // U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE + // U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE + // U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE + // U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE + // U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW + .setMoreKeysOf("e", + "\u00E8", "\u00E9", "\u1EBB", "\u1EBD", "\u1EB9", "\u00EA", "\u1EC1", + "\u1EBF", "\u1EC3", "\u1EC5", "\u1EC7") + // U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE + // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE + // U+1EF7: "ỷ" LATIN SMALL LETTER Y WITH HOOK ABOVE + // U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE + // U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW + .setMoreKeysOf("y", "\u1EF3", "\u00FD", "\u1EF7", "\u1EF9", "\u1EF5") + // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE + // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE + // U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE + // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE + // U+1EE5: "ụ" LATIN SMALL LETTER U WITH DOT BELOW + // U+01B0: "ư" LATIN SMALL LETTER U WITH HORN + // U+1EEB: "ừ" LATIN SMALL LETTER U WITH HORN AND GRAVE + // U+1EE9: "ứ" LATIN SMALL LETTER U WITH HORN AND ACUTE + // U+1EED: "ử" LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE + // U+1EEF: "ữ" LATIN SMALL LETTER U WITH HORN AND TILDE + // U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW + .setMoreKeysOf("u", + "\u00F9", "\u00FA", "\u1EE7", "\u0169", "\u1EE5", "\u01B0", "\u1EEB", + "\u1EE9", "\u1EED", "\u1EEF", "\u1EF1") + // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE + // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE + // U+1EC9: "ỉ" LATIN SMALL LETTER I WITH HOOK ABOVE + // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE + // U+1ECB: "ị" LATIN SMALL LETTER I WITH DOT BELOW + .setMoreKeysOf("i", "\u00EC", "\u00ED", "\u1EC9", "\u0129", "\u1ECB") + // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE + // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE + // U+1ECF: "ỏ" LATIN SMALL LETTER O WITH HOOK ABOVE + // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE + // U+1ECD: "ọ" LATIN SMALL LETTER O WITH DOT BELOW + // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX + // U+1ED3: "ồ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE + // U+1ED1: "ố" LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE + // U+1ED5: "ổ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE + // U+1ED7: "ỗ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE + // U+1ED9: "ộ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW + // U+01A1: "ơ" LATIN SMALL LETTER O WITH HORN + // U+1EDD: "ờ" LATIN SMALL LETTER O WITH HORN AND GRAVE + // U+1EDB: "ớ" LATIN SMALL LETTER O WITH HORN AND ACUTE + // U+1EDF: "ở" LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE + // U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE + // U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW + .setMoreKeysOf("o", + "\u00F2", "\u00F3", "\u1ECF", "\u00F5", "\u1ECD", "\u00F4", "\u1ED3", + "\u1ED1", "\u1ED5", "\u1ED7", "\u1ED9", "\u01A1", "\u1EDD", "\u1EDB", + "\u1EDF", "\u1EE1", "\u1EE3") + // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE + // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE + // U+1EA3: "ả" LATIN SMALL LETTER A WITH HOOK ABOVE + // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE + // U+1EA1: "ạ" LATIN SMALL LETTER A WITH DOT BELOW + // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE + // U+1EB1: "ằ" LATIN SMALL LETTER A WITH BREVE AND GRAVE + // U+1EAF: "ắ" LATIN SMALL LETTER A WITH BREVE AND ACUTE + // U+1EB3: "ẳ" LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE + // U+1EB5: "ẵ" LATIN SMALL LETTER A WITH BREVE AND TILDE + // U+1EB7: "ặ" LATIN SMALL LETTER A WITH BREVE AND DOT BELOW + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + // U+1EA7: "ầ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE + // U+1EA5: "ấ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE + // U+1EA9: "ẩ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE + // U+1EAB: "ẫ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE + // U+1EAD: "ậ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW + .setMoreKeysOf("a", + "\u00E0", "\u00E1", "\u1EA3", "\u00E3", "\u1EA1", "\u0103", "\u1EB1", + "\u1EAF", "\u1EB3", "\u1EB5", "\u1EB7", "\u00E2", "\u1EA7", "\u1EA5", + "\u1EA9", "\u1EAB", "\u1EAD") + // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE + .setMoreKeysOf("d", "\u0111"); + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsZulu.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsZulu.java new file mode 100644 index 000000000..e048e92c2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsZulu.java @@ -0,0 +1,36 @@ +/* + * 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.LayoutBase; +import com.android.inputmethod.keyboard.layout.Qwerty; + +import java.util.Locale; + +/** + * zu: Zulu/qwerty + */ +@SmallTest +public final class TestsZulu extends TestsEnglishUS { + private static final Locale LOCALE = new Locale("zu"); + private static final LayoutBase LAYOUT = new Qwerty(new EnglishCustomizer(LOCALE)); + + @Override + LayoutBase getLayout() { return LAYOUT; } +} diff --git a/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java b/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java new file mode 100644 index 000000000..c29257d34 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/AppWorkaroundsTests.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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.latin; + +import com.android.inputmethod.latin.settings.Settings; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build.VERSION_CODES; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.inputmethod.EditorInfo; + +@LargeTest +public class AppWorkaroundsTests extends InputTestsBase { + String packageNameOfAppBeforeJellyBean; + String packageNameOfAppAfterJellyBean; + + @Override + protected void setUp() throws Exception { + // NOTE: this will fail if there is no app installed that targets an SDK + // before Jelly Bean. For the moment, it's fine. + final PackageManager pm = getContext().getPackageManager(); + for (ApplicationInfo ai : pm.getInstalledApplications(0 /* flags */)) { + if (ai.targetSdkVersion < VERSION_CODES.JELLY_BEAN) { + packageNameOfAppBeforeJellyBean = ai.packageName; + } else { + packageNameOfAppAfterJellyBean = ai.packageName; + } + } + super.setUp(); + } + + // We want to test if the app package info is correctly retrieved by LatinIME. Since it + // asks this information to the package manager from the package name, and that it takes + // the package name from the EditorInfo, all we have to do it put the correct package + // name in the editor info. + // To this end, our base class InputTestsBase offers a hook for us to touch the EditorInfo. + // We override this hook to write the package name that we need. + @Override + protected EditorInfo enrichEditorInfo(final EditorInfo ei) { + if ("testBeforeJellyBeanTrue".equals(getName())) { + ei.packageName = packageNameOfAppBeforeJellyBean; + } else if ("testBeforeJellyBeanFalse".equals(getName())) { + ei.packageName = packageNameOfAppAfterJellyBean; + } + return ei; + } + + public void testBeforeJellyBeanTrue() { + assertTrue("Couldn't successfully detect this app targets < Jelly Bean (package is " + + packageNameOfAppBeforeJellyBean + ")", + Settings.getInstance().getCurrent().isBeforeJellyBean()); + } + + public void testBeforeJellyBeanFalse() { + assertFalse("Couldn't successfully detect this app targets >= Jelly Bean (package is " + + packageNameOfAppAfterJellyBean + ")", + Settings.getInstance().getCurrent().isBeforeJellyBean()); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java index cd5384ea4..ae184268c 100644 --- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java +++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java @@ -20,8 +20,18 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Pair; +import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; +import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.CodePointUtils; +import com.android.inputmethod.latin.makedict.DictDecoder; +import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; +import com.android.inputmethod.latin.utils.FileUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.File; import java.io.IOException; @@ -30,68 +40,183 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Random; +import java.util.concurrent.TimeUnit; @LargeTest public class BinaryDictionaryDecayingTests extends AndroidTestCase { private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; private static final String TEST_LOCALE = "test"; - - // Note that these are corresponding definitions in native code in - // latinime::DynamicPatriciaTriePolicy. - private static final String SET_NEEDS_TO_DECAY_FOR_TESTING_KEY = - "SET_NEEDS_TO_DECAY_FOR_TESTING"; - private static final int DUMMY_PROBABILITY = 0; + private static final int[] DICT_FORMAT_VERSIONS = + new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV }; + + private int mCurrentTime = 0; @Override protected void setUp() throws Exception { super.setUp(); + mCurrentTime = 0; } @Override protected void tearDown() throws Exception { + stopTestModeInNativeCode(); super.tearDown(); } + private static boolean supportsBeginningOfSentence(final int formatVersion) { + return formatVersion > FormatSpec.VERSION401; + } + + private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word, + final int probability) { + binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */, + BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */, + false /* isBeginningOfSentence */, false /* isNotAWord */, + false /* isBlacklisted */, mCurrentTime /* timestamp */); + } + + private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0, + final String word1, final int probability) { + binaryDictionary.addNgramEntry(new PrevWordsInfo(new WordInfo(word0)), word1, probability, + mCurrentTime /* timestamp */); + } + + private static boolean isValidBigram(final BinaryDictionary binaryDictionary, + final String word0, final String word1) { + return binaryDictionary.isValidNgram(new PrevWordsInfo(new WordInfo(word0)), word1); + } + private void forcePassingShortTime(final BinaryDictionary binaryDictionary) { - // Entries having low probability would be suppressed once in 3 GCs. - final int count = 3; - for (int i = 0; i < count; i++) { - binaryDictionary.getPropertyForTests(SET_NEEDS_TO_DECAY_FOR_TESTING_KEY); - binaryDictionary.flushWithGC(); - } + // 30 days. + final int timeToElapse = (int)TimeUnit.SECONDS.convert(30, TimeUnit.DAYS); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + binaryDictionary.flushWithGC(); } private void forcePassingLongTime(final BinaryDictionary binaryDictionary) { - // Currently, probabilities are decayed when GC is run. All entries that have never been - // typed in 128 GCs would be removed. - final int count = 128; - for (int i = 0; i < count; i++) { - binaryDictionary.getPropertyForTests(SET_NEEDS_TO_DECAY_FOR_TESTING_KEY); - binaryDictionary.flushWithGC(); + // 365 days. + final int timeToElapse = (int)TimeUnit.SECONDS.convert(365, TimeUnit.DAYS); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + binaryDictionary.flushWithGC(); + } + + private File createEmptyDictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + if (formatVersion == FormatSpec.VERSION4 + || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING + || formatVersion == FormatSpec.VERSION4_DEV) { + return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion); + } else { + throw new IOException("Dictionary format version " + formatVersion + + " is not supported."); } } - private File createEmptyDictionaryAndGetFile(final String filename) throws IOException { - final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION, + private File createEmptyVer4DictionaryAndGetFile(final String dictId, final int formatVersion) + throws IOException { + final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION, getContext().getCacheDir()); - Map<String, String> attributeMap = new HashMap<String, String>(); - attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE, - FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE); - attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE, - FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE); - if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), - 3 /* dictVersion */, attributeMap)) { + FileUtils.deleteRecursively(file); + Map<String, String> attributeMap = new HashMap<>(); + attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId); + attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, + String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); + attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), formatVersion, + LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) { return file; } else { - throw new IOException("Empty dictionary cannot be created."); + throw new IOException("Empty dictionary " + file.getAbsolutePath() + + " cannot be created. Foramt version: " + formatVersion); + } + } + + private static int setCurrentTimeForTestMode(final int currentTime) { + return BinaryDictionaryUtils.setCurrentTimeForTest(currentTime); + } + + private static int stopTestModeInNativeCode() { + return BinaryDictionaryUtils.setCurrentTimeForTest(-1); + } + + public void testReadDictInJavaSide() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testReadDictInJavaSide(formatVersion); } } + private void testReadDictInJavaSide(final int formatVersion) { + setCurrentTimeForTestMode(mCurrentTime); + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "ab", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "aaa", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "aaa", DUMMY_PROBABILITY); + binaryDictionary.flushWithGC(); + binaryDictionary.close(); + + final DictDecoder dictDecoder = + BinaryDictIOUtils.getDictDecoder(dictFile, 0, dictFile.length()); + try { + final FusionDictionary dict = + dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */); + PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, "a"); + assertNotNull(ptNode); + assertTrue(ptNode.isTerminal()); + assertNotNull(ptNode.getBigram("aaa")); + ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, "ab"); + assertNotNull(ptNode); + assertTrue(ptNode.isTerminal()); + ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, "aaa"); + assertNotNull(ptNode); + assertTrue(ptNode.isTerminal()); + } catch (IOException e) { + fail("IOException while reading dictionary: " + e); + } catch (UnsupportedFormatException e) { + fail("Unsupported format: " + e); + } + dictFile.delete(); + } + + public void testControlCurrentTime() { + final int TEST_COUNT = 1000; + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + final int startTime = stopTestModeInNativeCode(); + for (int i = 0; i < TEST_COUNT; i++) { + final int currentTime = random.nextInt(Integer.MAX_VALUE); + final int currentTimeInNativeCode = setCurrentTimeForTestMode(currentTime); + assertEquals(currentTime, currentTimeInNativeCode); + } + final int endTime = stopTestModeInNativeCode(); + final int MAX_ALLOWED_ELAPSED_TIME = 10; + assertTrue(startTime <= endTime && endTime <= startTime + MAX_ALLOWED_ELAPSED_TIME); + } + public void testAddValidAndInvalidWords() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddValidAndInvalidWords(formatVersion); + } + } + + private void testAddValidAndInvalidWords(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -99,46 +224,44 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY); + addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY); assertFalse(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY); + addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY); + addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY); assertTrue(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY); assertTrue(binaryDictionary.isValidWord("b")); - final int unigramProbability = binaryDictionary.getFrequency("a"); - binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidBigram("a", "b")); - binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidBigram("a", "b")); - binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidBigram("a", "b")); - binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY); - assertTrue(binaryDictionary.isValidBigram("a", "b")); + addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY); + assertFalse(isValidBigram(binaryDictionary, "a", "b")); + addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "a", "b")); - binaryDictionary.addUnigramWord("c", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "c", DUMMY_PROBABILITY); - assertTrue(binaryDictionary.isValidBigram("a", "c")); + addUnigramWord(binaryDictionary, "c", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "c", DUMMY_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "a", "c")); // Add bigrams of not valid unigrams. - binaryDictionary.addBigramWords("x", "y", Dictionary.NOT_A_PROBABILITY); - assertFalse(binaryDictionary.isValidBigram("x", "y")); - binaryDictionary.addBigramWords("x", "y", DUMMY_PROBABILITY); - assertFalse(binaryDictionary.isValidBigram("x", "y")); + addBigramWords(binaryDictionary, "x", "y", Dictionary.NOT_A_PROBABILITY); + assertFalse(isValidBigram(binaryDictionary, "x", "y")); + addBigramWords(binaryDictionary, "x", "y", DUMMY_PROBABILITY); + assertFalse(isValidBigram(binaryDictionary, "x", "y")); binaryDictionary.close(); dictFile.delete(); } public void testDecayingProbability() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testDecayingProbability(formatVersion); + } + } + + private void testDecayingProbability(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -146,50 +269,53 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); assertTrue(binaryDictionary.isValidWord("a")); forcePassingShortTime(binaryDictionary); assertFalse(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + assertTrue(binaryDictionary.isValidWord("a")); forcePassingShortTime(binaryDictionary); assertTrue(binaryDictionary.isValidWord("a")); forcePassingLongTime(binaryDictionary); assertFalse(binaryDictionary.isValidWord("a")); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY); - assertTrue(binaryDictionary.isValidBigram("a", "b")); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "a", "b")); forcePassingShortTime(binaryDictionary); - assertFalse(binaryDictionary.isValidBigram("a", "b")); - - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY); - binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY); - assertTrue(binaryDictionary.isValidBigram("a", "b")); + assertFalse(isValidBigram(binaryDictionary, "a", "b")); + + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "a", "b")); forcePassingShortTime(binaryDictionary); - assertTrue(binaryDictionary.isValidBigram("a", "b")); + assertTrue(isValidBigram(binaryDictionary, "a", "b")); forcePassingLongTime(binaryDictionary); - assertFalse(binaryDictionary.isValidBigram("a", "b")); + assertFalse(isValidBigram(binaryDictionary, "a", "b")); binaryDictionary.close(); dictFile.delete(); } public void testAddManyUnigramsToDecayingDict() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddManyUnigramsToDecayingDict(formatVersion); + } + } + + private void testAddManyUnigramsToDecayingDict(final int formatVersion) { final int unigramCount = 30000; final int unigramTypedCount = 100000; final int codePointSetSize = 50; @@ -198,16 +324,17 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + setCurrentTimeForTestMode(mCurrentTime); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); - final ArrayList<String> words = new ArrayList<String>(); + final ArrayList<String> words = new ArrayList<>(); for (int i = 0; i < unigramCount; i++) { final String word = CodePointUtils.generateWord(random, codePointSet); @@ -215,32 +342,102 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { } final int maxUnigramCount = Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_UNIGRAM_COUNT_QUERY)); + binaryDictionary.getPropertyForTest(BinaryDictionary.MAX_UNIGRAM_COUNT_QUERY)); for (int i = 0; i < unigramTypedCount; i++) { final String word = words.get(random.nextInt(words.size())); - binaryDictionary.addUnigramWord(word, DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY); if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { final int unigramCountBeforeGC = - Integer.parseInt(binaryDictionary.getPropertyForTests( + Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.UNIGRAM_COUNT_QUERY)); while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { - binaryDictionary.flushWithGC(); + forcePassingShortTime(binaryDictionary); } final int unigramCountAfterGC = - Integer.parseInt(binaryDictionary.getPropertyForTests( + Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.UNIGRAM_COUNT_QUERY)); assertTrue(unigramCountBeforeGC > unigramCountAfterGC); } } - assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests( + assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.UNIGRAM_COUNT_QUERY)) > 0); - assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests( + assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.UNIGRAM_COUNT_QUERY)) <= maxUnigramCount); + forcePassingLongTime(binaryDictionary); + assertEquals(0, Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.UNIGRAM_COUNT_QUERY))); + } + + public void testOverflowUnigrams() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testOverflowUnigrams(formatVersion); + } + } + + private void testOverflowUnigrams(final int formatVersion) { + final int unigramCount = 20000; + final int eachUnigramTypedCount = 2; + final int strongUnigramTypedCount = 20; + final int weakUnigramTypedCount = 1; + final int codePointSetSize = 50; + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + setCurrentTimeForTestMode(mCurrentTime); + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + + final String strong = "strong"; + final String weak = "weak"; + for (int j = 0; j < strongUnigramTypedCount; j++) { + addUnigramWord(binaryDictionary, strong, DUMMY_PROBABILITY); + } + for (int j = 0; j < weakUnigramTypedCount; j++) { + addUnigramWord(binaryDictionary, weak, DUMMY_PROBABILITY); + } + assertTrue(binaryDictionary.isValidWord(strong)); + assertTrue(binaryDictionary.isValidWord(weak)); + + for (int i = 0; i < unigramCount; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + for (int j = 0; j < eachUnigramTypedCount; j++) { + addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY); + } + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + final int unigramCountBeforeGC = + Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.UNIGRAM_COUNT_QUERY)); + assertTrue(binaryDictionary.isValidWord(strong)); + assertTrue(binaryDictionary.isValidWord(weak)); + binaryDictionary.flushWithGC(); + final int unigramCountAfterGC = + Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.UNIGRAM_COUNT_QUERY)); + assertTrue(unigramCountBeforeGC > unigramCountAfterGC); + assertFalse(binaryDictionary.isValidWord(weak)); + assertTrue(binaryDictionary.isValidWord(strong)); + break; + } + } } public void testAddManyBigramsToDecayingDict() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddManyBigramsToDecayingDict(formatVersion); + } + } + + private void testAddManyBigramsToDecayingDict(final int formatVersion) { final int unigramCount = 5000; final int bigramCount = 30000; final int bigramTypedCount = 100000; @@ -250,17 +447,18 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + setCurrentTimeForTestMode(mCurrentTime); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); - final ArrayList<String> words = new ArrayList<String>(); - final ArrayList<Pair<String, String>> bigrams = new ArrayList<Pair<String, String>>(); + final ArrayList<String> words = new ArrayList<>(); + final ArrayList<Pair<String, String>> bigrams = new ArrayList<>(); for (int i = 0; i < unigramCount; ++i) { final String word = CodePointUtils.generateWord(random, codePointSet); @@ -274,35 +472,221 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { } final String word0 = words.get(word0Index); final String word1 = words.get(word1Index); - final Pair<String, String> bigram = new Pair<String, String>(word0, word1); + final Pair<String, String> bigram = new Pair<>(word0, word1); bigrams.add(bigram); } final int maxBigramCount = Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_BIGRAM_COUNT_QUERY)); + binaryDictionary.getPropertyForTest(BinaryDictionary.MAX_BIGRAM_COUNT_QUERY)); for (int i = 0; i < bigramTypedCount; ++i) { final Pair<String, String> bigram = bigrams.get(random.nextInt(bigrams.size())); - binaryDictionary.addUnigramWord(bigram.first, DUMMY_PROBABILITY); - binaryDictionary.addUnigramWord(bigram.second, DUMMY_PROBABILITY); - binaryDictionary.addBigramWords(bigram.first, bigram.second, DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, bigram.first, DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, bigram.second, DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, bigram.first, bigram.second, DUMMY_PROBABILITY); if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { final int bigramCountBeforeGC = - Integer.parseInt(binaryDictionary.getPropertyForTests( + Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.BIGRAM_COUNT_QUERY)); while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { - binaryDictionary.flushWithGC(); + forcePassingShortTime(binaryDictionary); } final int bigramCountAfterGC = - Integer.parseInt(binaryDictionary.getPropertyForTests( + Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.BIGRAM_COUNT_QUERY)); assertTrue(bigramCountBeforeGC > bigramCountAfterGC); } } - assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests( + assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.BIGRAM_COUNT_QUERY)) > 0); - assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests( + assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTest( BinaryDictionary.BIGRAM_COUNT_QUERY)) <= maxBigramCount); + forcePassingLongTime(binaryDictionary); + assertEquals(0, Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.BIGRAM_COUNT_QUERY))); + } + + public void testOverflowBigrams() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testOverflowBigrams(formatVersion); + } + } + + private void testOverflowBigrams(final int formatVersion) { + final int bigramCount = 20000; + final int unigramCount = 1000; + final int unigramTypedCount = 20; + final int eachBigramTypedCount = 2; + final int strongBigramTypedCount = 20; + final int weakBigramTypedCount = 1; + final int codePointSetSize = 50; + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + setCurrentTimeForTestMode(mCurrentTime); + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + + final ArrayList<String> words = new ArrayList<>(); + for (int i = 0; i < unigramCount; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + words.add(word); + for (int j = 0; j < unigramTypedCount; j++) { + addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY); + } + } + final String strong = "strong"; + final String weak = "weak"; + final String target = "target"; + for (int j = 0; j < unigramTypedCount; j++) { + addUnigramWord(binaryDictionary, strong, DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, weak, DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, target, DUMMY_PROBABILITY); + } + binaryDictionary.flushWithGC(); + for (int j = 0; j < strongBigramTypedCount; j++) { + addBigramWords(binaryDictionary, strong, target, DUMMY_PROBABILITY); + } + for (int j = 0; j < weakBigramTypedCount; j++) { + addBigramWords(binaryDictionary, weak, target, DUMMY_PROBABILITY); + } + assertTrue(isValidBigram(binaryDictionary, strong, target)); + assertTrue(isValidBigram(binaryDictionary, weak, target)); + + for (int i = 0; i < bigramCount; i++) { + final int word0Index = random.nextInt(words.size()); + final String word0 = words.get(word0Index); + final int index = random.nextInt(words.size() - 1); + final int word1Index = (index >= word0Index) ? index + 1 : index; + final String word1 = words.get(word1Index); + + for (int j = 0; j < eachBigramTypedCount; j++) { + addBigramWords(binaryDictionary, word0, word1, DUMMY_PROBABILITY); + } + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + final int bigramCountBeforeGC = + Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.BIGRAM_COUNT_QUERY)); + binaryDictionary.flushWithGC(); + final int bigramCountAfterGC = + Integer.parseInt(binaryDictionary.getPropertyForTest( + BinaryDictionary.BIGRAM_COUNT_QUERY)); + assertTrue(bigramCountBeforeGC > bigramCountAfterGC); + assertTrue(isValidBigram(binaryDictionary, strong, target)); + assertFalse(isValidBigram(binaryDictionary, weak, target)); + break; + } + } + } + + public void testDictMigration() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion); + } + } + + private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) { + setCurrentTimeForTestMode(mCurrentTime); + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", fromFormatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + addUnigramWord(binaryDictionary, "aaa", DUMMY_PROBABILITY); + assertTrue(binaryDictionary.isValidWord("aaa")); + addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY); + assertFalse(binaryDictionary.isValidWord("bbb")); + addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY); + addUnigramWord(binaryDictionary, "abc", DUMMY_PROBABILITY); + addBigramWords(binaryDictionary, "aaa", "abc", DUMMY_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "aaa", "abc")); + addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY); + assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb")); + + assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion()); + assertTrue(binaryDictionary.migrateTo(toFormatVersion)); + assertTrue(binaryDictionary.isValidDictionary()); + assertEquals(toFormatVersion, binaryDictionary.getFormatVersion()); + assertTrue(binaryDictionary.isValidWord("aaa")); + assertFalse(binaryDictionary.isValidWord("bbb")); + assertTrue(binaryDictionary.getFrequency("aaa") < binaryDictionary.getFrequency("ccc")); + addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY); + assertTrue(binaryDictionary.isValidWord("bbb")); + assertTrue(isValidBigram(binaryDictionary, "aaa", "abc")); + assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb")); + addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY); + assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb")); + binaryDictionary.close(); + dictFile.delete(); + } + + public void testBeginningOfSentence() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + if (supportsBeginningOfSentence(formatVersion)) { + testBeginningOfSentence(formatVersion); + } + } + } + + private void testBeginningOfSentence(final int formatVersion) { + setCurrentTimeForTestMode(mCurrentTime); + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + binaryDictionary.addUnigramEntry("", DUMMY_PROBABILITY, "" /* shortcutTarget */, + BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */, + true /* isBeginningOfSentence */, true /* isNotAWord */, false /* isBlacklisted */, + mCurrentTime); + final PrevWordsInfo prevWordsInfoStartOfSentence = PrevWordsInfo.BEGINNING_OF_SENTENCE; + addUnigramWord(binaryDictionary, "aaa", DUMMY_PROBABILITY); + binaryDictionary.addNgramEntry(prevWordsInfoStartOfSentence, "aaa", DUMMY_PROBABILITY, + mCurrentTime); + assertTrue(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "aaa")); + binaryDictionary.addNgramEntry(prevWordsInfoStartOfSentence, "aaa", DUMMY_PROBABILITY, + mCurrentTime); + addUnigramWord(binaryDictionary, "bbb", DUMMY_PROBABILITY); + binaryDictionary.addNgramEntry(prevWordsInfoStartOfSentence, "bbb", DUMMY_PROBABILITY, + mCurrentTime); + assertTrue(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "aaa")); + assertTrue(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "bbb")); + + forcePassingLongTime(binaryDictionary); + assertFalse(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "aaa")); + assertFalse(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "bbb")); + + addUnigramWord(binaryDictionary, "aaa", DUMMY_PROBABILITY); + binaryDictionary.addNgramEntry(prevWordsInfoStartOfSentence, "aaa", DUMMY_PROBABILITY, + mCurrentTime); + addUnigramWord(binaryDictionary, "bbb", DUMMY_PROBABILITY); + binaryDictionary.addNgramEntry(prevWordsInfoStartOfSentence, "bbb", DUMMY_PROBABILITY, + mCurrentTime); + assertTrue(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "aaa")); + assertTrue(binaryDictionary.isValidNgram(prevWordsInfoStartOfSentence, "bbb")); + binaryDictionary.close(); + dictFile.delete(); } } diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java index 5b8f0e977..6ba18d665 100644 --- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java @@ -21,8 +21,14 @@ import android.test.suitebuilder.annotation.LargeTest; import android.text.TextUtils; import android.util.Pair; +import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; import com.android.inputmethod.latin.makedict.CodePointUtils; import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.makedict.WeightedString; +import com.android.inputmethod.latin.makedict.WordProperty; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; +import com.android.inputmethod.latin.utils.FileUtils; +import com.android.inputmethod.latin.utils.LanguageModelParam; import java.io.File; import java.io.IOException; @@ -33,39 +39,60 @@ import java.util.Locale; import java.util.Map; import java.util.Random; +// TODO Use the seed passed as an argument for makedict test. @LargeTest public class BinaryDictionaryTests extends AndroidTestCase { private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; private static final String TEST_LOCALE = "test"; + private static final int[] DICT_FORMAT_VERSIONS = + new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV }; - @Override - protected void setUp() throws Exception { - super.setUp(); + private static boolean canCheckBigramProbability(final int formatVersion) { + return formatVersion > FormatSpec.VERSION401; } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + private static boolean supportsBeginningOfSentence(final int formatVersion) { + return formatVersion > FormatSpec.VERSION401; } - private File createEmptyDictionaryAndGetFile(final String filename) throws IOException { - final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION, + private File createEmptyDictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + if (formatVersion == FormatSpec.VERSION4 + || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING + || formatVersion == FormatSpec.VERSION4_DEV) { + return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion); + } else { + throw new IOException("Dictionary format version " + formatVersion + + " is not supported."); + } + } + + private File createEmptyVer4DictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION, getContext().getCacheDir()); - Map<String, String> attributeMap = new HashMap<String, String>(); - attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE, - FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE); - if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), - 3 /* dictVersion */, attributeMap)) { + file.delete(); + file.mkdir(); + Map<String, String> attributeMap = new HashMap<>(); + if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), formatVersion, + Locale.ENGLISH, attributeMap)) { return file; } else { - throw new IOException("Empty dictionary cannot be created."); + throw new IOException("Empty dictionary " + file.getAbsolutePath() + + " cannot be created. Format version: " + formatVersion); } } public void testIsValidDictionary() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testIsValidDictionary(formatVersion); + } + } + + private void testIsValidDictionary(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -77,7 +104,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { binaryDictionary.close(); assertFalse("binaryDictionary must be invalid after closing.", binaryDictionary.isValidDictionary()); - dictFile.delete(); + FileUtils.deleteRecursively(dictFile); binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); @@ -86,10 +113,126 @@ public class BinaryDictionaryTests extends AndroidTestCase { binaryDictionary.close(); } + public void testConstructingDictionaryOnMemory() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testConstructingDictionaryOnMemory(formatVersion); + } + } + + private void testConstructingDictionaryOnMemory(final int formatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + FileUtils.deleteRecursively(dictFile); + assertFalse(dictFile.exists()); + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, formatVersion, + new HashMap<String, String>()); + assertTrue(binaryDictionary.isValidDictionary()); + assertEquals(formatVersion, binaryDictionary.getFormatVersion()); + final int probability = 100; + addUnigramWord(binaryDictionary, "word", probability); + assertEquals(probability, binaryDictionary.getFrequency("word")); + assertFalse(dictFile.exists()); + binaryDictionary.flush(); + assertTrue(dictFile.exists()); + assertTrue(binaryDictionary.isValidDictionary()); + assertEquals(formatVersion, binaryDictionary.getFormatVersion()); + assertEquals(probability, binaryDictionary.getFrequency("word")); + binaryDictionary.close(); + dictFile.delete(); + } + + public void testAddTooLongWord() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddTooLongWord(formatVersion); + } + } + + private void testAddTooLongWord(final int formatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + final StringBuffer stringBuilder = new StringBuffer(); + for (int i = 0; i < Constants.DICTIONARY_MAX_WORD_LENGTH; i++) { + stringBuilder.append('a'); + } + final String validLongWord = stringBuilder.toString(); + stringBuilder.append('a'); + final String invalidLongWord = stringBuilder.toString(); + final int probability = 100; + addUnigramWord(binaryDictionary, "aaa", probability); + addUnigramWord(binaryDictionary, validLongWord, probability); + addUnigramWord(binaryDictionary, invalidLongWord, probability); + // Too long short cut. + binaryDictionary.addUnigramEntry("a", probability, invalidLongWord, + 10 /* shortcutProbability */, false /* isBeginningOfSentence */, + false /* isNotAWord */, false /* isBlacklisted */, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); + addUnigramWord(binaryDictionary, "abc", probability); + final int updatedProbability = 200; + // Update. + addUnigramWord(binaryDictionary, validLongWord, updatedProbability); + addUnigramWord(binaryDictionary, invalidLongWord, updatedProbability); + addUnigramWord(binaryDictionary, "abc", updatedProbability); + + assertEquals(probability, binaryDictionary.getFrequency("aaa")); + assertEquals(updatedProbability, binaryDictionary.getFrequency(validLongWord)); + assertEquals(BinaryDictionary.NOT_A_PROBABILITY, + binaryDictionary.getFrequency(invalidLongWord)); + assertEquals(updatedProbability, binaryDictionary.getFrequency("abc")); + dictFile.delete(); + } + + private static void addUnigramWord(final BinaryDictionary binaryDictionary, final String word, + final int probability) { + binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */, + BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */, + false /* isBeginningOfSentence */, false /* isNotAWord */, + false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */); + } + + private static void addBigramWords(final BinaryDictionary binaryDictionary, final String word0, + final String word1, final int probability) { + binaryDictionary.addNgramEntry(new PrevWordsInfo(new WordInfo(word0)), word1, probability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */); + } + + private static boolean isValidBigram(final BinaryDictionary binaryDictionary, + final String word0, final String word1) { + return binaryDictionary.isValidNgram(new PrevWordsInfo(new WordInfo(word0)), word1); + } + + private static void removeBigramEntry(final BinaryDictionary binaryDictionary, + final String word0, final String word1) { + binaryDictionary.removeNgramEntry(new PrevWordsInfo(new WordInfo(word0)), word1); + } + + private static int getBigramProbability(final BinaryDictionary binaryDictionary, + final String word0, final String word1) { + return binaryDictionary.getNgramProbability(new PrevWordsInfo(new WordInfo(word0)), word1); + } + public void testAddUnigramWord() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddUnigramWord(formatVersion); + } + } + + private void testAddUnigramWord(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -98,21 +241,21 @@ public class BinaryDictionaryTests extends AndroidTestCase { Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); final int probability = 100; - binaryDictionary.addUnigramWord("aaa", probability); + addUnigramWord(binaryDictionary, "aaa", probability); // Reallocate and create. - binaryDictionary.addUnigramWord("aab", probability); + addUnigramWord(binaryDictionary, "aab", probability); // Insert into children. - binaryDictionary.addUnigramWord("aac", probability); + addUnigramWord(binaryDictionary, "aac", probability); // Make terminal. - binaryDictionary.addUnigramWord("aa", probability); + addUnigramWord(binaryDictionary, "aa", probability); // Create children. - binaryDictionary.addUnigramWord("aaaa", probability); + addUnigramWord(binaryDictionary, "aaaa", probability); // Reallocate and make termianl. - binaryDictionary.addUnigramWord("a", probability); + addUnigramWord(binaryDictionary, "a", probability); final int updatedProbability = 200; // Update. - binaryDictionary.addUnigramWord("aaa", updatedProbability); + addUnigramWord(binaryDictionary, "aaa", updatedProbability); assertEquals(probability, binaryDictionary.getFrequency("aab")); assertEquals(probability, binaryDictionary.getFrequency("aac")); @@ -125,13 +268,19 @@ public class BinaryDictionaryTests extends AndroidTestCase { } public void testRandomlyAddUnigramWord() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testRandomlyAddUnigramWord(formatVersion); + } + } + + private void testRandomlyAddUnigramWord(final int formatVersion) { final int wordCount = 1000; final int codePointSetSize = 50; final long seed = System.currentTimeMillis(); File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -139,7 +288,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final HashMap<String, Integer> probabilityMap = new HashMap<String, Integer>(); + final HashMap<String, Integer> probabilityMap = new HashMap<>(); // Test a word that isn't contained within the dictionary. final Random random = new Random(seed); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); @@ -148,7 +297,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { probabilityMap.put(word, random.nextInt(0xFF)); } for (String word : probabilityMap.keySet()) { - binaryDictionary.addUnigramWord(word, probabilityMap.get(word)); + addUnigramWord(binaryDictionary, word, probabilityMap.get(word)); } for (String word : probabilityMap.keySet()) { assertEquals(word, (int)probabilityMap.get(word), binaryDictionary.getFrequency(word)); @@ -157,9 +306,15 @@ public class BinaryDictionaryTests extends AndroidTestCase { } public void testAddBigramWords() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddBigramWords(formatVersion); + } + } + + private void testAddBigramWords(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -168,59 +323,73 @@ public class BinaryDictionaryTests extends AndroidTestCase { Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); final int unigramProbability = 100; - final int bigramProbability = 10; - final int updatedBigramProbability = 15; - binaryDictionary.addUnigramWord("aaa", unigramProbability); - binaryDictionary.addUnigramWord("abb", unigramProbability); - binaryDictionary.addUnigramWord("bcc", unigramProbability); - binaryDictionary.addBigramWords("aaa", "abb", bigramProbability); - binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability); - binaryDictionary.addBigramWords("abb", "aaa", bigramProbability); - binaryDictionary.addBigramWords("abb", "bcc", bigramProbability); - - final int probability = binaryDictionary.calculateProbability(unigramProbability, - bigramProbability); - assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb")); - assertEquals(true, binaryDictionary.isValidBigram("aaa", "bcc")); - assertEquals(true, binaryDictionary.isValidBigram("abb", "aaa")); - assertEquals(true, binaryDictionary.isValidBigram("abb", "bcc")); - assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "abb")); - assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "bcc")); - assertEquals(probability, binaryDictionary.getBigramProbability("abb", "aaa")); - assertEquals(probability, binaryDictionary.getBigramProbability("abb", "bcc")); - - binaryDictionary.addBigramWords("aaa", "abb", updatedBigramProbability); - final int updatedProbability = binaryDictionary.calculateProbability(unigramProbability, - updatedBigramProbability); - assertEquals(updatedProbability, binaryDictionary.getBigramProbability("aaa", "abb")); - - assertEquals(false, binaryDictionary.isValidBigram("bcc", "aaa")); - assertEquals(false, binaryDictionary.isValidBigram("bcc", "bbc")); - assertEquals(false, binaryDictionary.isValidBigram("aaa", "aaa")); + final int bigramProbability = 150; + final int updatedBigramProbability = 200; + addUnigramWord(binaryDictionary, "aaa", unigramProbability); + addUnigramWord(binaryDictionary, "abb", unigramProbability); + addUnigramWord(binaryDictionary, "bcc", unigramProbability); + addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability); + addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability); + addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability); + addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability); + + assertTrue(isValidBigram(binaryDictionary, "aaa", "abb")); + assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc")); + assertTrue(isValidBigram(binaryDictionary, "abb", "aaa")); + assertTrue(isValidBigram(binaryDictionary, "abb", "bcc")); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); + } + + addBigramWords(binaryDictionary, "aaa", "abb", updatedBigramProbability); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(updatedBigramProbability, + getBigramProbability(binaryDictionary, "aaa", "abb")); + } + + assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa")); + assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc")); + assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa")); assertEquals(Dictionary.NOT_A_PROBABILITY, - binaryDictionary.getBigramProbability("bcc", "aaa")); + getBigramProbability(binaryDictionary, "bcc", "aaa")); assertEquals(Dictionary.NOT_A_PROBABILITY, - binaryDictionary.getBigramProbability("bcc", "bbc")); + getBigramProbability(binaryDictionary, "bcc", "bbc")); assertEquals(Dictionary.NOT_A_PROBABILITY, - binaryDictionary.getBigramProbability("aaa", "aaa")); + getBigramProbability(binaryDictionary, "aaa", "aaa")); // Testing bigram link. - binaryDictionary.addUnigramWord("abcde", unigramProbability); - binaryDictionary.addUnigramWord("fghij", unigramProbability); - binaryDictionary.addBigramWords("abcde", "fghij", bigramProbability); - binaryDictionary.addUnigramWord("fgh", unigramProbability); - binaryDictionary.addUnigramWord("abc", unigramProbability); - binaryDictionary.addUnigramWord("f", unigramProbability); - assertEquals(probability, binaryDictionary.getBigramProbability("abcde", "fghij")); + addUnigramWord(binaryDictionary, "abcde", unigramProbability); + addUnigramWord(binaryDictionary, "fghij", unigramProbability); + addBigramWords(binaryDictionary, "abcde", "fghij", bigramProbability); + addUnigramWord(binaryDictionary, "fgh", unigramProbability); + addUnigramWord(binaryDictionary, "abc", unigramProbability); + addUnigramWord(binaryDictionary, "f", unigramProbability); + + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, "abcde", "fghij")); + } assertEquals(Dictionary.NOT_A_PROBABILITY, - binaryDictionary.getBigramProbability("abcde", "fgh")); - binaryDictionary.addBigramWords("abcde", "fghij", updatedBigramProbability); - assertEquals(updatedProbability, binaryDictionary.getBigramProbability("abcde", "fghij")); + getBigramProbability(binaryDictionary, "abcde", "fgh")); + addBigramWords(binaryDictionary, "abcde", "fghij", updatedBigramProbability); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(updatedBigramProbability, + getBigramProbability(binaryDictionary, "abcde", "fghij")); + } dictFile.delete(); } public void testRandomlyAddBigramWords() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testRandomlyAddBigramWords(formatVersion); + } + } + + private void testRandomlyAddBigramWords(final int formatVersion) { final int wordCount = 100; final int bigramCount = 1000; final int codePointSetSize = 50; @@ -229,7 +398,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -237,19 +406,18 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final ArrayList<String> words = new ArrayList<String>(); - final ArrayList<Pair<String, String>> bigramWords = new ArrayList<Pair<String,String>>(); + final ArrayList<String> words = new ArrayList<>(); + final ArrayList<Pair<String, String>> bigramWords = new ArrayList<>(); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); - final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>(); - final HashMap<Pair<String, String>, Integer> bigramProbabilities = - new HashMap<Pair<String, String>, Integer>(); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); for (int i = 0; i < wordCount; ++i) { final String word = CodePointUtils.generateWord(random, codePointSet); words.add(word); final int unigramProbability = random.nextInt(0xFF); unigramProbabilities.put(word, unigramProbability); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } for (int i = 0; i < bigramCount; i++) { @@ -258,29 +426,38 @@ public class BinaryDictionaryTests extends AndroidTestCase { if (TextUtils.equals(word0, word1)) { continue; } - final Pair<String, String> bigram = new Pair<String, String>(word0, word1); + final Pair<String, String> bigram = new Pair<>(word0, word1); bigramWords.add(bigram); - final int bigramProbability = random.nextInt(0xF); + final int unigramProbability = unigramProbabilities.get(word1); + final int bigramProbability = + unigramProbability + random.nextInt(0xFF - unigramProbability); bigramProbabilities.put(bigram, bigramProbability); - binaryDictionary.addBigramWords(word0, word1, bigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); } for (final Pair<String, String> bigram : bigramWords) { - final int unigramProbability = unigramProbabilities.get(bigram.second); final int bigramProbability = bigramProbabilities.get(bigram); - final int probability = binaryDictionary.calculateProbability(unigramProbability, - bigramProbability); - assertEquals(probability, - binaryDictionary.getBigramProbability(bigram.first, bigram.second)); + assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY, + isValidBigram(binaryDictionary, bigram.first, bigram.second)); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); + } } dictFile.delete(); } public void testRemoveBigramWords() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testRemoveBigramWords(formatVersion); + } + } + + private void testRemoveBigramWords(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -288,45 +465,51 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); final int unigramProbability = 100; - final int bigramProbability = 10; - binaryDictionary.addUnigramWord("aaa", unigramProbability); - binaryDictionary.addUnigramWord("abb", unigramProbability); - binaryDictionary.addUnigramWord("bcc", unigramProbability); - binaryDictionary.addBigramWords("aaa", "abb", bigramProbability); - binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability); - binaryDictionary.addBigramWords("abb", "aaa", bigramProbability); - binaryDictionary.addBigramWords("abb", "bcc", bigramProbability); - - assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb")); - assertEquals(true, binaryDictionary.isValidBigram("aaa", "bcc")); - assertEquals(true, binaryDictionary.isValidBigram("abb", "aaa")); - assertEquals(true, binaryDictionary.isValidBigram("abb", "bcc")); - - binaryDictionary.removeBigramWords("aaa", "abb"); - assertEquals(false, binaryDictionary.isValidBigram("aaa", "abb")); - binaryDictionary.addBigramWords("aaa", "abb", bigramProbability); - assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb")); - - - binaryDictionary.removeBigramWords("aaa", "bcc"); - assertEquals(false, binaryDictionary.isValidBigram("aaa", "bcc")); - binaryDictionary.removeBigramWords("abb", "aaa"); - assertEquals(false, binaryDictionary.isValidBigram("abb", "aaa")); - binaryDictionary.removeBigramWords("abb", "bcc"); - assertEquals(false, binaryDictionary.isValidBigram("abb", "bcc")); - - binaryDictionary.removeBigramWords("aaa", "abb"); + final int bigramProbability = 150; + addUnigramWord(binaryDictionary, "aaa", unigramProbability); + addUnigramWord(binaryDictionary, "abb", unigramProbability); + addUnigramWord(binaryDictionary, "bcc", unigramProbability); + addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability); + addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability); + addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability); + addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability); + + assertTrue(isValidBigram(binaryDictionary, "aaa", "abb")); + assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc")); + assertTrue(isValidBigram(binaryDictionary, "abb", "aaa")); + assertTrue(isValidBigram(binaryDictionary, "abb", "bcc")); + + removeBigramEntry(binaryDictionary, "aaa", "abb"); + assertFalse(isValidBigram(binaryDictionary, "aaa", "abb")); + addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability); + assertTrue(isValidBigram(binaryDictionary, "aaa", "abb")); + + + removeBigramEntry(binaryDictionary, "aaa", "bcc"); + assertFalse(isValidBigram(binaryDictionary, "aaa", "bcc")); + removeBigramEntry(binaryDictionary, "abb", "aaa"); + assertFalse(isValidBigram(binaryDictionary, "abb", "aaa")); + removeBigramEntry(binaryDictionary, "abb", "bcc"); + assertFalse(isValidBigram(binaryDictionary, "abb", "bcc")); + + removeBigramEntry(binaryDictionary, "aaa", "abb"); // Test remove non-existing bigram operation. - binaryDictionary.removeBigramWords("aaa", "abb"); - binaryDictionary.removeBigramWords("bcc", "aaa"); + removeBigramEntry(binaryDictionary, "aaa", "abb"); + removeBigramEntry(binaryDictionary, "bcc", "aaa"); dictFile.delete(); } public void testFlushDictionary() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testFlushDictionary(formatVersion); + } + } + + private void testFlushDictionary(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -335,8 +518,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); final int probability = 100; - binaryDictionary.addUnigramWord("aaa", probability); - binaryDictionary.addUnigramWord("abcd", probability); + addUnigramWord(binaryDictionary, "aaa", probability); + addUnigramWord(binaryDictionary, "abcd", probability); // Close without flushing. binaryDictionary.close(); @@ -347,8 +530,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("aaa")); assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("abcd")); - binaryDictionary.addUnigramWord("aaa", probability); - binaryDictionary.addUnigramWord("abcd", probability); + addUnigramWord(binaryDictionary, "aaa", probability); + addUnigramWord(binaryDictionary, "abcd", probability); binaryDictionary.flush(); binaryDictionary.close(); @@ -358,7 +541,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertEquals(probability, binaryDictionary.getFrequency("aaa")); assertEquals(probability, binaryDictionary.getFrequency("abcd")); - binaryDictionary.addUnigramWord("bcde", probability); + addUnigramWord(binaryDictionary, "bcde", probability); binaryDictionary.flush(); binaryDictionary.close(); @@ -372,9 +555,15 @@ public class BinaryDictionaryTests extends AndroidTestCase { } public void testFlushWithGCDictionary() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testFlushWithGCDictionary(formatVersion); + } + } + + private void testFlushWithGCDictionary(final int formatVersion) { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -383,40 +572,46 @@ public class BinaryDictionaryTests extends AndroidTestCase { Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); final int unigramProbability = 100; - final int bigramProbability = 10; - binaryDictionary.addUnigramWord("aaa", unigramProbability); - binaryDictionary.addUnigramWord("abb", unigramProbability); - binaryDictionary.addUnigramWord("bcc", unigramProbability); - binaryDictionary.addBigramWords("aaa", "abb", bigramProbability); - binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability); - binaryDictionary.addBigramWords("abb", "aaa", bigramProbability); - binaryDictionary.addBigramWords("abb", "bcc", bigramProbability); + final int bigramProbability = 150; + addUnigramWord(binaryDictionary, "aaa", unigramProbability); + addUnigramWord(binaryDictionary, "abb", unigramProbability); + addUnigramWord(binaryDictionary, "bcc", unigramProbability); + addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability); + addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability); + addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability); + addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability); binaryDictionary.flushWithGC(); binaryDictionary.close(); binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final int probability = binaryDictionary.calculateProbability(unigramProbability, - bigramProbability); assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa")); assertEquals(unigramProbability, binaryDictionary.getFrequency("abb")); assertEquals(unigramProbability, binaryDictionary.getFrequency("bcc")); - assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "abb")); - assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "bcc")); - assertEquals(probability, binaryDictionary.getBigramProbability("abb", "aaa")); - assertEquals(probability, binaryDictionary.getBigramProbability("abb", "bcc")); - assertEquals(false, binaryDictionary.isValidBigram("bcc", "aaa")); - assertEquals(false, binaryDictionary.isValidBigram("bcc", "bbc")); - assertEquals(false, binaryDictionary.isValidBigram("aaa", "aaa")); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); + } + assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa")); + assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc")); + assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa")); binaryDictionary.flushWithGC(); binaryDictionary.close(); dictFile.delete(); } - // TODO: Evaluate performance of GC public void testAddBigramWordsAndFlashWithGC() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddBigramWordsAndFlashWithGC(formatVersion); + } + } + + // TODO: Evaluate performance of GC + private void testAddBigramWordsAndFlashWithGC(final int formatVersion) { final int wordCount = 100; final int bigramCount = 1000; final int codePointSetSize = 30; @@ -425,7 +620,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -434,19 +629,18 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final ArrayList<String> words = new ArrayList<String>(); - final ArrayList<Pair<String, String>> bigramWords = new ArrayList<Pair<String,String>>(); + final ArrayList<String> words = new ArrayList<>(); + final ArrayList<Pair<String, String>> bigramWords = new ArrayList<>(); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); - final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>(); - final HashMap<Pair<String, String>, Integer> bigramProbabilities = - new HashMap<Pair<String, String>, Integer>(); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); for (int i = 0; i < wordCount; ++i) { final String word = CodePointUtils.generateWord(random, codePointSet); words.add(word); final int unigramProbability = random.nextInt(0xFF); unigramProbabilities.put(word, unigramProbability); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } for (int i = 0; i < bigramCount; i++) { @@ -455,11 +649,13 @@ public class BinaryDictionaryTests extends AndroidTestCase { if (TextUtils.equals(word0, word1)) { continue; } - final Pair<String, String> bigram = new Pair<String, String>(word0, word1); + final Pair<String, String> bigram = new Pair<>(word0, word1); bigramWords.add(bigram); - final int bigramProbability = random.nextInt(0xF); + final int unigramProbability = unigramProbabilities.get(word1); + final int bigramProbability = + unigramProbability + random.nextInt(0xFF - unigramProbability); bigramProbabilities.put(bigram, bigramProbability); - binaryDictionary.addBigramWords(word0, word1, bigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); } binaryDictionary.flushWithGC(); @@ -468,19 +664,27 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + for (final Pair<String, String> bigram : bigramWords) { - final int unigramProbability = unigramProbabilities.get(bigram.second); final int bigramProbability = bigramProbabilities.get(bigram); - final int probability = binaryDictionary.calculateProbability(unigramProbability, - bigramProbability); - assertEquals(probability, - binaryDictionary.getBigramProbability(bigram.first, bigram.second)); + assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY, + isValidBigram(binaryDictionary, bigram.first, bigram.second)); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); + } } dictFile.delete(); } - public void testRandomOperetionsAndFlashWithGC() { + public void testRandomOperationsAndFlashWithGC() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testRandomOperationsAndFlashWithGC(formatVersion); + } + } + + private void testRandomOperationsAndFlashWithGC(final int formatVersion) { final int flashWithGCIterationCount = 50; final int operationCountInEachIteration = 200; final int initialUnigramCount = 100; @@ -494,7 +698,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } @@ -502,18 +706,17 @@ public class BinaryDictionaryTests extends AndroidTestCase { BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final ArrayList<String> words = new ArrayList<String>(); - final ArrayList<Pair<String, String>> bigramWords = new ArrayList<Pair<String,String>>(); + final ArrayList<String> words = new ArrayList<>(); + final ArrayList<Pair<String, String>> bigramWords = new ArrayList<>(); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); - final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>(); - final HashMap<Pair<String, String>, Integer> bigramProbabilities = - new HashMap<Pair<String, String>, Integer>(); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); for (int i = 0; i < initialUnigramCount; ++i) { final String word = CodePointUtils.generateWord(random, codePointSet); words.add(word); final int unigramProbability = random.nextInt(0xFF); unigramProbabilities.put(word, unigramProbability); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } binaryDictionary.flushWithGC(); binaryDictionary.close(); @@ -529,7 +732,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { words.add(word); final int unigramProbability = random.nextInt(0xFF); unigramProbabilities.put(word, unigramProbability); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } // Add bigram. if (random.nextFloat() < addBigramProb && words.size() > 2) { @@ -543,11 +746,13 @@ public class BinaryDictionaryTests extends AndroidTestCase { if (TextUtils.equals(word0, word1)) { continue; } - final int bigramProbability = random.nextInt(0xF); - final Pair<String, String> bigram = new Pair<String, String>(word0, word1); + final int unigramProbability = unigramProbabilities.get(word1); + final int bigramProbability = + unigramProbability + random.nextInt(0xFF - unigramProbability); + final Pair<String, String> bigram = new Pair<>(word0, word1); bigramWords.add(bigram); bigramProbabilities.put(bigram, bigramProbability); - binaryDictionary.addBigramWords(word0, word1, bigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); } // Remove bigram. if (random.nextFloat() < removeBigramProb && !bigramWords.isEmpty()) { @@ -555,7 +760,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { final Pair<String, String> bigram = bigramWords.get(bigramIndex); bigramWords.remove(bigramIndex); bigramProbabilities.remove(bigram); - binaryDictionary.removeBigramWords(bigram.first, bigram.second); + removeBigramEntry(binaryDictionary, bigram.first, bigram.second); } } @@ -568,17 +773,20 @@ public class BinaryDictionaryTests extends AndroidTestCase { // Test whether the all bigram operations are collectlly handled. for (int i = 0; i < bigramWords.size(); i++) { final Pair<String, String> bigram = bigramWords.get(i); - final int unigramProbability = unigramProbabilities.get(bigram.second); final int probability; if (bigramProbabilities.containsKey(bigram)) { final int bigramProbability = bigramProbabilities.get(bigram); - probability = binaryDictionary.calculateProbability(unigramProbability, - bigramProbability); + probability = bigramProbability; } else { probability = Dictionary.NOT_A_PROBABILITY; } - assertEquals(probability, - binaryDictionary.getBigramProbability(bigram.first, bigram.second)); + + if (canCheckBigramProbability(formatVersion)) { + assertEquals(probability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); + } + assertEquals(probability != Dictionary.NOT_A_PROBABILITY, + isValidBigram(binaryDictionary, bigram.first, bigram.second)); } binaryDictionary.flushWithGC(); binaryDictionary.close(); @@ -588,6 +796,12 @@ public class BinaryDictionaryTests extends AndroidTestCase { } public void testAddManyUnigramsAndFlushWithGC() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddManyUnigramsAndFlushWithGC(formatVersion); + } + } + + private void testAddManyUnigramsAndFlushWithGC(final int formatVersion) { final int flashWithGCIterationCount = 3; final int codePointSetSize = 50; @@ -596,13 +810,13 @@ public class BinaryDictionaryTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } - final ArrayList<String> words = new ArrayList<String>(); - final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>(); + final ArrayList<String> words = new ArrayList<>(); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); BinaryDictionary binaryDictionary; @@ -615,7 +829,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { words.add(word); final int unigramProbability = random.nextInt(0xFF); unigramProbabilities.put(word, unigramProbability); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } for (int j = 0; j < words.size(); j++) { @@ -632,6 +846,12 @@ public class BinaryDictionaryTests extends AndroidTestCase { } public void testUnigramAndBigramCount() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testUnigramAndBigramCount(formatVersion); + } + } + + private void testUnigramAndBigramCount(final int formatVersion) { final int flashWithGCIterationCount = 10; final int codePointSetSize = 50; final int unigramCountPerIteration = 1000; @@ -641,13 +861,13 @@ public class BinaryDictionaryTests extends AndroidTestCase { File dictFile = null; try { - dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary"); + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); } catch (IOException e) { fail("IOException while writing an initial dictionary : " + e); } - final ArrayList<String> words = new ArrayList<String>(); - final HashSet<Pair<String, String>> bigrams = new HashSet<Pair<String, String>>(); + final ArrayList<String> words = new ArrayList<>(); + final HashSet<Pair<String, String>> bigrams = new HashSet<>(); final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); BinaryDictionary binaryDictionary; @@ -659,7 +879,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { final String word = CodePointUtils.generateWord(random, codePointSet); words.add(word); final int unigramProbability = random.nextInt(0xFF); - binaryDictionary.addUnigramWord(word, unigramProbability); + addUnigramWord(binaryDictionary, word, unigramProbability); } for (int j = 0; j < bigramCountPerIteration; j++) { final String word0 = words.get(random.nextInt(words.size())); @@ -667,22 +887,630 @@ public class BinaryDictionaryTests extends AndroidTestCase { if (TextUtils.equals(word0, word1)) { continue; } - bigrams.add(new Pair<String, String>(word0, word1)); + bigrams.add(new Pair<>(word0, word1)); final int bigramProbability = random.nextInt(0xF); - binaryDictionary.addBigramWords(word0, word1, bigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); } - assertEquals(new HashSet<String>(words).size(), Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.UNIGRAM_COUNT_QUERY))); - assertEquals(new HashSet<Pair<String, String>>(bigrams).size(), Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.BIGRAM_COUNT_QUERY))); + assertEquals(new HashSet<>(words).size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.UNIGRAM_COUNT_QUERY))); + assertEquals(new HashSet<>(bigrams).size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY))); binaryDictionary.flushWithGC(); - assertEquals(new HashSet<String>(words).size(), Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.UNIGRAM_COUNT_QUERY))); - assertEquals(new HashSet<Pair<String, String>>(bigrams).size(), Integer.parseInt( - binaryDictionary.getPropertyForTests(BinaryDictionary.BIGRAM_COUNT_QUERY))); + assertEquals(new HashSet<>(words).size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.UNIGRAM_COUNT_QUERY))); + assertEquals(new HashSet<>(bigrams).size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY))); binaryDictionary.close(); } dictFile.delete(); } + + public void testAddMultipleDictionaryEntries() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddMultipleDictionaryEntries(formatVersion); + } + } + + private void testAddMultipleDictionaryEntries(final int formatVersion) { + final int codePointSetSize = 20; + final int lmParamCount = 1000; + final double bigramContinueRate = 0.9; + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); + + final LanguageModelParam[] languageModelParams = new LanguageModelParam[lmParamCount]; + String prevWord = null; + for (int i = 0; i < languageModelParams.length; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + final int probability = random.nextInt(0xFF); + final int bigramProbability = probability + random.nextInt(0xFF - probability); + unigramProbabilities.put(word, probability); + if (prevWord == null) { + languageModelParams[i] = new LanguageModelParam(word, probability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); + } else { + languageModelParams[i] = new LanguageModelParam(prevWord, word, probability, + bigramProbability, BinaryDictionary.NOT_A_VALID_TIMESTAMP); + bigramProbabilities.put(new Pair<>(prevWord, word), + bigramProbability); + } + prevWord = (random.nextDouble() < bigramContinueRate) ? word : null; + } + + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + binaryDictionary.addMultipleDictionaryEntries(languageModelParams); + + for (Map.Entry<String, Integer> entry : unigramProbabilities.entrySet()) { + assertEquals((int)entry.getValue(), binaryDictionary.getFrequency(entry.getKey())); + } + + for (Map.Entry<Pair<String, String>, Integer> entry : bigramProbabilities.entrySet()) { + final String word0 = entry.getKey().first; + final String word1 = entry.getKey().second; + final int bigramProbability = entry.getValue(); + assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY, + isValidBigram(binaryDictionary, word0, word1)); + if (canCheckBigramProbability(formatVersion)) { + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, word0, word1)); + } + } + } + + public void testGetWordProperties() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testGetWordProperties(formatVersion); + } + } + + private void testGetWordProperties(final int formatVersion) { + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + final int UNIGRAM_COUNT = 1000; + final int BIGRAM_COUNT = 1000; + final int codePointSetSize = 20; + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + final WordProperty invalidWordProperty = binaryDictionary.getWordProperty("dummyWord", + false /* isBeginningOfSentence */); + assertFalse(invalidWordProperty.isValid()); + + final ArrayList<String> words = new ArrayList<>(); + final HashMap<String, Integer> wordProbabilities = new HashMap<>(); + final HashMap<String, HashSet<String>> bigrams = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); + + for (int i = 0; i < UNIGRAM_COUNT; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + final int unigramProbability = random.nextInt(0xFF); + final boolean isNotAWord = random.nextBoolean(); + final boolean isBlacklisted = random.nextBoolean(); + // TODO: Add tests for historical info. + binaryDictionary.addUnigramEntry(word, unigramProbability, + null /* shortcutTarget */, BinaryDictionary.NOT_A_PROBABILITY, + false /* isBeginningOfSentence */, isNotAWord, isBlacklisted, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + words.add(word); + wordProbabilities.put(word, unigramProbability); + final WordProperty wordProperty = binaryDictionary.getWordProperty(word, + false /* isBeginningOfSentence */); + assertEquals(word, wordProperty.mWord); + assertTrue(wordProperty.isValid()); + assertEquals(isNotAWord, wordProperty.mIsNotAWord); + assertEquals(isBlacklisted, wordProperty.mIsBlacklistEntry); + assertEquals(false, wordProperty.mHasBigrams); + assertEquals(false, wordProperty.mHasShortcuts); + assertEquals(unigramProbability, wordProperty.mProbabilityInfo.mProbability); + assertTrue(wordProperty.mShortcutTargets.isEmpty()); + } + + for (int i = 0; i < BIGRAM_COUNT; i++) { + final int word0Index = random.nextInt(wordProbabilities.size()); + final int word1Index = random.nextInt(wordProbabilities.size()); + if (word0Index == word1Index) { + continue; + } + final String word0 = words.get(word0Index); + final String word1 = words.get(word1Index); + final int unigramProbability = wordProbabilities.get(word1); + final int bigramProbability = + unigramProbability + random.nextInt(0xFF - unigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + if (!bigrams.containsKey(word0)) { + final HashSet<String> bigramWord1s = new HashSet<>(); + bigrams.put(word0, bigramWord1s); + } + bigrams.get(word0).add(word1); + bigramProbabilities.put(new Pair<>(word0, word1), bigramProbability); + } + + for (int i = 0; i < words.size(); i++) { + final String word0 = words.get(i); + if (!bigrams.containsKey(word0)) { + continue; + } + final HashSet<String> bigramWord1s = bigrams.get(word0); + final WordProperty wordProperty = binaryDictionary.getWordProperty(word0, + false /* isBeginningOfSentence */); + assertEquals(bigramWord1s.size(), wordProperty.mBigrams.size()); + for (int j = 0; j < wordProperty.mBigrams.size(); j++) { + final String word1 = wordProperty.mBigrams.get(j).mWord; + assertTrue(bigramWord1s.contains(word1)); + if (canCheckBigramProbability(formatVersion)) { + final int bigramProbability = bigramProbabilities.get(new Pair<>(word0, word1)); + assertEquals(bigramProbability, wordProperty.mBigrams.get(j).getProbability()); + } + } + } + } + + public void testIterateAllWords() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testIterateAllWords(formatVersion); + } + } + + private void testIterateAllWords(final int formatVersion) { + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + final int UNIGRAM_COUNT = 1000; + final int BIGRAM_COUNT = 1000; + final int codePointSetSize = 20; + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + final WordProperty invalidWordProperty = binaryDictionary.getWordProperty("dummyWord", + false /* isBeginningOfSentence */); + assertFalse(invalidWordProperty.isValid()); + + final ArrayList<String> words = new ArrayList<>(); + final HashMap<String, Integer> wordProbabilitiesToCheckLater = new HashMap<>(); + final HashMap<String, HashSet<String>> bigrams = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilitiesToCheckLater = + new HashMap<>(); + + for (int i = 0; i < UNIGRAM_COUNT; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + final int unigramProbability = random.nextInt(0xFF); + addUnigramWord(binaryDictionary, word, unigramProbability); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + words.add(word); + wordProbabilitiesToCheckLater.put(word, unigramProbability); + } + + for (int i = 0; i < BIGRAM_COUNT; i++) { + final int word0Index = random.nextInt(wordProbabilitiesToCheckLater.size()); + final int word1Index = random.nextInt(wordProbabilitiesToCheckLater.size()); + if (word0Index == word1Index) { + continue; + } + final String word0 = words.get(word0Index); + final String word1 = words.get(word1Index); + final int unigramProbability = wordProbabilitiesToCheckLater.get(word1); + final int bigramProbability = + unigramProbability + random.nextInt(0xFF - unigramProbability); + addBigramWords(binaryDictionary, word0, word1, bigramProbability); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + if (!bigrams.containsKey(word0)) { + final HashSet<String> bigramWord1s = new HashSet<>(); + bigrams.put(word0, bigramWord1s); + } + bigrams.get(word0).add(word1); + bigramProbabilitiesToCheckLater.put(new Pair<>(word0, word1), bigramProbability); + } + + final HashSet<String> wordSet = new HashSet<>(words); + final HashSet<Pair<String, String>> bigramSet = + new HashSet<>(bigramProbabilitiesToCheckLater.keySet()); + int token = 0; + do { + final BinaryDictionary.GetNextWordPropertyResult result = + binaryDictionary.getNextWordProperty(token); + final WordProperty wordProperty = result.mWordProperty; + final String word0 = wordProperty.mWord; + assertEquals((int)wordProbabilitiesToCheckLater.get(word0), + wordProperty.mProbabilityInfo.mProbability); + wordSet.remove(word0); + final HashSet<String> bigramWord1s = bigrams.get(word0); + for (int j = 0; j < wordProperty.mBigrams.size(); j++) { + final String word1 = wordProperty.mBigrams.get(j).mWord; + assertTrue(bigramWord1s.contains(word1)); + final Pair<String, String> bigram = new Pair<>(word0, word1); + if (canCheckBigramProbability(formatVersion)) { + final int bigramProbability = bigramProbabilitiesToCheckLater.get(bigram); + assertEquals(bigramProbability, wordProperty.mBigrams.get(j).getProbability()); + } + bigramSet.remove(bigram); + } + token = result.mNextToken; + } while (token != 0); + assertTrue(wordSet.isEmpty()); + assertTrue(bigramSet.isEmpty()); + } + + public void testAddShortcuts() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddShortcuts(formatVersion); + } + } + + private void testAddShortcuts(final int formatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + final int unigramProbability = 100; + final int shortcutProbability = 10; + binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz", + shortcutProbability, false /* isBeginningOfSentence */, + false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */); + WordProperty wordProperty = binaryDictionary.getWordProperty("aaa", + false /* isBeginningOfSentence */); + assertEquals(1, wordProperty.mShortcutTargets.size()); + assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord); + assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).getProbability()); + final int updatedShortcutProbability = 2; + binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz", + updatedShortcutProbability, false /* isBeginningOfSentence */, + false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */); + wordProperty = binaryDictionary.getWordProperty("aaa", + false /* isBeginningOfSentence */); + assertEquals(1, wordProperty.mShortcutTargets.size()); + assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord); + assertEquals(updatedShortcutProbability, + wordProperty.mShortcutTargets.get(0).getProbability()); + binaryDictionary.addUnigramEntry("aaa", unigramProbability, "yyy", + shortcutProbability, false /* isBeginningOfSentence */, false /* isNotAWord */, + false /* isBlacklisted */, 0 /* timestamp */); + final HashMap<String, Integer> shortcutTargets = new HashMap<>(); + shortcutTargets.put("zzz", updatedShortcutProbability); + shortcutTargets.put("yyy", shortcutProbability); + wordProperty = binaryDictionary.getWordProperty("aaa", + false /* isBeginningOfSentence */); + assertEquals(2, wordProperty.mShortcutTargets.size()); + for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord)); + assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), + shortcutTarget.getProbability()); + shortcutTargets.remove(shortcutTarget.mWord); + } + shortcutTargets.put("zzz", updatedShortcutProbability); + shortcutTargets.put("yyy", shortcutProbability); + binaryDictionary.flushWithGC(); + wordProperty = binaryDictionary.getWordProperty("aaa", + false /* isBeginningOfSentence */); + assertEquals(2, wordProperty.mShortcutTargets.size()); + for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord)); + assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), + shortcutTarget.getProbability()); + shortcutTargets.remove(shortcutTarget.mWord); + } + } + + public void testAddManyShortcuts() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testAddManyShortcuts(formatVersion); + } + } + + private void testAddManyShortcuts(final int formatVersion) { + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + final int UNIGRAM_COUNT = 1000; + final int SHORTCUT_COUNT = 10000; + final int codePointSetSize = 20; + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + + final ArrayList<String> words = new ArrayList<>(); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<String, HashMap<String, Integer>> shortcutTargets = new HashMap<>(); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + for (int i = 0; i < UNIGRAM_COUNT; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + final int unigramProbability = random.nextInt(0xFF); + addUnigramWord(binaryDictionary, word, unigramProbability); + words.add(word); + unigramProbabilities.put(word, unigramProbability); + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + } + for (int i = 0; i < SHORTCUT_COUNT; i++) { + final String shortcutTarget = CodePointUtils.generateWord(random, codePointSet); + final int shortcutProbability = random.nextInt(0xF); + final String word = words.get(random.nextInt(words.size())); + final int unigramProbability = unigramProbabilities.get(word); + binaryDictionary.addUnigramEntry(word, unigramProbability, shortcutTarget, + shortcutProbability, false /* isBeginningOfSentence */, false /* isNotAWord */, + false /* isBlacklisted */, 0 /* timestamp */); + if (shortcutTargets.containsKey(word)) { + final HashMap<String, Integer> shortcutTargetsOfWord = shortcutTargets.get(word); + shortcutTargetsOfWord.put(shortcutTarget, shortcutProbability); + } else { + final HashMap<String, Integer> shortcutTargetsOfWord = new HashMap<>(); + shortcutTargetsOfWord.put(shortcutTarget, shortcutProbability); + shortcutTargets.put(word, shortcutTargetsOfWord); + } + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + } + + for (final String word : words) { + final WordProperty wordProperty = binaryDictionary.getWordProperty(word, + false /* isBeginningOfSentence */); + assertEquals((int)unigramProbabilities.get(word), + wordProperty.mProbabilityInfo.mProbability); + if (!shortcutTargets.containsKey(word)) { + // The word does not have shortcut targets. + continue; + } + assertEquals(shortcutTargets.get(word).size(), wordProperty.mShortcutTargets.size()); + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + final String targetCodePonts = shortcutTarget.mWord; + assertEquals((int)shortcutTargets.get(word).get(targetCodePonts), + shortcutTarget.getProbability()); + } + } + } + + public void testDictMigration() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion); + } + } + + private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", fromFormatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + final int unigramProbability = 100; + addUnigramWord(binaryDictionary, "aaa", unigramProbability); + addUnigramWord(binaryDictionary, "bbb", unigramProbability); + final int bigramProbability = 150; + addBigramWords(binaryDictionary, "aaa", "bbb", bigramProbability); + final int shortcutProbability = 10; + binaryDictionary.addUnigramEntry("ccc", unigramProbability, "xxx", shortcutProbability, + false /* isBeginningOfSentence */, false /* isNotAWord */, + false /* isBlacklisted */, 0 /* timestamp */); + binaryDictionary.addUnigramEntry("ddd", unigramProbability, null /* shortcutTarget */, + Dictionary.NOT_A_PROBABILITY, false /* isBeginningOfSentence */, + true /* isNotAWord */, true /* isBlacklisted */, 0 /* timestamp */); + binaryDictionary.addNgramEntry(PrevWordsInfo.BEGINNING_OF_SENTENCE, + "aaa", bigramProbability, 0 /* timestamp */); + assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa")); + assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb")); + assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb")); + assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion()); + assertTrue(binaryDictionary.migrateTo(toFormatVersion)); + assertTrue(binaryDictionary.isValidDictionary()); + assertEquals(toFormatVersion, binaryDictionary.getFormatVersion()); + assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa")); + assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb")); + if (canCheckBigramProbability(toFormatVersion)) { + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bbb")); + assertEquals(bigramProbability, binaryDictionary.getNgramProbability( + PrevWordsInfo.BEGINNING_OF_SENTENCE, "aaa")); + } + assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb")); + WordProperty wordProperty = binaryDictionary.getWordProperty("ccc", + false /* isBeginningOfSentence */); + assertEquals(1, wordProperty.mShortcutTargets.size()); + assertEquals("xxx", wordProperty.mShortcutTargets.get(0).mWord); + wordProperty = binaryDictionary.getWordProperty("ddd", + false /* isBeginningOfSentence */); + assertTrue(wordProperty.mIsBlacklistEntry); + assertTrue(wordProperty.mIsNotAWord); + } + + public void testLargeDictMigration() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testLargeDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion); + } + } + + private void testLargeDictMigration(final int fromFormatVersion, final int toFormatVersion) { + final int UNIGRAM_COUNT = 3000; + final int BIGRAM_COUNT = 3000; + final int codePointSetSize = 50; + final long seed = System.currentTimeMillis(); + final Random random = new Random(seed); + + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", fromFormatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + + final ArrayList<String> words = new ArrayList<>(); + final ArrayList<Pair<String, String>> bigrams = new ArrayList<>(); + final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); + final HashMap<String, Integer> unigramProbabilities = new HashMap<>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = new HashMap<>(); + + for (int i = 0; i < UNIGRAM_COUNT; i++) { + final String word = CodePointUtils.generateWord(random, codePointSet); + final int unigramProbability = random.nextInt(0xFF); + addUnigramWord(binaryDictionary, word, unigramProbability); + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + words.add(word); + unigramProbabilities.put(word, unigramProbability); + } + + for (int i = 0; i < BIGRAM_COUNT; i++) { + final int word0Index = random.nextInt(words.size()); + final int word1Index = random.nextInt(words.size()); + if (word0Index == word1Index) { + continue; + } + final String word0 = words.get(word0Index); + final String word1 = words.get(word1Index); + final int unigramProbability = unigramProbabilities.get(word1); + final int bigramProbability = + random.nextInt(0xFF - unigramProbability) + unigramProbability; + addBigramWords(binaryDictionary, word0, word1, bigramProbability); + if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + final Pair<String, String> bigram = new Pair<>(word0, word1); + bigrams.add(bigram); + bigramProbabilities.put(bigram, bigramProbability); + } + assertTrue(binaryDictionary.migrateTo(toFormatVersion)); + + for (final String word : words) { + assertEquals((int)unigramProbabilities.get(word), binaryDictionary.getFrequency(word)); + } + assertEquals(unigramProbabilities.size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.UNIGRAM_COUNT_QUERY))); + + for (final Pair<String, String> bigram : bigrams) { + if (canCheckBigramProbability(toFormatVersion)) { + assertEquals((int)bigramProbabilities.get(bigram), + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); + } + assertTrue(isValidBigram(binaryDictionary, bigram.first, bigram.second)); + } + assertEquals(bigramProbabilities.size(), Integer.parseInt( + binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY))); + } + + public void testBeginningOfSentence() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + if (supportsBeginningOfSentence(formatVersion)) { + testBeginningOfSentence(formatVersion); + } + } + } + + private void testBeginningOfSentence(final int formatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + final int dummyProbability = 0; + final PrevWordsInfo prevWordsInfoBeginningOfSentence = PrevWordsInfo.BEGINNING_OF_SENTENCE; + final int bigramProbability = 200; + addUnigramWord(binaryDictionary, "aaa", dummyProbability); + binaryDictionary.addNgramEntry(prevWordsInfoBeginningOfSentence, "aaa", bigramProbability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */); + assertEquals(bigramProbability, + binaryDictionary.getNgramProbability(prevWordsInfoBeginningOfSentence, "aaa")); + binaryDictionary.addNgramEntry(prevWordsInfoBeginningOfSentence, "aaa", bigramProbability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */); + addUnigramWord(binaryDictionary, "bbb", dummyProbability); + binaryDictionary.addNgramEntry(prevWordsInfoBeginningOfSentence, "bbb", bigramProbability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */); + binaryDictionary.flushWithGC(); + assertEquals(bigramProbability, + binaryDictionary.getNgramProbability(prevWordsInfoBeginningOfSentence, "aaa")); + assertEquals(bigramProbability, + binaryDictionary.getNgramProbability(prevWordsInfoBeginningOfSentence, "bbb")); + } + + public void testGetMaxFrequencyOfExactMatches() { + for (final int formatVersion : DICT_FORMAT_VERSIONS) { + testGetMaxFrequencyOfExactMatches(formatVersion); + } + } + + private void testGetMaxFrequencyOfExactMatches(final int formatVersion) { + File dictFile = null; + try { + dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + addUnigramWord(binaryDictionary, "abc", 10); + addUnigramWord(binaryDictionary, "aBc", 15); + assertEquals(15, binaryDictionary.getMaxFrequencyOfExactMatches("abc")); + addUnigramWord(binaryDictionary, "ab'c", 20); + assertEquals(20, binaryDictionary.getMaxFrequencyOfExactMatches("abc")); + addUnigramWord(binaryDictionary, "a-b-c", 25); + assertEquals(25, binaryDictionary.getMaxFrequencyOfExactMatches("abc")); + addUnigramWord(binaryDictionary, "ab-'-'-'-c", 30); + assertEquals(30, binaryDictionary.getMaxFrequencyOfExactMatches("abc")); + addUnigramWord(binaryDictionary, "ab c", 255); + assertEquals(30, binaryDictionary.getMaxFrequencyOfExactMatches("abc")); + } } diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java index c4fd5a0c4..6e894decf 100644 --- a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java +++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java @@ -50,8 +50,7 @@ public class BlueUnderlineTests extends InputTestsBase { final SpanGetter spanBefore = new SpanGetter(mEditText.getText(), SuggestionSpan.class); assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart); assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd); - assertEquals("extend blue underline, span color", true, - spanBefore.isAutoCorrectionIndicator()); + assertTrue("extend blue underline, span color", spanBefore.isAutoCorrectionIndicator()); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span @@ -61,6 +60,7 @@ public class BlueUnderlineTests extends InputTestsBase { public void testBlueUnderlineOnBackspace() { final String STRING_TO_TYPE = "tgis"; + final int typedLength = STRING_TO_TYPE.length(); final int EXPECTED_SUGGESTION_SPAN_START = -1; final int EXPECTED_UNDERLINE_SPAN_START = 0; final int EXPECTED_UNDERLINE_SPAN_END = 4; @@ -68,6 +68,8 @@ public class BlueUnderlineTests extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); type(Constants.CODE_SPACE); + // typedLength + 1 because we also typed a space + mLatinIME.onUpdateSelection(0, 0, typedLength + 1, typedLength + 1, -1, -1); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); type(Constants.CODE_DELETE); @@ -77,8 +79,8 @@ public class BlueUnderlineTests extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); final SpanGetter suggestionSpan = new SpanGetter(mEditText.getText(), SuggestionSpan.class); - assertEquals("show no blue underline after backspace, span start should be -1", - EXPECTED_SUGGESTION_SPAN_START, suggestionSpan.mStart); + assertFalse("show no blue underline after backspace, span should not be the auto-" + + "correction indicator", suggestionSpan.isAutoCorrectionIndicator()); final SpanGetter underlineSpan = new SpanGetter(mEditText.getText(), UnderlineSpan.class); assertEquals("should be composing, so should have an underline span", EXPECTED_UNDERLINE_SPAN_START, underlineSpan.mStart); @@ -104,7 +106,8 @@ public class BlueUnderlineTests extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class); - assertNull("blue underline removed when cursor is moved", span.mSpan); + assertFalse("blue underline removed when cursor is moved", + span.isAutoCorrectionIndicator()); } public void testComposingStopsOnSpace() { diff --git a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java new file mode 100644 index 000000000..70b8f530a --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java @@ -0,0 +1,151 @@ +/* + * 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.latin; + +import java.util.Locale; + +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatches; + +/** + * Unit test for DistracterFilter + */ +@LargeTest +public class DistracterFilterTest extends InputTestsBase { + private DistracterFilterCheckingExactMatches mDistracterFilter; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mDistracterFilter = new DistracterFilterCheckingExactMatches(getContext()); + mDistracterFilter.updateEnabledSubtypes(mLatinIME.getEnabledSubtypesForTest()); + } + + public void testIsDistractorToWordsInDictionaries() { + final PrevWordsInfo EMPTY_PREV_WORDS_INFO = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; + + final Locale localeEnUs = new Locale("en", "US"); + String typedWord; + + typedWord = "Bill"; + // For this test case, we consider "Bill" is a distracter to "bill". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "nOt"; + // For this test case, we consider "nOt" is a distracter to "not". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "youre"; + // For this test case, we consider "youre" is a distracter to "you're". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "Banana"; + // For this test case, we consider "Banana" is a distracter to "banana". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "orange"; + // For this test case, we consider "orange" is not a distracter to any word in dictionaries. + assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "Orange"; + // For this test case, we consider "Orange" is a distracter to "orange". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "café"; + // For this test case, we consider "café" is a distracter to "cafe". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "cafe"; + // For this test case, we consider "cafe" is not a distracter to any word in dictionaries. + assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "I'll"; + // For this test case, we consider "I'll" is not a distracter to any word in dictionaries. + assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "ill"; + // For this test case, we consider "ill" is a distracter to "I'll" + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "asdfd"; + // For this test case, we consider "asdfd" is not a distracter to any word in dictionaries. + assertFalse( + mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + typedWord = "thank"; + // For this test case, we consider "thank" is not a distracter to any other word + // in dictionaries. + assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs)); + + final Locale localeDeDe = new Locale("de", "DE"); + + typedWord = "fuer"; + // For this test case, we consider "fuer" is a distracter to "für". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe)); + + typedWord = "fUEr"; + // For this test case, we consider "fUEr" is a distracter to "für". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe)); + + typedWord = "fur"; + // For this test case, we consider "fur" is a distracter to "für". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe)); + + final Locale localeFrFr = new Locale("fr", "FR"); + + typedWord = "a"; + // For this test case, we consider "a" is a distracter to "à". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr)); + + typedWord = "à"; + // For this test case, we consider "à" is not a distracter to any word in dictionaries. + assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr)); + + typedWord = "etre"; + // For this test case, we consider "etre" is a distracter to "être". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr)); + + typedWord = "États-unis"; + // For this test case, we consider "États-unis" is a distracter to "États-Unis". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr)); + + typedWord = "ÉtatsUnis"; + // For this test case, we consider "ÉtatsUnis" is a distracter to "États-Unis". + assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries( + EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr)); + } +} diff --git a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java deleted file mode 100644 index 6aae1044e..000000000 --- a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2013 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.latin; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; - -/** - * Unit test for ExpandableDictionary - */ -@SmallTest -public class ExpandableDictionaryTests extends AndroidTestCase { - - private final static int UNIGRAM_FREQ = 50; - // See UserBinaryDictionary for more information about this variable. - // For tests, its actual value does not matter. - private final static int SHORTCUT_FREQ = 14; - - public void testAddWordAndGetWordFrequency() { - final ExpandableDictionary dict = new ExpandableDictionary(Dictionary.TYPE_USER); - - // Add words - dict.addWord("abcde", "abcde", UNIGRAM_FREQ, SHORTCUT_FREQ); - dict.addWord("abcef", null, UNIGRAM_FREQ + 1, 0); - - // Check words - assertFalse(dict.isValidWord("abcde")); - assertEquals(UNIGRAM_FREQ, dict.getWordFrequency("abcde")); - assertTrue(dict.isValidWord("abcef")); - assertEquals(UNIGRAM_FREQ+1, dict.getWordFrequency("abcef")); - - dict.addWord("abc", null, UNIGRAM_FREQ + 2, 0); - assertTrue(dict.isValidWord("abc")); - assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc")); - - // Add existing word with lower frequency - dict.addWord("abc", null, UNIGRAM_FREQ, 0); - assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc")); - - // Add existing word with higher frequency - dict.addWord("abc", null, UNIGRAM_FREQ + 3, 0); - assertEquals(UNIGRAM_FREQ + 3, dict.getWordFrequency("abc")); - } -} diff --git a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java index cadd0f8f3..09309bcc0 100644 --- a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java @@ -19,7 +19,9 @@ package com.android.inputmethod.latin; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.ProbabilityInfo; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; import java.util.HashMap; @@ -31,18 +33,18 @@ import java.util.HashMap; public class FusionDictionaryTests extends AndroidTestCase { public void testFindWordInTree() { FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); + new DictionaryOptions(new HashMap<String,String>())); - dict.add("abc", 10, null, false /* isNotAWord */); + dict.add("abc", new ProbabilityInfo(10), null, false /* isNotAWord */); assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aaa")); assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "abc")); - dict.add("aa", 10, null, false /* isNotAWord */); + dict.add("aa", new ProbabilityInfo(10), null, false /* isNotAWord */); assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aaa")); assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aa")); - dict.add("babcd", 10, null, false /* isNotAWord */); - dict.add("bacde", 10, null, false /* isNotAWord */); + dict.add("babcd", new ProbabilityInfo(10), null, false /* isNotAWord */); + dict.add("bacde", new ProbabilityInfo(10), null, false /* isNotAWord */); assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "ba")); assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "babcd")); assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "bacde")); diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 8ad8689d8..de9475af4 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -16,7 +16,10 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.settings.Settings; + import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; import android.view.inputmethod.BaseInputConnection; @LargeTest @@ -32,7 +35,7 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_TYPE = "this"; final String EXPECTED_RESULT = "thi"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); type(Constants.CODE_DELETE); assertEquals("press suggestion then backspace", EXPECTED_RESULT, @@ -44,9 +47,8 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_PICK = "this"; final String EXPECTED_RESULT = "thi"; type(WORD_TO_TYPE); - // Choose the auto-correction, which is always in position 0. For "tgis", the - // auto-correction should be "this". - pickSuggestionManually(0, WORD_TO_PICK); + // Choose the auto-correction. For "tgis", the auto-correction should be "this". + pickSuggestionManually(WORD_TO_PICK); mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK, mEditText.getText().toString()); @@ -59,9 +61,8 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_TYPE = "tgis"; final String EXPECTED_RESULT = "tgi"; type(WORD_TO_TYPE); - // Choose the typed word, which should be in position 1 (because position 0 should - // be occupied by the "this" auto-correction, as checked by testAutoCorrect()) - pickSuggestionManually(1, WORD_TO_TYPE); + // Choose the typed word. + pickSuggestionManually(WORD_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE, mEditText.getText().toString()); @@ -75,9 +76,8 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_PICK = "thus"; final String EXPECTED_RESULT = "thu"; type(WORD_TO_TYPE); - // Choose the second suggestion, which should be in position 2 and should be "thus" - // when "tgis is typed. - pickSuggestionManually(2, WORD_TO_PICK); + // Choose the second suggestion, which should be "thus" when "tgis" is typed. + pickSuggestionManually(WORD_TO_PICK); mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick different suggestion then backspace", WORD_TO_PICK, mEditText.getText().toString()); @@ -179,6 +179,8 @@ public class InputLogicTests extends InputTestsBase { } public void testDoubleSpace() { + // Set default pref just in case + setBooleanPreference(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true, true); // U+1F607 is an emoji final String[] STRINGS_TO_TYPE = new String[] { "this ", "a+ ", "\u1F607 ", ".. ", ") ", "( ", "% " }; @@ -200,6 +202,76 @@ public class InputLogicTests extends InputTestsBase { assertEquals("double space make a period", EXPECTED_RESULT, mEditText.getText().toString()); } + private void testDoubleSpacePeriodWithSettings(final boolean expectsPeriod, + final Object... settingsKeysValues) { + final Object[] oldSettings = new Object[settingsKeysValues.length / 2]; + final String STRING_WITHOUT_PERIOD = "this "; + final String STRING_WITH_PERIOD = "this. "; + final String EXPECTED_RESULT = expectsPeriod ? STRING_WITH_PERIOD : STRING_WITHOUT_PERIOD; + try { + for (int i = 0; i < settingsKeysValues.length; i += 2) { + if (settingsKeysValues[i + 1] instanceof String) { + oldSettings[i / 2] = setStringPreference((String)settingsKeysValues[i], + (String)settingsKeysValues[i + 1], "0"); + } else { + oldSettings[i / 2] = setBooleanPreference((String)settingsKeysValues[i], + (Boolean)settingsKeysValues[i + 1], false); + } + } + mLatinIME.loadSettings(); + mEditText.setText(""); + type(STRING_WITHOUT_PERIOD); + assertEquals("double-space-to-period with specific settings " + + TextUtils.join(" ", settingsKeysValues), + EXPECTED_RESULT, mEditText.getText().toString()); + } finally { + // Restore old settings + for (int i = 0; i < settingsKeysValues.length; i += 2) { + if (null == oldSettings[i / 2]) { + break; + } if (oldSettings[i / 2] instanceof String) { + setStringPreference((String)settingsKeysValues[i], (String)oldSettings[i / 2], + ""); + } else { + setBooleanPreference((String)settingsKeysValues[i], (Boolean)oldSettings[i / 2], + false); + } + } + } + } + + public void testDoubleSpacePeriod() { + // Reset settings to default, else these tests will go flaky. + setStringPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING, "0", "0"); + setStringPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD, "1", "1"); + setBooleanPreference(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true, true); + testDoubleSpacePeriodWithSettings(true /* expectsPeriod */); + // "Suggestion visibility" to "always hide" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_SHOW_SUGGESTIONS_SETTING, "2"); + // "Suggestion visibility" to "portrait only" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_SHOW_SUGGESTIONS_SETTING, "1"); + // "Suggestion visibility" to "always show" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_SHOW_SUGGESTIONS_SETTING, "0"); + + // "Double-space period" to "off" + testDoubleSpacePeriodWithSettings(false, Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, false); + + // "Auto-correction" to "off" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_AUTO_CORRECTION_THRESHOLD, "0"); + // "Auto-correction" to "modest" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_AUTO_CORRECTION_THRESHOLD, "1"); + // "Auto-correction" to "very aggressive" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_AUTO_CORRECTION_THRESHOLD, "3"); + + // "Suggestion visibility" to "always hide" and "Auto-correction" to "off" + testDoubleSpacePeriodWithSettings(true, Settings.PREF_SHOW_SUGGESTIONS_SETTING, "0", + Settings.PREF_AUTO_CORRECTION_THRESHOLD, "0"); + // "Suggestion visibility" to "always hide" and "Auto-correction" to "off" + testDoubleSpacePeriodWithSettings(false, Settings.PREF_SHOW_SUGGESTIONS_SETTING, "0", + Settings.PREF_AUTO_CORRECTION_THRESHOLD, "0", + Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, false); + } + public void testBackspaceAtStartAfterAutocorrect() { final String STRING_TO_TYPE = "tgis "; final int typedLength = STRING_TO_TYPE.length(); @@ -234,7 +306,7 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_TYPE = "this"; final String EXPECTED_RESULT = WORD_TO_TYPE; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); assertEquals("no space after manual pick", EXPECTED_RESULT, mEditText.getText().toString()); } @@ -244,7 +316,7 @@ public class InputLogicTests extends InputTestsBase { final String WORD2_TO_TYPE = "is"; final String EXPECTED_RESULT = "this is"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); type(WORD2_TO_TYPE); assertEquals("manual pick then type", EXPECTED_RESULT, mEditText.getText().toString()); } @@ -254,20 +326,32 @@ public class InputLogicTests extends InputTestsBase { final String WORD2_TO_TYPE = "!"; final String EXPECTED_RESULT = "this!"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); type(WORD2_TO_TYPE); assertEquals("manual pick then separator", EXPECTED_RESULT, mEditText.getText().toString()); } + // This test matches the one in InputLogicTestsNonEnglish. In some non-English languages, + // ! and ? are clustering punctuation signs. + public void testClusteringPunctuation() { + final String WORD1_TO_TYPE = "test"; + final String WORD2_TO_TYPE = "!!?!:!"; + final String EXPECTED_RESULT = "test!!?!:!"; + type(WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); + type(WORD2_TO_TYPE); + assertEquals("clustering punctuation", EXPECTED_RESULT, mEditText.getText().toString()); + } + public void testManualPickThenStripperThenPick() { final String WORD_TO_TYPE = "this"; final String STRIPPER = "\n"; final String EXPECTED_RESULT = "this\nthis"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); type(STRIPPER); type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); assertEquals("manual pick then \\n then manual pick", EXPECTED_RESULT, mEditText.getText().toString()); } @@ -277,7 +361,7 @@ public class InputLogicTests extends InputTestsBase { final String WORD2_TO_TYPE = " is"; final String EXPECTED_RESULT = "this is"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); type(WORD2_TO_TYPE); assertEquals("manual pick then space then type", EXPECTED_RESULT, mEditText.getText().toString()); @@ -288,11 +372,9 @@ public class InputLogicTests extends InputTestsBase { final String WORD2_TO_PICK = "is"; final String EXPECTED_RESULT = "this is"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); - // Here we fake picking a word through bigram prediction. This test is taking - // advantage of the fact that Latin IME blindly trusts the caller of #pickSuggestionManually - // to actually pass the right string. - pickSuggestionManually(1, WORD2_TO_PICK); + pickSuggestionManually(WORD1_TO_TYPE); + // Here we fake picking a word through bigram prediction. + pickSuggestionManually(WORD2_TO_PICK); assertEquals("manual pick then manual pick", EXPECTED_RESULT, mEditText.getText().toString()); } @@ -307,12 +389,14 @@ public class InputLogicTests extends InputTestsBase { } public void testResumeSuggestionOnBackspace() { - final String WORD_TO_TYPE = "and this "; - type(WORD_TO_TYPE); + final String STRING_TO_TYPE = "and this "; + final int typedLength = STRING_TO_TYPE.length(); + type(STRING_TO_TYPE); assertEquals("resume suggestion on backspace", -1, BaseInputConnection.getComposingSpanStart(mEditText.getText())); assertEquals("resume suggestion on backspace", -1, BaseInputConnection.getComposingSpanEnd(mEditText.getText())); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); type(Constants.CODE_DELETE); assertEquals("resume suggestion on backspace", 4, BaseInputConnection.getComposingSpanStart(mEditText.getText())); @@ -348,4 +432,211 @@ public class InputLogicTests extends InputTestsBase { helperTestComposing("a'", true); } // TODO: Add some tests for non-BMP characters + + public void testAutoCorrectByUserHistory() { + final String WORD_TO_BE_CORRECTED = "qpmx"; + final String NOT_CORRECTED_RESULT = "qpmx "; + final String DESIRED_WORD = "qpmz"; + final String CORRECTED_RESULT = "qpmz "; + final int typeCountNotToAutocorrect = 1; + final int typeCountToAutoCorrect = 16; + int startIndex = 0; + int endIndex = 0; + + for (int i = 0; i < typeCountNotToAutocorrect; i++) { + type(DESIRED_WORD); + type(Constants.CODE_SPACE); + } + startIndex = mEditText.getText().length(); + type(WORD_TO_BE_CORRECTED); + type(Constants.CODE_SPACE); + endIndex = mEditText.getText().length(); + assertEquals("not auto-corrected by user history", NOT_CORRECTED_RESULT, + mEditText.getText().subSequence(startIndex, endIndex).toString()); + for (int i = typeCountNotToAutocorrect; i < typeCountToAutoCorrect; i++) { + type(DESIRED_WORD); + type(Constants.CODE_SPACE); + } + startIndex = mEditText.getText().length(); + type(WORD_TO_BE_CORRECTED); + type(Constants.CODE_SPACE); + endIndex = mEditText.getText().length(); + assertEquals("auto-corrected by user history", + CORRECTED_RESULT, mEditText.getText().subSequence(startIndex, endIndex).toString()); + } + + public void testPredictionsAfterSpace() { + final String WORD_TO_TYPE = "Barack "; + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the first prediction is displayed + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions after space", "Obama", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + + public void testPredictionsWithDoubleSpaceToPeriod() { + mLatinIME.clearPersonalizedDictionariesForTest(); + final String WORD_TO_TYPE = "Barack "; + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // No need to test here, testPredictionsAfterSpace is testing it already + type(" "); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the predictions have been cleared + SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions cleared after double-space-to-period", suggestedWords.size(), 0); + type(Constants.CODE_DELETE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the first prediction is displayed + suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions after cancel double-space-to-period", "Obama", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + + public void testPredictionsAfterManualPick() { + final String WORD_TO_TYPE = "Barack"; + type(WORD_TO_TYPE); + // Choose the auto-correction. For "Barack", the auto-correction should be "Barack". + pickSuggestionManually(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the first prediction is displayed + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions after manual pick", "Obama", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + + public void testPredictionsAfterPeriod() { + mLatinIME.clearPersonalizedDictionariesForTest(); + final String WORD_TO_TYPE = "Barack. "; + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("No prediction after period after inputting once.", 0, suggestedWords.size()); + + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("Beginning-of-Sentence prediction after inputting 2 times.", "Barack", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + + public void testPredictionsAfterRecorrection() { + final String PREFIX = "A "; + final String WORD_TO_TYPE = "Barack"; + final String FIRST_NON_TYPED_SUGGESTION = "Barrack"; + final int endOfPrefix = PREFIX.length(); + final int endOfWord = endOfPrefix + WORD_TO_TYPE.length(); + final int endOfSuggestion = endOfPrefix + FIRST_NON_TYPED_SUGGESTION.length(); + final int indexForManualCursor = endOfPrefix + 3; // +3 because it's after "Bar" in "Barack" + type(PREFIX); + mLatinIME.onUpdateSelection(0, 0, endOfPrefix, endOfPrefix, -1, -1); + type(WORD_TO_TYPE); + pickSuggestionManually(FIRST_NON_TYPED_SUGGESTION); + mLatinIME.onUpdateSelection(endOfPrefix, endOfPrefix, endOfSuggestion, endOfSuggestion, + -1, -1); + runMessages(); + type(" "); + mLatinIME.onUpdateSelection(endOfSuggestion, endOfSuggestion, + endOfSuggestion + 1, endOfSuggestion + 1, -1, -1); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Simulate a manual cursor move + mInputConnection.setSelection(indexForManualCursor, indexForManualCursor); + mLatinIME.onUpdateSelection(endOfSuggestion + 1, endOfSuggestion + 1, + indexForManualCursor, indexForManualCursor, -1, -1); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + pickSuggestionManually(WORD_TO_TYPE); + mLatinIME.onUpdateSelection(indexForManualCursor, indexForManualCursor, + endOfWord, endOfWord, -1, -1); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the first prediction is displayed + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions after recorrection", "Obama", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + + public void testComposingMultipleBackspace() { + final String WORD_TO_TYPE = "radklro"; + final int TIMES_TO_TYPE = 3; + final int TIMES_TO_BACKSPACE = 8; + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + assertEquals("composing with multiple backspace", + WORD_TO_TYPE.length() * TIMES_TO_TYPE - TIMES_TO_BACKSPACE, + mEditText.getText().length()); + } + + public void testManySingleQuotes() { + final String WORD_TO_AUTOCORRECT = "i"; + final String WORD_AUTOCORRECTED = "I"; + final String QUOTES = "''''''''''''''''''''"; + final String WORD_TO_TYPE = WORD_TO_AUTOCORRECT + QUOTES + " "; + final String EXPECTED_RESULT = WORD_AUTOCORRECTED + QUOTES + " "; + type(WORD_TO_TYPE); + assertEquals("auto-correct with many trailing single quotes", EXPECTED_RESULT, + mEditText.getText().toString()); + } + + public void testManySingleQuotesOneByOne() { + final String WORD_TO_AUTOCORRECT = "i"; + final String WORD_AUTOCORRECTED = "I"; + final String QUOTES = "''''''''''''''''''''"; + final String WORD_TO_TYPE = WORD_TO_AUTOCORRECT + QUOTES + " "; + final String EXPECTED_RESULT = WORD_AUTOCORRECTED + QUOTES + " "; + + for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { + type(WORD_TO_TYPE.substring(i, i+1)); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + } + assertEquals("type many trailing single quotes one by one", EXPECTED_RESULT, + mEditText.getText().toString()); + } + + public void testTypingSingleQuotesOneByOne() { + final String WORD_TO_TYPE = "it's "; + final String EXPECTED_RESULT = WORD_TO_TYPE; + for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { + type(WORD_TO_TYPE.substring(i, i+1)); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + } + assertEquals("type words letter by letter", EXPECTED_RESULT, + mEditText.getText().toString()); + } + + public void testSwitchLanguages() { + final String WORD_TO_TYPE_FIRST_PART = "com"; + final String WORD_TO_TYPE_SECOND_PART = "md"; + final String EXPECTED_RESULT = "comme"; + changeLanguage("en"); + type(WORD_TO_TYPE_FIRST_PART); + changeLanguage("fr"); + runMessages(); + type(WORD_TO_TYPE_SECOND_PART); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("Suggestions updated after switching languages", + EXPECTED_RESULT, suggestedWords.size() > 0 ? suggestedWords.getWord(1) : null); + } } diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java index 0f0ebafb9..2560407dc 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java @@ -19,8 +19,6 @@ package com.android.inputmethod.latin; import android.test.suitebuilder.annotation.LargeTest; import android.view.inputmethod.BaseInputConnection; -import com.android.inputmethod.latin.suggestions.SuggestionStripView; - @LargeTest public class InputLogicTestsLanguageWithoutSpaces extends InputTestsBase { public void testAutoCorrectForLanguageWithoutSpaces() { @@ -99,7 +97,8 @@ public class InputLogicTestsLanguageWithoutSpaces extends InputTestsBase { assertEquals("predictions in lang without spaces", "Barack", mEditText.getText().toString()); // Test the first prediction is displayed + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); assertEquals("predictions in lang without spaces", "Obama", - mLatinIME.getFirstSuggestedWord()); + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); } } diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java index 2d736e338..866f8894c 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java @@ -18,8 +18,6 @@ package com.android.inputmethod.latin; import android.test.suitebuilder.annotation.LargeTest; -import com.android.inputmethod.latin.suggestions.SuggestionStripView; - @LargeTest public class InputLogicTestsNonEnglish extends InputTestsBase { final String NEXT_WORD_PREDICTION_OPTION = "next_word_prediction"; @@ -39,12 +37,25 @@ public class InputLogicTestsNonEnglish extends InputTestsBase { final String EXPECTED_RESULT = "test !"; changeLanguage("fr"); type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); type(WORD2_TO_TYPE); assertEquals("manual pick then separator for French", EXPECTED_RESULT, mEditText.getText().toString()); } + public void testClusteringPunctuationForFrench() { + final String WORD1_TO_TYPE = "test"; + final String WORD2_TO_TYPE = "!!?!:!"; + // In English, the expected result would be "test!!?!:!" + final String EXPECTED_RESULT = "test !!?! : !"; + changeLanguage("fr"); + type(WORD1_TO_TYPE); + pickSuggestionManually(WORD1_TO_TYPE); + type(WORD2_TO_TYPE); + assertEquals("clustering punctuation for French", EXPECTED_RESULT, + mEditText.getText().toString()); + } + public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() { final String WORD_TO_TYPE = "test "; final String PUNCTUATION_FROM_STRIP = "!"; @@ -60,9 +71,9 @@ public class InputLogicTestsNonEnglish extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); assertTrue("type word then type space should display punctuation strip", - mLatinIME.isShowingPunctuationList()); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); + mLatinIME.getSuggestedWordsForTest().isPunctuationSuggestions()); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); assertEquals("type word then type space then punctuation from strip twice for French", EXPECTED_RESULT, mEditText.getText().toString()); } finally { @@ -84,8 +95,9 @@ public class InputLogicTestsNonEnglish extends InputTestsBase { type(WORD_TO_TYPE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); + final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); assertEquals("type word then type space yields predictions for French", - EXPECTED_RESULT, mLatinIME.getFirstSuggestedWord()); + EXPECTED_RESULT, suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); } finally { setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, previousNextWordPredictionOption, defaultNextWordPredictionOption); diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsReorderingMyanmar.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsReorderingMyanmar.java new file mode 100644 index 000000000..61eae4e8b --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsReorderingMyanmar.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012 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.latin; + +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Pair; + +/* + * Relevant characters for this test : + * Spurs the need to reorder : + * U+1031 MYANMAR VOWEL SIGN E : ေ + * U+1004 U+103A U+1039 Kinzi. It's a compound character. + * + * List of consonants : + * U+1000 MYANMAR LETTER KA က + * U+1001 MYANMAR LETTER KHA ခ + * U+1002 MYANMAR LETTER GA ဂ + * U+1003 MYANMAR LETTER GHA ဃ + * U+1004 MYANMAR LETTER NGA င + * U+1005 MYANMAR LETTER CA စ + * U+1006 MYANMAR LETTER CHA ဆ + * U+1007 MYANMAR LETTER JA ဇ + * U+1008 MYANMAR LETTER JHA ဈ + * U+1009 MYANMAR LETTER NYA ဉ + * U+100A MYANMAR LETTER NNYA ည + * U+100B MYANMAR LETTER TTA ဋ + * U+100C MYANMAR LETTER TTHA ဌ + * U+100D MYANMAR LETTER DDA ဍ + * U+100E MYANMAR LETTER DDHA ဎ + * U+100F MYANMAR LETTER NNA ဏ + * U+1010 MYANMAR LETTER TA တ + * U+1011 MYANMAR LETTER THA ထ + * U+1012 MYANMAR LETTER DA ဒ + * U+1013 MYANMAR LETTER DHA ဓ + * U+1014 MYANMAR LETTER NA န + * U+1015 MYANMAR LETTER PA ပ + * U+1016 MYANMAR LETTER PHA ဖ + * U+1017 MYANMAR LETTER BA ဗ + * U+1018 MYANMAR LETTER BHA ဘ + * U+1019 MYANMAR LETTER MA မ + * U+101A MYANMAR LETTER YA ယ + * U+101B MYANMAR LETTER RA ရ + * U+101C MYANMAR LETTER LA လ + * U+101D MYANMAR LETTER WA ဝ + * U+101E MYANMAR LETTER SA သ + * U+101F MYANMAR LETTER HA ဟ + * U+1020 MYANMAR LETTER LLA ဠ + * U+103F MYANMAR LETTER GREAT SA ဿ + * + * List of medials : + * U+103B MYANMAR CONSONANT SIGN MEDIAL YA ျ + * U+103C MYANMAR CONSONANT SIGN MEDIAL RA ြ + * U+103D MYANMAR CONSONANT SIGN MEDIAL WA ွ + * U+103E MYANMAR CONSONANT SIGN MEDIAL HA ှ + * U+105E MYANMAR CONSONANT SIGN MON MEDIAL NA ၞ + * U+105F MYANMAR CONSONANT SIGN MON MEDIAL MA ၟ + * U+1060 MYANMAR CONSONANT SIGN MON MEDIAL LA ၠ + * U+1082 MYANMAR CONSONANT SIGN SHAN MEDIAL WA ႂ + * + * Other relevant characters : + * U+200C ZERO WIDTH NON-JOINER + * U+200B ZERO WIDTH SPACE + */ + +@LargeTest +@SuppressWarnings("rawtypes") +public class InputLogicTestsReorderingMyanmar extends InputTestsBase { + // The tests are formatted as follows. + // Each test is an entry in the array of Pair arrays. + + // One test is an array of pairs. Each pair contains, in the `first' member, + // the code points that the next key press should contain. In the `second' + // member is stored the string that should be in the text view after this + // key press. + + private static final Pair[][] TESTS = { + + // Tests for U+1031 MYANMAR VOWEL SIGN E : ေ + new Pair[] { // Type : U+1031 U+1000 U+101F ေ က ဟ + Pair.create(new int[] { 0x1031 }, "\u1031"), // ေ + Pair.create(new int[] { 0x1000 }, "\u1000\u1031"), // ကေ + Pair.create(new int[] { 0x101F }, "\u1000\u1031\u101F") // ကေဟ + }, + + new Pair[] { // Type : U+1000 U+1031 U+101F က ေ ဟ + Pair.create(new int[] { 0x1000 }, "\u1000"), // က + Pair.create(new int[] { 0x1031 }, "\u1000\u200B\u1031"), // ကေ + Pair.create(new int[] { 0x101F }, "\u1000\u101F\u1031") // ကဟေ + }, + + new Pair[] { // Type : U+1031 U+101D U+103E U+1018 ေ ဝ ှ ဘ + Pair.create(new int[] { 0x1031 }, "\u1031"), // ေ + Pair.create(new int[] { 0x101D }, "\u101D\u1031"), // ဝေ + Pair.create(new int[] { 0x103E }, "\u101D\u103E\u1031"), // ဝှေ + Pair.create(new int[] { 0x1018 }, "\u101D\u103E\u1031\u1018") // ဝှေဘ + }, + + new Pair[] { // Type : U+1031 U+1014 U+1031 U+1000 U+102C U+1004 U+103A U+1038 U+101C + // U+102C U+1038 U+104B ေ န ေ က ာ င ် း လ ာ း ။ + Pair.create(new int[] { 0x1031 }, "\u1031"), // ေ + Pair.create(new int[] { 0x1014 }, "\u1014\u1031"), // နေ + Pair.create(new int[] { 0x1031 }, "\u1014\u1031\u1031"), // နေေ + Pair.create(new int[] { 0x1000 }, "\u1014\u1031\u1000\u1031"), // နေကေ + Pair.create(new int[] { 0x102C }, "\u1014\u1031\u1000\u1031\u102C"), // နေကော + Pair.create(new int[] { 0x1004 }, "\u1014\u1031\u1000\u1031\u102C\u1004"), // နေကောင + Pair.create(new int[] { 0x103A }, // နေကောင် + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A"), + Pair.create(new int[] { 0x1038 }, // နေကောင်း + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A\u1038"), + Pair.create(new int[] { 0x101C }, // နေကောင်းလ + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A\u1038\u101C"), + Pair.create(new int[] { 0x102C }, // နေကောင်းလာ + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A\u1038\u101C\u102C"), + Pair.create(new int[] { 0x1038 }, // နေကောင်းလား + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A\u1038\u101C\u102C\u1038"), + Pair.create(new int[] { 0x104B }, // နေကောင်းလား။ + "\u1014\u1031\u1000\u1031\u102C\u1004\u103A\u1038\u101C\u102C\u1038\u104B") + }, + + new Pair[] { // Type : U+1031 U+1031 U+1031 U+1000 ေ ေ ေ က + Pair.create(new int[] { 0x1031 }, "\u1031"), // ေ + Pair.create(new int[] { 0x1031 }, "\u1031\u1031"), // ေေ + Pair.create(new int[] { 0x1031 }, "\u1031\u1031\u1031"), // U+1031ေေေ + Pair.create(new int[] { 0x1000 }, "\u1031\u1031\u1000\u1031") // ေေကေ + }, + + new Pair[] { // Type : U+1031 U+1001 U+103B U+103D U+1038 ေ ခ ျ ွ း + Pair.create(new int[] { 0x1031 }, "\u1031"), // ေ + Pair.create(new int[] { 0x1001 }, "\u1001\u1031"), // ခေ + Pair.create(new int[] { 0x103B }, "\u1001\u103B\u1031"), // ချေ + Pair.create(new int[] { 0x103D }, "\u1001\u103B\u103D\u1031"), // ချွေ + Pair.create(new int[] { 0x1038 }, "\u1001\u103B\u103D\u1031\u1038") // ချွေး + }, + + // Tests for Kinzi U+1004 U+103A U+1039 : + + /* Kinzi reordering is not implemented yet. Uncomment these tests when it is. + + new Pair[] { // Type : U+1021 U+1002 (U+1004 U+103A U+1039) + // U+101C U+1014 U+103A အ ဂ (င ် ္) လ န ် + Pair.create(new int[] { 0x1021 }, "\u1021"), // အ + Pair.create(new int[] { 0x1002 }, "\u1021\u1002"), // အဂ + Pair.create(new int[] { 0x1004, 0x103A, 0x1039 }, // အင်္ဂ + "\u1021\u1004\u103A\u1039\u1002"), + Pair.create(new int[] { 0x101C }, // အင်္ဂလ + "\u1021\u1004\u103A\u1039\u1002\u101C"), + Pair.create(new int[] { 0x1014 }, // အင်္ဂလန + "\u1021\u1004\u103A\u1039\u1002\u101C\u1014"), + Pair.create(new int[] { 0x103A }, // အင်္ဂလန် + "\u1021\u1004\u103A\u1039\u1002\u101C\u1014\u103A") + }, + + new Pair[] { //Type : kinzi after a whole syllable U+101E U+1001 U+103B U+102D U+102F + // (U+1004 U+103A U+1039) U+1004 U+103A U+1038 သ ခ ျ ိ ု င ် ္ င ် း + Pair.create(new int[] { 0x101E }, "\u101E"), // သခ + Pair.create(new int[] { 0x1001 }, "\u101E\u1001"), // သခ + Pair.create(new int[] { 0x103B }, "\u101E\u1001\u103B"), // သချ + Pair.create(new int[] { 0x102D }, "\u101E\u1001\u103B\u102D"), // သချိ + Pair.create(new int[] { 0x102F }, "\u101E\u1001\u103B\u102D\u102F"), // သချို + Pair.create(new int[] { 0x1004, 0x103A, 0x1039}, // သင်္ချို + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F"), + Pair.create(new int[] { 0x1004 }, // သင်္ချိုင + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004"), + Pair.create(new int[] { 0x103A }, // သင်္ချိုင် + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004\u103A"), + Pair.create(new int[] { 0x1038 }, // သင်္ချိုင်း + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004\u103A\u1038") + }, + + new Pair[] { // Type : kinzi after the consonant U+101E U+1001 (U+1004 U+103A U+1039) + // U+103B U+102D U+102F U+1004 U+103A U+1038 သ ခ င ် ္ ျ ိ ု င ် း + Pair.create(new int[] { 0x101E }, "\u101E"), // သခ + Pair.create(new int[] { 0x1001 }, "\u101E\u1001"), // သခ + Pair.create(new int[] { 0x1004, 0x103A, 0x1039 }, // သင်္ခ + "\u101E\u1004\u103A\u1039\u1001"), + Pair.create(new int[] { 0x103B }, // သင်္ချ + "\u101E\u1004\u103A\u1039\u1001\u103B"), + Pair.create(new int[] { 0x102D }, // သင်္ချိ + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D"), + Pair.create(new int[] { 0x102F }, // သင်္ချို + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F"), + Pair.create(new int[] { 0x1004 }, // သင်္ချိုင + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004"), + Pair.create(new int[] { 0x103A }, // သင်္ချိုင် + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004\u103A"), + Pair.create(new int[] { 0x1038 }, // သင်္ချိုင်း + "\u101E\u1004\u103A\u1039\u1001\u103B\u102D\u102F\u1004\u103A\u1038") + }, + */ + }; + + @SuppressWarnings("unchecked") + private void doMyanmarTest(final int testNumber, final Pair[] test) { + int stepNumber = 0; + for (final Pair<int[], String> step : test) { + ++stepNumber; + final int[] input = step.first; + final String expectedResult = step.second; + if (input.length > 1) { + mLatinIME.onTextInput(new String(input, 0, input.length)); + } else { + type(input[0]); + } + assertEquals("Myanmar reordering test " + testNumber + ", step " + stepNumber, + expectedResult, mEditText.getText().toString()); + } + } + + public void testMyanmarReordering() { + int testNumber = 0; + changeLanguage("my_MM", "CombiningRules=MyanmarReordering"); + for (final Pair[] test : TESTS) { + // Small trick to reset LatinIME : setText("") and send updateSelection with values + // LatinIME has never seen, and cursor pos 0,0. + mEditText.setText(""); + mLatinIME.onUpdateSelection(1, 1, 0, 0, -1, -1); + doMyanmarTest(++testNumber, test); + } + } +} diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java index 5095f9606..1a47cddf4 100644 --- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java +++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java @@ -55,14 +55,22 @@ public class InputPointersTests extends AndroidTestCase { final InputPointers src = new InputPointers(DEFAULT_CAPACITY); final int limit = src.getXCoordinates().length * 2 + 10; for (int i = 0; i < limit; i++) { - src.addPointer(i, i * 2, i * 3, i * 4); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + src.addPointer(x, y, pointerId, time); assertEquals("size after add " + i, i + 1, src.getPointerSize()); } for (int i = 0; i < limit; i++) { - assertEquals("xCoordinates at " + i, i, src.getXCoordinates()[i]); - assertEquals("yCoordinates at " + i, i * 2, src.getYCoordinates()[i]); - assertEquals("pointerIds at " + i, i * 3, src.getPointerIds()[i]); - assertEquals("times at " + i, i * 4, src.getTimes()[i]); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + assertEquals("xCoordinates at " + i, x, src.getXCoordinates()[i]); + assertEquals("yCoordinates at " + i, y, src.getYCoordinates()[i]); + assertEquals("pointerIds at " + i, pointerId, src.getPointerIds()[i]); + assertEquals("times at " + i, time, src.getTimes()[i]); } } @@ -70,14 +78,22 @@ public class InputPointersTests extends AndroidTestCase { final InputPointers src = new InputPointers(DEFAULT_CAPACITY); final int limit = 1000, step = 100; for (int i = 0; i < limit; i += step) { - src.addPointer(i, i, i * 2, i * 3, i * 4); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + src.addPointerAt(i, x, y, pointerId, time); assertEquals("size after add at " + i, i + 1, src.getPointerSize()); } for (int i = 0; i < limit; i += step) { - assertEquals("xCoordinates at " + i, i, src.getXCoordinates()[i]); - assertEquals("yCoordinates at " + i, i * 2, src.getYCoordinates()[i]); - assertEquals("pointerIds at " + i, i * 3, src.getPointerIds()[i]); - assertEquals("times at " + i, i * 4, src.getTimes()[i]); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + assertEquals("xCoordinates at " + i, x, src.getXCoordinates()[i]); + assertEquals("yCoordinates at " + i, y, src.getYCoordinates()[i]); + assertEquals("pointerIds at " + i, pointerId, src.getPointerIds()[i]); + assertEquals("times at " + i, time, src.getTimes()[i]); } } @@ -85,7 +101,11 @@ public class InputPointersTests extends AndroidTestCase { final InputPointers src = new InputPointers(DEFAULT_CAPACITY); final int limit = src.getXCoordinates().length * 2 + 10; for (int i = 0; i < limit; i++) { - src.addPointer(i, i * 2, i * 3, i * 4); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + src.addPointer(x, y, pointerId, time); } final InputPointers dst = new InputPointers(DEFAULT_CAPACITY); dst.set(src); @@ -100,7 +120,11 @@ public class InputPointersTests extends AndroidTestCase { final InputPointers src = new InputPointers(DEFAULT_CAPACITY); final int limit = 100; for (int i = 0; i < limit; i++) { - src.addPointer(i, i * 2, i * 3, i * 4); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + src.addPointer(x, y, pointerId, time); } final InputPointers dst = new InputPointers(DEFAULT_CAPACITY); dst.copy(src); @@ -121,106 +145,135 @@ public class InputPointersTests extends AndroidTestCase { } public void testAppend() { - final InputPointers src = new InputPointers(DEFAULT_CAPACITY); - final int srcLen = 100; - for (int i = 0; i < srcLen; i++) { - src.addPointer(i, i * 2, i * 3, i * 4); - } - final int dstLen = 50; + final int dstLength = 50; final InputPointers dst = new InputPointers(DEFAULT_CAPACITY); - for (int i = 0; i < dstLen; i++) { - final int value = -i - 1; - dst.addPointer(value * 4, value * 3, value * 2, value); + for (int i = 0; i < dstLength; i++) { + final int x = i * 4; + final int y = i * 3; + final int pointerId = i * 2; + final int time = i; + dst.addPointer(x, y, pointerId, time); } final InputPointers dstCopy = new InputPointers(DEFAULT_CAPACITY); dstCopy.copy(dst); - dst.append(src, 0, 0); - assertEquals("size after append zero", dstLen, dst.getPointerSize()); + final ResizableIntArray srcXCoords = new ResizableIntArray(DEFAULT_CAPACITY); + final ResizableIntArray srcYCoords = new ResizableIntArray(DEFAULT_CAPACITY); + final ResizableIntArray srcPointerIds = new ResizableIntArray(DEFAULT_CAPACITY); + final ResizableIntArray srcTimes = new ResizableIntArray(DEFAULT_CAPACITY); + final int srcLength = 100; + final int srcPointerId = 10; + for (int i = 0; i < srcLength; i++) { + final int x = i; + final int y = i * 2; + // The time value must be larger than <code>dst</code>. + final int time = i * 4 + dstLength; + srcXCoords.add(x); + srcYCoords.add(y); + srcPointerIds.add(srcPointerId); + srcTimes.add(time); + } + + final int startPos = 0; + dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, + startPos, 0 /* length */); + assertEquals("size after append zero", dstLength, dst.getPointerSize()); assertIntArrayEquals("xCoordinates after append zero", - dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); + dstCopy.getXCoordinates(), startPos, dst.getXCoordinates(), startPos, dstLength); assertIntArrayEquals("yCoordinates after append zero", - dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); + dstCopy.getYCoordinates(), startPos, dst.getYCoordinates(), startPos, dstLength); assertIntArrayEquals("pointerIds after append zero", - dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); + dstCopy.getPointerIds(), startPos, dst.getPointerIds(), startPos, dstLength); assertIntArrayEquals("times after append zero", - dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); + dstCopy.getTimes(), startPos, dst.getTimes(), startPos, dstLength); - dst.append(src, 0, srcLen); - assertEquals("size after append", dstLen + srcLen, dst.getPointerSize()); + dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, + startPos, srcLength); + assertEquals("size after append", dstLength + srcLength, dst.getPointerSize()); assertTrue("primitive length after append", - dst.getPointerIds().length >= dstLen + srcLen); + dst.getPointerIds().length >= dstLength + srcLength); assertIntArrayEquals("original xCoordinates values after append", - dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); + dstCopy.getXCoordinates(), startPos, dst.getXCoordinates(), startPos, dstLength); assertIntArrayEquals("original yCoordinates values after append", - dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); + dstCopy.getYCoordinates(), startPos, dst.getYCoordinates(), startPos, dstLength); assertIntArrayEquals("original pointerIds values after append", - dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); + dstCopy.getPointerIds(), startPos, dst.getPointerIds(), startPos, dstLength); assertIntArrayEquals("original times values after append", - dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); + dstCopy.getTimes(), startPos, dst.getTimes(), startPos, dstLength); assertIntArrayEquals("appended xCoordinates values after append", - src.getXCoordinates(), 0, dst.getXCoordinates(), dstLen, srcLen); + srcXCoords.getPrimitiveArray(), startPos, dst.getXCoordinates(), + dstLength, srcLength); assertIntArrayEquals("appended yCoordinates values after append", - src.getYCoordinates(), 0, dst.getYCoordinates(), dstLen, srcLen); + srcYCoords.getPrimitiveArray(), startPos, dst.getYCoordinates(), + dstLength, srcLength); assertIntArrayEquals("appended pointerIds values after append", - src.getPointerIds(), 0, dst.getPointerIds(), dstLen, srcLen); + srcPointerIds.getPrimitiveArray(), startPos, dst.getPointerIds(), + dstLength, srcLength); assertIntArrayEquals("appended times values after append", - src.getTimes(), 0, dst.getTimes(), dstLen, srcLen); + srcTimes.getPrimitiveArray(), startPos, dst.getTimes(), dstLength, srcLength); } public void testAppendResizableIntArray() { - final int srcLen = 100; + final int dstLength = 50; + final InputPointers dst = new InputPointers(DEFAULT_CAPACITY); + for (int i = 0; i < dstLength; i++) { + final int x = i * 4; + final int y = i * 3; + final int pointerId = i * 2; + final int time = i; + dst.addPointer(x, y, pointerId, time); + } + final InputPointers dstCopy = new InputPointers(DEFAULT_CAPACITY); + dstCopy.copy(dst); + + final int srcLength = 100; final int srcPointerId = 1; - final int[] srcPointerIds = new int[srcLen]; + final int[] srcPointerIds = new int[srcLength]; Arrays.fill(srcPointerIds, srcPointerId); final ResizableIntArray srcTimes = new ResizableIntArray(DEFAULT_CAPACITY); final ResizableIntArray srcXCoords = new ResizableIntArray(DEFAULT_CAPACITY); final ResizableIntArray srcYCoords= new ResizableIntArray(DEFAULT_CAPACITY); - for (int i = 0; i < srcLen; i++) { - srcTimes.add(i * 2); - srcXCoords.add(i * 3); - srcYCoords.add(i * 4); + for (int i = 0; i < srcLength; i++) { + // The time value must be larger than <code>dst</code>. + final int time = i * 2 + dstLength; + final int x = i * 3; + final int y = i * 4; + srcTimes.add(time); + srcXCoords.add(x); + srcYCoords.add(y); } - final int dstLen = 50; - final InputPointers dst = new InputPointers(DEFAULT_CAPACITY); - for (int i = 0; i < dstLen; i++) { - final int value = -i - 1; - dst.addPointer(value * 4, value * 3, value * 2, value); - } - final InputPointers dstCopy = new InputPointers(DEFAULT_CAPACITY); - dstCopy.copy(dst); dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, 0); - assertEquals("size after append zero", dstLen, dst.getPointerSize()); + assertEquals("size after append zero", dstLength, dst.getPointerSize()); assertIntArrayEquals("xCoordinates after append zero", - dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); + dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLength); assertIntArrayEquals("yCoordinates after append zero", - dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); + dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLength); assertIntArrayEquals("pointerIds after append zero", - dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); + dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLength); assertIntArrayEquals("times after append zero", - dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); + dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLength); - dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLen); - assertEquals("size after append", dstLen + srcLen, dst.getPointerSize()); + dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLength); + assertEquals("size after append", dstLength + srcLength, dst.getPointerSize()); assertTrue("primitive length after append", - dst.getPointerIds().length >= dstLen + srcLen); + dst.getPointerIds().length >= dstLength + srcLength); assertIntArrayEquals("original xCoordinates values after append", - dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); + dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLength); assertIntArrayEquals("original yCoordinates values after append", - dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); + dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLength); assertIntArrayEquals("original pointerIds values after append", - dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); + dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLength); assertIntArrayEquals("original times values after append", - dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); + dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLength); assertIntArrayEquals("appended xCoordinates values after append", - srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLen, srcLen); + srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLength, srcLength); assertIntArrayEquals("appended yCoordinates values after append", - srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLen, srcLen); + srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLength, srcLength); assertIntArrayEquals("appended pointerIds values after append", - srcPointerIds, 0, dst.getPointerIds(), dstLen, srcLen); + srcPointerIds, 0, dst.getPointerIds(), dstLength, srcLength); assertIntArrayEquals("appended times values after append", - srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLen, srcLen); + srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLength, srcLength); } // TODO: Consolidate this method with @@ -250,14 +303,24 @@ public class InputPointersTests extends AndroidTestCase { final int limit = 100; final int shiftAmount = 20; for (int i = 0; i < limit; i++) { - src.addPointer(i, i * 2, i * 3, i * 4); + final int x = i; + final int y = i * 2; + final int pointerId = i * 3; + final int time = i * 4; + src.addPointer(x, y, pointerId, time); } src.shift(shiftAmount); + assertEquals("length after shift", src.getPointerSize(), limit - shiftAmount); for (int i = 0; i < limit - shiftAmount; ++i) { - assertEquals("xCoordinates at " + i, i + shiftAmount, src.getXCoordinates()[i]); - assertEquals("yCoordinates at " + i, (i + shiftAmount) * 2, src.getYCoordinates()[i]); - assertEquals("pointerIds at " + i, (i + shiftAmount) * 3, src.getPointerIds()[i]); - assertEquals("times at " + i, (i + shiftAmount) * 4, src.getTimes()[i]); + final int oldIndex = i + shiftAmount; + final int x = oldIndex; + final int y = oldIndex * 2; + final int pointerId = oldIndex * 3; + final int time = oldIndex * 4; + assertEquals("xCoordinates at " + i, x, src.getXCoordinates()[i]); + assertEquals("yCoordinates at " + i, y, src.getYCoordinates()[i]); + assertEquals("pointerIds at " + i, pointerId, src.getPointerIds()[i]); + assertEquals("times at " + i, time, src.getTimes()[i]); } } } diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java index b9b52a6f3..986fb1097 100644 --- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -25,35 +25,48 @@ import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.style.CharacterStyle; import android.text.style.SuggestionSpan; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodSubtype; import android.widget.EditText; import android.widget.FrameLayout; +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.settings.DebugSettings; +import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.Locale; +import java.util.concurrent.TimeUnit; public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { + private static final String TAG = InputTestsBase.class.getSimpleName(); - private static final String PREF_DEBUG_MODE = "debug_mode"; + // Default value for auto-correction threshold. This is the string representation of the + // index in the resources array of auto-correction threshold settings. + private static final String DEFAULT_AUTO_CORRECTION_THRESHOLD = "1"; - // The message that sets the underline is posted with a 200 ms delay - protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; + // The message that sets the underline is posted with a 500 ms delay + protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 500; // The message that sets predictions is posted with a 200 ms delay protected static final int DELAY_TO_WAIT_FOR_PREDICTIONS = 200; + private final int TIMEOUT_TO_WAIT_FOR_LOADING_MAIN_DICTIONARY_IN_SECONDS = 60; protected LatinIME mLatinIME; protected Keyboard mKeyboard; protected MyEditText mEditText; protected View mInputView; protected InputConnection mInputConnection; + private boolean mPreviousBigramPredictionSettings; + private String mPreviousAutoCorrectSetting; // A helper class to ease span tests public static class SpanGetter { @@ -135,13 +148,30 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { final boolean previousSetting = prefs.getBoolean(key, defaultValue); final SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(key, value); - editor.commit(); + editor.apply(); return previousSetting; } - // returns the previous setting value - protected boolean setDebugMode(final boolean value) { - return setBooleanPreference(PREF_DEBUG_MODE, value, false); + protected String setStringPreference(final String key, final String value, + final String defaultValue) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); + final String previousSetting = prefs.getString(key, defaultValue); + final SharedPreferences.Editor editor = prefs.edit(); + editor.putString(key, value); + editor.apply(); + return previousSetting; + } + + protected void setDebugMode(final boolean value) { + setBooleanPreference(DebugSettings.PREF_DEBUG_MODE, value, false); + setBooleanPreference(Settings.PREF_KEY_IS_INTERNAL, value, false); + } + + protected EditorInfo enrichEditorInfo(final EditorInfo ei) { + // Some tests that inherit from us need to add some data in the EditorInfo (see + // AppWorkaroundsTests#enrichEditorInfo() for a concrete example of this). Since we + // control the EditorInfo, we supply a hook here for children to override. + return ei; } @Override @@ -154,15 +184,19 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { mEditText.setEnabled(true); setupService(); mLatinIME = getService(); - final boolean previousDebugSetting = setDebugMode(true); + setDebugMode(true); + mPreviousBigramPredictionSettings = setBooleanPreference(Settings.PREF_BIGRAM_PREDICTIONS, + true, true /* defaultValue */); + mPreviousAutoCorrectSetting = setStringPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD, + DEFAULT_AUTO_CORRECTION_THRESHOLD, DEFAULT_AUTO_CORRECTION_THRESHOLD); mLatinIME.onCreate(); - setDebugMode(previousDebugSetting); - final EditorInfo ei = new EditorInfo(); + EditorInfo ei = new EditorInfo(); final InputConnection ic = mEditText.onCreateInputConnection(ei); final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); final ViewGroup vg = new FrameLayout(getContext()); mInputView = inflater.inflate(R.layout.input_view, vg); + ei = enrichEditorInfo(ei); mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); mLatinIME.setInputView(mInputView); mLatinIME.onBindInput(); @@ -170,6 +204,27 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { mLatinIME.onStartInputView(ei, false); mInputConnection = ic; changeLanguage("en_US"); + // Run messages to avoid the messages enqueued by startInputView() and its friends + // to run on a later call and ruin things. We need to wait first because some of them + // can be posted with a delay (notably, MSG_RESUME_SUGGESTIONS) + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + } + + @Override + protected void tearDown() throws Exception { + mLatinIME.onFinishInputView(true); + mLatinIME.onFinishInput(); + runMessages(); + mLatinIME.mHandler.removeAllMessages(); + setBooleanPreference(Settings.PREF_BIGRAM_PREDICTIONS, mPreviousBigramPredictionSettings, + true /* defaultValue */); + setStringPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD, mPreviousAutoCorrectSetting, + DEFAULT_AUTO_CORRECTION_THRESHOLD); + setDebugMode(false); + mLatinIME.recycle(); + super.tearDown(); + mLatinIME = null; } // We need to run the messages added to the handler from LatinIME. The only way to do @@ -199,7 +254,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { } // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. - protected void type(final int codePoint) { + protected void typeInternal(final int codePoint, final boolean isKeyRepeat) { // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the // code (although multitouch/slide input and other factors make the sequencing complicated). // They are supposed to be entirely deconnected from the input logic from LatinIME point of @@ -208,60 +263,90 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { // but keep them in mind if something breaks. Commenting them out as is should work. //mLatinIME.onPressKey(codePoint, 0 /* repeatCount */, true /* isSinglePointer */); final Key key = mKeyboard.getKey(codePoint); - if (key != null) { + if (key == null) { + mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, + isKeyRepeat); + } else { final int x = key.getX() + key.getWidth() / 2; final int y = key.getY() + key.getHeight() / 2; - mLatinIME.onCodeInput(codePoint, x, y); - return; + mLatinIME.onCodeInput(codePoint, x, y, isKeyRepeat); } - mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + // Also see the comment at the top of this function about onReleaseKey //mLatinIME.onReleaseKey(codePoint, false /* withSliding */); } + protected void type(final int codePoint) { + typeInternal(codePoint, false /* isKeyRepeat */); + } + + protected void repeatKey(final int codePoint) { + typeInternal(codePoint, true /* isKeyRepeat */); + } + protected void type(final String stringToType) { for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { type(stringToType.codePointAt(i)); } } - protected void waitForDictionaryToBeLoaded() { - int remainingAttempts = 300; - while (remainingAttempts > 0 && mLatinIME.isCurrentlyWaitingForMainDictionary()) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - // Don't do much - } finally { - --remainingAttempts; - } + protected void waitForDictionariesToBeLoaded() { + try { + mLatinIME.waitForLoadingDictionaries( + TIMEOUT_TO_WAIT_FOR_LOADING_MAIN_DICTIONARY_IN_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted during waiting for loading main dictionary.", e); } } protected void changeLanguage(final String locale) { - changeLanguageWithoutWait(locale); - waitForDictionaryToBeLoaded(); + changeLanguage(locale, null); + } + + protected void changeLanguage(final String locale, final String combiningSpec) { + changeLanguageWithoutWait(locale, combiningSpec); + waitForDictionariesToBeLoaded(); } - protected void changeLanguageWithoutWait(final String locale) { + protected void changeLanguageWithoutWait(final String locale, final String combiningSpec) { mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale); - SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale); - mLatinIME.loadKeyboard(); + // TODO: this is forcing a QWERTY keyboard for all locales, which is wrong. + // It's still better than using whatever keyboard is the current one, but we + // should actually use the default keyboard for this locale. + // TODO: Use {@link InputMethodSubtype.InputMethodSubtypeBuilder} directly or indirectly so + // that {@link InputMethodSubtype#isAsciiCapable} can return the correct value. + final String EXTRA_VALUE_FOR_TEST = + "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY + + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE + + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE + + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE + + null == combiningSpec ? "" : ("," + combiningSpec); + final InputMethodSubtype subtype = InputMethodSubtypeCompatUtils.newInputMethodSubtype( + R.string.subtype_no_language_qwerty, + R.drawable.ic_ime_switcher_dark, + locale, + Constants.Subtype.KEYBOARD_MODE, + EXTRA_VALUE_FOR_TEST, + false /* isAuxiliary */, + false /* overridesImplicitlyEnabledSubtype */, + 0 /* id */); + SubtypeSwitcher.getInstance().forceSubtype(subtype); + mLatinIME.onCurrentInputMethodSubtypeChanged(subtype); runMessages(); mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); + mLatinIME.clearPersonalizedDictionariesForTest(); } protected void changeKeyboardLocaleAndDictLocale(final String keyboardLocale, final String dictLocale) { changeLanguage(keyboardLocale); if (!keyboardLocale.equals(dictLocale)) { - mLatinIME.replaceMainDictionaryForTest( - LocaleUtils.constructLocaleFromString(dictLocale)); + mLatinIME.replaceDictionariesForTest(LocaleUtils.constructLocaleFromString(dictLocale)); } - waitForDictionaryToBeLoaded(); + waitForDictionariesToBeLoaded(); } - protected void pickSuggestionManually(final int index, final String suggestion) { - mLatinIME.pickSuggestionManually(index, new SuggestedWordInfo(suggestion, 1, + protected void pickSuggestionManually(final String suggestion) { + mLatinIME.pickSuggestionManually(new SuggestedWordInfo(suggestion, 1, SuggestedWordInfo.KIND_CORRECTION, null /* sourceDict */, SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); diff --git a/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java index 5e98cdf8d..f5e993de8 100644 --- a/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java +++ b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java @@ -30,10 +30,10 @@ public class LatinImeStressTests extends InputTestsBase { final int maxWordCountToTypeInEachIteration = 20; final long seed = System.currentTimeMillis(); final Random random = new Random(seed); - final int codePointSetSize = 30; final int[] codePointSet = CodePointUtils.LATIN_ALPHABETS_LOWER; for (int i = 0; i < switchCount; ++i) { - changeLanguageWithoutWait(locales[random.nextInt(locales.length)]); + changeLanguageWithoutWait(locales[random.nextInt(locales.length)], + null /* combiningSpec */); final int wordCount = random.nextInt(maxWordCountToTypeInEachIteration); for (int j = 0; j < wordCount; ++j) { final String word = CodePointUtils.generateWord(random, codePointSet); @@ -41,7 +41,7 @@ public class LatinImeStressTests extends InputTestsBase { } } } - public void testSwitchLanguagesAndInputRandamCodePoints() { + public void testSwitchLanguagesAndInputRandomCodePoints() { final String[] locales = {"en_US", "de", "el", "es", "fi", "it", "nl", "pt", "ru"}; final int switchCount = 50; final int maxWordCountToTypeInEachIteration = 20; @@ -50,7 +50,8 @@ public class LatinImeStressTests extends InputTestsBase { final int codePointSetSize = 30; final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); for (int i = 0; i < switchCount; ++i) { - changeLanguageWithoutWait(locales[random.nextInt(locales.length)]); + changeLanguageWithoutWait(locales[random.nextInt(locales.length)], + null /* combiningSpec */); final int wordCount = random.nextInt(maxWordCountToTypeInEachIteration); for (int j = 0; j < wordCount; ++j) { final String word = CodePointUtils.generateWord(random, codePointSet); diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java index 84ff6b307..64750fbda 100644 --- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java +++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import android.provider.Settings.Secure; import android.test.suitebuilder.annotation.LargeTest; import com.android.inputmethod.latin.R; @@ -40,9 +41,9 @@ public class PunctuationTests extends InputTestsBase { sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); assertTrue("type word then type space should display punctuation strip", - mLatinIME.isShowingPunctuationList()); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); + mLatinIME.getSuggestedWordsForTest().isPunctuationSuggestions()); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); assertEquals("type word then type space then punctuation from strip twice", EXPECTED_RESULT, mEditText.getText().toString()); } finally { @@ -65,9 +66,9 @@ public class PunctuationTests extends InputTestsBase { final String PUNCTUATION_FROM_STRIP = "!"; final String EXPECTED_RESULT = "this!! is"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); + pickSuggestionManually(WORD1_TO_TYPE); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); + pickSuggestionManually(PUNCTUATION_FROM_STRIP); type(WORD2_TO_TYPE); assertEquals("pick word then pick punctuation twice then type", EXPECTED_RESULT, mEditText.getText().toString()); @@ -78,8 +79,8 @@ public class PunctuationTests extends InputTestsBase { final String WORD2_TO_PICK = "!is"; final String EXPECTED_RESULT = "this!is"; type(WORD1_TO_TYPE); - pickSuggestionManually(0, WORD1_TO_TYPE); - pickSuggestionManually(1, WORD2_TO_PICK); + pickSuggestionManually(WORD1_TO_TYPE); + pickSuggestionManually(WORD2_TO_PICK); assertEquals("manual pick then manual pick a word with punct at start", EXPECTED_RESULT, mEditText.getText().toString()); } @@ -89,7 +90,7 @@ public class PunctuationTests extends InputTestsBase { final String PUNCTUATION = ":"; final String EXPECTED_RESULT = "this:"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); type(PUNCTUATION); assertEquals("manually pick word then colon", EXPECTED_RESULT, mEditText.getText().toString()); @@ -100,7 +101,7 @@ public class PunctuationTests extends InputTestsBase { final String PUNCTUATION = "("; final String EXPECTED_RESULT = "this ("; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); type(PUNCTUATION); assertEquals("manually pick word then open paren", EXPECTED_RESULT, mEditText.getText().toString()); @@ -111,7 +112,7 @@ public class PunctuationTests extends InputTestsBase { final String PUNCTUATION = ")"; final String EXPECTED_RESULT = "this)"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); type(PUNCTUATION); assertEquals("manually pick word then close paren", EXPECTED_RESULT, mEditText.getText().toString()); @@ -122,7 +123,7 @@ public class PunctuationTests extends InputTestsBase { final String SPECIAL_KEY = ":-)"; final String EXPECTED_RESULT = "this :-)"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); mLatinIME.onTextInput(SPECIAL_KEY); assertEquals("manually pick word then press the smiley key", EXPECTED_RESULT, mEditText.getText().toString()); @@ -133,7 +134,7 @@ public class PunctuationTests extends InputTestsBase { final String SPECIAL_KEY = ".com"; final String EXPECTED_RESULT = "this.com"; type(WORD_TO_TYPE); - pickSuggestionManually(0, WORD_TO_TYPE); + pickSuggestionManually(WORD_TO_TYPE); mLatinIME.onTextInput(SPECIAL_KEY); assertEquals("manually pick word then press the .com key", EXPECTED_RESULT, mEditText.getText().toString()); @@ -153,7 +154,9 @@ public class PunctuationTests extends InputTestsBase { final String WORD_TO_TYPE = "you'f "; final String EXPECTED_RESULT = "you'd "; type(WORD_TO_TYPE); - assertEquals("auto-correction with single quote inside", + assertEquals("auto-correction with single quote inside. ID = " + + Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID) + + " ; Suggestions = " + mLatinIME.getSuggestedWordsForTest(), EXPECTED_RESULT, mEditText.getText().toString()); } @@ -161,7 +164,37 @@ public class PunctuationTests extends InputTestsBase { final String WORD_TO_TYPE = "'tgis' "; final String EXPECTED_RESULT = "'this' "; type(WORD_TO_TYPE); - assertEquals("auto-correction with single quotes around", + assertEquals("auto-correction with single quotes around. ID = " + + Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID) + + " ; Suggestions = " + mLatinIME.getSuggestedWordsForTest(), + EXPECTED_RESULT, mEditText.getText().toString()); + } + + public void testAutoSpaceWithDoubleQuotes() { + final String STRING_TO_TYPE = "He said\"hello\"to me. I replied,\"hi\"." + + "Then, 5\"passed. He said\"bye\"and left."; + final String EXPECTED_RESULT = "He said \"hello\" to me. I replied, \"hi\". " + + "Then, 5\" passed. He said \"bye\" and left. \""; + // Split by double quote, so that we can type the double quotes individually. + for (final String partToType : STRING_TO_TYPE.split("\"")) { + // Split at word boundaries. This regexp means "anywhere that is preceded + // by a word character but not followed by a word character, OR that is not + // preceded by a word character but followed by a word character". + // We need to input word by word because auto-spaces are only active when + // manually picking or gesturing (which we can't simulate yet), but only words + // can be picked. + final String[] wordsToType = partToType.split("(?<=\\w)(?!\\w)|(?<!\\w)(?=\\w)"); + for (final String wordToType : wordsToType) { + type(wordToType); + if (wordToType.matches("^\\w+$")) { + // Only pick selection if that was a word, because if that was not a word, + // then we don't have a composition. + pickSuggestionManually(wordToType); + } + } + type("\""); + } + assertEquals("auto-space with double quotes", EXPECTED_RESULT, mEditText.getText().toString()); } } diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java index c0dd9933c..199922491 100644 --- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java +++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java @@ -16,15 +16,13 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.utils.TextRange; - +import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.os.Parcel; import android.test.AndroidTestCase; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableString; -import android.text.Spanned; import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.view.inputmethod.ExtractedText; @@ -32,6 +30,14 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; +import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import com.android.inputmethod.latin.utils.PrevWordsInfoUtils; +import com.android.inputmethod.latin.utils.RunInLocale; +import com.android.inputmethod.latin.utils.ScriptUtils; +import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.TextRange; + import java.util.Locale; @SmallTest @@ -39,11 +45,19 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { // The following is meant to be a reasonable default for // the "word_separators" resource. - private static final String sSeparators = ".,:;!?-"; + private SpacingAndPunctuations mSpacingAndPunctuations; @Override protected void setUp() throws Exception { super.setUp(); + final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(final Resources res) { + return new SpacingAndPunctuations(res); + } + }; + final Resources res = getContext().getResources(); + mSpacingAndPunctuations = job.runInLocale(res, Locale.ENGLISH); } private class MockConnection extends InputConnectionWrapper { @@ -78,6 +92,10 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { mExtractedText = extractedText; } + public int cursorPos() { + return mTextBefore.length(); + } + /* (non-Javadoc) * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int) */ @@ -120,13 +138,16 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { } private class MockInputMethodService extends InputMethodService { - InputConnection mInputConnection; - public void setInputConnection(final InputConnection inputConnection) { - mInputConnection = inputConnection; + private MockConnection mMockConnection; + public void setInputConnection(final MockConnection mockConnection) { + mMockConnection = mockConnection; + } + public int cursorPos() { + return mMockConnection.cursorPos(); } @Override public InputConnection getCurrentInputConnection() { - return mInputConnection; + return mMockConnection; } } @@ -137,9 +158,26 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { */ public void testGetPreviousWord() { // If one of the following cases breaks, the bigram suggestions won't work. - assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc"); - assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2)); - assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2)); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc. def", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE); + + assertFalse(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mIsBeginningOfSentence); + assertTrue(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mIsBeginningOfSentence); + + // For n-gram + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[1].mWord, "abc"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[1], + WordInfo.BEGINNING_OF_SENTENCE); // The following tests reflect the current behavior of the function // RichInputConnection#getNthPreviousWord. @@ -148,20 +186,46 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { // this function if needed - especially since it does not seem very // logical. These tests are just there to catch any unintentional // changes in the behavior of the RichInputConnection#getPreviousWord method. - assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc"); - assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc"); - assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def"); - assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2)); - - assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def"); - assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def"); - assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1)); - assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1)); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def ", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def.", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def .", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "def"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc ", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE); + + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def ", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc 'def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "'def"); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def.", mSpacingAndPunctuations, 1), PrevWordsInfo.BEGINNING_OF_SENTENCE); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc def .", mSpacingAndPunctuations, 1), PrevWordsInfo.BEGINNING_OF_SENTENCE); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc, def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc? def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc! def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); + assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord( + "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO); } /** * Test logic in getting the word range at the cursor. */ + private static final int[] SPACE = { Constants.CODE_SPACE }; + static final int[] TAB = { Constants.CODE_TAB }; + private static final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t"); + // A character that needs surrogate pair to represent its code point (U+2008A). + private static final String SUPPLEMENTARY_CHAR = "\uD840\uDC8A"; + private static final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお + private static final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και + public void testGetWordRangeAtCursor() { ExtractedText et = new ExtractedText(); final MockInputMethodService mockInputMethodService = new MockInputMethodService(); @@ -173,50 +237,42 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { ic.beginBatchEdit(); // basic case - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); assertTrue(TextUtils.equals("word", r.mWord)); - // more than one word - r = ic.getWordRangeAtCursor(" ", 1); - assertTrue(TextUtils.equals("word word", r.mWord)); - ic.endBatchEdit(); - // tab character instead of space mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et)); ic.beginBatchEdit(); - r = ic.getWordRangeAtCursor("\t", 1); + r = ic.getWordRangeAtCursor(TAB, ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); - assertTrue(TextUtils.equals("word\tword", r.mWord)); - - // only one word doesn't go too far - mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et)); - ic.beginBatchEdit(); - r = ic.getWordRangeAtCursor("\t", 1); - ic.endBatchEdit(); - assertTrue(TextUtils.equals("word\tword", r.mWord)); + assertTrue(TextUtils.equals("word", r.mWord)); - // tab or space - mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et)); + // splitting on supplementary character + mockInputMethodService.setInputConnection( + new MockConnection("one word" + SUPPLEMENTARY_CHAR + "wo", "rd", et)); ic.beginBatchEdit(); - r = ic.getWordRangeAtCursor(" \t", 1); + r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), + ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); - assertTrue(TextUtils.equals("word\tword", r.mWord)); + assertTrue(TextUtils.equals("word", r.mWord)); - // tab or space multiword - mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et)); + // split on chars outside the specified script + mockInputMethodService.setInputConnection( + new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); ic.beginBatchEdit(); - r = ic.getWordRangeAtCursor(" \t", 2); + r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), + ScriptUtils.SCRIPT_LATIN); ic.endBatchEdit(); - assertTrue(TextUtils.equals("one word\tword", r.mWord)); + assertTrue(TextUtils.equals("word", r.mWord)); - // splitting on supplementary character - final String supplementaryChar = "\uD840\uDC8A"; + // likewise for greek mockInputMethodService.setInputConnection( - new MockConnection("one word" + supplementaryChar + "wo", "rd", et)); + new MockConnection("text" + GREEK_WORD, "text", et)); ic.beginBatchEdit(); - r = ic.getWordRangeAtCursor(supplementaryChar, 0); + r = ic.getWordRangeAtCursor(StringUtils.toSortedCodePointArray(SUPPLEMENTARY_CHAR), + ScriptUtils.SCRIPT_GREEK); ic.endBatchEdit(); - assertTrue(TextUtils.equals("word", r.mWord)); + assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); } /** @@ -244,7 +300,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { TextRange r; SuggestionSpan[] suggestions; - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 1); MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); @@ -256,7 +312,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 10 /* start */, 16 /* end */, 0 /* flags */); mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 2); MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); @@ -269,7 +325,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 5 /* start */, 16 /* end */, 0 /* flags */); mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 1); MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); @@ -281,7 +337,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 10 /* start */, 20 /* end */, 0 /* flags */); mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 1); MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); @@ -293,7 +349,7 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 5 /* start */, 20 /* end */, 0 /* flags */); mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 1); MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); @@ -305,8 +361,86 @@ public class RichInputConnectionAndTextRangeTests extends AndroidTestCase { text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 5 /* start */, 20 /* end */, 0 /* flags */); mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); - r = ic.getWordRangeAtCursor(" ", 0); + r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); suggestions = r.getSuggestionSpansAtWord(); assertEquals(suggestions.length, 0); } + + public void testCursorTouchingWord() { + final MockInputMethodService ims = new MockInputMethodService(); + final RichInputConnection ic = new RichInputConnection(ims); + final SpacingAndPunctuations sap = mSpacingAndPunctuations; + + ims.setInputConnection(new MockConnection("users", 5)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("users'", 5)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("users'", 6)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("'users'", 6)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("'users'", 7)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("users '", 6)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("users '", 7)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("re-", 3)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("re--", 4)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("-", 1)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection("--", 2)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" -", 2)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" --", 3)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" users '", 1)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" users '", 3)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" users '", 7)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" users are", 7)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertTrue(ic.isCursorTouchingWord(sap)); + + ims.setInputConnection(new MockConnection(" users 'are", 7)); + ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); + assertFalse(ic.isCursorTouchingWord(sap)); + } } diff --git a/tests/src/com/android/inputmethod/latin/ShiftModeTests.java b/tests/src/com/android/inputmethod/latin/ShiftModeTests.java new file mode 100644 index 000000000..db3c9baa9 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/ShiftModeTests.java @@ -0,0 +1,126 @@ +/* + * 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.latin; + +import android.os.Build; +import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; +import android.view.inputmethod.EditorInfo; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.WordComposer; + +@LargeTest +public class ShiftModeTests extends InputTestsBase { + + @Override + protected EditorInfo enrichEditorInfo(final EditorInfo ei) { + ei.inputType |= TextUtils.CAP_MODE_SENTENCES; + ei.initialCapsMode = TextUtils.CAP_MODE_SENTENCES; + return ei; + } + + private boolean isCapsModeAutoShifted() { + return mLatinIME.mKeyboardSwitcher.getKeyboardShiftMode() + == WordComposer.CAPS_MODE_AUTO_SHIFTED; + } + + public void testTypicalSentence() { + assertTrue("Initial auto caps state", isCapsModeAutoShifted()); + type("Test"); + assertFalse("Caps after letter", isCapsModeAutoShifted()); + type(" "); + assertFalse("Caps after space", isCapsModeAutoShifted()); + type("some,"); + assertFalse("Caps after comma", isCapsModeAutoShifted()); + type(" "); + assertFalse("Caps after comma space", isCapsModeAutoShifted()); + type("words."); + assertFalse("Caps directly after period", isCapsModeAutoShifted()); + type(" "); + assertTrue("Caps after period space", isCapsModeAutoShifted()); + } + + public void testBackspace() { + assertTrue("Initial auto caps state", isCapsModeAutoShifted()); + type("A"); + assertFalse("Caps state after one letter", isCapsModeAutoShifted()); + type(Constants.CODE_DELETE); + assertTrue("Auto caps state at start after delete", isCapsModeAutoShifted()); + } + + public void testRepeatingBackspace() { + final String SENTENCE_TO_TYPE = "Test sentence. Another."; + final int BACKSPACE_COUNT = + SENTENCE_TO_TYPE.length() - SENTENCE_TO_TYPE.lastIndexOf(' ') - 1; + + type(SENTENCE_TO_TYPE); + assertFalse("Caps after typing \"" + SENTENCE_TO_TYPE + "\"", isCapsModeAutoShifted()); + type(Constants.CODE_DELETE); + for (int i = 1; i < BACKSPACE_COUNT; ++i) { + repeatKey(Constants.CODE_DELETE); + } + assertFalse("Caps immediately after repeating Backspace a lot", isCapsModeAutoShifted()); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + assertTrue("Caps after a while after repeating Backspace a lot", isCapsModeAutoShifted()); + } + + public void testAutoCapsAfterDigitsPeriod() { + changeLanguage("en"); + type("On 22.11."); + assertFalse("(English) Auto caps after digits-period", isCapsModeAutoShifted()); + type(" "); + assertTrue("(English) Auto caps after digits-period-whitespace", isCapsModeAutoShifted()); + mEditText.setText(""); + changeLanguage("fr"); + type("Le 22."); + assertFalse("(French) Auto caps after digits-period", isCapsModeAutoShifted()); + type(" "); + assertTrue("(French) Auto caps after digits-period-whitespace", isCapsModeAutoShifted()); + mEditText.setText(""); + changeLanguage("de"); + type("Am 22."); + assertFalse("(German) Auto caps after digits-period", isCapsModeAutoShifted()); + type(" "); + // For German, no auto-caps in this case + assertFalse("(German) Auto caps after digits-period-whitespace", isCapsModeAutoShifted()); + } + + public void testAutoCapsAfterInvertedMarks() { + changeLanguage("es"); + assertTrue("(Spanish) Auto caps at start", isCapsModeAutoShifted()); + type("Hey. ¿"); + assertTrue("(Spanish) Auto caps after inverted what", isCapsModeAutoShifted()); + mEditText.setText(""); + type("¡"); + assertTrue("(Spanish) Auto caps after inverted bang", isCapsModeAutoShifted()); + } + + public void testOtherSentenceSeparators() { + changeLanguage("hy_AM"); + assertTrue("(Armenian) Auto caps at start", isCapsModeAutoShifted()); + type("Hey. "); + assertFalse("(Armenian) No auto-caps after latin period", isCapsModeAutoShifted()); + type("Hey\u0589"); + assertFalse("(Armenian) No auto-caps directly after armenian period", + isCapsModeAutoShifted()); + type(" "); + assertTrue("(Armenian) Auto-caps after armenian period-whitespace", + isCapsModeAutoShifted()); + } +} diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java index 375352067..66b4a9c71 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java +++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java @@ -16,12 +16,10 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; -import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.util.ArrayList; import java.util.Locale; @@ -33,7 +31,7 @@ public class SuggestedWordsTests extends AndroidTestCase { final String TYPED_WORD = "typed"; final int TYPED_WORD_FREQ = 5; final int NUMBER_OF_ADDED_SUGGESTIONS = 5; - final ArrayList<SuggestedWordInfo> list = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> list = new ArrayList<>(); list.add(new SuggestedWordInfo(TYPED_WORD, TYPED_WORD_FREQ, SuggestedWordInfo.KIND_TYPED, null /* sourceDict */, SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, @@ -46,24 +44,23 @@ public class SuggestedWordsTests extends AndroidTestCase { } final SuggestedWords words = new SuggestedWords( - list, + list, null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */, - false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, false /* isPrediction*/); assertEquals(NUMBER_OF_ADDED_SUGGESTIONS + 1, words.size()); assertEquals("typed", words.getWord(0)); - assertEquals(SuggestedWordInfo.KIND_TYPED, words.getInfo(0).mKind); + assertTrue(words.getInfo(0).isKindOf(SuggestedWordInfo.KIND_TYPED)); assertEquals("0", words.getWord(1)); - assertEquals(SuggestedWordInfo.KIND_CORRECTION, words.getInfo(1).mKind); + assertTrue(words.getInfo(1).isKindOf(SuggestedWordInfo.KIND_CORRECTION)); assertEquals("4", words.getWord(5)); - assertEquals(SuggestedWordInfo.KIND_CORRECTION, words.getInfo(5).mKind); + assertTrue(words.getInfo(5).isKindOf(SuggestedWordInfo.KIND_CORRECTION)); final SuggestedWords wordsWithoutTyped = words.getSuggestedWordsExcludingTypedWord(); assertEquals(words.size() - 1, wordsWithoutTyped.size()); assertEquals("0", wordsWithoutTyped.getWord(0)); - assertEquals(SuggestedWordInfo.KIND_CORRECTION, wordsWithoutTyped.getInfo(0).mKind); + assertTrue(wordsWithoutTyped.getInfo(0).isKindOf(SuggestedWordInfo.KIND_CORRECTION)); } // Helper for testGetTransformedWordInfo diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java index 1434c6b63..c44544f3d 100644 --- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java +++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java @@ -19,6 +19,9 @@ package com.android.inputmethod.latin; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.utils.StringUtils; + /** * Unit tests for WordComposer. */ @@ -26,10 +29,19 @@ import android.test.suitebuilder.annotation.SmallTest; public class WordComposerTests extends AndroidTestCase { public void testMoveCursor() { final WordComposer wc = new WordComposer(); + // BMP is the Basic Multilingual Plane, as defined by Unicode. This includes + // most characters for most scripts, including all Roman alphabet languages, + // CJK, Arabic, Hebrew. Notable exceptions include some emoji and some + // very rare Chinese ideograms. BMP characters can be encoded on 2 bytes + // in UTF-16, whereas those outside the BMP need 4 bytes. + // http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane final String STR_WITHIN_BMP = "abcdef"; - wc.setComposingWord(STR_WITHIN_BMP, null); - assertEquals(wc.size(), - STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length())); + final int[] CODEPOINTS_WITHIN_BMP = StringUtils.toCodePointArray(STR_WITHIN_BMP); + final int[] COORDINATES_WITHIN_BMP = + CoordinateUtils.newCoordinateArray(CODEPOINTS_WITHIN_BMP.length, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP); + assertEquals(wc.size(), STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length())); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); wc.setCursorPositionWithinWord(2); assertTrue(wc.isCursorFrontOrMiddleOfComposingWord()); @@ -46,12 +58,20 @@ public class WordComposerTests extends AndroidTestCase { // Move the cursor past the end of the word assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15)); + // Do what LatinIME does when the cursor is moved outside of the word, + // and check the behavior is correct. + wc.reset(); // \uD861\uDED7 is 𨛗, a character outside the BMP final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh"; - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); - assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0, - STR_WITH_SUPPLEMENTARY_CHAR.length())); + final int[] CODEPOINTS_WITH_SUPPLEMENTARY_CHAR = + StringUtils.toCodePointArray(STR_WITH_SUPPLEMENTARY_CHAR); + final int[] COORDINATES_WITH_SUPPLEMENTARY_CHAR = + CoordinateUtils.newCoordinateArray(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); + assertEquals(wc.size(), CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); wc.setCursorPositionWithinWord(3); assertTrue(wc.isCursorFrontOrMiddleOfComposingWord()); @@ -60,33 +80,42 @@ public class WordComposerTests extends AndroidTestCase { assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1)); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); wc.setCursorPositionWithinWord(3); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); wc.setCursorPositionWithinWord(3); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); wc.setCursorPositionWithinWord(3); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); wc.setCursorPositionWithinWord(3); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0)); - wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); + wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, + COORDINATES_WITH_SUPPLEMENTARY_CHAR); wc.setCursorPositionWithinWord(2); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0)); } diff --git a/tests/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java new file mode 100644 index 000000000..bc856f113 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.TreeMap; + +/** + * A base class of the binary dictionary decoder. + */ +public abstract class AbstractDictDecoder implements DictDecoder { + private static final int SUCCESS = 0; + private static final int ERROR_CANNOT_READ = 1; + private static final int ERROR_WRONG_FORMAT = 2; + + @Override @UsedForTesting + public int getTerminalPosition(final String word) + throws IOException, UnsupportedFormatException { + if (!isDictBufferOpen()) { + openDictBuffer(); + } + return BinaryDictIOUtils.getTerminalPosition(this, word); + } + + @Override @UsedForTesting + public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words, + final TreeMap<Integer, Integer> frequencies, + final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams) + throws IOException, UnsupportedFormatException { + if (!isDictBufferOpen()) { + openDictBuffer(); + } + BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams); + } + + /** + * Check whether the header contains the expected information. This is a no-error method, + * that will return an error code and never throw a checked exception. + * @return an error code, either ERROR_* or SUCCESS. + */ + private int checkHeader() { + try { + readHeader(); + } catch (IOException e) { + return ERROR_CANNOT_READ; + } catch (UnsupportedFormatException e) { + return ERROR_WRONG_FORMAT; + } + return SUCCESS; + } + + @Override + public boolean hasValidRawBinaryDictionary() { + return checkHeader() == SUCCESS; + } + + // Placeholder implementations below. These are actually unused. + @Override + public void openDictBuffer() throws FileNotFoundException, IOException, + UnsupportedFormatException { + } + + @Override + public boolean isDictBufferOpen() { + return false; + } + + @Override + public PtNodeInfo readPtNode(final int ptNodePos) { + return null; + } + + @Override + public void setPosition(int newPos) { + } + + @Override + public int getPosition() { + return 0; + } + + @Override + public int readPtNodeCount() { + return 0; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java index 32c07e106..406046a74 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java @@ -17,30 +17,28 @@ package com.android.inputmethod.latin.makedict; import android.test.AndroidTestCase; -import android.test.MoreAsserts; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; +import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.ByteArrayDictBuffer; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map.Entry; import java.util.Random; import java.util.Set; @@ -52,39 +50,21 @@ import java.util.TreeMap; @LargeTest public class BinaryDictDecoderEncoderTests extends AndroidTestCase { private static final String TAG = BinaryDictDecoderEncoderTests.class.getSimpleName(); - private static final int DEFAULT_MAX_UNIGRAMS = 100; + private static final int DEFAULT_MAX_UNIGRAMS = 300; private static final int DEFAULT_CODE_POINT_SET_SIZE = 50; + private static final int LARGE_CODE_POINT_SET_SIZE = 300; private static final int UNIGRAM_FREQ = 10; private static final int BIGRAM_FREQ = 50; private static final int TOLERANCE_OF_BIGRAM_FREQ = 5; private static final int NUM_OF_NODES_HAVING_SHORTCUTS = 50; private static final int NUM_OF_SHORTCUTS = 5; - private static final int USE_BYTE_ARRAY = 1; - private static final int USE_BYTE_BUFFER = 2; - - private static final ArrayList<String> sWords = CollectionUtils.newArrayList(); - private static final SparseArray<List<Integer>> sEmptyBigrams = - CollectionUtils.newSparseArray(); - private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray(); - private static final SparseArray<List<Integer>> sChainBigrams = - CollectionUtils.newSparseArray(); - private static final HashMap<String, List<String>> sShortcuts = CollectionUtils.newHashMap(); - - private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2); - private static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE = - new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */); - private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE = - new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */); - private static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE = - new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */); - private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE = - new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */); - private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP = - new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */, - true /* hasTimestamp */); - - private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; + private static final ArrayList<String> sWords = new ArrayList<>(); + private static final ArrayList<String> sWordsWithVariousCodePoints = new ArrayList<>(); + private static final SparseArray<List<Integer>> sEmptyBigrams = new SparseArray<>(); + private static final SparseArray<List<Integer>> sStarBigrams = new SparseArray<>(); + private static final SparseArray<List<Integer>> sChainBigrams = new SparseArray<>(); + private static final HashMap<String, List<String>> sShortcuts = new HashMap<>(); public BinaryDictDecoderEncoderTests() { this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS); @@ -92,12 +72,12 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { public BinaryDictDecoderEncoderTests(final long seed, final int maxUnigrams) { super(); + BinaryDictionaryUtils.setCurrentTimeForTest(0); Log.e(TAG, "Testing dictionary: seed is " + seed); final Random random = new Random(seed); sWords.clear(); - final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, - random); - generateWords(maxUnigrams, random, codePointSet); + sWordsWithVariousCodePoints.clear(); + generateWords(maxUnigrams, random); for (int i = 0; i < sWords.size(); ++i) { sChainBigrams.put(i, new ArrayList<Integer>()); @@ -124,23 +104,35 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } } - private DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) { - if (formatOptions.mVersion == FormatSpec.VERSION4) { - return new Ver4DictEncoder(getContext().getCacheDir()); - } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) { - return new Ver3DictEncoder(file); - } else { - throw new RuntimeException("The format option has a wrong version : " - + formatOptions.mVersion); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + BinaryDictionaryUtils.setCurrentTimeForTest(0); } - private void generateWords(final int number, final Random random, final int[] codePointSet) { - final Set<String> wordSet = CollectionUtils.newHashSet(); + @Override + protected void tearDown() throws Exception { + // Quit test mode. + BinaryDictionaryUtils.setCurrentTimeForTest(-1); + super.tearDown(); + } + + private void generateWords(final int number, final Random random) { + final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, + random); + final Set<String> wordSet = new HashSet<>(); while (wordSet.size() < number) { wordSet.add(CodePointUtils.generateWord(random, codePointSet)); } sWords.addAll(wordSet); + + final int[] largeCodePointSet = CodePointUtils.generateCodePointSet( + LARGE_CODE_POINT_SET_SIZE, random); + wordSet.clear(); + while (wordSet.size() < number) { + wordSet.add(CodePointUtils.generateWord(random, largeCodePointSet)); + } + sWordsWithVariousCodePoints.addAll(wordSet); } /** @@ -150,14 +142,14 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final List<String> words, final HashMap<String, List<String>> shortcutMap) { for (int i = 0; i < number; ++i) { final String word = words.get(i); - final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList(); + final ArrayList<WeightedString> shortcuts = new ArrayList<>(); if (shortcutMap != null && shortcutMap.containsKey(word)) { for (final String shortcut : shortcutMap.get(word)) { shortcuts.add(new WeightedString(shortcut, UNIGRAM_FREQ)); } } - dict.add(word, UNIGRAM_FREQ, (shortcutMap == null) ? null : shortcuts, - false /* isNotAWord */); + dict.add(word, new ProbabilityInfo(UNIGRAM_FREQ), + (shortcutMap == null) ? null : shortcuts, false /* isNotAWord */); } } @@ -167,7 +159,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { for (int i = 0; i < bigrams.size(); ++i) { final int w1 = bigrams.keyAt(i); for (int w2 : bigrams.valueAt(i)) { - dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ); + dict.setBigram(words.get(w1), words.get(w2), new ProbabilityInfo(BIGRAM_FREQ)); } } } @@ -186,7 +178,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { long now = -1, diff = -1; try { - final DictEncoder dictEncoder = getDictEncoder(file, formatOptions); + final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions); now = System.currentTimeMillis(); // If you need to dump the dict to a textual file, uncomment the line below and the @@ -241,56 +233,23 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { private String outputOptions(final int bufferType, final FormatSpec.FormatOptions formatOptions) { String result = " : buffer type = " - + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array"); - result += " : version = " + formatOptions.mVersion; - return result + ", supportsDynamicUpdate = " + formatOptions.mSupportsDynamicUpdate; + + ((bufferType == BinaryDictUtils.USE_BYTE_BUFFER) ? "byte buffer" : "byte array"); + return result + " : version = " + formatOptions.mVersion; } - private DictionaryOptions getDictionaryOptions(final String id, final String version) { - final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(), - false, false); - options.mAttributes.put("version", version); - options.mAttributes.put("dictionary", id); - return options; - } - - private File setUpDictionaryFile(final String name, final String version) { - File file = null; - try { - file = new File(getContext().getCacheDir(), name + "." + version - + TEST_DICT_FILE_EXTENSION); - file.createNewFile(); - } catch (IOException e) { - // do nothing - } - assertTrue("Failed to create the dictionary file.", file.exists()); - return file; - } - - private DictDecoder getDictDecoder(final File file, final int bufferType, - final FormatOptions formatOptions, final DictionaryOptions dictOptions) { - if (formatOptions.mVersion == FormatSpec.VERSION4) { - final FileHeader header = new FileHeader(0, dictOptions, formatOptions); - return FormatSpec.getDictDecoder(new File(getContext().getCacheDir(), - header.getId() + "." + header.getVersion()), bufferType); - } else { - return FormatSpec.getDictDecoder(file, bufferType); - } - } // Tests for readDictionaryBinary and writeDictionaryBinary private long timeReadingAndCheckDict(final File file, final List<String> words, final SparseArray<List<Integer>> bigrams, - final HashMap<String, List<String>> shortcutMap, final int bufferType, - final FormatOptions formatOptions, final DictionaryOptions dictOptions) { + final HashMap<String, List<String>> shortcutMap, final int bufferType) { long now, diff = -1; FusionDictionary dict = null; try { - final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions, - dictOptions); + final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(file, 0, file.length(), + bufferType); now = System.currentTimeMillis(); - dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */); + dict = dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */); diff = System.currentTimeMillis() - now; } catch (IOException e) { Log.e(TAG, "IOException while reading dictionary", e); @@ -310,17 +269,17 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final String dictName = "runReadAndWrite"; final String dictVersion = Long.toString(System.currentTimeMillis()); - final File file = setUpDictionaryFile(dictName, dictVersion); + final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions, + getContext().getCacheDir()); final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - getDictionaryOptions(dictName, dictVersion)); + BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); addUnigrams(words.size(), dict, words, shortcuts); addBigrams(dict, words, bigrams); checkDictionary(dict, words, bigrams, shortcuts); final long write = timeWritingDictToFile(file, dict, formatOptions); - final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType, - formatOptions, dict.mOptions); + final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType); return "PROF: read=" + read + "ms, write=" + write + "ms :" + message + " : " + outputOptions(bufferType, formatOptions); @@ -340,6 +299,9 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { "chain with shortcuts")); results.add(runReadAndWrite(sWords, sStarBigrams, sShortcuts, bufferType, formatOptions, "star with shortcuts")); + results.add(runReadAndWrite(sWordsWithVariousCodePoints, sEmptyBigrams, + null /* shortcuts */, bufferType, formatOptions, + "unigram with various code points")); } // Unit test for CharEncoding.readString and CharEncoding.writeString. @@ -349,8 +311,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final byte[] buffer = new byte[50 * 3]; final DictBuffer dictBuffer = new ByteArrayDictBuffer(buffer); for (final String word : sWords) { - Log.d("testReadAndWriteString", "write : " + word); - Arrays.fill(buffer, (byte)0); + Arrays.fill(buffer, (byte) 0); CharEncoding.writeString(buffer, 0, word); dictBuffer.position(0); final String str = CharEncoding.readString(dictBuffer); @@ -359,29 +320,28 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testReadAndWriteWithByteBuffer() { - final List<String> results = CollectionUtils.newArrayList(); - - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2); - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); - + final List<String> results = new ArrayList<>(); + + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, + BinaryDictUtils.VERSION2_OPTIONS); + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, + BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP); + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, + BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP); for (final String result : results) { Log.d(TAG, result); } } public void testReadAndWriteWithByteArray() { - final List<String> results = CollectionUtils.newArrayList(); + final List<String> results = new ArrayList<>(); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE); - runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, + BinaryDictUtils.VERSION2_OPTIONS); + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, + BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP); + runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, + BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP); for (final String result : results) { Log.d(TAG, result); @@ -394,62 +354,62 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final SparseArray<List<Integer>> expectedBigrams, final TreeMap<Integer, String> resultWords, final TreeMap<Integer, Integer> resultFrequencies, - final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams) { + final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams, + final boolean checkProbability) { // check unigrams - final Set<String> actualWordsSet = new HashSet<String>(resultWords.values()); - final Set<String> expectedWordsSet = new HashSet<String>(expectedWords); + final Set<String> actualWordsSet = new HashSet<>(resultWords.values()); + final Set<String> expectedWordsSet = new HashSet<>(expectedWords); assertEquals(actualWordsSet, expectedWordsSet); - - for (int freq : resultFrequencies.values()) { - assertEquals(freq, UNIGRAM_FREQ); + if (checkProbability) { + for (int freq : resultFrequencies.values()) { + assertEquals(freq, UNIGRAM_FREQ); + } } // check bigrams - final HashMap<String, List<String>> expBigrams = new HashMap<String, List<String>>(); + final HashMap<String, Set<String>> expBigrams = new HashMap<>(); for (int i = 0; i < expectedBigrams.size(); ++i) { final String word1 = expectedWords.get(expectedBigrams.keyAt(i)); for (int w2 : expectedBigrams.valueAt(i)) { if (expBigrams.get(word1) == null) { - expBigrams.put(word1, new ArrayList<String>()); + expBigrams.put(word1, new HashSet<String>()); } expBigrams.get(word1).add(expectedWords.get(w2)); } } - final HashMap<String, List<String>> actBigrams = new HashMap<String, List<String>>(); + final HashMap<String, Set<String>> actBigrams = new HashMap<>(); for (Entry<Integer, ArrayList<PendingAttribute>> entry : resultBigrams.entrySet()) { final String word1 = resultWords.get(entry.getKey()); final int unigramFreq = resultFrequencies.get(entry.getKey()); for (PendingAttribute attr : entry.getValue()) { final String word2 = resultWords.get(attr.mAddress); if (actBigrams.get(word1) == null) { - actBigrams.put(word1, new ArrayList<String>()); + actBigrams.put(word1, new HashSet<String>()); } actBigrams.get(word1).add(word2); - final int bigramFreq = BinaryDictIOUtils.reconstructBigramFrequency( - unigramFreq, attr.mFrequency); - assertTrue(Math.abs(bigramFreq - BIGRAM_FREQ) < TOLERANCE_OF_BIGRAM_FREQ); + if (checkProbability) { + final int bigramFreq = BinaryDictIOUtils.reconstructBigramFrequency( + unigramFreq, attr.mFrequency); + assertTrue(Math.abs(bigramFreq - BIGRAM_FREQ) < TOLERANCE_OF_BIGRAM_FREQ); + } } } - assertEquals(actBigrams, expBigrams); } private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words, final SparseArray<List<Integer>> bigrams, final int bufferType, - final FormatOptions formatOptions, final DictionaryOptions dictOptions) { - FileInputStream inStream = null; - - final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap(); - final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams = - CollectionUtils.newTreeMap(); - final TreeMap<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap(); + final boolean checkProbability) { + final TreeMap<Integer, String> resultWords = new TreeMap<>(); + final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams = new TreeMap<>(); + final TreeMap<Integer, Integer> resultFreqs = new TreeMap<>(); long now = -1, diff = -1; try { - final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions, - dictOptions); + final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(file, 0, file.length(), + bufferType); now = System.currentTimeMillis(); dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams); diff = System.currentTimeMillis() - now; @@ -457,17 +417,9 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { Log.e(TAG, "IOException", e); } catch (UnsupportedFormatException e) { Log.e(TAG, "UnsupportedFormatException", e); - } finally { - if (inStream != null) { - try { - inStream.close(); - } catch (IOException e) { - // do nothing - } - } } - checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams); + checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams, checkProbability); return diff; } @@ -476,20 +428,24 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final FormatSpec.FormatOptions formatOptions, final String message) { final String dictName = "runReadUnigrams"; final String dictVersion = Long.toString(System.currentTimeMillis()); - final File file = setUpDictionaryFile(dictName, dictVersion); + final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions, + getContext().getCacheDir()); // making the dictionary from lists of words. final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - getDictionaryOptions(dictName, dictVersion)); + BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); addUnigrams(words.size(), dict, words, null /* shortcutMap */); addBigrams(dict, words, bigrams); timeWritingDictToFile(file, dict, formatOptions); + // Caveat: Currently, the Java code to read a v4 dictionary doesn't calculate the + // probability when there's a timestamp for the entry. + // TODO: Abandon the Java code, and implement the v4 dictionary reading code in native. long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType, - formatOptions, dict.mOptions); + !formatOptions.mHasTimestamp /* checkProbability */); long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */, - bufferType, formatOptions, dict.mOptions); + bufferType); return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap + " : " + message + " : " + outputOptions(bufferType, formatOptions); @@ -506,15 +462,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testReadUnigramsAndBigramsBinaryWithByteBuffer() { - final ArrayList<String> results = CollectionUtils.newArrayList(); + final ArrayList<String> results = new ArrayList<>(); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, - VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); + runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER, + BinaryDictUtils.VERSION2_OPTIONS); for (final String result : results) { Log.d(TAG, result); @@ -522,15 +473,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testReadUnigramsAndBigramsBinaryWithByteArray() { - final ArrayList<String> results = CollectionUtils.newArrayList(); + final ArrayList<String> results = new ArrayList<>(); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE); - runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, - VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); + runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY, + BinaryDictUtils.VERSION2_OPTIONS); for (final String result : results) { Log.d(TAG, result); @@ -541,7 +487,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { private String getWordFromBinary(final DictDecoder dictDecoder, final int address) { if (dictDecoder.getPosition() != 0) dictDecoder.setPosition(0); - FileHeader fileHeader = null; + DictionaryHeader fileHeader = null; try { fileHeader = dictDecoder.readHeader(); } catch (IOException e) { @@ -550,8 +496,8 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { return null; } if (fileHeader == null) return null; - return BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize, - address, fileHeader.mFormatOptions).mWord; + return BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mBodyOffset, + address).mWord; } private long checkGetTerminalPosition(final DictDecoder dictDecoder, final String word, @@ -578,20 +524,22 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final FormatOptions formatOptions, final String message) { final String dictName = "testGetTerminalPosition"; final String dictVersion = Long.toString(System.currentTimeMillis()); - final File file = setUpDictionaryFile(dictName, dictVersion); + final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions, + getContext().getCacheDir()); final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - getDictionaryOptions(dictName, dictVersion)); + BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */); addBigrams(dict, words, bigrams); timeWritingDictToFile(file, dict, formatOptions); - final DictDecoder dictDecoder = getDictDecoder(file, DictDecoder.USE_BYTEARRAY, - formatOptions, dict.mOptions); + final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(file, 0, file.length(), + DictDecoder.USE_BYTEARRAY); try { dictDecoder.openDictBuffer(); } catch (IOException e) { - // ignore + Log.e(TAG, "IOException while opening the buffer", e); + } catch (UnsupportedFormatException e) { Log.e(TAG, "IOException while opening the buffer", e); } assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen()); @@ -636,67 +584,113 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testGetTerminalPosition() { - final ArrayList<String> results = CollectionUtils.newArrayList(); - - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION2); - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); - - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION2); - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE); - runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP); + final ArrayList<String> results = new ArrayList<>(); + + runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY, + BinaryDictUtils.VERSION2_OPTIONS); + runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER, + BinaryDictUtils.VERSION2_OPTIONS); for (final String result : results) { Log.d(TAG, result); } } - private void runTestDeleteWord(final FormatOptions formatOptions) { - final String dictName = "testDeleteWord"; + public void testVer2DictGetWordProperty() { + final FormatOptions formatOptions = BinaryDictUtils.VERSION2_OPTIONS; + final ArrayList<String> words = sWords; + final HashMap<String, List<String>> shortcuts = sShortcuts; + final String dictName = "testGetWordProperty"; final String dictVersion = Long.toString(System.currentTimeMillis()); - final File file = setUpDictionaryFile(dictName, dictVersion); - final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions( - new HashMap<String, String>(), false, false)); - addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */); + BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); + addUnigrams(words.size(), dict, words, shortcuts); + addBigrams(dict, words, sEmptyBigrams); + final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions, + getContext().getCacheDir()); + file.delete(); timeWritingDictToFile(file, dict, formatOptions); - - final DictUpdater dictUpdater; - if (formatOptions.mVersion == 3) { - dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER); - } else if (formatOptions.mVersion == 4) { - dictUpdater = new Ver4DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER); - } else { - throw new RuntimeException("DictUpdater for version " + formatOptions.mVersion - + " doesn't exist."); - } - - try { - MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, - dictUpdater.getTerminalPosition(sWords.get(0))); - dictUpdater.deleteWord(sWords.get(0)); - assertEquals(FormatSpec.NOT_VALID_WORD, - dictUpdater.getTerminalPosition(sWords.get(0))); - - MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, - dictUpdater.getTerminalPosition(sWords.get(5))); - dictUpdater.deleteWord(sWords.get(5)); - assertEquals(FormatSpec.NOT_VALID_WORD, - dictUpdater.getTerminalPosition(sWords.get(5))); - } catch (IOException e) { - } catch (UnsupportedFormatException e) { + final BinaryDictionary binaryDictionary = new BinaryDictionary(file.getAbsolutePath(), + 0 /* offset */, file.length(), true /* useFullEditDistance */, + Locale.ENGLISH, dictName, false /* isUpdatable */); + for (final String word : words) { + final WordProperty wordProperty = binaryDictionary.getWordProperty(word, + false /* isBeginningOfSentence */); + assertEquals(word, wordProperty.mWord); + assertEquals(UNIGRAM_FREQ, wordProperty.getProbability()); + if (shortcuts.containsKey(word)) { + assertEquals(shortcuts.get(word).size(), wordProperty.mShortcutTargets.size()); + final List<String> shortcutList = shortcuts.get(word); + assertTrue(wordProperty.mHasShortcuts); + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + assertTrue(shortcutList.contains(shortcutTarget.mWord)); + assertEquals(UNIGRAM_FREQ, shortcutTarget.getProbability()); + shortcutList.remove(shortcutTarget.mWord); + } + assertTrue(shortcutList.isEmpty()); + } } } - public void testDeleteWord() { - runTestDeleteWord(VERSION3_WITH_DYNAMIC_UPDATE); - runTestDeleteWord(VERSION4_WITH_DYNAMIC_UPDATE); + public void testVer2DictIteration() { + final FormatOptions formatOptions = BinaryDictUtils.VERSION2_OPTIONS; + final ArrayList<String> words = sWords; + final HashMap<String, List<String>> shortcuts = sShortcuts; + final SparseArray<List<Integer>> bigrams = sEmptyBigrams; + final String dictName = "testGetWordProperty"; + final String dictVersion = Long.toString(System.currentTimeMillis()); + final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), + BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); + addUnigrams(words.size(), dict, words, shortcuts); + addBigrams(dict, words, bigrams); + final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions, + getContext().getCacheDir()); + timeWritingDictToFile(file, dict, formatOptions); + Log.d(TAG, file.getAbsolutePath()); + final BinaryDictionary binaryDictionary = new BinaryDictionary(file.getAbsolutePath(), + 0 /* offset */, file.length(), true /* useFullEditDistance */, + Locale.ENGLISH, dictName, false /* isUpdatable */); + + final HashSet<String> wordSet = new HashSet<>(words); + final HashSet<Pair<String, String>> bigramSet = new HashSet<>(); + + for (int i = 0; i < words.size(); i++) { + final List<Integer> bigramList = bigrams.get(i); + if (bigramList != null) { + for (final Integer word1Index : bigramList) { + final String word1 = words.get(word1Index); + bigramSet.add(new Pair<>(words.get(i), word1)); + } + } + } + int token = 0; + do { + final BinaryDictionary.GetNextWordPropertyResult result = + binaryDictionary.getNextWordProperty(token); + final WordProperty wordProperty = result.mWordProperty; + final String word0 = wordProperty.mWord; + assertEquals(UNIGRAM_FREQ, wordProperty.mProbabilityInfo.mProbability); + wordSet.remove(word0); + if (shortcuts.containsKey(word0)) { + assertEquals(shortcuts.get(word0).size(), wordProperty.mShortcutTargets.size()); + final List<String> shortcutList = shortcuts.get(word0); + assertNotNull(wordProperty.mShortcutTargets); + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + assertTrue(shortcutList.contains(shortcutTarget.mWord)); + assertEquals(UNIGRAM_FREQ, shortcutTarget.getProbability()); + shortcutList.remove(shortcutTarget.mWord); + } + assertTrue(shortcutList.isEmpty()); + } + for (int j = 0; j < wordProperty.mBigrams.size(); j++) { + final String word1 = wordProperty.mBigrams.get(j).mWord; + final Pair<String, String> bigram = new Pair<>(word0, word1); + assertTrue(bigramSet.contains(bigram)); + bigramSet.remove(bigram); + } + token = result.mNextToken; + } while (token != 0); + assertTrue(wordSet.isEmpty()); + assertTrue(bigramSet.isEmpty()); } } diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java new file mode 100644 index 000000000..96604a197 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * Decodes binary files for a FusionDictionary. + * + * All the methods in this class are static. + * + * TODO: Move this file to makedict/internal. + * TODO: Rename this class to DictDecoderUtils. + */ +public final class BinaryDictDecoderUtils { + private BinaryDictDecoderUtils() { + // This utility class is not publicly instantiable. + } + + @UsedForTesting + public interface DictBuffer { + public int readUnsignedByte(); + public int readUnsignedShort(); + public int readUnsignedInt24(); + public int readInt(); + public int position(); + public void position(int newPosition); + @UsedForTesting + public void put(final byte b); + public int limit(); + @UsedForTesting + public int capacity(); + } + + public static final class ByteBufferDictBuffer implements DictBuffer { + private ByteBuffer mBuffer; + + public ByteBufferDictBuffer(final ByteBuffer buffer) { + mBuffer = buffer; + } + + @Override + public int readUnsignedByte() { + return mBuffer.get() & 0xFF; + } + + @Override + public int readUnsignedShort() { + return mBuffer.getShort() & 0xFFFF; + } + + @Override + public int readUnsignedInt24() { + final int retval = readUnsignedByte(); + return (retval << 16) + readUnsignedShort(); + } + + @Override + public int readInt() { + return mBuffer.getInt(); + } + + @Override + public int position() { + return mBuffer.position(); + } + + @Override + public void position(int newPos) { + mBuffer.position(newPos); + } + + @Override + public void put(final byte b) { + mBuffer.put(b); + } + + @Override + public int limit() { + return mBuffer.limit(); + } + + @Override + public int capacity() { + return mBuffer.capacity(); + } + } + + /** + * A class grouping utility function for our specific character encoding. + */ + static final class CharEncoding { + private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; + private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF; + + /** + * Helper method to find out whether this code fits on one byte + */ + private static boolean fitsOnOneByte(final int character) { + return character >= MINIMAL_ONE_BYTE_CHARACTER_VALUE + && character <= MAXIMAL_ONE_BYTE_CHARACTER_VALUE; + } + + /** + * Compute the size of a character given its character code. + * + * Char format is: + * 1 byte = bbbbbbbb match + * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte + * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because + * unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with + * 00011111 would be outside unicode. + * else: iso-latin-1 code + * This allows for the whole unicode range to be encoded, including chars outside of + * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control + * characters which should never happen anyway (and still work, but take 3 bytes). + * + * @param character the character code. + * @return the size in binary encoded-form, either 1 or 3 bytes. + */ + static int getCharSize(final int character) { + // See char encoding in FusionDictionary.java + if (fitsOnOneByte(character)) return 1; + if (FormatSpec.INVALID_CHARACTER == character) return 1; + return 3; + } + + /** + * Compute the byte size of a character array. + */ + static int getCharArraySize(final int[] chars) { + int size = 0; + for (int character : chars) size += getCharSize(character); + return size; + } + + /** + * Writes a char array to a byte buffer. + * + * @param codePoints the code point array to write. + * @param buffer the byte buffer to write to. + * @param index the index in buffer to write the character array to. + * @return the index after the last character. + */ + static int writeCharArray(final int[] codePoints, final byte[] buffer, int index) { + for (int codePoint : codePoints) { + if (1 == getCharSize(codePoint)) { + buffer[index++] = (byte)codePoint; + } else { + buffer[index++] = (byte)(0xFF & (codePoint >> 16)); + buffer[index++] = (byte)(0xFF & (codePoint >> 8)); + buffer[index++] = (byte)(0xFF & codePoint); + } + } + return index; + } + + /** + * Writes a string with our character format to a byte buffer. + * + * This will also write the terminator byte. + * + * @param buffer the byte buffer to write to. + * @param origin the offset to write from. + * @param word the string to write. + * @return the size written, in bytes. + */ + static int writeString(final byte[] buffer, final int origin, final String word) { + final int length = word.length(); + int index = origin; + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + if (1 == getCharSize(codePoint)) { + buffer[index++] = (byte)codePoint; + } else { + buffer[index++] = (byte)(0xFF & (codePoint >> 16)); + buffer[index++] = (byte)(0xFF & (codePoint >> 8)); + buffer[index++] = (byte)(0xFF & codePoint); + } + } + buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR; + return index - origin; + } + + /** + * Writes a string with our character format to an OutputStream. + * + * This will also write the terminator byte. + * + * @param stream the OutputStream to write to. + * @param word the string to write. + * @return the size written, in bytes. + */ + static int writeString(final OutputStream stream, final String word) throws IOException { + final int length = word.length(); + int written = 0; + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + final int charSize = getCharSize(codePoint); + if (1 == charSize) { + stream.write((byte) codePoint); + } else { + stream.write((byte) (0xFF & (codePoint >> 16))); + stream.write((byte) (0xFF & (codePoint >> 8))); + stream.write((byte) (0xFF & codePoint)); + } + written += charSize; + } + stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR); + written += FormatSpec.PTNODE_TERMINATOR_SIZE; + return written; + } + + /** + * Reads a string from a DictBuffer. This is the converse of the above method. + */ + static String readString(final DictBuffer dictBuffer) { + final StringBuilder s = new StringBuilder(); + int character = readChar(dictBuffer); + while (character != FormatSpec.INVALID_CHARACTER) { + s.appendCodePoint(character); + character = readChar(dictBuffer); + } + return s.toString(); + } + + /** + * Reads a character from the buffer. + * + * This follows the character format documented earlier in this source file. + * + * @param dictBuffer the buffer, positioned over an encoded character. + * @return the character code. + */ + static int readChar(final DictBuffer dictBuffer) { + int character = dictBuffer.readUnsignedByte(); + if (!fitsOnOneByte(character)) { + if (FormatSpec.PTNODE_CHARACTERS_TERMINATOR == character) { + return FormatSpec.INVALID_CHARACTER; + } + character <<= 16; + character += dictBuffer.readUnsignedShort(); + } + return character; + } + } + + /** + * Reads and returns the PtNode count out of a buffer and forwards the pointer. + */ + /* package */ static int readPtNodeCount(final DictBuffer dictBuffer) { + final int msb = dictBuffer.readUnsignedByte(); + if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= msb) { + return msb; + } else { + return ((FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT & msb) << 8) + + dictBuffer.readUnsignedByte(); + } + } + + /** + * Finds, as a string, the word at the position passed as an argument. + * + * @param dictDecoder the dict decoder. + * @param headerSize the size of the header. + * @param pos the position to seek. + * @return the word with its frequency, as a weighted string. + */ + @UsedForTesting + /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder, + final int headerSize, final int pos) { + final WeightedString result; + final int originalPos = dictDecoder.getPosition(); + dictDecoder.setPosition(pos); + result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos); + dictDecoder.setPosition(originalPos); + return result; + } + + private static WeightedString getWordAtPositionWithoutParentAddress( + final DictDecoder dictDecoder, final int headerSize, final int pos) { + dictDecoder.setPosition(headerSize); + final int count = dictDecoder.readPtNodeCount(); + int groupPos = dictDecoder.getPosition(); + final StringBuilder builder = new StringBuilder(); + WeightedString result = null; + + PtNodeInfo last = null; + for (int i = count - 1; i >= 0; --i) { + PtNodeInfo info = dictDecoder.readPtNode(groupPos); + groupPos = info.mEndAddress; + if (info.mOriginalAddress == pos) { + builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); + result = new WeightedString(builder.toString(), info.mProbabilityInfo); + break; // and return + } + if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) { + if (info.mChildrenAddress > pos) { + if (null == last) continue; + builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); + dictDecoder.setPosition(last.mChildrenAddress); + i = dictDecoder.readPtNodeCount(); + groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i); + last = null; + continue; + } + last = info; + } + if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) { + builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); + dictDecoder.setPosition(last.mChildrenAddress); + i = dictDecoder.readPtNodeCount(); + groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i); + last = null; + continue; + } + } + return result; + } + + /** + * Helper method to pass a file name instead of a File object to isBinaryDictionary. + */ + public static boolean isBinaryDictionary(final String filename) { + final File file = new File(filename); + return isBinaryDictionary(file); + } + + /** + * Basic test to find out whether the file is a binary dictionary or not. + * + * @param file The file to test. + * @return true if it's a binary dictionary, false otherwise + */ + public static boolean isBinaryDictionary(final File file) { + final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(file, 0, file.length()); + if (dictDecoder == null) { + return false; + } + return dictDecoder.hasValidRawBinaryDictionary(); + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java new file mode 100644 index 000000000..084371944 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java @@ -0,0 +1,881 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; + +/** + * Encodes binary files for a FusionDictionary. + * + * All the methods in this class are static. + * + * TODO: Rename this class to DictEncoderUtils. + */ +public class BinaryDictEncoderUtils { + + private static final boolean DBG = MakedictLog.DBG; + + private BinaryDictEncoderUtils() { + // This utility class is not publicly instantiable. + } + + // Arbitrary limit to how much passes we consider address size compression should + // terminate in. At the time of this writing, our largest dictionary completes + // compression in five passes. + // If the number of passes exceeds this number, makedict bails with an exception on + // suspicion that a bug might be causing an infinite loop. + private static final int MAX_PASSES = 24; + + /** + * Compute the binary size of the character array. + * + * If only one character, this is the size of this character. If many, it's the sum of their + * sizes + 1 byte for the terminator. + * + * @param characters the character array + * @return the size of the char array, including the terminator if any + */ + static int getPtNodeCharactersSize(final int[] characters) { + int size = CharEncoding.getCharArraySize(characters); + if (characters.length > 1) size += FormatSpec.PTNODE_TERMINATOR_SIZE; + return size; + } + + /** + * Compute the binary size of the character array in a PtNode + * + * If only one character, this is the size of this character. If many, it's the sum of their + * sizes + 1 byte for the terminator. + * + * @param ptNode the PtNode + * @return the size of the char array, including the terminator if any + */ + private static int getPtNodeCharactersSize(final PtNode ptNode) { + return getPtNodeCharactersSize(ptNode.mChars); + } + + /** + * Compute the binary size of the PtNode count for a node array. + * @param nodeArray the nodeArray + * @return the size of the PtNode count, either 1 or 2 bytes. + */ + private static int getPtNodeCountSize(final PtNodeArray nodeArray) { + return BinaryDictIOUtils.getPtNodeCountSize(nodeArray.mData.size()); + } + + /** + * Compute the size of a shortcut in bytes. + */ + private static int getShortcutSize(final WeightedString shortcut) { + int size = FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE; + final String word = shortcut.mWord; + final int length = word.length(); + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + size += CharEncoding.getCharSize(codePoint); + } + size += FormatSpec.PTNODE_TERMINATOR_SIZE; + return size; + } + + /** + * Compute the size of a shortcut list in bytes. + * + * This is known in advance and does not change according to position in the file + * like address lists do. + */ + static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) { + if (null == shortcutList || shortcutList.isEmpty()) return 0; + int size = FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE; + for (final WeightedString shortcut : shortcutList) { + size += getShortcutSize(shortcut); + } + return size; + } + + /** + * Compute the maximum size of a PtNode, assuming 3-byte addresses for everything. + * + * @param ptNode the PtNode to compute the size of. + * @return the maximum size of the PtNode. + */ + private static int getPtNodeMaximumSize(final PtNode ptNode) { + int size = getNodeHeaderSize(ptNode); + if (ptNode.isTerminal()) { + // If terminal, one byte for the frequency. + size += FormatSpec.PTNODE_FREQUENCY_SIZE; + } + size += FormatSpec.PTNODE_MAX_ADDRESS_SIZE; // For children address + size += getShortcutListSize(ptNode.mShortcutTargets); + if (null != ptNode.mBigrams) { + size += (FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE + + FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE) + * ptNode.mBigrams.size(); + } + return size; + } + + /** + * Compute the maximum size of each PtNode of a PtNode array, assuming 3-byte addresses for + * everything, and caches it in the `mCachedSize' member of the nodes; deduce the size of + * the containing node array, and cache it it its 'mCachedSize' member. + * + * @param ptNodeArray the node array to compute the maximum size of. + */ + private static void calculatePtNodeArrayMaximumSize(final PtNodeArray ptNodeArray) { + int size = getPtNodeCountSize(ptNodeArray); + for (PtNode node : ptNodeArray.mData) { + final int nodeSize = getPtNodeMaximumSize(node); + node.mCachedSize = nodeSize; + size += nodeSize; + } + ptNodeArray.mCachedSize = size; + } + + /** + * Compute the size of the header (flag + [parent address] + characters size) of a PtNode. + * + * @param ptNode the PtNode of which to compute the size of the header + */ + private static int getNodeHeaderSize(final PtNode ptNode) { + return FormatSpec.PTNODE_FLAGS_SIZE + getPtNodeCharactersSize(ptNode); + } + + /** + * Compute the size, in bytes, that an address will occupy. + * + * This can be used either for children addresses (which are always positive) or for + * attribute, which may be positive or negative but + * store their sign bit separately. + * + * @param address the address + * @return the byte size. + */ + static int getByteSize(final int address) { + assert(address <= FormatSpec.UINT24_MAX); + if (!BinaryDictIOUtils.hasChildrenAddress(address)) { + return 0; + } else if (Math.abs(address) <= FormatSpec.UINT8_MAX) { + return 1; + } else if (Math.abs(address) <= FormatSpec.UINT16_MAX) { + return 2; + } else { + return 3; + } + } + + static int writeUIntToBuffer(final byte[] buffer, int position, final int value, + final int size) { + switch(size) { + case 4: + buffer[position++] = (byte) ((value >> 24) & 0xFF); + /* fall through */ + case 3: + buffer[position++] = (byte) ((value >> 16) & 0xFF); + /* fall through */ + case 2: + buffer[position++] = (byte) ((value >> 8) & 0xFF); + /* fall through */ + case 1: + buffer[position++] = (byte) (value & 0xFF); + break; + default: + /* nop */ + } + return position; + } + + static void writeUIntToStream(final OutputStream stream, final int value, final int size) + throws IOException { + switch(size) { + case 4: + stream.write((value >> 24) & 0xFF); + /* fall through */ + case 3: + stream.write((value >> 16) & 0xFF); + /* fall through */ + case 2: + stream.write((value >> 8) & 0xFF); + /* fall through */ + case 1: + stream.write(value & 0xFF); + break; + default: + /* nop */ + } + } + + @UsedForTesting + static void writeUIntToDictBuffer(final DictBuffer dictBuffer, final int value, + final int size) { + switch(size) { + case 4: + dictBuffer.put((byte) ((value >> 24) & 0xFF)); + /* fall through */ + case 3: + dictBuffer.put((byte) ((value >> 16) & 0xFF)); + /* fall through */ + case 2: + dictBuffer.put((byte) ((value >> 8) & 0xFF)); + /* fall through */ + case 1: + dictBuffer.put((byte) (value & 0xFF)); + break; + default: + /* nop */ + } + } + + // End utility methods + + // This method is responsible for finding a nice ordering of the nodes that favors run-time + // cache performance and dictionary size. + /* package for tests */ static ArrayList<PtNodeArray> flattenTree( + final PtNodeArray rootNodeArray) { + final int treeSize = FusionDictionary.countPtNodes(rootNodeArray); + MakedictLog.i("Counted nodes : " + treeSize); + final ArrayList<PtNodeArray> flatTree = new ArrayList<>(treeSize); + return flattenTreeInner(flatTree, rootNodeArray); + } + + private static ArrayList<PtNodeArray> flattenTreeInner(final ArrayList<PtNodeArray> list, + final PtNodeArray ptNodeArray) { + // Removing the node is necessary if the tails are merged, because we would then + // add the same node several times when we only want it once. A number of places in + // the code also depends on any node being only once in the list. + // Merging tails can only be done if there are no attributes. Searching for attributes + // in LatinIME code depends on a total breadth-first ordering, which merging tails + // breaks. If there are no attributes, it should be fine (and reduce the file size) + // to merge tails, and removing the node from the list would be necessary. However, + // we don't merge tails because breaking the breadth-first ordering would result in + // extreme overhead at bigram lookup time (it would make the search function O(n) instead + // of the current O(log(n)), where n=number of nodes in the dictionary which is pretty + // high). + // If no nodes are ever merged, we can't have the same node twice in the list, hence + // searching for duplicates in unnecessary. It is also very performance consuming, + // since `list' is an ArrayList so it's an O(n) operation that runs on all nodes, making + // this simple list.remove operation O(n*n) overall. On Android this overhead is very + // high. + // For future reference, the code to remove duplicate is a simple : list.remove(node); + list.add(ptNodeArray); + final ArrayList<PtNode> branches = ptNodeArray.mData; + for (PtNode ptNode : branches) { + if (null != ptNode.mChildren) flattenTreeInner(list, ptNode.mChildren); + } + return list; + } + + /** + * Get the offset from a position inside a current node array to a target node array, during + * update. + * + * If the current node array is before the target node array, the target node array has not + * been updated yet, so we should return the offset from the old position of the current node + * array to the old position of the target node array. If on the other hand the target is + * before the current node array, it already has been updated, so we should return the offset + * from the new position in the current node array to the new position in the target node + * array. + * + * @param currentNodeArray node array containing the PtNode where the offset will be written + * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray + * @param targetNodeArray the target node array to get the offset to + * @return the offset to the target node array + */ + private static int getOffsetToTargetNodeArrayDuringUpdate(final PtNodeArray currentNodeArray, + final int offsetFromStartOfCurrentNodeArray, final PtNodeArray targetNodeArray) { + final boolean isTargetBeforeCurrent = (targetNodeArray.mCachedAddressBeforeUpdate + < currentNodeArray.mCachedAddressBeforeUpdate); + if (isTargetBeforeCurrent) { + return targetNodeArray.mCachedAddressAfterUpdate + - (currentNodeArray.mCachedAddressAfterUpdate + + offsetFromStartOfCurrentNodeArray); + } else { + return targetNodeArray.mCachedAddressBeforeUpdate + - (currentNodeArray.mCachedAddressBeforeUpdate + + offsetFromStartOfCurrentNodeArray); + } + } + + /** + * Get the offset from a position inside a current node array to a target PtNode, during + * update. + * + * @param currentNodeArray node array containing the PtNode where the offset will be written + * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray + * @param targetPtNode the target PtNode to get the offset to + * @return the offset to the target PtNode + */ + // TODO: is there any way to factorize this method with the one above? + private static int getOffsetToTargetPtNodeDuringUpdate(final PtNodeArray currentNodeArray, + final int offsetFromStartOfCurrentNodeArray, final PtNode targetPtNode) { + final int oldOffsetBasePoint = currentNodeArray.mCachedAddressBeforeUpdate + + offsetFromStartOfCurrentNodeArray; + final boolean isTargetBeforeCurrent = (targetPtNode.mCachedAddressBeforeUpdate + < oldOffsetBasePoint); + // If the target is before the current node array, then its address has already been + // updated. We can use the AfterUpdate member, and compare it to our own member after + // update. Otherwise, the AfterUpdate member is not updated yet, so we need to use the + // BeforeUpdate member, and of course we have to compare this to our own address before + // update. + if (isTargetBeforeCurrent) { + final int newOffsetBasePoint = currentNodeArray.mCachedAddressAfterUpdate + + offsetFromStartOfCurrentNodeArray; + return targetPtNode.mCachedAddressAfterUpdate - newOffsetBasePoint; + } else { + return targetPtNode.mCachedAddressBeforeUpdate - oldOffsetBasePoint; + } + } + + /** + * Computes the actual node array size, based on the cached addresses of the children nodes. + * + * Each node array stores its tentative address. During dictionary address computing, these + * are not final, but they can be used to compute the node array size (the node array size + * depends on the address of the children because the number of bytes necessary to store an + * address depends on its numeric value. The return value indicates whether the node array + * contents (as in, any of the addresses stored in the cache fields) have changed with + * respect to their previous value. + * + * @param ptNodeArray the node array to compute the size of. + * @param dict the dictionary in which the word/attributes are to be found. + * @return false if none of the cached addresses inside the node array changed, true otherwise. + */ + private static boolean computeActualPtNodeArraySize(final PtNodeArray ptNodeArray, + final FusionDictionary dict) { + boolean changed = false; + int size = getPtNodeCountSize(ptNodeArray); + for (PtNode ptNode : ptNodeArray.mData) { + ptNode.mCachedAddressAfterUpdate = ptNodeArray.mCachedAddressAfterUpdate + size; + if (ptNode.mCachedAddressAfterUpdate != ptNode.mCachedAddressBeforeUpdate) { + changed = true; + } + int nodeSize = getNodeHeaderSize(ptNode); + if (ptNode.isTerminal()) { + nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE; + } + if (null != ptNode.mChildren) { + nodeSize += getByteSize(getOffsetToTargetNodeArrayDuringUpdate(ptNodeArray, + nodeSize + size, ptNode.mChildren)); + } + nodeSize += getShortcutListSize(ptNode.mShortcutTargets); + if (null != ptNode.mBigrams) { + for (WeightedString bigram : ptNode.mBigrams) { + final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray, + nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE, + FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord)); + nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE; + } + } + ptNode.mCachedSize = nodeSize; + size += nodeSize; + } + if (ptNodeArray.mCachedSize != size) { + ptNodeArray.mCachedSize = size; + changed = true; + } + return changed; + } + + /** + * Initializes the cached addresses of node arrays and their containing nodes from their size. + * + * @param flatNodes the list of node arrays. + * @return the byte size of the entire stack. + */ + private static int initializePtNodeArraysCachedAddresses( + final ArrayList<PtNodeArray> flatNodes) { + int nodeArrayOffset = 0; + for (final PtNodeArray nodeArray : flatNodes) { + nodeArray.mCachedAddressBeforeUpdate = nodeArrayOffset; + int nodeCountSize = getPtNodeCountSize(nodeArray); + int nodeffset = 0; + for (final PtNode ptNode : nodeArray.mData) { + ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate = + nodeCountSize + nodeArrayOffset + nodeffset; + nodeffset += ptNode.mCachedSize; + } + nodeArrayOffset += nodeArray.mCachedSize; + } + return nodeArrayOffset; + } + + /** + * Updates the cached addresses of node arrays after recomputing their new positions. + * + * @param flatNodes the list of node arrays. + */ + private static void updatePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes) { + for (final PtNodeArray nodeArray : flatNodes) { + nodeArray.mCachedAddressBeforeUpdate = nodeArray.mCachedAddressAfterUpdate; + for (final PtNode ptNode : nodeArray.mData) { + ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate; + } + } + } + + /** + * Compute the addresses and sizes of an ordered list of PtNode arrays. + * + * This method takes a list of PtNode arrays and will update their cached address and size + * values so that they can be written into a file. It determines the smallest size each of the + * PtNode arrays can be given the addresses of its children and attributes, and store that into + * each PtNode. + * The order of the PtNode is given by the order of the array. This method makes no effort + * to find a good order; it only mechanically computes the size this order results in. + * + * @param dict the dictionary + * @param flatNodes the ordered list of PtNode arrays + * @return the same array it was passed. The nodes have been updated for address and size. + */ + /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict, + final ArrayList<PtNodeArray> flatNodes) { + // First get the worst possible sizes and offsets + for (final PtNodeArray n : flatNodes) { + calculatePtNodeArrayMaximumSize(n); + } + final int offset = initializePtNodeArraysCachedAddresses(flatNodes); + + MakedictLog.i("Compressing the array addresses. Original size : " + offset); + MakedictLog.i("(Recursively seen size : " + offset + ")"); + + int passes = 0; + boolean changesDone = false; + do { + changesDone = false; + int ptNodeArrayStartOffset = 0; + for (final PtNodeArray ptNodeArray : flatNodes) { + ptNodeArray.mCachedAddressAfterUpdate = ptNodeArrayStartOffset; + final int oldNodeArraySize = ptNodeArray.mCachedSize; + final boolean changed = computeActualPtNodeArraySize(ptNodeArray, dict); + final int newNodeArraySize = ptNodeArray.mCachedSize; + if (oldNodeArraySize < newNodeArraySize) { + throw new RuntimeException("Increased size ?!"); + } + ptNodeArrayStartOffset += newNodeArraySize; + changesDone |= changed; + } + updatePtNodeArraysCachedAddresses(flatNodes); + ++passes; + if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug"); + } while (changesDone); + + final PtNodeArray lastPtNodeArray = flatNodes.get(flatNodes.size() - 1); + MakedictLog.i("Compression complete in " + passes + " passes."); + MakedictLog.i("After address compression : " + + (lastPtNodeArray.mCachedAddressAfterUpdate + lastPtNodeArray.mCachedSize)); + + return flatNodes; + } + + /** + * Sanity-checking method. + * + * This method checks a list of PtNode arrays for juxtaposition, that is, it will do + * nothing if each node array's cached address is actually the previous node array's address + * plus the previous node's size. + * If this is not the case, it will throw an exception. + * + * @param arrays the list of node arrays to check + */ + /* package */ static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) { + int offset = 0; + int index = 0; + for (final PtNodeArray ptNodeArray : arrays) { + // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter + // which we use. + if (ptNodeArray.mCachedAddressAfterUpdate != offset) { + throw new RuntimeException("Wrong address for node " + index + + " : expected " + offset + ", got " + + ptNodeArray.mCachedAddressAfterUpdate); + } + ++index; + offset += ptNodeArray.mCachedSize; + } + } + + /** + * Helper method to write a children position to a file. + * + * @param buffer the buffer to write to. + * @param index the index in the buffer to write the address to. + * @param position the position to write. + * @return the size in bytes the address actually took. + */ + /* package */ static int writeChildrenPosition(final byte[] buffer, int index, + final int position) { + switch (getByteSize(position)) { + case 1: + buffer[index++] = (byte)position; + return 1; + case 2: + buffer[index++] = (byte)(0xFF & (position >> 8)); + buffer[index++] = (byte)(0xFF & position); + return 2; + case 3: + buffer[index++] = (byte)(0xFF & (position >> 16)); + buffer[index++] = (byte)(0xFF & (position >> 8)); + buffer[index++] = (byte)(0xFF & position); + return 3; + case 0: + return 0; + default: + throw new RuntimeException("Position " + position + " has a strange size"); + } + } + + /** + * Helper method to write a signed children position to a file. + * + * @param buffer the buffer to write to. + * @param index the index in the buffer to write the address to. + * @param position the position to write. + * @return the size in bytes the address actually took. + */ + /* package */ static int writeSignedChildrenPosition(final byte[] buffer, int index, + final int position) { + if (!BinaryDictIOUtils.hasChildrenAddress(position)) { + buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; + } else { + final int absPosition = Math.abs(position); + buffer[index++] = + (byte)((position < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absPosition >> 16))); + buffer[index++] = (byte)(0xFF & (absPosition >> 8)); + buffer[index++] = (byte)(0xFF & absPosition); + } + return 3; + } + + /** + * Makes the flag value for a PtNode. + * + * @param hasMultipleChars whether the PtNode has multiple chars. + * @param isTerminal whether the PtNode is terminal. + * @param childrenAddressSize the size of a children address. + * @param hasShortcuts whether the PtNode has shortcuts. + * @param hasBigrams whether the PtNode has bigrams. + * @param isNotAWord whether the PtNode is not a word. + * @param isBlackListEntry whether the PtNode is a blacklist entry. + * @return the flags + */ + static int makePtNodeFlags(final boolean hasMultipleChars, final boolean isTerminal, + final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams, + final boolean isNotAWord, final boolean isBlackListEntry) { + byte flags = 0; + if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; + if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL; + switch (childrenAddressSize) { + case 1: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES; + break; + case 0: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS; + break; + default: + throw new RuntimeException("Node with a strange address"); + } + if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; + if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS; + if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD; + if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED; + return flags; + } + + /* package */ static byte makePtNodeFlags(final PtNode node, final int childrenOffset) { + return (byte) makePtNodeFlags(node.mChars.length > 1, node.isTerminal(), + getByteSize(childrenOffset), + node.mShortcutTargets != null && !node.mShortcutTargets.isEmpty(), + node.mBigrams != null && !node.mBigrams.isEmpty(), + node.mIsNotAWord, node.mIsBlacklistEntry); + } + + /** + * Makes the flag value for a bigram. + * + * @param more whether there are more bigrams after this one. + * @param offset the offset of the bigram. + * @param bigramFrequency the frequency of the bigram, 0..255. + * @param unigramFrequency the unigram frequency of the same word, 0..255. + * @param word the second bigram, for debugging purposes + * @return the flags + */ + /* package */ static final int makeBigramFlags(final boolean more, final int offset, + int bigramFrequency, final int unigramFrequency, final String word) { + int bigramFlags = (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0) + + (offset < 0 ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0); + switch (getByteSize(offset)) { + case 1: + bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES; + break; + default: + throw new RuntimeException("Strange offset size"); + } + if (unigramFrequency > bigramFrequency) { + MakedictLog.e("Unigram freq is superior to bigram freq for \"" + word + + "\". Bigram freq is " + bigramFrequency + ", unigram freq for " + + word + " is " + unigramFrequency); + bigramFrequency = unigramFrequency; + } + bigramFlags += getBigramFrequencyDiff(unigramFrequency, bigramFrequency) + & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY; + return bigramFlags; + } + + public static int getBigramFrequencyDiff(final int unigramFrequency, + final int bigramFrequency) { + // We compute the difference between 255 (which means probability = 1) and the + // unigram score. We split this into a number of discrete steps. + // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15 + // represents an increase of 16 steps: a value of 15 will be interpreted as the median + // value of the 16th step. In all justice, if the bigram frequency is low enough to be + // rounded below the first step (which means it is less than half a step higher than the + // unigram frequency) then the unigram frequency itself is the best approximation of the + // bigram freq that we could possibly supply, hence we should *not* include this bigram + // in the file at all. + // until this is done, we'll write 0 and slightly overestimate this case. + // In other words, 0 means "between 0.5 step and 1.5 step", 1 means "between 1.5 step + // and 2.5 steps", and 15 means "between 15.5 steps and 16.5 steps". So we want to + // divide our range [unigramFreq..MAX_TERMINAL_FREQUENCY] in 16.5 steps to get the + // step size. Then we compute the start of the first step (the one where value 0 starts) + // by adding half-a-step to the unigramFrequency. From there, we compute the integer + // number of steps to the bigramFrequency. One last thing: we want our steps to include + // their lower bound and exclude their higher bound so we need to have the first step + // start at exactly 1 unit higher than floor(unigramFreq + half a step). + // Note : to reconstruct the score, the dictionary reader will need to divide + // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step, + // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best + // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the + // step pointed by the discretized frequency. + final float stepSize = + (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency) + / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY); + final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f); + final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize); + // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1 + // here. The best approximation would be the unigram freq itself, so we should not + // include this bigram in the dictionary. For now, register as 0, and live with the + // small over-estimation that we get in this case. TODO: actually remove this bigram + // if discretizedFrequency < 0. + return discretizedFrequency > 0 ? discretizedFrequency : 0; + } + + /** + * Makes the flag value for a shortcut. + * + * @param more whether there are more attributes after this one. + * @param frequency the frequency of the attribute, 0..15 + * @return the flags + */ + static final int makeShortcutFlags(final boolean more, final int frequency) { + return (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0) + + (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY); + } + + /* package */ static final int getChildrenPosition(final PtNode ptNode) { + int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate + + getNodeHeaderSize(ptNode); + if (ptNode.isTerminal()) { + // A terminal node has the frequency. + // If positionOfChildrenPosField is incorrect, we may crash when jumping to the children + // position. + positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE; + } + return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS + : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField; + } + + /** + * Write a PtNodeArray. The PtNodeArray is expected to have its final position cached. + * + * @param dict the dictionary the node array is a part of (for relative offsets). + * @param dictEncoder the dictionary encoder. + * @param ptNodeArray the node array to write. + */ + @SuppressWarnings("unused") + /* package */ static void writePlacedPtNodeArray(final FusionDictionary dict, + final DictEncoder dictEncoder, final PtNodeArray ptNodeArray) { + // TODO: Make the code in common with BinaryDictIOUtils#writePtNode + dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate); + + final int ptNodeCount = ptNodeArray.mData.size(); + dictEncoder.writePtNodeCount(ptNodeCount); + final int parentPosition = + (ptNodeArray.mCachedParentAddress == FormatSpec.NO_PARENT_ADDRESS) + ? FormatSpec.NO_PARENT_ADDRESS + : ptNodeArray.mCachedParentAddress + ptNodeArray.mCachedAddressAfterUpdate; + for (int i = 0; i < ptNodeCount; ++i) { + final PtNode ptNode = ptNodeArray.mData.get(i); + if (dictEncoder.getPosition() != ptNode.mCachedAddressAfterUpdate) { + throw new RuntimeException("Bug: write index is not the same as the cached address " + + "of the node : " + dictEncoder.getPosition() + " <> " + + ptNode.mCachedAddressAfterUpdate); + } + // Sanity checks. + if (DBG && ptNode.getProbability() > FormatSpec.MAX_TERMINAL_FREQUENCY) { + throw new RuntimeException("A node has a frequency > " + + FormatSpec.MAX_TERMINAL_FREQUENCY + + " : " + ptNode.mProbabilityInfo.toString()); + } + dictEncoder.writePtNode(ptNode, dict); + } + if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate + + ptNodeArray.mCachedSize) { + throw new RuntimeException("Not the same size : written " + + (dictEncoder.getPosition() - ptNodeArray.mCachedAddressAfterUpdate) + + " bytes from a node that should have " + ptNodeArray.mCachedSize + " bytes"); + } + } + + /** + * Dumps a collection of useful statistics about a list of PtNode arrays. + * + * This prints purely informative stuff, like the total estimated file size, the + * number of PtNode arrays, of PtNodes, the repartition of each address size, etc + * + * @param ptNodeArrays the list of PtNode arrays. + */ + /* package */ static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) { + int firstTerminalAddress = Integer.MAX_VALUE; + int lastTerminalAddress = Integer.MIN_VALUE; + int size = 0; + int ptNodes = 0; + int maxNodes = 0; + int maxRuns = 0; + for (final PtNodeArray ptNodeArray : ptNodeArrays) { + if (maxNodes < ptNodeArray.mData.size()) maxNodes = ptNodeArray.mData.size(); + for (final PtNode ptNode : ptNodeArray.mData) { + ++ptNodes; + if (ptNode.mChars.length > maxRuns) maxRuns = ptNode.mChars.length; + if (ptNode.isTerminal()) { + if (ptNodeArray.mCachedAddressAfterUpdate < firstTerminalAddress) + firstTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate; + if (ptNodeArray.mCachedAddressAfterUpdate > lastTerminalAddress) + lastTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate; + } + } + if (ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize > size) { + size = ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize; + } + } + final int[] ptNodeCounts = new int[maxNodes + 1]; + final int[] runCounts = new int[maxRuns + 1]; + for (final PtNodeArray ptNodeArray : ptNodeArrays) { + ++ptNodeCounts[ptNodeArray.mData.size()]; + for (final PtNode ptNode : ptNodeArray.mData) { + ++runCounts[ptNode.mChars.length]; + } + } + + MakedictLog.i("Statistics:\n" + + " total file size " + size + "\n" + + " " + ptNodeArrays.size() + " node arrays\n" + + " " + ptNodes + " PtNodes (" + ((float)ptNodes / ptNodeArrays.size()) + + " PtNodes per node)\n" + + " first terminal at " + firstTerminalAddress + "\n" + + " last terminal at " + lastTerminalAddress + "\n" + + " PtNode stats : max = " + maxNodes); + for (int i = 0; i < ptNodeCounts.length; ++i) { + MakedictLog.i(" " + i + " : " + ptNodeCounts[i]); + } + MakedictLog.i(" Character run stats : max = " + maxRuns); + for (int i = 0; i < runCounts.length; ++i) { + MakedictLog.i(" " + i + " : " + runCounts[i]); + } + } + + /** + * Writes a file header to an output stream. + * + * @param destination the stream to write the file header to. + * @param dict the dictionary to write. + * @param formatOptions file format options. + * @return the size of the header. + */ + /* package */ static int writeDictionaryHeader(final OutputStream destination, + final FusionDictionary dict, final FormatOptions formatOptions) + throws IOException, UnsupportedFormatException { + final int version = formatOptions.mVersion; + if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION + || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) { + throw new UnsupportedFormatException("Requested file format version " + version + + ", but this implementation only supports versions " + + FormatSpec.MINIMUM_SUPPORTED_VERSION + " through " + + FormatSpec.MAXIMUM_SUPPORTED_VERSION); + } + + ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256); + + // The magic number in big-endian order. + // Magic number for all versions. + headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 24))); + headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 16))); + headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 8))); + headerBuffer.write((byte) (0xFF & FormatSpec.MAGIC_NUMBER)); + // Dictionary version. + headerBuffer.write((byte) (0xFF & (version >> 8))); + headerBuffer.write((byte) (0xFF & version)); + + // Options flags + // TODO: Remove this field. + final int options = 0; + headerBuffer.write((byte) (0xFF & (options >> 8))); + headerBuffer.write((byte) (0xFF & options)); + final int headerSizeOffset = headerBuffer.size(); + // Placeholder to be written later with header size. + for (int i = 0; i < 4; ++i) { + headerBuffer.write(0); + } + // Write out the options. + for (final String key : dict.mOptions.mAttributes.keySet()) { + final String value = dict.mOptions.mAttributes.get(key); + CharEncoding.writeString(headerBuffer, key); + CharEncoding.writeString(headerBuffer, value); + } + final int size = headerBuffer.size(); + final byte[] bytes = headerBuffer.toByteArray(); + // Write out the header size. + bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24)); + bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16)); + bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8)); + bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0)); + destination.write(bytes); + + headerBuffer.close(); + return size; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java new file mode 100644 index 000000000..9c3b37387 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2012 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Map; +import java.util.Stack; + +public final class BinaryDictIOUtils { + private static final boolean DBG = false; + + private BinaryDictIOUtils() { + // This utility class is not publicly instantiable. + } + + /** + * Returns new dictionary decoder. + * + * @param dictFile the dictionary file. + * @param bufferType The type of buffer, as one of USE_* in DictDecoder. + * @return new dictionary decoder if the dictionary file exists, otherwise null. + */ + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length, final int bufferType) { + if (dictFile.isDirectory()) { + return new Ver4DictDecoder(dictFile, bufferType); + } else if (dictFile.isFile()) { + return new Ver2DictDecoder(dictFile, offset, length, bufferType); + } + return null; + } + + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length, final DictionaryBufferFactory factory) { + if (dictFile.isDirectory()) { + return new Ver4DictDecoder(dictFile, factory); + } else if (dictFile.isFile()) { + return new Ver2DictDecoder(dictFile, offset, length, factory); + } + return null; + } + + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length) { + return getDictDecoder(dictFile, offset, length, DictDecoder.USE_READONLY_BYTEBUFFER); + } + + private static final class Position { + public static final int NOT_READ_PTNODE_COUNT = -1; + + public int mAddress; + public int mNumOfPtNode; + public int mPosition; + public int mLength; + + public Position(int address, int length) { + mAddress = address; + mLength = length; + mNumOfPtNode = NOT_READ_PTNODE_COUNT; + } + } + + /** + * Retrieves all node arrays without recursive call. + */ + private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder, + final int bodyOffset, final Map<Integer, String> words, + final Map<Integer, Integer> frequencies, + final Map<Integer, ArrayList<PendingAttribute>> bigrams) { + int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1]; + + Stack<Position> stack = new Stack<>(); + int index = 0; + + Position initPos = new Position(bodyOffset, 0); + stack.push(initPos); + + while (!stack.empty()) { + Position p = stack.peek(); + + if (DBG) { + MakedictLog.d("read: address=" + p.mAddress + ", numOfPtNode=" + + p.mNumOfPtNode + ", position=" + p.mPosition + ", length=" + p.mLength); + } + + if (dictDecoder.getPosition() != p.mAddress) dictDecoder.setPosition(p.mAddress); + if (index != p.mLength) index = p.mLength; + + if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) { + p.mNumOfPtNode = dictDecoder.readPtNodeCount(); + p.mAddress = dictDecoder.getPosition(); + p.mPosition = 0; + } + if (p.mNumOfPtNode == 0) { + stack.pop(); + continue; + } + final PtNodeInfo ptNodeInfo = dictDecoder.readPtNode(p.mAddress); + for (int i = 0; i < ptNodeInfo.mCharacters.length; ++i) { + pushedChars[index++] = ptNodeInfo.mCharacters[i]; + } + p.mPosition++; + if (ptNodeInfo.isTerminal()) {// found word + words.put(ptNodeInfo.mOriginalAddress, new String(pushedChars, 0, index)); + frequencies.put( + ptNodeInfo.mOriginalAddress, ptNodeInfo.mProbabilityInfo.mProbability); + if (ptNodeInfo.mBigrams != null) { + bigrams.put(ptNodeInfo.mOriginalAddress, ptNodeInfo.mBigrams); + } + } + + if (p.mPosition == p.mNumOfPtNode) { + stack.pop(); + } else { + // The PtNode array has more PtNodes. + p.mAddress = dictDecoder.getPosition(); + } + + if (hasChildrenAddress(ptNodeInfo.mChildrenAddress)) { + final Position childrenPos = new Position(ptNodeInfo.mChildrenAddress, index); + stack.push(childrenPos); + } + } + } + + /** + * Reads unigrams and bigrams from the binary file. + * Doesn't store a full memory representation of the dictionary. + * + * @param dictDecoder the dict decoder. + * @param words the map to store the address as a key and the word as a value. + * @param frequencies the map to store the address as a key and the frequency as a value. + * @param bigrams the map to store the address as a key and the list of address as a value. + * @throws IOException if the file can't be read. + * @throws UnsupportedFormatException if the format of the file is not recognized. + */ + /* package */ static void readUnigramsAndBigramsBinary(final DictDecoder dictDecoder, + final Map<Integer, String> words, final Map<Integer, Integer> frequencies, + final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException, + UnsupportedFormatException { + // Read header + final DictionaryHeader header = dictDecoder.readHeader(); + readUnigramsAndBigramsBinaryInner(dictDecoder, header.mBodyOffset, words, + frequencies, bigrams); + } + + /** + * Gets the address of the last PtNode of the exact matching word in the dictionary. + * If no match is found, returns NOT_VALID_WORD. + * + * @param dictDecoder the dict decoder. + * @param word the word we search for. + * @return the address of the terminal node. + * @throws IOException if the file can't be read. + * @throws UnsupportedFormatException if the format of the file is not recognized. + */ + @UsedForTesting + /* package */ static int getTerminalPosition(final DictDecoder dictDecoder, + final String word) throws IOException, UnsupportedFormatException { + if (word == null) return FormatSpec.NOT_VALID_WORD; + dictDecoder.setPosition(0); + dictDecoder.readHeader(); + int wordPos = 0; + final int wordLen = word.codePointCount(0, word.length()); + for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) { + if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD; + + do { + final int ptNodeCount = dictDecoder.readPtNodeCount(); + boolean foundNextPtNode = false; + for (int i = 0; i < ptNodeCount; ++i) { + final int ptNodePos = dictDecoder.getPosition(); + final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos); + boolean same = true; + for (int p = 0, j = word.offsetByCodePoints(0, wordPos); + p < currentInfo.mCharacters.length; + ++p, j = word.offsetByCodePoints(j, 1)) { + if (wordPos + p >= wordLen + || word.codePointAt(j) != currentInfo.mCharacters[p]) { + same = false; + break; + } + } + + if (same) { + // found the PtNode matches the word. + if (wordPos + currentInfo.mCharacters.length == wordLen) { + if (!currentInfo.isTerminal()) { + return FormatSpec.NOT_VALID_WORD; + } else { + return ptNodePos; + } + } + wordPos += currentInfo.mCharacters.length; + if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { + return FormatSpec.NOT_VALID_WORD; + } + foundNextPtNode = true; + dictDecoder.setPosition(currentInfo.mChildrenAddress); + break; + } + } + if (foundNextPtNode) break; + return FormatSpec.NOT_VALID_WORD; + } while(true); + } + return FormatSpec.NOT_VALID_WORD; + } + + /** + * Writes a PtNodeCount to the stream. + * + * @param destination the stream to write. + * @param ptNodeCount the count. + * @return the size written in bytes. + */ + @UsedForTesting + static int writePtNodeCount(final OutputStream destination, final int ptNodeCount) + throws IOException { + final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount); + // the count must fit on one byte or two bytes. + // Please see comments in FormatSpec. + if (countSize != 1 && countSize != 2) { + throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize); + } + final int encodedPtNodeCount = (countSize == 2) ? + (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount; + BinaryDictEncoderUtils.writeUIntToStream(destination, encodedPtNodeCount, countSize); + return countSize; + } + + /** + * Helper method to hide the actual value of the no children address. + */ + public static boolean hasChildrenAddress(final int address) { + return FormatSpec.NO_CHILDREN_ADDRESS != address; + } + + /** + * Compute the binary size of the node count + * @param count the node count + * @return the size of the node count, either 1 or 2 bytes. + */ + public static int getPtNodeCountSize(final int count) { + if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= count) { + return 1; + } else if (FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY >= count) { + return 2; + } else { + throw new RuntimeException("Can't have more than " + + FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY + " PtNode in a PtNodeArray (found " + + count + ")"); + } + } + + static int getChildrenAddressSize(final int optionFlags) { + switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) { + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE: + return 1; + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES: + return 2; + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES: + return 3; + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS: + default: + return 0; + } + } + + /** + * Calculate bigram frequency from compressed value + * + * @param unigramFrequency + * @param bigramFrequency compressed frequency + * @return approximate bigram frequency + */ + @UsedForTesting + public static int reconstructBigramFrequency(final int unigramFrequency, + final int bigramFrequency) { + final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency) + / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY); + final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f); + return (int)resultFreqFloat; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java deleted file mode 100644 index afe5adb73..000000000 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2012 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.latin.makedict; - -import android.test.AndroidTestCase; -import android.test.MoreAsserts; -import android.test.suitebuilder.annotation.LargeTest; -import android.util.Log; - -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; -import com.android.inputmethod.latin.utils.CollectionUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Random; - -@LargeTest -public class BinaryDictIOUtilsTests extends AndroidTestCase { - private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName(); - private static final FormatSpec.FormatOptions FORMAT_OPTIONS = - new FormatSpec.FormatOptions(3, true); - - private static final ArrayList<String> sWords = CollectionUtils.newArrayList(); - public static final int DEFAULT_MAX_UNIGRAMS = 1500; - private final int mMaxUnigrams; - - private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; - - private static final int VERSION3 = 3; - private static final int VERSION4 = 4; - - private static final String[] CHARACTERS = { - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", - "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "\u00FC" /* ü */, "\u00E2" /* â */, "\u00F1" /* ñ */, // accented characters - "\u4E9C" /* 亜 */, "\u4F0A" /* 伊 */, "\u5B87" /* 宇 */, // kanji - "\uD841\uDE28" /* 𠘨 */, "\uD840\uDC0B" /* 𠀋 */, "\uD861\uDED7" /* 𨛗 */ // surrogate pair - }; - - public BinaryDictIOUtilsTests() { - // 1500 is the default max unigrams - this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS); - } - - public BinaryDictIOUtilsTests(final long seed, final int maxUnigrams) { - super(); - Log.d(TAG, "Seed for test is " + seed + ", maxUnigrams is " + maxUnigrams); - mMaxUnigrams = maxUnigrams; - final Random random = new Random(seed); - sWords.clear(); - for (int i = 0; i < maxUnigrams; ++i) { - sWords.add(generateWord(random.nextInt())); - } - } - - // Utilities for test - private String generateWord(final int value) { - final int lengthOfChars = CHARACTERS.length; - StringBuilder builder = new StringBuilder(""); - long lvalue = Math.abs((long)value); - while (lvalue > 0) { - builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]); - lvalue /= lengthOfChars; - } - if (builder.toString().equals("")) return "a"; - return builder.toString(); - } - - private static void printPtNode(final PtNodeInfo info) { - Log.d(TAG, " PtNode at " + info.mOriginalAddress); - Log.d(TAG, " flags = " + info.mFlags); - Log.d(TAG, " parentAddress = " + info.mParentAddress); - Log.d(TAG, " characters = " + new String(info.mCharacters, 0, - info.mCharacters.length)); - if (info.mFrequency != -1) Log.d(TAG, " frequency = " + info.mFrequency); - if (info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { - Log.d(TAG, " children address = no children address"); - } else { - Log.d(TAG, " children address = " + info.mChildrenAddress); - } - if (info.mShortcutTargets != null) { - for (final WeightedString ws : info.mShortcutTargets) { - Log.d(TAG, " shortcuts = " + ws.mWord); - } - } - if (info.mBigrams != null) { - for (final PendingAttribute attr : info.mBigrams) { - Log.d(TAG, " bigram = " + attr.mAddress); - } - } - Log.d(TAG, " end address = " + info.mEndAddress); - } - - private static void printNode(final Ver3DictDecoder dictDecoder, - final FormatSpec.FormatOptions formatOptions) { - final DictBuffer dictBuffer = dictDecoder.getDictBuffer(); - Log.d(TAG, "Node at " + dictBuffer.position()); - final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer); - Log.d(TAG, " ptNodeCount = " + count); - for (int i = 0; i < count; ++i) { - final PtNodeInfo currentInfo = dictDecoder.readPtNode(dictBuffer.position(), - formatOptions); - printPtNode(currentInfo); - } - if (formatOptions.mSupportsDynamicUpdate) { - final int forwardLinkAddress = dictBuffer.readUnsignedInt24(); - Log.d(TAG, " forwardLinkAddress = " + forwardLinkAddress); - } - } - - @SuppressWarnings("unused") - private static void printBinaryFile(final Ver3DictDecoder dictDecoder) - throws IOException, UnsupportedFormatException { - final FileHeader fileHeader = dictDecoder.readHeader(); - final DictBuffer dictBuffer = dictDecoder.getDictBuffer(); - while (dictBuffer.position() < dictBuffer.limit()) { - printNode(dictDecoder, fileHeader.mFormatOptions); - } - } - - private int getWordPosition(final File file, final String word) { - int position = FormatSpec.NOT_VALID_WORD; - - try { - final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file, - DictDecoder.USE_READONLY_BYTEBUFFER); - position = dictDecoder.getTerminalPosition(word); - } catch (IOException e) { - } catch (UnsupportedFormatException e) { - } - return position; - } - - /** - * Find a word using the DictDecoder. - * - * @param dictDecoder the dict decoder - * @param word the word searched - * @return the found ptNodeInfo - * @throws IOException - * @throws UnsupportedFormatException - */ - private static PtNodeInfo findWordByBinaryDictReader(final DictDecoder dictDecoder, - final String word) throws IOException, UnsupportedFormatException { - int position = dictDecoder.getTerminalPosition(word); - if (position != FormatSpec.NOT_VALID_WORD) { - dictDecoder.setPosition(0); - final FileHeader header = dictDecoder.readHeader(); - dictDecoder.setPosition(position); - return dictDecoder.readPtNode(position, header.mFormatOptions); - } - return null; - } - - private PtNodeInfo findWordFromFile(final File file, final String word) { - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file); - PtNodeInfo info = null; - try { - dictDecoder.openDictBuffer(); - info = findWordByBinaryDictReader(dictDecoder, word); - } catch (IOException e) { - } catch (UnsupportedFormatException e) { - } - return info; - } - - // return amount of time to insert a word - private long insertAndCheckWord(final File file, final String word, final int frequency, - final boolean exist, final ArrayList<WeightedString> bigrams, - final ArrayList<WeightedString> shortcuts, final int formatVersion) { - long amountOfTime = -1; - try { - final DictUpdater dictUpdater; - if (formatVersion == VERSION3) { - dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER); - } else { - throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't" - + " exist."); - } - - if (!exist) { - assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); - } - final long now = System.nanoTime(); - dictUpdater.insertWord(word, frequency, bigrams, shortcuts, false, false); - amountOfTime = System.nanoTime() - now; - MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); - } catch (IOException e) { - Log.e(TAG, "Raised an IOException while inserting a word", e); - } catch (UnsupportedFormatException e) { - Log.e(TAG, "Raised an UnsupportedFormatException error while inserting a word", e); - } - return amountOfTime; - } - - private void deleteWord(final File file, final String word, final int formatVersion) { - try { - final DictUpdater dictUpdater; - if (formatVersion == VERSION3) { - dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER); - } else { - throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't" - + " exist."); - } - dictUpdater.deleteWord(word); - } catch (IOException e) { - } catch (UnsupportedFormatException e) { - } - } - - private void checkReverseLookup(final File file, final String word, final int position) { - - try { - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file); - final FileHeader fileHeader = dictDecoder.readHeader(); - assertEquals(word, - BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize, - position, fileHeader.mFormatOptions).mWord); - } catch (IOException e) { - Log.e(TAG, "Raised an IOException while looking up a word", e); - } catch (UnsupportedFormatException e) { - Log.e(TAG, "Raised an UnsupportedFormatException error while looking up a word", e); - } - } - - private void runTestInsertWord(final int formatVersion) { - File file = null; - try { - file = File.createTempFile("testInsertWord", TEST_DICT_FILE_EXTENSION, - getContext().getCacheDir()); - } catch (IOException e) { - fail("IOException while creating temporary file: " + e); - } - - // set an initial dictionary. - final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); - dict.add("abcd", 10, null, false); - - try { - final DictEncoder dictEncoder = new Ver3DictEncoder(file); - dictEncoder.writeDictionary(dict, FORMAT_OPTIONS); - } catch (IOException e) { - fail("IOException while writing an initial dictionary : " + e); - } catch (UnsupportedFormatException e) { - fail("UnsupportedFormatException while writing an initial dictionary : " + e); - } - - MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd")); - insertAndCheckWord(file, "abcde", 10, false, null, null, formatVersion); - - insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null, formatVersion); - checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn")); - - insertAndCheckWord(file, "abcdabcd", 10, false, null, null, formatVersion); - checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd")); - - // update the existing word. - insertAndCheckWord(file, "abcdabcd", 15, true, null, null, formatVersion); - - // split 1 - insertAndCheckWord(file, "ab", 20, false, null, null, formatVersion); - - // split 2 - insertAndCheckWord(file, "ami", 30, false, null, null, formatVersion); - - deleteWord(file, "ami", formatVersion); - assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "ami")); - - insertAndCheckWord(file, "abcdabfg", 30, false, null, null, formatVersion); - - deleteWord(file, "abcd", formatVersion); - assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd")); - } - - public void testInsertWord() { - runTestInsertWord(VERSION3); - } - - private void runTestInsertWordWithBigrams(final int formatVersion) { - File file = null; - try { - file = File.createTempFile("testInsertWordWithBigrams", TEST_DICT_FILE_EXTENSION, - getContext().getCacheDir()); - } catch (IOException e) { - fail("IOException while creating temporary file: " + e); - } - - // set an initial dictionary. - final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); - dict.add("abcd", 10, null, false); - dict.add("efgh", 15, null, false); - - try { - final DictEncoder dictEncoder = new Ver3DictEncoder(file); - dictEncoder.writeDictionary(dict, FORMAT_OPTIONS); - } catch (IOException e) { - fail("IOException while writing an initial dictionary : " + e); - } catch (UnsupportedFormatException e) { - fail("UnsupportedFormatException while writing an initial dictionary : " + e); - } - - final ArrayList<WeightedString> banana = new ArrayList<WeightedString>(); - banana.add(new WeightedString("banana", 10)); - - insertAndCheckWord(file, "banana", 0, false, null, null, formatVersion); - insertAndCheckWord(file, "recursive", 60, true, banana, null, formatVersion); - - final PtNodeInfo info = findWordFromFile(file, "recursive"); - int bananaPos = getWordPosition(file, "banana"); - assertNotNull(info.mBigrams); - assertEquals(info.mBigrams.size(), 1); - assertEquals(info.mBigrams.get(0).mAddress, bananaPos); - } - - public void testInsertWordWithBigrams() { - runTestInsertWordWithBigrams(VERSION3); - } - - private void runTestRandomWords(final int formatVersion) { - File file = null; - try { - file = File.createTempFile("testRandomWord", TEST_DICT_FILE_EXTENSION, - getContext().getCacheDir()); - } catch (IOException e) { - } - assertNotNull(file); - - // set an initial dictionary. - final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, - false)); - dict.add("initial", 10, null, false); - - try { - final DictEncoder dictEncoder = new Ver3DictEncoder(file); - dictEncoder.writeDictionary(dict, FORMAT_OPTIONS); - } catch (IOException e) { - assertTrue(false); - } catch (UnsupportedFormatException e) { - assertTrue(false); - } - - long maxTimeToInsert = 0, sum = 0; - long minTimeToInsert = 100000000; // 1000000000 is an upper bound for minTimeToInsert. - int cnt = 0; - for (final String word : sWords) { - final long diff = insertAndCheckWord(file, word, - cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null, formatVersion); - maxTimeToInsert = Math.max(maxTimeToInsert, diff); - minTimeToInsert = Math.min(minTimeToInsert, diff); - sum += diff; - cnt++; - } - cnt = 0; - for (final String word : sWords) { - MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); - } - - Log.d(TAG, "Test version " + formatVersion); - Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms."); - Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms."); - Log.d(TAG, "avg = " + ((double)sum/mMaxUnigrams/1000000) + " ms."); - } - - public void testRandomWords() { - runTestRandomWords(VERSION3); - } -} diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java new file mode 100644 index 000000000..5a3eba801 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; + +import java.io.File; +import java.util.HashMap; + +public class BinaryDictUtils { + public static final int USE_BYTE_ARRAY = 1; + public static final int USE_BYTE_BUFFER = 2; + + public static final String TEST_DICT_FILE_EXTENSION = ".testDict"; + + public static final FormatSpec.FormatOptions VERSION2_OPTIONS = + new FormatSpec.FormatOptions(FormatSpec.VERSION2); + public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITHOUT_TIMESTAMP = + new FormatSpec.FormatOptions(FormatSpec.VERSION4, false /* hasTimestamp */); + public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITH_TIMESTAMP = + new FormatSpec.FormatOptions(FormatSpec.VERSION4, true /* hasTimestamp */); + + public static DictionaryOptions makeDictionaryOptions(final String id, final String version, + final FormatSpec.FormatOptions formatOptions) { + final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>()); + options.mAttributes.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, "en_US"); + options.mAttributes.put(DictionaryHeader.DICTIONARY_ID_KEY, id); + options.mAttributes.put(DictionaryHeader.DICTIONARY_VERSION_KEY, version); + if (formatOptions.mHasTimestamp) { + options.mAttributes.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + options.mAttributes.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + } + return options; + } + + public static File getDictFile(final String name, final String version, + final FormatOptions formatOptions, final File directory) { + if (formatOptions.mVersion == FormatSpec.VERSION2) { + return new File(directory, name + "." + version + TEST_DICT_FILE_EXTENSION); + } else if (formatOptions.mVersion == FormatSpec.VERSION4) { + return new File(directory, name + "." + version); + } else { + throw new RuntimeException("the format option has a wrong version : " + + formatOptions.mVersion); + } + } + + public static DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) { + if (formatOptions.mVersion == FormatSpec.VERSION4) { + if (!file.isDirectory()) { + file.mkdir(); + } + return new Ver4DictEncoder(file); + } else if (formatOptions.mVersion == FormatSpec.VERSION2) { + return new Ver2DictEncoder(file); + } else { + throw new RuntimeException("The format option has a wrong version : " + + formatOptions.mVersion); + } + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/DictDecoder.java new file mode 100644 index 000000000..a3b28a702 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/DictDecoder.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; +import com.android.inputmethod.latin.utils.ByteArrayDictBuffer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.TreeMap; + +/** + * An interface of binary dictionary decoders. + */ +// TODO: Straighten out responsibility for the buffer's file pointer. +public interface DictDecoder { + + /** + * Reads and returns the file header. + */ + public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException; + + /** + * Reads PtNode from ptNodePos. + * @param ptNodePos the position of PtNode. + * @return PtNodeInfo. + */ + public PtNodeInfo readPtNode(final int ptNodePos); + + /** + * Reads a buffer and returns the memory representation of the dictionary. + * + * This high-level method takes a buffer and reads its contents, populating a + * FusionDictionary structure. + * + * @param deleteDictIfBroken a flag indicating whether this method should remove the broken + * dictionary or not. + * @return the created dictionary. + */ + @UsedForTesting + public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken) + throws FileNotFoundException, IOException, UnsupportedFormatException; + + /** + * Gets the address of the last PtNode of the exact matching word in the dictionary. + * If no match is found, returns NOT_VALID_WORD. + * + * @param word the word we search for. + * @return the address of the terminal node. + * @throws IOException if the file can't be read. + * @throws UnsupportedFormatException if the format of the file is not recognized. + */ + @UsedForTesting + public int getTerminalPosition(final String word) + throws IOException, UnsupportedFormatException; + + /** + * Reads unigrams and bigrams from the binary file. + * Doesn't store a full memory representation of the dictionary. + * + * @param words the map to store the address as a key and the word as a value. + * @param frequencies the map to store the address as a key and the frequency as a value. + * @param bigrams the map to store the address as a key and the list of address as a value. + * @throws IOException if the file can't be read. + * @throws UnsupportedFormatException if the format of the file is not recognized. + */ + @UsedForTesting + public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words, + final TreeMap<Integer, Integer> frequencies, + final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams) + throws IOException, UnsupportedFormatException; + + /** + * Sets the position of the buffer to the given value. + * + * @param newPos the new position + */ + public void setPosition(final int newPos); + + /** + * Gets the position of the buffer. + * + * @return the position + */ + public int getPosition(); + + /** + * Reads and returns the PtNode count out of a buffer and forwards the pointer. + */ + public int readPtNodeCount(); + + /** + * Opens the dictionary file and makes DictBuffer. + */ + @UsedForTesting + public void openDictBuffer() throws FileNotFoundException, IOException, + UnsupportedFormatException; + @UsedForTesting + public boolean isDictBufferOpen(); + + // Constants for DictionaryBufferFactory. + public static final int USE_READONLY_BYTEBUFFER = 0x01000000; + public static final int USE_BYTEARRAY = 0x02000000; + public static final int USE_WRITABLE_BYTEBUFFER = 0x03000000; + public static final int MASK_DICTBUFFER = 0x0F000000; + + public interface DictionaryBufferFactory { + public DictBuffer getDictionaryBuffer(final File file) + throws FileNotFoundException, IOException; + } + + /** + * Creates DictionaryBuffer using a ByteBuffer + * + * This class uses less memory than DictionaryBufferFromByteArrayFactory, + * but doesn't perform as fast. + * When operating on a big dictionary, this class is preferred. + */ + public static final class DictionaryBufferFromReadOnlyByteBufferFactory + implements DictionaryBufferFactory { + @Override + public DictBuffer getDictionaryBuffer(final File file) + throws FileNotFoundException, IOException { + FileInputStream inStream = null; + ByteBuffer buffer = null; + try { + inStream = new FileInputStream(file); + buffer = inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, + 0, file.length()); + } finally { + if (inStream != null) { + inStream.close(); + } + } + if (buffer != null) { + return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer); + } + return null; + } + } + + /** + * Creates DictionaryBuffer using a byte array + * + * This class performs faster than other classes, but consumes more memory. + * When operating on a small dictionary, this class is preferred. + */ + public static final class DictionaryBufferFromByteArrayFactory + implements DictionaryBufferFactory { + @Override + public DictBuffer getDictionaryBuffer(final File file) + throws FileNotFoundException, IOException { + FileInputStream inStream = null; + try { + inStream = new FileInputStream(file); + final byte[] array = new byte[(int) file.length()]; + inStream.read(array); + return new ByteArrayDictBuffer(array); + } finally { + if (inStream != null) { + inStream.close(); + } + } + } + } + + /** + * Creates DictionaryBuffer using a writable ByteBuffer and a RandomAccessFile. + * + * This class doesn't perform as fast as other classes, + * but this class is the only option available for destructive operations (insert or delete) + * on a dictionary. + */ + @UsedForTesting + public static final class DictionaryBufferFromWritableByteBufferFactory + implements DictionaryBufferFactory { + @Override + public DictBuffer getDictionaryBuffer(final File file) + throws FileNotFoundException, IOException { + RandomAccessFile raFile = null; + ByteBuffer buffer = null; + try { + raFile = new RandomAccessFile(file, "rw"); + buffer = raFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length()); + } finally { + if (raFile != null) { + raFile.close(); + } + } + if (buffer != null) { + return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer); + } + return null; + } + } + + /** + * @return whether this decoder has a valid binary dictionary that it can decode. + */ + public boolean hasValidRawBinaryDictionary(); +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/DictEncoder.java new file mode 100644 index 000000000..678c5ca6b --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/DictEncoder.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; + +import java.io.IOException; + +/** + * An interface of binary dictionary encoder. + */ +public interface DictEncoder { + @UsedForTesting + public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) + throws IOException, UnsupportedFormatException; + + public void setPosition(final int position); + public int getPosition(); + public void writePtNodeCount(final int ptNodeCount); + public void writeForwardLinkAddress(final int forwardLinkAddress); + public void writePtNode(final PtNode ptNode, final FusionDictionary dict); +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/tests/src/com/android/inputmethod/latin/makedict/FusionDictionary.java new file mode 100644 index 000000000..4a8c178b5 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2011 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * A dictionary that can fusion heads and tails of words for more compression. + */ +@UsedForTesting +public final class FusionDictionary implements Iterable<WordProperty> { + private static final boolean DBG = MakedictLog.DBG; + + private static int CHARACTER_NOT_FOUND_INDEX = -1; + + /** + * A node array of the dictionary, containing several PtNodes. + * + * A PtNodeArray is but an ordered array of PtNodes, which essentially contain all the + * real information. + * This class also contains fields to cache size and address, to help with binary + * generation. + */ + public static final class PtNodeArray { + ArrayList<PtNode> mData; + // To help with binary generation + int mCachedSize = Integer.MIN_VALUE; + // mCachedAddressBefore/AfterUpdate are helpers for binary dictionary generation. They + // always hold the same value except between dictionary address compression, during which + // the update process needs to know about both values at the same time. Updating will + // update the AfterUpdate value, and the code will move them to BeforeUpdate before + // the next update pass. + int mCachedAddressBeforeUpdate = Integer.MIN_VALUE; + int mCachedAddressAfterUpdate = Integer.MIN_VALUE; + int mCachedParentAddress = 0; + + public PtNodeArray() { + mData = new ArrayList<>(); + } + public PtNodeArray(ArrayList<PtNode> data) { + Collections.sort(data, PTNODE_COMPARATOR); + mData = data; + } + } + + /** + * PtNode is a group of characters, with probability information, shortcut targets, bigrams, + * and children (Pt means Patricia Trie). + * + * This is the central class of the in-memory representation. A PtNode is what can + * be seen as a traditional "trie node", except it can hold several characters at the + * same time. A PtNode essentially represents one or several characters in the middle + * of the trie tree; as such, it can be a terminal, and it can have children. + * In this in-memory representation, whether the PtNode is a terminal or not is represented + * by mProbabilityInfo. The PtNode is a terminal when the mProbabilityInfo is not null and the + * PtNode is not a terminal when the mProbabilityInfo is null. A terminal may have non-null + * shortcuts and/or bigrams, but a non-terminal may not. Moreover, children, if present, + * are non-null. + */ + public static final class PtNode { + private static final int NOT_A_TERMINAL = -1; + final int mChars[]; + ArrayList<WeightedString> mShortcutTargets; + ArrayList<WeightedString> mBigrams; + // null == mProbabilityInfo indicates this is not a terminal. + ProbabilityInfo mProbabilityInfo; + int mTerminalId; // NOT_A_TERMINAL == mTerminalId indicates this is not a terminal. + PtNodeArray mChildren; + boolean mIsNotAWord; // Only a shortcut + boolean mIsBlacklistEntry; + // mCachedSize and mCachedAddressBefore/AfterUpdate are helpers for binary dictionary + // generation. Before and After always hold the same value except during dictionary + // address compression, where the update process needs to know about both values at the + // same time. Updating will update the AfterUpdate value, and the code will move them + // to BeforeUpdate before the next update pass. + // The update process does not need two versions of mCachedSize. + int mCachedSize; // The size, in bytes, of this PtNode. + int mCachedAddressBeforeUpdate; // The address of this PtNode (before update) + int mCachedAddressAfterUpdate; // The address of this PtNode (after update) + + public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets, + final ArrayList<WeightedString> bigrams, final ProbabilityInfo probabilityInfo, + final boolean isNotAWord, final boolean isBlacklistEntry) { + mChars = chars; + mProbabilityInfo = probabilityInfo; + mTerminalId = probabilityInfo == null ? NOT_A_TERMINAL : probabilityInfo.mProbability; + mShortcutTargets = shortcutTargets; + mBigrams = bigrams; + mChildren = null; + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklistEntry; + } + + public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets, + final ArrayList<WeightedString> bigrams, final ProbabilityInfo probabilityInfo, + final boolean isNotAWord, final boolean isBlacklistEntry, + final PtNodeArray children) { + mChars = chars; + mProbabilityInfo = probabilityInfo; + mShortcutTargets = shortcutTargets; + mBigrams = bigrams; + mChildren = children; + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklistEntry; + } + + public void addChild(PtNode n) { + if (null == mChildren) { + mChildren = new PtNodeArray(); + } + mChildren.mData.add(n); + } + + public int getTerminalId() { + return mTerminalId; + } + + public boolean isTerminal() { + return mProbabilityInfo != null; + } + + public int getProbability() { + if (isTerminal()) { + return mProbabilityInfo.mProbability; + } else { + return NOT_A_TERMINAL; + } + } + + public boolean getIsNotAWord() { + return mIsNotAWord; + } + + public boolean getIsBlacklistEntry() { + return mIsBlacklistEntry; + } + + public ArrayList<WeightedString> getShortcutTargets() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mShortcutTargets) return null; + final ArrayList<WeightedString> copyOfShortcutTargets = + new ArrayList<>(mShortcutTargets); + return copyOfShortcutTargets; + } + + public ArrayList<WeightedString> getBigrams() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mBigrams) return null; + final ArrayList<WeightedString> copyOfBigrams = new ArrayList<>(mBigrams); + return copyOfBigrams; + } + + public boolean hasSeveralChars() { + assert(mChars.length > 0); + return 1 < mChars.length; + } + + /** + * Adds a word to the bigram list. Updates the probability information if the word already + * exists. + */ + public void addBigram(final String word, final ProbabilityInfo probabilityInfo) { + if (mBigrams == null) { + mBigrams = new ArrayList<>(); + } + WeightedString bigram = getBigram(word); + if (bigram != null) { + bigram.mProbabilityInfo = probabilityInfo; + } else { + bigram = new WeightedString(word, probabilityInfo); + mBigrams.add(bigram); + } + } + + /** + * Gets the shortcut target for the given word. Returns null if the word is not in the + * shortcut list. + */ + public WeightedString getShortcut(final String word) { + // TODO: Don't do a linear search + if (mShortcutTargets != null) { + final int size = mShortcutTargets.size(); + for (int i = 0; i < size; ++i) { + WeightedString shortcut = mShortcutTargets.get(i); + if (shortcut.mWord.equals(word)) { + return shortcut; + } + } + } + return null; + } + + /** + * Gets the bigram for the given word. + * Returns null if the word is not in the bigrams list. + */ + public WeightedString getBigram(final String word) { + // TODO: Don't do a linear search + if (mBigrams != null) { + final int size = mBigrams.size(); + for (int i = 0; i < size; ++i) { + WeightedString bigram = mBigrams.get(i); + if (bigram.mWord.equals(word)) { + return bigram; + } + } + } + return null; + } + + /** + * Updates the PtNode with the given properties. Adds the shortcut and bigram lists to + * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only + * updated if they are higher than the existing ones. + */ + private void update(final ProbabilityInfo probabilityInfo, + final ArrayList<WeightedString> shortcutTargets, + final ArrayList<WeightedString> bigrams, + final boolean isNotAWord, final boolean isBlacklistEntry) { + mProbabilityInfo = ProbabilityInfo.max(mProbabilityInfo, probabilityInfo); + if (shortcutTargets != null) { + if (mShortcutTargets == null) { + mShortcutTargets = shortcutTargets; + } else { + final int size = shortcutTargets.size(); + for (int i = 0; i < size; ++i) { + final WeightedString shortcut = shortcutTargets.get(i); + final WeightedString existingShortcut = getShortcut(shortcut.mWord); + if (existingShortcut == null) { + mShortcutTargets.add(shortcut); + } else { + existingShortcut.mProbabilityInfo = ProbabilityInfo.max( + existingShortcut.mProbabilityInfo, shortcut.mProbabilityInfo); + } + } + } + } + if (bigrams != null) { + if (mBigrams == null) { + mBigrams = bigrams; + } else { + final int size = bigrams.size(); + for (int i = 0; i < size; ++i) { + final WeightedString bigram = bigrams.get(i); + final WeightedString existingBigram = getBigram(bigram.mWord); + if (existingBigram == null) { + mBigrams.add(bigram); + } else { + existingBigram.mProbabilityInfo = ProbabilityInfo.max( + existingBigram.mProbabilityInfo, bigram.mProbabilityInfo); + } + } + } + } + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklistEntry; + } + } + + public final DictionaryOptions mOptions; + public final PtNodeArray mRootNodeArray; + + public FusionDictionary(final PtNodeArray rootNodeArray, final DictionaryOptions options) { + mRootNodeArray = rootNodeArray; + mOptions = options; + } + + public void addOptionAttribute(final String key, final String value) { + mOptions.mAttributes.put(key, value); + } + + /** + * Helper method to convert a String to an int array. + */ + static int[] getCodePoints(final String word) { + // TODO: this is a copy-paste of the old contents of StringUtils.toCodePointArray, + // which is not visible from the makedict package. Factor this code. + final int length = word.length(); + if (length <= 0) return new int[] {}; + final char[] characters = word.toCharArray(); + final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; + int codePoint = Character.codePointAt(characters, 0); + int dsti = 0; + for (int srci = Character.charCount(codePoint); + srci < length; srci += Character.charCount(codePoint), ++dsti) { + codePoints[dsti] = codePoint; + codePoint = Character.codePointAt(characters, srci); + } + codePoints[dsti] = codePoint; + return codePoints; + } + + /** + * Helper method to add a word as a string. + * + * This method adds a word to the dictionary with the given frequency. Optional + * lists of bigrams and shortcuts can be passed here. For each word inside, + * they will be added to the dictionary as necessary. + * + * @param word the word to add. + * @param probabilityInfo probability information of the word. + * @param shortcutTargets a list of shortcut targets for this word, or null. + * @param isNotAWord true if this should not be considered a word (e.g. shortcut only) + */ + public void add(final String word, final ProbabilityInfo probabilityInfo, + final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) { + add(getCodePoints(word), probabilityInfo, shortcutTargets, isNotAWord, + false /* isBlacklistEntry */); + } + + /** + * Helper method to add a blacklist entry as a string. + * + * @param word the word to add as a blacklist entry. + * @param shortcutTargets a list of shortcut targets for this word, or null. + * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so) + */ + public void addBlacklistEntry(final String word, + final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) { + add(getCodePoints(word), new ProbabilityInfo(0), shortcutTargets, isNotAWord, + true /* isBlacklistEntry */); + } + + /** + * Sanity check for a PtNode array. + * + * This method checks that all PtNodes in a node array are ordered as expected. + * If they are, nothing happens. If they aren't, an exception is thrown. + */ + private void checkStack(PtNodeArray ptNodeArray) { + ArrayList<PtNode> stack = ptNodeArray.mData; + int lastValue = -1; + for (int i = 0; i < stack.size(); ++i) { + int currentValue = stack.get(i).mChars[0]; + if (currentValue <= lastValue) + throw new RuntimeException("Invalid stack"); + else + lastValue = currentValue; + } + } + + /** + * Helper method to add a new bigram to the dictionary. + * + * @param word0 the previous word of the context + * @param word1 the next word of the context + * @param probabilityInfo the bigram probability info + */ + public void setBigram(final String word0, final String word1, + final ProbabilityInfo probabilityInfo) { + PtNode ptNode0 = findWordInTree(mRootNodeArray, word0); + if (ptNode0 != null) { + final PtNode ptNode1 = findWordInTree(mRootNodeArray, word1); + if (ptNode1 == null) { + add(getCodePoints(word1), new ProbabilityInfo(0), null, false /* isNotAWord */, + false /* isBlacklistEntry */); + // The PtNode for the first word may have moved by the above insertion, + // if word1 and word2 share a common stem that happens not to have been + // a cutting point until now. In this case, we need to refresh ptNode. + ptNode0 = findWordInTree(mRootNodeArray, word0); + } + ptNode0.addBigram(word1, probabilityInfo); + } else { + throw new RuntimeException("First word of bigram not found " + word0); + } + } + + /** + * Add a word to this dictionary. + * + * The shortcuts, if any, have to be in the dictionary already. If they aren't, + * an exception is thrown. + * + * @param word the word, as an int array. + * @param probabilityInfo the probability information of the word. + * @param shortcutTargets an optional list of shortcut targets for this word (null if none). + * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so) + * @param isBlacklistEntry true if this is a blacklisted word, false otherwise + */ + private void add(final int[] word, final ProbabilityInfo probabilityInfo, + final ArrayList<WeightedString> shortcutTargets, + final boolean isNotAWord, final boolean isBlacklistEntry) { + assert(probabilityInfo.mProbability <= FormatSpec.MAX_TERMINAL_FREQUENCY); + if (word.length >= Constants.DICTIONARY_MAX_WORD_LENGTH) { + MakedictLog.w("Ignoring a word that is too long: word.length = " + word.length); + return; + } + + PtNodeArray currentNodeArray = mRootNodeArray; + int charIndex = 0; + + PtNode currentPtNode = null; + int differentCharIndex = 0; // Set by the loop to the index of the char that differs + int nodeIndex = findIndexOfChar(mRootNodeArray, word[charIndex]); + while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) { + currentPtNode = currentNodeArray.mData.get(nodeIndex); + differentCharIndex = compareCharArrays(currentPtNode.mChars, word, charIndex); + if (ARRAYS_ARE_EQUAL != differentCharIndex + && differentCharIndex < currentPtNode.mChars.length) break; + if (null == currentPtNode.mChildren) break; + charIndex += currentPtNode.mChars.length; + if (charIndex >= word.length) break; + currentNodeArray = currentPtNode.mChildren; + nodeIndex = findIndexOfChar(currentNodeArray, word[charIndex]); + } + + if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) { + // No node at this point to accept the word. Create one. + final int insertionIndex = findInsertionIndex(currentNodeArray, word[charIndex]); + final PtNode newPtNode = new PtNode(Arrays.copyOfRange(word, charIndex, word.length), + shortcutTargets, null /* bigrams */, probabilityInfo, isNotAWord, + isBlacklistEntry); + currentNodeArray.mData.add(insertionIndex, newPtNode); + if (DBG) checkStack(currentNodeArray); + } else { + // There is a word with a common prefix. + if (differentCharIndex == currentPtNode.mChars.length) { + if (charIndex + differentCharIndex >= word.length) { + // The new word is a prefix of an existing word, but the node on which it + // should end already exists as is. Since the old PtNode was not a terminal, + // make it one by filling in its frequency and other attributes + currentPtNode.update(probabilityInfo, shortcutTargets, null, isNotAWord, + isBlacklistEntry); + } else { + // The new word matches the full old word and extends past it. + // We only have to create a new node and add it to the end of this. + final PtNode newNode = new PtNode( + Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length), + shortcutTargets, null /* bigrams */, probabilityInfo, + isNotAWord, isBlacklistEntry); + currentPtNode.mChildren = new PtNodeArray(); + currentPtNode.mChildren.mData.add(newNode); + } + } else { + if (0 == differentCharIndex) { + // Exact same word. Update the frequency if higher. This will also add the + // new shortcuts to the existing shortcut list if it already exists. + currentPtNode.update(probabilityInfo, shortcutTargets, null, + currentPtNode.mIsNotAWord && isNotAWord, + currentPtNode.mIsBlacklistEntry || isBlacklistEntry); + } else { + // Partial prefix match only. We have to replace the current node with a node + // containing the current prefix and create two new ones for the tails. + PtNodeArray newChildren = new PtNodeArray(); + final PtNode newOldWord = new PtNode( + Arrays.copyOfRange(currentPtNode.mChars, differentCharIndex, + currentPtNode.mChars.length), currentPtNode.mShortcutTargets, + currentPtNode.mBigrams, currentPtNode.mProbabilityInfo, + currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry, + currentPtNode.mChildren); + newChildren.mData.add(newOldWord); + + final PtNode newParent; + if (charIndex + differentCharIndex >= word.length) { + newParent = new PtNode( + Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex), + shortcutTargets, null /* bigrams */, probabilityInfo, + isNotAWord, isBlacklistEntry, newChildren); + } else { + newParent = new PtNode( + Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex), + null /* shortcutTargets */, null /* bigrams */, + null /* probabilityInfo */, false /* isNotAWord */, + false /* isBlacklistEntry */, newChildren); + final PtNode newWord = new PtNode(Arrays.copyOfRange(word, + charIndex + differentCharIndex, word.length), + shortcutTargets, null /* bigrams */, probabilityInfo, + isNotAWord, isBlacklistEntry); + final int addIndex = word[charIndex + differentCharIndex] + > currentPtNode.mChars[differentCharIndex] ? 1 : 0; + newChildren.mData.add(addIndex, newWord); + } + currentNodeArray.mData.set(nodeIndex, newParent); + } + if (DBG) checkStack(currentNodeArray); + } + } + } + + private static int ARRAYS_ARE_EQUAL = 0; + + /** + * Custom comparison of two int arrays taken to contain character codes. + * + * This method compares the two arrays passed as an argument in a lexicographic way, + * with an offset in the dst string. + * This method does NOT test for the first character. It is taken to be equal. + * I repeat: this method starts the comparison at 1 <> dstOffset + 1. + * The index where the strings differ is returned. ARRAYS_ARE_EQUAL = 0 is returned if the + * strings are equal. This works BECAUSE we don't look at the first character. + * + * @param src the left-hand side string of the comparison. + * @param dst the right-hand side string of the comparison. + * @param dstOffset the offset in the right-hand side string. + * @return the index at which the strings differ, or ARRAYS_ARE_EQUAL = 0 if they don't. + */ + private static int compareCharArrays(final int[] src, final int[] dst, int dstOffset) { + // We do NOT test the first char, because we come from a method that already + // tested it. + for (int i = 1; i < src.length; ++i) { + if (dstOffset + i >= dst.length) return i; + if (src[i] != dst[dstOffset + i]) return i; + } + if (dst.length > src.length) return src.length; + return ARRAYS_ARE_EQUAL; + } + + /** + * Helper class that compares and sorts two PtNodes according to their + * first element only. I repeat: ONLY the first element is considered, the rest + * is ignored. + * This comparator imposes orderings that are inconsistent with equals. + */ + static private final class PtNodeComparator implements java.util.Comparator<PtNode> { + @Override + public int compare(PtNode p1, PtNode p2) { + if (p1.mChars[0] == p2.mChars[0]) return 0; + return p1.mChars[0] < p2.mChars[0] ? -1 : 1; + } + } + final static private PtNodeComparator PTNODE_COMPARATOR = new PtNodeComparator(); + + /** + * Finds the insertion index of a character within a node array. + */ + private static int findInsertionIndex(final PtNodeArray nodeArray, int character) { + final ArrayList<PtNode> data = nodeArray.mData; + final PtNode reference = new PtNode(new int[] { character }, + null /* shortcutTargets */, null /* bigrams */, null /* probabilityInfo */, + false /* isNotAWord */, false /* isBlacklistEntry */); + int result = Collections.binarySearch(data, reference, PTNODE_COMPARATOR); + return result >= 0 ? result : -result - 1; + } + + /** + * Find the index of a char in a node array, if it exists. + * + * @param nodeArray the node array to search in. + * @param character the character to search for. + * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else. + */ + private static int findIndexOfChar(final PtNodeArray nodeArray, int character) { + final int insertionIndex = findInsertionIndex(nodeArray, character); + if (nodeArray.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX; + return character == nodeArray.mData.get(insertionIndex).mChars[0] ? insertionIndex + : CHARACTER_NOT_FOUND_INDEX; + } + + /** + * Helper method to find a word in a given branch. + */ + public static PtNode findWordInTree(PtNodeArray nodeArray, final String string) { + int index = 0; + final StringBuilder checker = DBG ? new StringBuilder() : null; + final int[] codePoints = getCodePoints(string); + + PtNode currentPtNode; + do { + int indexOfGroup = findIndexOfChar(nodeArray, codePoints[index]); + if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null; + currentPtNode = nodeArray.mData.get(indexOfGroup); + + if (codePoints.length - index < currentPtNode.mChars.length) return null; + int newIndex = index; + while (newIndex < codePoints.length && newIndex - index < currentPtNode.mChars.length) { + if (currentPtNode.mChars[newIndex - index] != codePoints[newIndex]) return null; + newIndex++; + } + index = newIndex; + + if (DBG) { + checker.append(new String(currentPtNode.mChars, 0, currentPtNode.mChars.length)); + } + if (index < codePoints.length) { + nodeArray = currentPtNode.mChildren; + } + } while (null != nodeArray && index < codePoints.length); + + if (index < codePoints.length) return null; + if (!currentPtNode.isTerminal()) return null; + if (DBG && !string.equals(checker.toString())) return null; + return currentPtNode; + } + + /** + * Helper method to find out whether a word is in the dict or not. + */ + public boolean hasWord(final String s) { + if (null == s || "".equals(s)) { + throw new RuntimeException("Can't search for a null or empty string"); + } + return null != findWordInTree(mRootNodeArray, s); + } + + /** + * Recursively count the number of PtNodes in a given branch of the trie. + * + * @param nodeArray the parent node. + * @return the number of PtNodes in all the branch under this node. + */ + public static int countPtNodes(final PtNodeArray nodeArray) { + final int nodeSize = nodeArray.mData.size(); + int size = nodeSize; + for (int i = nodeSize - 1; i >= 0; --i) { + PtNode ptNode = nodeArray.mData.get(i); + if (null != ptNode.mChildren) + size += countPtNodes(ptNode.mChildren); + } + return size; + } + + /** + * Iterator to walk through a dictionary. + * + * This is purely for convenience. + */ + public static final class DictionaryIterator implements Iterator<WordProperty> { + private static final class Position { + public Iterator<PtNode> pos; + public int length; + public Position(ArrayList<PtNode> ptNodes) { + pos = ptNodes.iterator(); + length = 0; + } + } + final StringBuilder mCurrentString; + final LinkedList<Position> mPositions; + + public DictionaryIterator(ArrayList<PtNode> ptRoot) { + mCurrentString = new StringBuilder(); + mPositions = new LinkedList<>(); + final Position rootPos = new Position(ptRoot); + mPositions.add(rootPos); + } + + @Override + public boolean hasNext() { + for (Position p : mPositions) { + if (p.pos.hasNext()) { + return true; + } + } + return false; + } + + @Override + public WordProperty next() { + Position currentPos = mPositions.getLast(); + mCurrentString.setLength(currentPos.length); + + do { + if (currentPos.pos.hasNext()) { + final PtNode currentPtNode = currentPos.pos.next(); + currentPos.length = mCurrentString.length(); + for (int i : currentPtNode.mChars) { + mCurrentString.append(Character.toChars(i)); + } + if (null != currentPtNode.mChildren) { + currentPos = new Position(currentPtNode.mChildren.mData); + currentPos.length = mCurrentString.length(); + mPositions.addLast(currentPos); + } + if (currentPtNode.isTerminal()) { + return new WordProperty(mCurrentString.toString(), + currentPtNode.mProbabilityInfo, + currentPtNode.mShortcutTargets, currentPtNode.mBigrams, + currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry); + } + } else { + mPositions.removeLast(); + currentPos = mPositions.getLast(); + mCurrentString.setLength(mPositions.getLast().length); + } + } while (true); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Unsupported yet"); + } + + } + + /** + * Method to return an iterator. + * + * This method enables Java's enhanced for loop. With this you can have a FusionDictionary x + * and say : for (Word w : x) {} + */ + @Override + public Iterator<WordProperty> iterator() { + return new DictionaryIterator(mRootNodeArray.mData); + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/tests/src/com/android/inputmethod/latin/makedict/MakedictLog.java new file mode 100644 index 000000000..7eccff2b4 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/MakedictLog.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 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.latin.makedict; + +/** + * Wrapper to redirect log events to the right output medium. + */ +public class MakedictLog { + public static final boolean DBG = true; + + private static void print(String message) { + System.out.println(message); + } + + public static void d(String message) { + print(message); + } + + public static void i(String message) { + print(message); + } + + public static void w(String message) { + print(message); + } + + public static void e(String message) { + print(message); + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/tests/src/com/android/inputmethod/latin/makedict/PendingAttribute.java new file mode 100644 index 000000000..70e24cc98 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/PendingAttribute.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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.latin.makedict; + +/** + * A not-yet-resolved attribute. + * + * An attribute is either a bigram or a shortcut. + * All instances of this class are always immutable. + */ +public final class PendingAttribute { + public final int mFrequency; + public final int mAddress; + public PendingAttribute(final int frequency, final int address) { + mFrequency = frequency; + mAddress = address; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java b/tests/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java new file mode 100644 index 000000000..862e8c101 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.latin.makedict; + +import java.util.ArrayList; + +/** + * Raw PtNode info straight out of a file. This will contain numbers for addresses. + */ +public final class PtNodeInfo { + public final int mOriginalAddress; + public final int mEndAddress; + public final int mFlags; + public final int[] mCharacters; + public final ProbabilityInfo mProbabilityInfo; + public final int mChildrenAddress; + public final ArrayList<WeightedString> mShortcutTargets; + public final ArrayList<PendingAttribute> mBigrams; + + public PtNodeInfo(final int originalAddress, final int endAddress, final int flags, + final int[] characters, final ProbabilityInfo probabilityInfo, + final int childrenAddress, final ArrayList<WeightedString> shortcutTargets, + final ArrayList<PendingAttribute> bigrams) { + mOriginalAddress = originalAddress; + mEndAddress = endAddress; + mFlags = flags; + mCharacters = characters; + mProbabilityInfo = probabilityInfo; + mChildrenAddress = childrenAddress; + mShortcutTargets = shortcutTargets; + mBigrams = bigrams; + } + + public boolean isTerminal() { + return mProbabilityInfo != null; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java b/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java deleted file mode 100644 index aeb8552bd..000000000 --- a/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2013 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.latin.makedict; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.util.Log; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Random; - -/** - * Unit tests for SparseTable. - */ -@LargeTest -public class SparseTableTests extends AndroidTestCase { - private static final String TAG = SparseTableTests.class.getSimpleName(); - - private final Random mRandom; - private final ArrayList<Integer> mRandomIndex; - - private static final int DEFAULT_SIZE = 10000; - private static final int BLOCK_SIZE = 8; - - public SparseTableTests() { - this(System.currentTimeMillis(), DEFAULT_SIZE); - } - - public SparseTableTests(final long seed, final int tableSize) { - super(); - Log.d(TAG, "Seed for test is " + seed + ", size is " + tableSize); - mRandom = new Random(seed); - mRandomIndex = new ArrayList<Integer>(tableSize); - for (int i = 0; i < tableSize; ++i) { - mRandomIndex.add(SparseTable.NOT_EXIST); - } - } - - public void testSet() { - final SparseTable table = new SparseTable(16, BLOCK_SIZE, 1); - table.set(0, 3, 6); - table.set(0, 8, 16); - for (int i = 0; i < 16; ++i) { - if (i == 3 || i == 8) { - assertEquals(i * 2, table.get(0, i)); - } else { - assertEquals(SparseTable.NOT_EXIST, table.get(0, i)); - } - } - } - - private void generateRandomIndex(final int size, final int prop) { - for (int i = 0; i < size; ++i) { - if (mRandom.nextInt(100) < prop) { - mRandomIndex.set(i, mRandom.nextInt()); - } else { - mRandomIndex.set(i, SparseTable.NOT_EXIST); - } - } - } - - private void runTestRandomSet() { - final SparseTable table = new SparseTable(DEFAULT_SIZE, BLOCK_SIZE, 1); - int elementCount = 0; - for (int i = 0; i < DEFAULT_SIZE; ++i) { - if (mRandomIndex.get(i) != SparseTable.NOT_EXIST) { - table.set(0, i, mRandomIndex.get(i)); - elementCount++; - } - } - - Log.d(TAG, "table size = " + table.getLookupTableSize() + " + " - + table.getContentTableSize()); - Log.d(TAG, "the table has " + elementCount + " elements"); - for (int i = 0; i < DEFAULT_SIZE; ++i) { - assertEquals(table.get(0, i), (int)mRandomIndex.get(i)); - } - - // flush and reload - OutputStream lookupOutStream = null; - OutputStream contentOutStream = null; - try { - final File lookupIndexFile = File.createTempFile("testRandomSet", ".small"); - final File contentFile = File.createTempFile("testRandomSet", ".big"); - lookupOutStream = new FileOutputStream(lookupIndexFile); - contentOutStream = new FileOutputStream(contentFile); - table.write(lookupOutStream, new OutputStream[] { contentOutStream }); - lookupOutStream.flush(); - contentOutStream.flush(); - final SparseTable newTable = SparseTable.readFromFiles(lookupIndexFile, - new File[] { contentFile }, BLOCK_SIZE); - for (int i = 0; i < DEFAULT_SIZE; ++i) { - assertEquals(table.get(0, i), newTable.get(0, i)); - } - } catch (IOException e) { - Log.d(TAG, "IOException while flushing and realoding", e); - } finally { - if (lookupOutStream != null) { - try { - lookupOutStream.close(); - } catch (IOException e) { - Log.d(TAG, "IOException while closing the stream", e); - } - } - if (contentOutStream != null) { - try { - contentOutStream.close(); - } catch (IOException e) { - Log.d(TAG, "IOException while closing contentStream.", e); - } - } - } - } - - public void testRandomSet() { - for (int i = 0; i <= 100; i += 10) { - generateRandomIndex(DEFAULT_SIZE, i); - runTestRandomSet(); - } - } - - public void testMultipleContents() { - final int numOfContents = 5; - generateRandomIndex(DEFAULT_SIZE, 20); - final SparseTable table = new SparseTable(DEFAULT_SIZE, BLOCK_SIZE, numOfContents); - for (int i = 0; i < mRandomIndex.size(); ++i) { - if (mRandomIndex.get(i) != SparseTable.NOT_EXIST) { - for (int j = 0; j < numOfContents; ++j) { - table.set(j, i, mRandomIndex.get(i)); - } - } - } - - OutputStream lookupOutStream = null; - OutputStream[] contentsOutStream = new OutputStream[numOfContents]; - try { - final File lookupIndexFile = File.createTempFile("testMultipleContents", "small"); - lookupOutStream = new FileOutputStream(lookupIndexFile); - final File[] contentFiles = new File[numOfContents]; - for (int i = 0; i < numOfContents; ++i) { - contentFiles[i] = File.createTempFile("testMultipleContents", "big" + i); - contentsOutStream[i] = new FileOutputStream(contentFiles[i]); - } - table.write(lookupOutStream, contentsOutStream); - lookupOutStream.flush(); - for (int i = 0; i < numOfContents; ++i) { - contentsOutStream[i].flush(); - } - final SparseTable newTable = SparseTable.readFromFiles(lookupIndexFile, contentFiles, - BLOCK_SIZE); - for (int i = 0; i < numOfContents; ++i) { - for (int j = 0; j < DEFAULT_SIZE; ++j) { - assertEquals(table.get(i, j), newTable.get(i, j)); - } - } - } catch (IOException e) { - Log.d(TAG, "IOException while flushing and reloading", e); - } finally { - if (lookupOutStream != null) { - try { - lookupOutStream.close(); - } catch (IOException e) { - Log.d(TAG, "IOException while closing the stream", e); - } - } - for (int i = 0; i < numOfContents; ++i) { - if (contentsOutStream[i] != null) { - try { - contentsOutStream[i].close(); - } catch (IOException e) { - Log.d(TAG, "IOException while closing the stream.", e); - } - } - } - } - } -} diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java new file mode 100644 index 000000000..65b84d5f7 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * An implementation of DictDecoder for version 2 binary dictionary. + */ +// TODO: Separate logics that are used only for testing. +@UsedForTesting +public class Ver2DictDecoder extends AbstractDictDecoder { + /** + * A utility class for reading a PtNode. + */ + protected static class PtNodeReader { + private static ProbabilityInfo readProbabilityInfo(final DictBuffer dictBuffer) { + // Ver2 dicts don't contain historical information. + return new ProbabilityInfo(dictBuffer.readUnsignedByte()); + } + + protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) { + return dictBuffer.readUnsignedByte(); + } + + protected static int readChildrenAddress(final DictBuffer dictBuffer, + final int ptNodeFlags) { + switch (ptNodeFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) { + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE: + return dictBuffer.readUnsignedByte(); + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES: + return dictBuffer.readUnsignedShort(); + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES: + return dictBuffer.readUnsignedInt24(); + case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS: + default: + return FormatSpec.NO_CHILDREN_ADDRESS; + } + } + + // Reads shortcuts and returns the read length. + protected static int readShortcut(final DictBuffer dictBuffer, + final ArrayList<WeightedString> shortcutTargets) { + final int pointerBefore = dictBuffer.position(); + dictBuffer.readUnsignedShort(); // skip the size + while (true) { + final int targetFlags = dictBuffer.readUnsignedByte(); + final String word = CharEncoding.readString(dictBuffer); + shortcutTargets.add(new WeightedString(word, + targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY)); + if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break; + } + return dictBuffer.position() - pointerBefore; + } + + protected static int readBigramAddresses(final DictBuffer dictBuffer, + final ArrayList<PendingAttribute> bigrams, final int baseAddress) { + int readLength = 0; + int bigramCount = 0; + while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) { + final int bigramFlags = dictBuffer.readUnsignedByte(); + ++readLength; + final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE) + ? 1 : -1; + int bigramAddress = baseAddress + readLength; + switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) { + case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE: + bigramAddress += sign * dictBuffer.readUnsignedByte(); + readLength += 1; + break; + case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES: + bigramAddress += sign * dictBuffer.readUnsignedShort(); + readLength += 2; + break; + case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES: + bigramAddress += sign * dictBuffer.readUnsignedInt24(); + readLength += 3; + break; + default: + throw new RuntimeException("Has bigrams with no address"); + } + bigrams.add(new PendingAttribute( + bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY, + bigramAddress)); + if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break; + } + return readLength; + } + } + + protected final File mDictionaryBinaryFile; + protected final long mOffset; + protected final long mLength; + // TODO: Remove mBufferFactory and mDictBuffer from this class members because they are now + // used only for testing. + private final DictionaryBufferFactory mBufferFactory; + protected DictBuffer mDictBuffer; + + @UsedForTesting + /* package */ Ver2DictDecoder(final File file, final long offset, final long length, + final int factoryFlag) { + mDictionaryBinaryFile = file; + mOffset = offset; + mLength = length; + mDictBuffer = null; + if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) { + mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory(); + } else if ((factoryFlag & MASK_DICTBUFFER) == USE_BYTEARRAY) { + mBufferFactory = new DictionaryBufferFromByteArrayFactory(); + } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) { + mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory(); + } else { + mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory(); + } + } + + /* package */ Ver2DictDecoder(final File file, final long offset, final long length, + final DictionaryBufferFactory factory) { + mDictionaryBinaryFile = file; + mOffset = offset; + mLength = length; + mBufferFactory = factory; + } + + @Override + public void openDictBuffer() throws FileNotFoundException, IOException { + mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile); + } + + @Override + public boolean isDictBufferOpen() { + return mDictBuffer != null; + } + + /* package */ DictBuffer getDictBuffer() { + return mDictBuffer; + } + + @UsedForTesting + /* package */ DictBuffer openAndGetDictBuffer() throws FileNotFoundException, IOException { + openDictBuffer(); + return getDictBuffer(); + } + + @Override + public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException { + // dictType is not being used in dicttool. Passing an empty string. + final BinaryDictionary binaryDictionary = new BinaryDictionary( + mDictionaryBinaryFile.getAbsolutePath(), mOffset, mLength, + true /* useFullEditDistance */, null /* locale */, "" /* dictType */, + false /* isUpdatable */); + final DictionaryHeader header = binaryDictionary.getHeader(); + binaryDictionary.close(); + if (header == null) { + throw new IOException("Cannot read the dictionary header."); + } + if (header.mFormatOptions.mVersion != FormatSpec.VERSION2) { + throw new UnsupportedFormatException("File header has a wrong version : " + + header.mFormatOptions.mVersion); + } + if (!isDictBufferOpen()) { + openDictBuffer(); + } + // Advance buffer reading position to the head of dictionary body. + setPosition(header.mBodyOffset); + return header; + } + + // TODO: Make this buffer multi thread safe. + private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH]; + @Override + public PtNodeInfo readPtNode(final int ptNodePos) { + int addressPointer = ptNodePos; + final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); + addressPointer += FormatSpec.PTNODE_FLAGS_SIZE; + final int characters[]; + if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) { + int index = 0; + int character = CharEncoding.readChar(mDictBuffer); + addressPointer += CharEncoding.getCharSize(character); + while (FormatSpec.INVALID_CHARACTER != character) { + // FusionDictionary is making sure that the length of the word is smaller than + // MAX_WORD_LENGTH. + // So we'll never write past the end of mCharacterBuffer. + mCharacterBuffer[index++] = character; + character = CharEncoding.readChar(mDictBuffer); + addressPointer += CharEncoding.getCharSize(character); + } + characters = Arrays.copyOfRange(mCharacterBuffer, 0, index); + } else { + final int character = CharEncoding.readChar(mDictBuffer); + addressPointer += CharEncoding.getCharSize(character); + characters = new int[] { character }; + } + final ProbabilityInfo probabilityInfo; + if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) { + probabilityInfo = PtNodeReader.readProbabilityInfo(mDictBuffer); + addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE; + } else { + probabilityInfo = null; + } + int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags); + if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + childrenAddress += addressPointer; + } + addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags); + final ArrayList<WeightedString> shortcutTargets; + if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) { + // readShortcut will add shortcuts to shortcutTargets. + shortcutTargets = new ArrayList<>(); + addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets); + } else { + shortcutTargets = null; + } + + final ArrayList<PendingAttribute> bigrams; + if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { + bigrams = new ArrayList<>(); + addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams, + addressPointer); + if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) { + throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size() + + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")"); + } + } else { + bigrams = null; + } + return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, probabilityInfo, + childrenAddress, shortcutTargets, bigrams); + } + + @Override + public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken) + throws FileNotFoundException, IOException, UnsupportedFormatException { + // dictType is not being used in dicttool. Passing an empty string. + final BinaryDictionary binaryDictionary = new BinaryDictionary( + mDictionaryBinaryFile.getAbsolutePath(), 0 /* offset */, + mDictionaryBinaryFile.length() /* length */, true /* useFullEditDistance */, + null /* locale */, "" /* dictType */, false /* isUpdatable */); + final DictionaryHeader header = readHeader(); + final FusionDictionary fusionDict = + new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions); + int token = 0; + final ArrayList<WordProperty> wordProperties = new ArrayList<>(); + do { + final BinaryDictionary.GetNextWordPropertyResult result = + binaryDictionary.getNextWordProperty(token); + final WordProperty wordProperty = result.mWordProperty; + if (wordProperty == null) { + binaryDictionary.close(); + if (deleteDictIfBroken) { + mDictionaryBinaryFile.delete(); + } + return null; + } + wordProperties.add(wordProperty); + token = result.mNextToken; + } while (token != 0); + + // Insert unigrams into the fusion dictionary. + for (final WordProperty wordProperty : wordProperties) { + if (wordProperty.mIsBlacklistEntry) { + fusionDict.addBlacklistEntry(wordProperty.mWord, wordProperty.mShortcutTargets, + wordProperty.mIsNotAWord); + } else { + fusionDict.add(wordProperty.mWord, wordProperty.mProbabilityInfo, + wordProperty.mShortcutTargets, wordProperty.mIsNotAWord); + } + } + // Insert bigrams into the fusion dictionary. + for (final WordProperty wordProperty : wordProperties) { + if (wordProperty.mBigrams == null) { + continue; + } + final String word0 = wordProperty.mWord; + for (final WeightedString bigram : wordProperty.mBigrams) { + fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo); + } + } + binaryDictionary.close(); + return fusionDict; + } + + @Override + public void setPosition(int newPos) { + mDictBuffer.position(newPos); + } + + @Override + public int getPosition() { + return mDictBuffer.position(); + } + + @Override + public int readPtNodeCount() { + return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer); + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver3DictDecoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoderTests.java index 9611599b9..3882c2c55 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/Ver3DictDecoderTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoderTests.java @@ -32,10 +32,10 @@ import java.io.FileOutputStream; import java.io.IOException; /** - * Unit tests for Ver3DictDecoder + * Unit tests for Ver2DictDecoder */ -public class Ver3DictDecoderTests extends AndroidTestCase { - private static final String TAG = Ver3DictDecoderTests.class.getSimpleName(); +public class Ver2DictDecoderTests extends AndroidTestCase { + private static final String TAG = Ver2DictDecoderTests.class.getSimpleName(); private final byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; @@ -58,7 +58,6 @@ public class Ver3DictDecoderTests extends AndroidTestCase { } } - @SuppressWarnings("null") public void runTestOpenBuffer(final String testName, final DictionaryBufferFactory factory) { File testFile = null; try { @@ -68,7 +67,8 @@ public class Ver3DictDecoderTests extends AndroidTestCase { } assertNotNull(testFile); - final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(testFile, factory); + final Ver2DictDecoder dictDecoder = new Ver2DictDecoder(testFile, 0, testFile.length(), + factory); try { dictDecoder.openDictBuffer(); } catch (Exception e) { @@ -101,7 +101,6 @@ public class Ver3DictDecoderTests extends AndroidTestCase { new DictionaryBufferFromWritableByteBufferFactory()); } - @SuppressWarnings("null") public void runTestGetBuffer(final String testName, final DictionaryBufferFactory factory) { File testFile = null; try { @@ -110,7 +109,8 @@ public class Ver3DictDecoderTests extends AndroidTestCase { Log.e(TAG, "IOException while the creating temporary file", e); } - final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(testFile, factory); + final Ver2DictDecoder dictDecoder = new Ver2DictDecoder(testFile, 0, testFile.length(), + factory); // the default return value of getBuffer() must be null. assertNull("the default return value of getBuffer() is not null", diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java new file mode 100644 index 000000000..a286190cb --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * An implementation of DictEncoder for version 2 binary dictionary. + */ +@UsedForTesting +public class Ver2DictEncoder implements DictEncoder { + + private final File mDictFile; + private OutputStream mOutStream; + private byte[] mBuffer; + private int mPosition; + + @UsedForTesting + public Ver2DictEncoder(final File dictFile) { + mDictFile = dictFile; + mOutStream = null; + mBuffer = null; + } + + // This constructor is used only by BinaryDictOffdeviceUtilsTests. + // If you want to use this in the production code, you should consider keeping consistency of + // the interface of Ver3DictDecoder by using factory. + @UsedForTesting + public Ver2DictEncoder(final OutputStream outStream) { + mDictFile = null; + mOutStream = outStream; + } + + private void openStream() throws FileNotFoundException { + mOutStream = new FileOutputStream(mDictFile); + } + + private void close() throws IOException { + if (mOutStream != null) { + mOutStream.close(); + mOutStream = null; + } + } + + @Override + public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) + throws IOException, UnsupportedFormatException { + if (formatOptions.mVersion > FormatSpec.VERSION2) { + throw new UnsupportedFormatException( + "The given format options has wrong version number : " + + formatOptions.mVersion); + } + + if (mOutStream == null) { + openStream(); + } + BinaryDictEncoderUtils.writeDictionaryHeader(mOutStream, dict, formatOptions); + + // Addresses are limited to 3 bytes, but since addresses can be relative to each node + // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding + // the order of the PtNode arrays becomes a quite complicated problem, because though the + // dictionary itself does not have a size limit, each node array must still be within 16MB + // of all its children and parents. As long as this is ensured, the dictionary file may + // grow to any size. + + // Leave the choice of the optimal node order to the flattenTree function. + MakedictLog.i("Flattening the tree..."); + ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray); + + MakedictLog.i("Computing addresses..."); + BinaryDictEncoderUtils.computeAddresses(dict, flatNodes); + MakedictLog.i("Checking PtNode array..."); + if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes); + + // Create a buffer that matches the final dictionary size. + final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1); + final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize; + mBuffer = new byte[bufferSize]; + + MakedictLog.i("Writing file..."); + + for (PtNodeArray nodeArray : flatNodes) { + BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray); + } + if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes); + mOutStream.write(mBuffer, 0, mPosition); + + MakedictLog.i("Done"); + close(); + } + + @Override + public void setPosition(final int position) { + if (mBuffer == null || position < 0 || position >= mBuffer.length) return; + mPosition = position; + } + + @Override + public int getPosition() { + return mPosition; + } + + @Override + public void writePtNodeCount(final int ptNodeCount) { + final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount); + if (countSize != 1 && countSize != 2) { + throw new RuntimeException("Strange size from getGroupCountSize : " + countSize); + } + final int encodedPtNodeCount = (countSize == 2) ? + (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount; + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, encodedPtNodeCount, + countSize); + } + + private void writePtNodeFlags(final PtNode ptNode) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode); + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, + BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos), + FormatSpec.PTNODE_FLAGS_SIZE); + } + + private void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) { + mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition); + if (hasSeveralChars) { + mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR; + } + } + + private void writeFrequency(final int frequency) { + if (frequency >= 0) { + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, frequency, + FormatSpec.PTNODE_FREQUENCY_SIZE); + } + } + + private void writeChildrenPosition(final PtNode ptNode) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode); + mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, + childrenPos); + } + + /** + * Write a shortcut attributes list to mBuffer. + * + * @param shortcuts the shortcut attributes list. + */ + private void writeShortcuts(final ArrayList<WeightedString> shortcuts) { + if (null == shortcuts || shortcuts.isEmpty()) return; + + final int indexOfShortcutByteSize = mPosition; + mPosition += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE; + final Iterator<WeightedString> shortcutIterator = shortcuts.iterator(); + while (shortcutIterator.hasNext()) { + final WeightedString target = shortcutIterator.next(); + final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags( + shortcutIterator.hasNext(), + target.getProbability()); + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags, + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); + final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord); + mPosition += shortcutShift; + } + final int shortcutByteSize = mPosition - indexOfShortcutByteSize; + if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) { + throw new RuntimeException("Shortcut list too large"); + } + BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, indexOfShortcutByteSize, shortcutByteSize, + FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE); + } + + /** + * Write a bigram attributes list to mBuffer. + * + * @param bigrams the bigram attributes list. + * @param dict the dictionary the node array is a part of (for relative offsets). + */ + private void writeBigrams(final ArrayList<WeightedString> bigrams, + final FusionDictionary dict) { + if (bigrams == null) return; + + final Iterator<WeightedString> bigramIterator = bigrams.iterator(); + while (bigramIterator.hasNext()) { + final WeightedString bigram = bigramIterator.next(); + final PtNode target = + FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord); + final int addressOfBigram = target.mCachedAddressAfterUpdate; + final int unigramFrequencyForThisWord = target.getProbability(); + final int offset = addressOfBigram + - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); + final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(), + offset, bigram.getProbability(), unigramFrequencyForThisWord, bigram.mWord); + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags, + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); + mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, + Math.abs(offset)); + } + } + + @Override + public void writeForwardLinkAddress(final int forwardLinkAddress) { + mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, forwardLinkAddress, + FormatSpec.FORWARD_LINK_ADDRESS_SIZE); + } + + @Override + public void writePtNode(final PtNode ptNode, final FusionDictionary dict) { + writePtNodeFlags(ptNode); + writeCharacters(ptNode.mChars, ptNode.hasSeveralChars()); + writeFrequency(ptNode.getProbability()); + writeChildrenPosition(ptNode); + writeShortcuts(ptNode.mShortcutTargets); + writeBigrams(ptNode.mBigrams, dict); + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java new file mode 100644 index 000000000..5e8417ed6 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.utils.FileUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; + +/** + * An implementation of binary dictionary decoder for version 4 binary dictionary. + */ +@UsedForTesting +public class Ver4DictDecoder extends AbstractDictDecoder { + final File mDictDirectory; + + @UsedForTesting + /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) { + this(dictDirectory, null /* factory */); + } + + @UsedForTesting + /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) { + mDictDirectory = dictDirectory; + + } + + @Override + public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException { + // dictType is not being used in dicttool. Passing an empty string. + final BinaryDictionary binaryDictionary= new BinaryDictionary( + mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */, + true /* useFullEditDistance */, null /* locale */, + "" /* dictType */, true /* isUpdatable */); + final DictionaryHeader header = binaryDictionary.getHeader(); + binaryDictionary.close(); + if (header == null) { + throw new IOException("Cannot read the dictionary header."); + } + return header; + } + + @Override + public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken) + throws FileNotFoundException, IOException, UnsupportedFormatException { + // dictType is not being used in dicttool. Passing an empty string. + final BinaryDictionary binaryDictionary = new BinaryDictionary( + mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */, + true /* useFullEditDistance */, null /* locale */, + "" /* dictType */, true /* isUpdatable */); + final DictionaryHeader header = readHeader(); + final FusionDictionary fusionDict = + new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions); + int token = 0; + final ArrayList<WordProperty> wordProperties = new ArrayList<>(); + do { + final BinaryDictionary.GetNextWordPropertyResult result = + binaryDictionary.getNextWordProperty(token); + final WordProperty wordProperty = result.mWordProperty; + if (wordProperty == null) { + binaryDictionary.close(); + if (deleteDictIfBroken) { + FileUtils.deleteRecursively(mDictDirectory); + } + return null; + } + wordProperties.add(wordProperty); + token = result.mNextToken; + } while (token != 0); + + // Insert unigrams into the fusion dictionary. + for (final WordProperty wordProperty : wordProperties) { + if (wordProperty.mIsBlacklistEntry) { + fusionDict.addBlacklistEntry(wordProperty.mWord, wordProperty.mShortcutTargets, + wordProperty.mIsNotAWord); + } else { + fusionDict.add(wordProperty.mWord, wordProperty.mProbabilityInfo, + wordProperty.mShortcutTargets, wordProperty.mIsNotAWord); + } + } + // Insert bigrams into the fusion dictionary. + for (final WordProperty wordProperty : wordProperties) { + if (wordProperty.mBigrams == null) { + continue; + } + final String word0 = wordProperty.mWord; + for (final WeightedString bigram : wordProperty.mBigrams) { + fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo); + } + } + binaryDictionary.close(); + return fusionDict; + } +} diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java new file mode 100644 index 000000000..76eaef431 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2013 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.latin.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.PrevWordsInfo; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; + +import java.io.File; +import java.io.IOException; + +/** + * An implementation of DictEncoder for version 4 binary dictionary. + */ +@UsedForTesting +public class Ver4DictEncoder implements DictEncoder { + private final File mDictPlacedDir; + + @UsedForTesting + public Ver4DictEncoder(final File dictPlacedDir) { + mDictPlacedDir = dictPlacedDir; + } + + // TODO: This builds a FusionDictionary first and iterates it to add words to the binary + // dictionary. However, it is possible to just add words directly to the binary dictionary + // instead. + // In the long run, when we stop supporting version 2, FusionDictionary will become deprecated + // and we can remove it. Then we'll be able to just call BinaryDictionary directly. + @Override + public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions) + throws IOException, UnsupportedFormatException { + if (formatOptions.mVersion != FormatSpec.VERSION4) { + throw new UnsupportedFormatException("File header has a wrong version number : " + + formatOptions.mVersion); + } + if (!mDictPlacedDir.isDirectory()) { + throw new UnsupportedFormatException("Given path is not a directory."); + } + if (!BinaryDictionaryUtils.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(), + FormatSpec.VERSION4, LocaleUtils.constructLocaleFromString( + dict.mOptions.mAttributes.get(DictionaryHeader.DICTIONARY_LOCALE_KEY)), + dict.mOptions.mAttributes)) { + throw new IOException("Cannot create dictionary file : " + + mDictPlacedDir.getAbsolutePath()); + } + final BinaryDictionary binaryDict = new BinaryDictionary(mDictPlacedDir.getAbsolutePath(), + 0l, mDictPlacedDir.length(), true /* useFullEditDistance */, + LocaleUtils.constructLocaleFromString(dict.mOptions.mAttributes.get( + DictionaryHeader.DICTIONARY_LOCALE_KEY)), + Dictionary.TYPE_USER /* Dictionary type. Does not matter for us */, + true /* isUpdatable */); + if (!binaryDict.isValidDictionary()) { + // Somehow createEmptyDictFile returned true, but the file was not created correctly + throw new IOException("Cannot create dictionary file"); + } + for (final WordProperty wordProperty : dict) { + // TODO: switch to addMultipleDictionaryEntries when they support shortcuts + if (null == wordProperty.mShortcutTargets || wordProperty.mShortcutTargets.isEmpty()) { + if (!binaryDict.addUnigramEntry(wordProperty.mWord, wordProperty.getProbability(), + null /* shortcutTarget */, 0 /* shortcutProbability */, + wordProperty.mIsBeginningOfSentence, wordProperty.mIsNotAWord, + wordProperty.mIsBlacklistEntry, 0 /* timestamp */)) { + MakedictLog.e("Cannot add unigram entry for " + wordProperty.mWord); + } + } else { + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + if (!binaryDict.addUnigramEntry(wordProperty.mWord, + wordProperty.getProbability(), + shortcutTarget.mWord, shortcutTarget.getProbability(), + wordProperty.mIsBeginningOfSentence, wordProperty.mIsNotAWord, + wordProperty.mIsBlacklistEntry, 0 /* timestamp */)) { + MakedictLog.e("Cannot add unigram entry for " + wordProperty.mWord + + ", shortcutTarget: " + shortcutTarget.mWord); + return; + } + } + } + if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) { + if (!binaryDict.flushWithGC()) { + MakedictLog.e("Cannot flush dict with GC."); + return; + } + } + } + for (final WordProperty word0Property : dict) { + if (null == word0Property.mBigrams) continue; + for (final WeightedString word1 : word0Property.mBigrams) { + final PrevWordsInfo prevWordsInfo = + new PrevWordsInfo(new PrevWordsInfo.WordInfo(word0Property.mWord)); + if (!binaryDict.addNgramEntry(prevWordsInfo, word1.mWord, + word1.getProbability(), 0 /* timestamp */)) { + MakedictLog.e("Cannot add n-gram entry for " + + prevWordsInfo + " -> " + word1.mWord); + return; + } + if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) { + if (!binaryDict.flushWithGC()) { + MakedictLog.e("Cannot flush dict with GC."); + return; + } + } + } + } + if (!binaryDict.flushWithGC()) { + MakedictLog.e("Cannot flush dict with GC."); + return; + } + binaryDict.close(); + } + + @Override + public void setPosition(int position) { + } + + @Override + public int getPosition() { + return 0; + } + + @Override + public void writePtNodeCount(int ptNodeCount) { + } + + @Override + public void writeForwardLinkAddress(int forwardLinkAddress) { + } + + @Override + public void writePtNode(PtNode ptNode, FusionDictionary dict) { + } +} diff --git a/tests/src/com/android/inputmethod/latin/personalization/ContextualDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/ContextualDictionaryTests.java new file mode 100644 index 000000000..565fadb2a --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/personalization/ContextualDictionaryTests.java @@ -0,0 +1,75 @@ +/* + * 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.latin.personalization; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.DictionaryFacilitator; +import com.android.inputmethod.latin.ExpandableBinaryDictionary; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +/** + * Unit tests for contextual dictionary + */ +@LargeTest +public class ContextualDictionaryTests extends AndroidTestCase { + private static final String TAG = ContextualDictionaryTests.class.getSimpleName(); + + private static final Locale LOCALE_EN_US = new Locale("en", "US"); + + private DictionaryFacilitator getDictionaryFacilitator() { + final ArrayList<String> dictTypes = new ArrayList<>(); + dictTypes.add(Dictionary.TYPE_CONTEXTUAL); + final DictionaryFacilitator dictionaryFacilitator = new DictionaryFacilitator(); + dictionaryFacilitator.resetDictionariesForTesting(getContext(), LOCALE_EN_US, dictTypes, + new HashMap<String, File>(), new HashMap<String, Map<String, String>>()); + return dictionaryFacilitator; + } + + public void testAddPhrase() { + final DictionaryFacilitator dictionaryFacilitator = getDictionaryFacilitator(); + final String[] phrase = new String[] {"a", "b", "c", "d"}; + final int probability = 100; + final int bigramProbabilityForWords = 150; + final int bigramProbabilityForPhrases = 200; + dictionaryFacilitator.addPhraseToContextualDictionary( + phrase, probability, bigramProbabilityForWords, bigramProbabilityForPhrases); + final ExpandableBinaryDictionary contextualDictionary = + dictionaryFacilitator.getSubDictForTesting(Dictionary.TYPE_CONTEXTUAL); + contextualDictionary.waitAllTasksForTests(); + // Word + assertTrue(contextualDictionary.isInDictionary("a")); + assertTrue(contextualDictionary.isInDictionary("b")); + assertTrue(contextualDictionary.isInDictionary("c")); + assertTrue(contextualDictionary.isInDictionary("d")); + // Phrase + assertTrue(contextualDictionary.isInDictionary("a b c d")); + assertTrue(contextualDictionary.isInDictionary("b c d")); + assertTrue(contextualDictionary.isInDictionary("c d")); + assertFalse(contextualDictionary.isInDictionary("a b c")); + assertFalse(contextualDictionary.isInDictionary("abcd")); + // TODO: Add tests for probability. + // TODO: Add tests for n-grams. + } +} diff --git a/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java new file mode 100644 index 000000000..0f2f9814b --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java @@ -0,0 +1,117 @@ +/* + * 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.latin.personalization; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.DictionaryFacilitator; +import com.android.inputmethod.latin.ExpandableBinaryDictionary; +import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback; +import com.android.inputmethod.latin.makedict.CodePointUtils; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +/** + * Unit tests for personalization dictionary + */ +@LargeTest +public class PersonalizationDictionaryTests extends AndroidTestCase { + private static final String TAG = PersonalizationDictionaryTests.class.getSimpleName(); + + private static final Locale LOCALE_EN_US = new Locale("en", "US"); + private static final String DUMMY_PACKAGE_NAME = "test.package.name"; + private static final long TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS = 120; + + private DictionaryFacilitator getDictionaryFacilitator() { + final ArrayList<String> dictTypes = new ArrayList<>(); + dictTypes.add(Dictionary.TYPE_MAIN); + dictTypes.add(Dictionary.TYPE_PERSONALIZATION); + final DictionaryFacilitator dictionaryFacilitator = new DictionaryFacilitator(); + dictionaryFacilitator.resetDictionariesForTesting(getContext(), LOCALE_EN_US, dictTypes, + new HashMap<String, File>(), new HashMap<String, Map<String, String>>()); + return dictionaryFacilitator; + } + + public void testAddManyTokens() { + final DictionaryFacilitator dictionaryFacilitator = getDictionaryFacilitator(); + dictionaryFacilitator.clearPersonalizationDictionary(); + final int dataChunkCount = 20; + final int wordCountInOneChunk = 2000; + final Random random = new Random(System.currentTimeMillis()); + final int[] codePointSet = CodePointUtils.LATIN_ALPHABETS_LOWER; + + final SpacingAndPunctuations spacingAndPunctuations = + new SpacingAndPunctuations(getContext().getResources()); + + final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( + System.currentTimeMillis()); + + for (int i = 0; i < dataChunkCount; i++) { + final ArrayList<String> tokens = new ArrayList<>(); + for (int j = 0; j < wordCountInOneChunk; j++) { + tokens.add(CodePointUtils.generateWord(random, codePointSet)); + } + final PersonalizationDataChunk personalizationDataChunk = new PersonalizationDataChunk( + true /* inputByUser */, tokens, timeStampInSeconds, DUMMY_PACKAGE_NAME); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final AddMultipleDictionaryEntriesCallback callback = + new AddMultipleDictionaryEntriesCallback() { + @Override + public void onFinished() { + countDownLatch.countDown(); + } + }; + dictionaryFacilitator.addEntriesToPersonalizationDictionary(personalizationDataChunk, + spacingAndPunctuations, callback); + try { + countDownLatch.await(TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS, + TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); + } + } + dictionaryFacilitator.flushPersonalizationDictionary(); + try { + dictionaryFacilitator.waitForLoadingDictionariesForTesting( + TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); + } + final String dictName = ExpandableBinaryDictionary.getDictName( + PersonalizationDictionary.NAME, LOCALE_EN_US, null /* dictFile */); + final File dictFile = ExpandableBinaryDictionary.getDictFile( + getContext(), dictName, null /* dictFile */); + + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, 0 /* size */, + true /* useFullEditDistance */, LOCALE_EN_US, Dictionary.TYPE_PERSONALIZATION, + true /* isUpdatable */); + assertTrue(binaryDictionary.isValidDictionary()); + } +} diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java index 7c1decb71..f87f3b494 100644 --- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java @@ -16,20 +16,23 @@ package com.android.inputmethod.latin.personalization; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import com.android.inputmethod.latin.ExpandableBinaryDictionary; -import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.PrevWordsInfo; +import com.android.inputmethod.latin.PrevWordsInfo.WordInfo; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; +import com.android.inputmethod.latin.utils.DistracterFilter; +import com.android.inputmethod.latin.utils.FileUtils; import java.io.File; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Random; -import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -38,25 +41,57 @@ import java.util.concurrent.TimeUnit; @LargeTest public class UserHistoryDictionaryTests extends AndroidTestCase { private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName(); - private SharedPreferences mPrefs; private static final String[] CHARACTERS = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; - private static final int MIN_USER_HISTORY_DICTIONARY_FILE_SIZE = 1000; - private static final int WAIT_TERMINATING_IN_MILLISECONDS = 100; + private int mCurrentTime = 0; @Override - public void setUp() { - mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + protected void setUp() throws Exception { + super.setUp(); + resetCurrentTimeForTestMode(); + } + + @Override + protected void tearDown() throws Exception { + stopTestModeInNativeCode(); + super.tearDown(); + } + + private void resetCurrentTimeForTestMode() { + mCurrentTime = 0; + setCurrentTimeForTestMode(mCurrentTime); + } + + private void forcePassingShortTime() { + // 3 days. + final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(3); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + } + + private void forcePassingLongTime() { + // 365 days. + final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(365); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + } + + private static int setCurrentTimeForTestMode(final int currentTime) { + return BinaryDictionaryUtils.setCurrentTimeForTest(currentTime); + } + + private static int stopTestModeInNativeCode() { + return BinaryDictionaryUtils.setCurrentTimeForTest(-1); } /** * Generates a random word. */ - private String generateWord(final int value) { + private static String generateWord(final int value) { final int lengthOfChars = CHARACTERS.length; StringBuilder builder = new StringBuilder(); long lvalue = Math.abs((long)value); @@ -67,19 +102,21 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { return builder.toString(); } - private List<String> generateWords(final int number, final Random random) { - final Set<String> wordSet = CollectionUtils.newHashSet(); + private static List<String> generateWords(final int number, final Random random) { + final HashSet<String> wordSet = new HashSet<>(); while (wordSet.size() < number) { wordSet.add(generateWord(random.nextInt())); } - return new ArrayList<String>(wordSet); + return new ArrayList<>(wordSet); } - private void addToDict(final UserHistoryDictionary dict, final List<String> words) { - String prevWord = null; + private static void addToDict(final UserHistoryDictionary dict, final List<String> words) { + PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; for (String word : words) { - dict.addToDictionary(prevWord, word, true); - prevWord = word; + UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, + (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + DistracterFilter.EMPTY_DISTRACTER_FILTER); + prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(new WordInfo(word)); } } @@ -87,22 +124,18 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { * @param checkContents if true, checks whether written words are actually in the dictionary * or not. */ - private void addAndWriteRandomWords(final String testFilenameSuffix, final int numberOfWords, + private void addAndWriteRandomWords(final Locale locale, final int numberOfWords, final Random random, final boolean checkContents) { final List<String> words = generateWords(numberOfWords, random); - final UserHistoryDictionary dict = - PersonalizationHelper.getUserHistoryDictionary(getContext(), - testFilenameSuffix /* locale */, mPrefs); + final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( + mContext, locale); // Add random words to the user history dictionary. addToDict(dict, words); if (checkContents) { - try { - Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS)); - } catch (InterruptedException e) { - } + dict.waitAllTasksForTests(); for (int i = 0; i < numberOfWords; ++i) { final String word = words.get(i); - assertTrue(dict.isInDictionaryForTests(word)); + assertTrue(dict.isInDictionary(word)); } } // write to file. @@ -111,57 +144,48 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { /** * Clear all entries in the user history dictionary. - * @param testFilenameSuffix file name suffix used for testing. + * @param locale dummy locale for testing. */ - private void clearHistory(final String testFilenameSuffix) { - final UserHistoryDictionary dict = - PersonalizationHelper.getUserHistoryDictionary(getContext(), - testFilenameSuffix /* locale */, mPrefs); - dict.clearAndFlushDictionary(); + private void clearHistory(final Locale locale) { + final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( + mContext, locale); + dict.waitAllTasksForTests(); + dict.clear(); dict.close(); + dict.waitAllTasksForTests(); } /** * Shut down executer and wait until all operations of user history are done. - * @param testFilenameSuffix file name suffix used for testing. + * @param locale dummy locale for testing. */ - private void waitForWriting(final String testFilenameSuffix) { - try { - final UserHistoryDictionary dict = - PersonalizationHelper.getUserHistoryDictionary(getContext(), - testFilenameSuffix, mPrefs); - dict.shutdownExecutorForTests(); - while (!dict.isTerminatedForTests()) { - Thread.sleep(WAIT_TERMINATING_IN_MILLISECONDS); - } - } catch (InterruptedException e) { - Log.d(TAG, "InterruptedException: ", e); - } + private void waitForWriting(final Locale locale) { + final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( + mContext, locale); + dict.waitAllTasksForTests(); } public void testRandomWords() { Log.d(TAG, "This test can be used for profiling."); Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true."); - final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis(); - final String fileName = UserHistoryDictionary.NAME + "." + testFilenameSuffix - + ExpandableBinaryDictionary.DICT_FILE_EXTENSION; + final Locale dummyLocale = new Locale("test_random_words" + System.currentTimeMillis()); + final String dictName = ExpandableBinaryDictionary.getDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final File dictFile = ExpandableBinaryDictionary.getDictFile( + mContext, dictName, null /* dictFile */); final int numberOfWords = 1000; final Random random = new Random(123456); try { - clearHistory(testFilenameSuffix); - addAndWriteRandomWords(testFilenameSuffix, numberOfWords, random, + clearHistory(dummyLocale); + addAndWriteRandomWords(dummyLocale, numberOfWords, random, true /* checksContents */); } finally { Log.d(TAG, "waiting for writing ..."); - waitForWriting(testFilenameSuffix); - final File dictFile = new File(getContext().getFilesDir(), fileName); - if (dictFile != null) { - assertTrue(dictFile.exists()); - assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE); - dictFile.delete(); - } + waitForWriting(dummyLocale); + assertTrue("check exisiting of " + dictFile, dictFile.exists()); + FileUtils.deleteRecursively(dictFile); } } @@ -171,17 +195,18 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { final int numberOfWordsInsertedForEachLanguageSwitch = 100; final File dictFiles[] = new File[numberOfLanguages]; - final String testFilenameSuffixes[] = new String[numberOfLanguages]; + final Locale dummyLocales[] = new Locale[numberOfLanguages]; try { final Random random = new Random(123456); // Create filename suffixes for this test. for (int i = 0; i < numberOfLanguages; i++) { - testFilenameSuffixes[i] = "testSwitchingLanguages" + i; - final String fileName = UserHistoryDictionary.NAME + "." + - testFilenameSuffixes[i] + ExpandableBinaryDictionary.DICT_FILE_EXTENSION; - dictFiles[i] = new File(getContext().getFilesDir(), fileName); - clearHistory(testFilenameSuffixes[i]); + dummyLocales[i] = new Locale("test_switching_languages" + i); + final String dictName = ExpandableBinaryDictionary.getDictName( + UserHistoryDictionary.NAME, dummyLocales[i], null /* dictFile */); + dictFiles[i] = ExpandableBinaryDictionary.getDictFile( + mContext, dictName, null /* dictFile */); + clearHistory(dummyLocales[i]); } final long start = System.currentTimeMillis(); @@ -189,7 +214,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { for (int i = 0; i < numberOfLanguageSwitching; i++) { final int index = i % numberOfLanguages; // Switch languages to testFilenameSuffixes[index]. - addAndWriteRandomWords(testFilenameSuffixes[index], + addAndWriteRandomWords(dummyLocales[index], numberOfWordsInsertedForEachLanguageSwitch, random, false /* checksContents */); } @@ -200,40 +225,63 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { } finally { Log.d(TAG, "waiting for writing ..."); for (int i = 0; i < numberOfLanguages; i++) { - waitForWriting(testFilenameSuffixes[i]); + waitForWriting(dummyLocales[i]); } - for (final File file : dictFiles) { - if (file != null) { - assertTrue(file.exists()); - assertTrue(file.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE); - file.delete(); - } + for (final File dictFile : dictFiles) { + assertTrue("check exisiting of " + dictFile, dictFile.exists()); + FileUtils.deleteRecursively(dictFile); } } } public void testAddManyWords() { - final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis(); - final int numberOfWords = - ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ? - 10000 : 1000; + final Locale dummyLocale = new Locale("test_random_words" + System.currentTimeMillis()); + final String dictName = ExpandableBinaryDictionary.getDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final File dictFile = ExpandableBinaryDictionary.getDictFile( + mContext, dictName, null /* dictFile */); + final int numberOfWords = 10000; final Random random = new Random(123456); - clearHistory(testFilenameSuffix); + clearHistory(dummyLocale); try { - addAndWriteRandomWords(testFilenameSuffix, numberOfWords, random, - true /* checksContents */); + addAndWriteRandomWords(dummyLocale, numberOfWords, random, true /* checksContents */); } finally { Log.d(TAG, "waiting for writing ..."); - waitForWriting(testFilenameSuffix); - final String fileName = UserHistoryDictionary.NAME + "." + testFilenameSuffix - + ExpandableBinaryDictionary.DICT_FILE_EXTENSION; - final File dictFile = new File(getContext().getFilesDir(), fileName); - if (dictFile != null) { - assertTrue(dictFile.exists()); - assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE); - dictFile.delete(); - } + waitForWriting(dummyLocale); + assertTrue("check exisiting of " + dictFile, dictFile.exists()); + FileUtils.deleteRecursively(dictFile); } } + public void testDecaying() { + final Locale dummyLocale = new Locale("test_decaying" + System.currentTimeMillis()); + final int numberOfWords = 5000; + final Random random = new Random(123456); + resetCurrentTimeForTestMode(); + clearHistory(dummyLocale); + final List<String> words = generateWords(numberOfWords, random); + final UserHistoryDictionary dict = + PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale); + dict.waitAllTasksForTests(); + PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; + for (final String word : words) { + UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, mCurrentTime, + DistracterFilter.EMPTY_DISTRACTER_FILTER); + prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(new WordInfo(word)); + dict.waitAllTasksForTests(); + assertTrue(dict.isInDictionary(word)); + } + forcePassingShortTime(); + dict.runGCIfRequired(); + dict.waitAllTasksForTests(); + for (final String word : words) { + assertTrue(dict.isInDictionary(word)); + } + forcePassingLongTime(); + dict.runGCIfRequired(); + dict.waitAllTasksForTests(); + for (final String word : words) { + assertFalse(dict.isInDictionary(word)); + } + } } diff --git a/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java new file mode 100644 index 000000000..2cc22fae4 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java @@ -0,0 +1,477 @@ +/* + * 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.latin.settings; + +import android.content.res.Resources; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.utils.RunInLocale; + +import junit.framework.AssertionFailedError; + +import java.util.Locale; + +@SmallTest +public class SpacingAndPunctuationsTests extends AndroidTestCase { + private static final int ARMENIAN_FULL_STOP = '\u0589'; + private static final int ARMENIAN_COMMA = '\u055D'; + + private int mScreenMetrics; + + private boolean isPhone() { + return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE + || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE; + } + + private boolean isTablet() { + return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET + || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET; + } + + private SpacingAndPunctuations ENGLISH; + private SpacingAndPunctuations FRENCH; + private SpacingAndPunctuations GERMAN; + private SpacingAndPunctuations ARMENIAN; + private SpacingAndPunctuations THAI; + private SpacingAndPunctuations KHMER; + private SpacingAndPunctuations LAO; + private SpacingAndPunctuations ARABIC; + private SpacingAndPunctuations PERSIAN; + private SpacingAndPunctuations HEBREW; + + private SpacingAndPunctuations UNITED_STATES; + private SpacingAndPunctuations UNITED_KINGDOM; + private SpacingAndPunctuations CANADA_FRENCH; + private SpacingAndPunctuations SWISS_GERMAN; + private SpacingAndPunctuations INDIA_ENGLISH; + private SpacingAndPunctuations ARMENIA_ARMENIAN; + private SpacingAndPunctuations CAMBODIA_KHMER; + private SpacingAndPunctuations LAOS_LAO; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics); + + // Language only + ENGLISH = getSpacingAndPunctuations(Locale.ENGLISH); + FRENCH = getSpacingAndPunctuations(Locale.FRENCH); + GERMAN = getSpacingAndPunctuations(Locale.GERMAN); + THAI = getSpacingAndPunctuations(new Locale("th")); + ARMENIAN = getSpacingAndPunctuations(new Locale("hy")); + KHMER = getSpacingAndPunctuations(new Locale("km")); + LAO = getSpacingAndPunctuations(new Locale("lo")); + ARABIC = getSpacingAndPunctuations(new Locale("ar")); + PERSIAN = getSpacingAndPunctuations(new Locale("fa")); + HEBREW = getSpacingAndPunctuations(new Locale("iw")); + + // Language and Country + UNITED_STATES = getSpacingAndPunctuations(Locale.US); + UNITED_KINGDOM = getSpacingAndPunctuations(Locale.UK); + CANADA_FRENCH = getSpacingAndPunctuations(Locale.CANADA_FRENCH); + SWISS_GERMAN = getSpacingAndPunctuations(new Locale("de", "CH")); + INDIA_ENGLISH = getSpacingAndPunctuations(new Locale("en", "IN")); + ARMENIA_ARMENIAN = getSpacingAndPunctuations(new Locale("hy", "AM")); + CAMBODIA_KHMER = getSpacingAndPunctuations(new Locale("km", "KH")); + LAOS_LAO = getSpacingAndPunctuations(new Locale("lo", "LA")); + } + + private SpacingAndPunctuations getSpacingAndPunctuations(final Locale locale) { + final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(Resources res) { + return new SpacingAndPunctuations(res); + } + }; + return job.runInLocale(getContext().getResources(), locale); + } + + private static void testingStandardWordSeparator(final SpacingAndPunctuations sp) { + assertTrue("Tab", sp.isWordSeparator('\t')); + assertTrue("Newline", sp.isWordSeparator('\n')); + assertTrue("Space", sp.isWordSeparator(' ')); + assertTrue("Exclamation", sp.isWordSeparator('!')); + assertTrue("Quotation", sp.isWordSeparator('"')); + assertFalse("Number", sp.isWordSeparator('#')); + assertFalse("Dollar", sp.isWordSeparator('$')); + assertFalse("Percent", sp.isWordSeparator('%')); + assertTrue("Ampersand", sp.isWordSeparator('&')); + assertFalse("Apostrophe", sp.isWordSeparator('\'')); + assertTrue("L Paren", sp.isWordSeparator('(')); + assertTrue("R Paren", sp.isWordSeparator(')')); + assertTrue("Asterisk", sp.isWordSeparator('*')); + assertTrue("Plus", sp.isWordSeparator('+')); + assertTrue("Comma", sp.isWordSeparator(',')); + assertFalse("Minus", sp.isWordSeparator('-')); + assertTrue("Period", sp.isWordSeparator('.')); + assertTrue("Slash", sp.isWordSeparator('/')); + assertTrue("Colon", sp.isWordSeparator(':')); + assertTrue("Semicolon", sp.isWordSeparator(';')); + assertTrue("L Angle", sp.isWordSeparator('<')); + assertTrue("Equal", sp.isWordSeparator('=')); + assertTrue("R Angle", sp.isWordSeparator('>')); + assertTrue("Question", sp.isWordSeparator('?')); + assertFalse("Atmark", sp.isWordSeparator('@')); + assertTrue("L S Bracket", sp.isWordSeparator('[')); + assertFalse("B Slash", sp.isWordSeparator('\\')); + assertTrue("R S Bracket", sp.isWordSeparator(']')); + assertFalse("Circumflex", sp.isWordSeparator('^')); + assertTrue("Underscore", sp.isWordSeparator('_')); + assertFalse("Grave", sp.isWordSeparator('`')); + assertTrue("L C Brace", sp.isWordSeparator('{')); + assertTrue("V Line", sp.isWordSeparator('|')); + assertTrue("R C Brace", sp.isWordSeparator('}')); + assertFalse("Tilde", sp.isWordSeparator('~')); + } + + public void testWordSeparator() { + testingStandardWordSeparator(ENGLISH); + testingStandardWordSeparator(FRENCH); + testingStandardWordSeparator(CANADA_FRENCH); + testingStandardWordSeparator(ARMENIA_ARMENIAN); + assertTrue(ARMENIA_ARMENIAN.isWordSeparator(ARMENIAN_FULL_STOP)); + assertTrue(ARMENIA_ARMENIAN.isWordSeparator(ARMENIAN_COMMA)); + // TODO: We should fix these. + testingStandardWordSeparator(ARMENIAN); + assertFalse(ARMENIAN.isWordSeparator(ARMENIAN_FULL_STOP)); + assertFalse(ARMENIAN.isWordSeparator(ARMENIAN_COMMA)); + } + + private static void testingStandardWordConnector(final SpacingAndPunctuations sp) { + assertFalse("Tab", sp.isWordConnector('\t')); + assertFalse("Newline", sp.isWordConnector('\n')); + assertFalse("Space", sp.isWordConnector(' ')); + assertFalse("Exclamation", sp.isWordConnector('!')); + assertFalse("Quotation", sp.isWordConnector('"')); + assertFalse("Number", sp.isWordConnector('#')); + assertFalse("Dollar", sp.isWordConnector('$')); + assertFalse("Percent", sp.isWordConnector('%')); + assertFalse("Ampersand", sp.isWordConnector('&')); + assertTrue("Apostrophe", sp.isWordConnector('\'')); + assertFalse("L Paren", sp.isWordConnector('(')); + assertFalse("R Paren", sp.isWordConnector(')')); + assertFalse("Asterisk", sp.isWordConnector('*')); + assertFalse("Plus", sp.isWordConnector('+')); + assertFalse("Comma", sp.isWordConnector(',')); + assertTrue("Minus", sp.isWordConnector('-')); + assertFalse("Period", sp.isWordConnector('.')); + assertFalse("Slash", sp.isWordConnector('/')); + assertFalse("Colon", sp.isWordConnector(':')); + assertFalse("Semicolon", sp.isWordConnector(';')); + assertFalse("L Angle", sp.isWordConnector('<')); + assertFalse("Equal", sp.isWordConnector('=')); + assertFalse("R Angle", sp.isWordConnector('>')); + assertFalse("Question", sp.isWordConnector('?')); + assertFalse("Atmark", sp.isWordConnector('@')); + assertFalse("L S Bracket", sp.isWordConnector('[')); + assertFalse("B Slash", sp.isWordConnector('\\')); + assertFalse("R S Bracket", sp.isWordConnector(']')); + assertFalse("Circumflex", sp.isWordConnector('^')); + assertFalse("Underscore", sp.isWordConnector('_')); + assertFalse("Grave", sp.isWordConnector('`')); + assertFalse("L C Brace", sp.isWordConnector('{')); + assertFalse("V Line", sp.isWordConnector('|')); + assertFalse("R C Brace", sp.isWordConnector('}')); + assertFalse("Tilde", sp.isWordConnector('~')); + + } + + public void testWordConnector() { + testingStandardWordConnector(ENGLISH); + testingStandardWordConnector(FRENCH); + testingStandardWordConnector(CANADA_FRENCH); + testingStandardWordConnector(ARMENIA_ARMENIAN); + } + + private static void testingCommonPrecededBySpace(final SpacingAndPunctuations sp) { + assertFalse("Tab", sp.isUsuallyPrecededBySpace('\t')); + assertFalse("Newline", sp.isUsuallyPrecededBySpace('\n')); + assertFalse("Space", sp.isUsuallyPrecededBySpace(' ')); + //assertFalse("Exclamation", sp.isUsuallyPrecededBySpace('!')); + assertFalse("Quotation", sp.isUsuallyPrecededBySpace('"')); + assertFalse("Number", sp.isUsuallyPrecededBySpace('#')); + assertFalse("Dollar", sp.isUsuallyPrecededBySpace('$')); + assertFalse("Percent", sp.isUsuallyPrecededBySpace('%')); + assertTrue("Ampersand", sp.isUsuallyPrecededBySpace('&')); + assertFalse("Apostrophe", sp.isUsuallyPrecededBySpace('\'')); + assertTrue("L Paren", sp.isUsuallyPrecededBySpace('(')); + assertFalse("R Paren", sp.isUsuallyPrecededBySpace(')')); + assertFalse("Asterisk", sp.isUsuallyPrecededBySpace('*')); + assertFalse("Plus", sp.isUsuallyPrecededBySpace('+')); + assertFalse("Comma", sp.isUsuallyPrecededBySpace(',')); + assertFalse("Minus", sp.isUsuallyPrecededBySpace('-')); + assertFalse("Period", sp.isUsuallyPrecededBySpace('.')); + assertFalse("Slash", sp.isUsuallyPrecededBySpace('/')); + //assertFalse("Colon", sp.isUsuallyPrecededBySpace(':')); + //assertFalse("Semicolon", sp.isUsuallyPrecededBySpace(';')); + assertFalse("L Angle", sp.isUsuallyPrecededBySpace('<')); + assertFalse("Equal", sp.isUsuallyPrecededBySpace('=')); + assertFalse("R Angle", sp.isUsuallyPrecededBySpace('>')); + //assertFalse("Question", sp.isUsuallyPrecededBySpace('?')); + assertFalse("Atmark", sp.isUsuallyPrecededBySpace('@')); + assertTrue("L S Bracket", sp.isUsuallyPrecededBySpace('[')); + assertFalse("B Slash", sp.isUsuallyPrecededBySpace('\\')); + assertFalse("R S Bracket", sp.isUsuallyPrecededBySpace(']')); + assertFalse("Circumflex", sp.isUsuallyPrecededBySpace('^')); + assertFalse("Underscore", sp.isUsuallyPrecededBySpace('_')); + assertFalse("Grave", sp.isUsuallyPrecededBySpace('`')); + assertTrue("L C Brace", sp.isUsuallyPrecededBySpace('{')); + assertFalse("V Line", sp.isUsuallyPrecededBySpace('|')); + assertFalse("R C Brace", sp.isUsuallyPrecededBySpace('}')); + assertFalse("Tilde", sp.isUsuallyPrecededBySpace('~')); + } + + private static void testingStandardPrecededBySpace(final SpacingAndPunctuations sp) { + testingCommonPrecededBySpace(sp); + assertFalse("Exclamation", sp.isUsuallyPrecededBySpace('!')); + assertFalse("Colon", sp.isUsuallyPrecededBySpace(':')); + assertFalse("Semicolon", sp.isUsuallyPrecededBySpace(';')); + assertFalse("Question", sp.isUsuallyPrecededBySpace('?')); + } + + public void testIsUsuallyPrecededBySpace() { + testingStandardPrecededBySpace(ENGLISH); + testingCommonPrecededBySpace(FRENCH); + assertTrue("Exclamation", FRENCH.isUsuallyPrecededBySpace('!')); + assertTrue("Colon", FRENCH.isUsuallyPrecededBySpace(':')); + assertTrue("Semicolon", FRENCH.isUsuallyPrecededBySpace(';')); + assertTrue("Question", FRENCH.isUsuallyPrecededBySpace('?')); + testingCommonPrecededBySpace(CANADA_FRENCH); + assertFalse("Exclamation", CANADA_FRENCH.isUsuallyPrecededBySpace('!')); + assertTrue("Colon", CANADA_FRENCH.isUsuallyPrecededBySpace(':')); + assertFalse("Semicolon", CANADA_FRENCH.isUsuallyPrecededBySpace(';')); + assertFalse("Question", CANADA_FRENCH.isUsuallyPrecededBySpace('?')); + testingStandardPrecededBySpace(ARMENIA_ARMENIAN); + } + + private static void testingStandardFollowedBySpace(final SpacingAndPunctuations sp) { + assertFalse("Tab", sp.isUsuallyFollowedBySpace('\t')); + assertFalse("Newline", sp.isUsuallyFollowedBySpace('\n')); + assertFalse("Space", sp.isUsuallyFollowedBySpace(' ')); + assertTrue("Exclamation", sp.isUsuallyFollowedBySpace('!')); + assertFalse("Quotation", sp.isUsuallyFollowedBySpace('"')); + assertFalse("Number", sp.isUsuallyFollowedBySpace('#')); + assertFalse("Dollar", sp.isUsuallyFollowedBySpace('$')); + assertFalse("Percent", sp.isUsuallyFollowedBySpace('%')); + assertTrue("Ampersand", sp.isUsuallyFollowedBySpace('&')); + assertFalse("Apostrophe", sp.isUsuallyFollowedBySpace('\'')); + assertFalse("L Paren", sp.isUsuallyFollowedBySpace('(')); + assertTrue("R Paren", sp.isUsuallyFollowedBySpace(')')); + assertFalse("Asterisk", sp.isUsuallyFollowedBySpace('*')); + assertFalse("Plus", sp.isUsuallyFollowedBySpace('+')); + assertTrue("Comma", sp.isUsuallyFollowedBySpace(',')); + assertFalse("Minus", sp.isUsuallyFollowedBySpace('-')); + assertTrue("Period", sp.isUsuallyFollowedBySpace('.')); + assertFalse("Slash", sp.isUsuallyFollowedBySpace('/')); + assertTrue("Colon", sp.isUsuallyFollowedBySpace(':')); + assertTrue("Semicolon", sp.isUsuallyFollowedBySpace(';')); + assertFalse("L Angle", sp.isUsuallyFollowedBySpace('<')); + assertFalse("Equal", sp.isUsuallyFollowedBySpace('=')); + assertFalse("R Angle", sp.isUsuallyFollowedBySpace('>')); + assertTrue("Question", sp.isUsuallyFollowedBySpace('?')); + assertFalse("Atmark", sp.isUsuallyFollowedBySpace('@')); + assertFalse("L S Bracket", sp.isUsuallyFollowedBySpace('[')); + assertFalse("B Slash", sp.isUsuallyFollowedBySpace('\\')); + assertTrue("R S Bracket", sp.isUsuallyFollowedBySpace(']')); + assertFalse("Circumflex", sp.isUsuallyFollowedBySpace('^')); + assertFalse("Underscore", sp.isUsuallyFollowedBySpace('_')); + assertFalse("Grave", sp.isUsuallyFollowedBySpace('`')); + assertFalse("L C Brace", sp.isUsuallyFollowedBySpace('{')); + assertFalse("V Line", sp.isUsuallyFollowedBySpace('|')); + assertTrue("R C Brace", sp.isUsuallyFollowedBySpace('}')); + assertFalse("Tilde", sp.isUsuallyFollowedBySpace('~')); + } + + public void testIsUsuallyFollowedBySpace() { + testingStandardFollowedBySpace(ENGLISH); + testingStandardFollowedBySpace(FRENCH); + testingStandardFollowedBySpace(CANADA_FRENCH); + testingStandardFollowedBySpace(ARMENIA_ARMENIAN); + assertTrue(ARMENIA_ARMENIAN.isUsuallyFollowedBySpace(ARMENIAN_FULL_STOP)); + assertTrue(ARMENIA_ARMENIAN.isUsuallyFollowedBySpace(ARMENIAN_COMMA)); + } + + private static void testingStandardSentenceSeparator(final SpacingAndPunctuations sp) { + assertFalse("Tab", sp.isUsuallyFollowedBySpace('\t')); + assertFalse("Newline", sp.isUsuallyFollowedBySpace('\n')); + assertFalse("Space", sp.isUsuallyFollowedBySpace(' ')); + assertFalse("Exclamation", sp.isUsuallyFollowedBySpace('!')); + assertFalse("Quotation", sp.isUsuallyFollowedBySpace('"')); + assertFalse("Number", sp.isUsuallyFollowedBySpace('#')); + assertFalse("Dollar", sp.isUsuallyFollowedBySpace('$')); + assertFalse("Percent", sp.isUsuallyFollowedBySpace('%')); + assertFalse("Ampersand", sp.isUsuallyFollowedBySpace('&')); + assertFalse("Apostrophe", sp.isUsuallyFollowedBySpace('\'')); + assertFalse("L Paren", sp.isUsuallyFollowedBySpace('(')); + assertFalse("R Paren", sp.isUsuallyFollowedBySpace(')')); + assertFalse("Asterisk", sp.isUsuallyFollowedBySpace('*')); + assertFalse("Plus", sp.isUsuallyFollowedBySpace('+')); + assertFalse("Comma", sp.isUsuallyFollowedBySpace(',')); + assertFalse("Minus", sp.isUsuallyFollowedBySpace('-')); + assertTrue("Period", sp.isUsuallyFollowedBySpace('.')); + assertFalse("Slash", sp.isUsuallyFollowedBySpace('/')); + assertFalse("Colon", sp.isUsuallyFollowedBySpace(':')); + assertFalse("Semicolon", sp.isUsuallyFollowedBySpace(';')); + assertFalse("L Angle", sp.isUsuallyFollowedBySpace('<')); + assertFalse("Equal", sp.isUsuallyFollowedBySpace('=')); + assertFalse("R Angle", sp.isUsuallyFollowedBySpace('>')); + assertFalse("Question", sp.isUsuallyFollowedBySpace('?')); + assertFalse("Atmark", sp.isUsuallyFollowedBySpace('@')); + assertFalse("L S Bracket", sp.isUsuallyFollowedBySpace('[')); + assertFalse("B Slash", sp.isUsuallyFollowedBySpace('\\')); + assertFalse("R S Bracket", sp.isUsuallyFollowedBySpace(']')); + assertFalse("Circumflex", sp.isUsuallyFollowedBySpace('^')); + assertFalse("Underscore", sp.isUsuallyFollowedBySpace('_')); + assertFalse("Grave", sp.isUsuallyFollowedBySpace('`')); + assertFalse("L C Brace", sp.isUsuallyFollowedBySpace('{')); + assertFalse("V Line", sp.isUsuallyFollowedBySpace('|')); + assertFalse("R C Brace", sp.isUsuallyFollowedBySpace('}')); + assertFalse("Tilde", sp.isUsuallyFollowedBySpace('~')); + } + + public void isSentenceSeparator() { + testingStandardSentenceSeparator(ENGLISH); + try { + testingStandardSentenceSeparator(ARMENIA_ARMENIAN); + fail("Armenian Sentence Separator"); + } catch (final AssertionFailedError e) { + assertEquals("Period", e.getMessage()); + } + assertTrue(ARMENIA_ARMENIAN.isSentenceSeparator(ARMENIAN_FULL_STOP)); + assertFalse(ARMENIA_ARMENIAN.isSentenceSeparator(ARMENIAN_COMMA)); + } + + public void testLanguageHasSpace() { + assertTrue(ENGLISH.mCurrentLanguageHasSpaces); + assertTrue(FRENCH.mCurrentLanguageHasSpaces); + assertTrue(GERMAN.mCurrentLanguageHasSpaces); + assertFalse(THAI.mCurrentLanguageHasSpaces); + assertFalse(CAMBODIA_KHMER.mCurrentLanguageHasSpaces); + assertFalse(LAOS_LAO.mCurrentLanguageHasSpaces); + // TODO: We should fix these. + assertTrue(KHMER.mCurrentLanguageHasSpaces); + assertTrue(LAO.mCurrentLanguageHasSpaces); + } + + public void testUsesAmericanTypography() { + assertTrue(ENGLISH.mUsesAmericanTypography); + assertTrue(UNITED_STATES.mUsesAmericanTypography); + assertTrue(UNITED_KINGDOM.mUsesAmericanTypography); + assertTrue(INDIA_ENGLISH.mUsesAmericanTypography); + assertFalse(FRENCH.mUsesAmericanTypography); + assertFalse(GERMAN.mUsesAmericanTypography); + assertFalse(SWISS_GERMAN.mUsesAmericanTypography); + } + + public void testUsesGermanRules() { + assertFalse(ENGLISH.mUsesGermanRules); + assertFalse(FRENCH.mUsesGermanRules); + assertTrue(GERMAN.mUsesGermanRules); + assertTrue(SWISS_GERMAN.mUsesGermanRules); + } + + // Punctuations for phone. + private static final String[] PUNCTUATION_LABELS_PHONE = { + "!", "?", ",", ":", ";", "\"", "(", ")", "'", "-", "/", "@", "_" + }; + private static final String[] PUNCTUATION_WORDS_PHONE_LTR = PUNCTUATION_LABELS_PHONE; + private static final String[] PUNCTUATION_WORDS_PHONE_HEBREW = { + "!", "?", ",", ":", ";", "\"", ")", "(", "'", "-", "/", "@", "_" + }; + // U+061F: "؟" ARABIC QUESTION MARK + // U+060C: "،" ARABIC COMMA + // U+061B: "؛" ARABIC SEMICOLON + private static final String[] PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN = { + "!", "\u061F", "\u060C", ":", "\u061B", "\"", "(", ")", "'", "-", "/", "@", "_" + }; + private static final String[] PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN = { + "!", "\u061F", "\u060C", ":", "\u061B", "\"", ")", "(", "'", "-", "/", "@", "_" + }; + + // Punctuations for tablet. + private static final String[] PUNCTUATION_LABELS_TABLET = { + ":", ";", "\"", "(", ")", "'", "-", "/", "@", "_" + }; + private static final String[] PUNCTUATION_WORDS_TABLET_LTR = PUNCTUATION_LABELS_TABLET; + private static final String[] PUNCTUATION_WORDS_TABLET_HEBREW = { + ":", ";", "\"", ")", "(", "'", "-", "/", "@", "_" + }; + private static final String[] PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN = { + "!", "\u061F", ":", "\u061B", "\"", "'", "(", ")", "-", "/", "@", "_" + }; + private static final String[] PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN = { + "!", "\u061F", ":", "\u061B", "\"", "'", ")", "(", "-", "/", "@", "_" + }; + + private static void testingStandardPunctuationSuggestions(final SpacingAndPunctuations sp, + final String[] punctuationLabels, final String[] punctuationWords) { + final SuggestedWords suggestedWords = sp.mSuggestPuncList; + assertFalse("typedWordValid", suggestedWords.mTypedWordValid); + assertFalse("willAutoCorrect", suggestedWords.mWillAutoCorrect); + assertTrue("isPunctuationSuggestions", suggestedWords.isPunctuationSuggestions()); + assertFalse("isObsoleteSuggestions", suggestedWords.mIsObsoleteSuggestions); + assertFalse("isPrediction", suggestedWords.mIsPrediction); + assertEquals("size", punctuationLabels.length, suggestedWords.size()); + for (int index = 0; index < suggestedWords.size(); index++) { + assertEquals("punctuation label at " + index, + punctuationLabels[index], suggestedWords.getLabel(index)); + assertEquals("punctuation word at " + index, + punctuationWords[index], suggestedWords.getWord(index)); + } + } + + public void testPhonePunctuationSuggestions() { + if (!isPhone()) { + return; + } + testingStandardPunctuationSuggestions(ENGLISH, + PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR); + testingStandardPunctuationSuggestions(FRENCH, + PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR); + testingStandardPunctuationSuggestions(GERMAN, + PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_LTR); + testingStandardPunctuationSuggestions(ARABIC, + PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN, PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN); + testingStandardPunctuationSuggestions(PERSIAN, + PUNCTUATION_LABELS_PHONE_ARABIC_PERSIAN, PUNCTUATION_WORDS_PHONE_ARABIC_PERSIAN); + testingStandardPunctuationSuggestions(HEBREW, + PUNCTUATION_LABELS_PHONE, PUNCTUATION_WORDS_PHONE_HEBREW); + } + + public void testTabletPunctuationSuggestions() { + if (!isTablet()) { + return; + } + testingStandardPunctuationSuggestions(ENGLISH, + PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR); + testingStandardPunctuationSuggestions(FRENCH, + PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR); + testingStandardPunctuationSuggestions(GERMAN, + PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_LTR); + testingStandardPunctuationSuggestions(ARABIC, + PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN, PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN); + testingStandardPunctuationSuggestions(PERSIAN, + PUNCTUATION_LABELS_TABLET_ARABIC_PERSIAN, PUNCTUATION_WORDS_TABLET_ARABIC_PERSIAN); + testingStandardPunctuationSuggestions(HEBREW, + PUNCTUATION_LABELS_TABLET, PUNCTUATION_WORDS_TABLET_HEBREW); + } +} diff --git a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java index 995d7f07b..2272d6ba0 100644 --- a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java +++ b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java @@ -39,7 +39,7 @@ public class AndroidSpellCheckerServiceTest extends InputTestsBase { // it yields 5). assertTrue(suggestions.length >= 2); // We also assume the top suggestion should be "this". - assertEquals("", "this", suggestions[0]); + assertEquals("Test basic spell checking", "this", suggestions[0]); } public void testRussianSpellchecker() { @@ -62,4 +62,21 @@ public class AndroidSpellCheckerServiceTest extends InputTestsBase { // Russian dictionary. assertEquals("", "года", suggestions[0]); } + + public void testSpellcheckWithPeriods() { + changeLanguage("en_US"); + mEditText.setText("I'm.sure "); + mEditText.setSelection(mEditText.getText().length()); + mEditText.onAttachedToWindow(); + sleep(1000); + runMessages(); + sleep(1000); + + final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class); + // If no span, the following will crash + final String[] suggestions = span.getSuggestions(); + // The first suggestion should be "I'm sure". + assertEquals("Test spell checking of mistyped period for space", "I'm sure", + suggestions[0]); + } } diff --git a/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java new file mode 100644 index 000000000..91c9c3775 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java @@ -0,0 +1,175 @@ +/* + * 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.latin.utils; + +import android.content.Context; +import android.os.Build; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; + +import java.util.Locale; + +import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue + .UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; + +@SmallTest +public class AdditionalSubtypeUtilsTests extends AndroidTestCase { + + /** + * Predictable subtype ID for en_US dvorak layout. This is actually a hash code calculated as + * follows. + * <code> + * final boolean isAuxiliary = false; + * final boolean overrideImplicitlyEnabledSubtype = false; + * final int SUBTYPE_ID_EN_US_DVORAK = Arrays.hashCode(new Object[] { + * "en_US", + * "keyboard", + * "KeyboardLayoutSet=dvorak" + * + ",AsciiCapable" + * + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + * + ",EmojiCapable" + * + ",isAdditionalSubtype", + * isAuxiliary, + * overrideImplicitlyEnabledSubtype }); + * </code> + */ + private static int SUBTYPE_ID_EN_US_DVORAK = 0xb3c0cc56; + private static String EXTRA_VALUE_EN_US_DVORAK_ICS = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_EN_US_DVORAK_JELLY_BEAN = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_EN_US_DVORAK_KITKAT = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + + ",EmojiCapable" + + ",isAdditionalSubtype"; + + /** + * Predictable subtype ID for azerty layout. This is actually a hash code calculated as follows. + * <code> + * final boolean isAuxiliary = false; + * final boolean overrideImplicitlyEnabledSubtype = false; + * final int SUBTYPE_ID_ZZ_AZERTY = Arrays.hashCode(new Object[] { + * "zz", + * "keyboard", + * "KeyboardLayoutSet=azerty" + * + ",AsciiCapable" + * + ",EmojiCapable" + * + ",isAdditionalSubtype", + * isAuxiliary, + * overrideImplicitlyEnabledSubtype }); + * </code> + */ + private static int SUBTYPE_ID_ZZ_AZERTY = 0x5b6be697; + private static String EXTRA_VALUE_ZZ_AZERTY_ICS = + "KeyboardLayoutSet=azerty" + + ",AsciiCapable" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_ZZ_AZERTY_KITKAT = + "KeyboardLayoutSet=azerty" + + ",AsciiCapable" + + ",EmojiCapable" + + ",isAdditionalSubtype"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getContext(); + SubtypeLocaleUtils.init(context); + } + + private static void assertEnUsDvorak(InputMethodSubtype subtype) { + assertEquals("en_US", subtype.getLocale()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_KITKAT, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_JELLY_BEAN, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_ICS, subtype.getExtraValue()); + } + assertTrue(subtype.containsExtraValueKey(ASCII_CAPABLE)); + assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)); + // TODO: Enable following test + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapableWithAPI(subtype)); + // } + assertTrue(subtype.containsExtraValueKey(EMOJI_CAPABLE)); + assertTrue(subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE)); + assertEquals("dvorak", subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET)); + assertEquals("Dvorak", subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)); + assertEquals(KEYBOARD_MODE, subtype.getMode()); + assertEquals(SUBTYPE_ID_EN_US_DVORAK, subtype.hashCode()); + } + + private static void assertAzerty(InputMethodSubtype subtype) { + assertEquals("zz", subtype.getLocale()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertEquals(EXTRA_VALUE_ZZ_AZERTY_KITKAT, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + assertEquals(EXTRA_VALUE_ZZ_AZERTY_ICS, subtype.getExtraValue()); + } + assertTrue(subtype.containsExtraValueKey(ASCII_CAPABLE)); + assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)); + // TODO: Enable following test + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapableWithAPI(subtype)); + // } + assertTrue(subtype.containsExtraValueKey(EMOJI_CAPABLE)); + assertTrue(subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE)); + assertEquals("azerty", subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET)); + assertFalse(subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)); + assertEquals(KEYBOARD_MODE, subtype.getMode()); + assertEquals(SUBTYPE_ID_ZZ_AZERTY, subtype.hashCode()); + } + + public void testRestorable() { + final InputMethodSubtype EN_UK_DVORAK = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "dvorak"); + final InputMethodSubtype ZZ_AZERTY = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + assertEnUsDvorak(EN_UK_DVORAK); + assertAzerty(ZZ_AZERTY); + + // Make sure the subtype can be stored and restored in a deterministic manner. + final InputMethodSubtype[] subtypes = { EN_UK_DVORAK, ZZ_AZERTY }; + final String prefSubtype = AdditionalSubtypeUtils.createPrefSubtypes(subtypes); + final InputMethodSubtype[] restoredSubtypes = + AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); + assertEquals(2, restoredSubtypes.length); + final InputMethodSubtype restored_EN_UK_DVORAK = restoredSubtypes[0]; + final InputMethodSubtype restored_ZZ_AZERTY = restoredSubtypes[1]; + + assertEnUsDvorak(restored_EN_UK_DVORAK); + assertAzerty(restored_ZZ_AZERTY); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java index 7fd167977..1501e942a 100644 --- a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java @@ -45,27 +45,27 @@ public class AsyncResultHolderTests extends AndroidTestCase { } public void testGetWithoutSet() { - final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>(); + final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>(); final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS); assertEquals(DEFAULT_VALUE, resultValue); } public void testGetBeforeSet() { - final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>(); + final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>(); setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS + MARGIN_IN_MILLISECONDS); final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS); assertEquals(DEFAULT_VALUE, resultValue); } public void testGetAfterSet() { - final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>(); + final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>(); holder.set(SET_VALUE); final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS); assertEquals(SET_VALUE, resultValue); } public void testGetBeforeTimeout() { - final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>(); + final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>(); setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS - MARGIN_IN_MILLISECONDS); final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS); assertEquals(SET_VALUE, resultValue); diff --git a/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java new file mode 100644 index 000000000..a333ee9bc --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java @@ -0,0 +1,92 @@ +/* + * 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.latin.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.FormatSpec; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@LargeTest +public class BinaryDictionaryUtilsTests extends AndroidTestCase { + private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; + private static final String TEST_LOCALE = "test"; + + private File createEmptyDictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + if (formatVersion == FormatSpec.VERSION4) { + return createEmptyVer4DictionaryAndGetFile(dictId); + } else { + throw new IOException("Dictionary format version " + formatVersion + + " is not supported."); + } + } + + private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException { + final File file = getDictFile(dictId); + FileUtils.deleteRecursively(file); + Map<String, String> attributeMap = new HashMap<>(); + attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId); + attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, + String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); + attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4, + LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) { + return file; + } else { + throw new IOException("Empty dictionary " + file.getAbsolutePath() + + " cannot be created."); + } + } + + private File getDictFile(final String dictId) { + return new File(getContext().getCacheDir(), dictId + TEST_DICT_FILE_EXTENSION); + } + + public void testRenameDictionary() { + final int formatVersion = FormatSpec.VERSION4; + File dictFile0 = null; + try { + dictFile0 = createEmptyDictionaryAndGetFile("MoveFromDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final File dictFile1 = getDictFile("MoveToDictionary"); + FileUtils.deleteRecursively(dictFile1); + assertTrue(BinaryDictionaryUtils.renameDict(dictFile0, dictFile1)); + assertFalse(dictFile0.exists()); + assertTrue(dictFile1.exists()); + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile1.getAbsolutePath(), + 0 /* offset */, dictFile1.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + assertTrue(binaryDictionary.isValidDictionary()); + assertTrue(binaryDictionary.getFormatVersion() == formatVersion); + binaryDictionary.close(); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java b/tests/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java new file mode 100644 index 000000000..2028298f2 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 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.latin.utils; + +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; + +/** + * This class provides an implementation for the FusionDictionary buffer interface that is backed + * by a simpled byte array. It allows to create a binary dictionary in memory. + */ +public final class ByteArrayDictBuffer implements DictBuffer { + private byte[] mBuffer; + private int mPosition; + + public ByteArrayDictBuffer(final byte[] buffer) { + mBuffer = buffer; + mPosition = 0; + } + + @Override + public int readUnsignedByte() { + return mBuffer[mPosition++] & 0xFF; + } + + @Override + public int readUnsignedShort() { + final int retval = readUnsignedByte(); + return (retval << 8) + readUnsignedByte(); + } + + @Override + public int readUnsignedInt24() { + final int retval = readUnsignedShort(); + return (retval << 8) + readUnsignedByte(); + } + + @Override + public int readInt() { + final int retval = readUnsignedShort(); + return (retval << 16) + readUnsignedShort(); + } + + @Override + public int position() { + return mPosition; + } + + @Override + public void position(int position) { + mPosition = position; + } + + @Override + public void put(final byte b) { + mBuffer[mPosition++] = b; + } + + @Override + public int limit() { + return mBuffer.length - 1; + } + + @Override + public int capacity() { + return mBuffer.length; + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java index 1fd5c989a..c746c8345 100644 --- a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java @@ -16,75 +16,113 @@ package com.android.inputmethod.latin.utils; -import com.android.inputmethod.latin.settings.SettingsValues; - +import android.content.res.Resources; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.text.TextUtils; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import com.android.inputmethod.latin.utils.LocaleUtils; + import java.util.Locale; @SmallTest public class CapsModeUtilsTests extends AndroidTestCase { private static void onePathForCaps(final CharSequence cs, final int expectedResult, - final int mask, final SettingsValues sv, final boolean hasSpaceBefore) { - int oneTimeResult = expectedResult & mask; + final int mask, final SpacingAndPunctuations sp, final boolean hasSpaceBefore) { + final int oneTimeResult = expectedResult & mask; assertEquals("After >" + cs + "<", oneTimeResult, - CapsModeUtils.getCapsMode(cs, mask, sv, hasSpaceBefore)); + CapsModeUtils.getCapsMode(cs, mask, sp, hasSpaceBefore)); } private static void allPathsForCaps(final CharSequence cs, final int expectedResult, - final SettingsValues sv, final boolean hasSpaceBefore) { + final SpacingAndPunctuations sp, final boolean hasSpaceBefore) { final int c = TextUtils.CAP_MODE_CHARACTERS; final int w = TextUtils.CAP_MODE_WORDS; final int s = TextUtils.CAP_MODE_SENTENCES; - onePathForCaps(cs, expectedResult, c | w | s, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, w | s, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, c | s, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, c | w, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, c, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, w, sv, hasSpaceBefore); - onePathForCaps(cs, expectedResult, s, sv, hasSpaceBefore); + onePathForCaps(cs, expectedResult, c | w | s, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, w | s, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, c | s, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, c | w, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, c, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, w, sp, hasSpaceBefore); + onePathForCaps(cs, expectedResult, s, sp, hasSpaceBefore); } public void testGetCapsMode() { final int c = TextUtils.CAP_MODE_CHARACTERS; final int w = TextUtils.CAP_MODE_WORDS; final int s = TextUtils.CAP_MODE_SENTENCES; - SettingsValues sv = SettingsValues.makeDummySettingsValuesForTest(Locale.ENGLISH); - allPathsForCaps("", c | w | s, sv, false); - allPathsForCaps("Word", c, sv, false); - allPathsForCaps("Word.", c, sv, false); - allPathsForCaps("Word ", c | w, sv, false); - allPathsForCaps("Word. ", c | w | s, sv, false); - allPathsForCaps("Word..", c, sv, false); - allPathsForCaps("Word.. ", c | w | s, sv, false); - allPathsForCaps("Word... ", c | w | s, sv, false); - allPathsForCaps("Word ... ", c | w | s, sv, false); - allPathsForCaps("Word . ", c | w, sv, false); - allPathsForCaps("In the U.S ", c | w, sv, false); - allPathsForCaps("In the U.S. ", c | w, sv, false); - allPathsForCaps("Some stuff (e.g. ", c | w, sv, false); - allPathsForCaps("In the U.S.. ", c | w | s, sv, false); - allPathsForCaps("\"Word.\" ", c | w | s, sv, false); - allPathsForCaps("\"Word\". ", c | w | s, sv, false); - allPathsForCaps("\"Word\" ", c | w, sv, false); + final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(final Resources res) { + return new SpacingAndPunctuations(res); + } + }; + final Resources res = getContext().getResources(); + SpacingAndPunctuations sp = job.runInLocale(res, Locale.ENGLISH); + allPathsForCaps("", c | w | s, sp, false); + allPathsForCaps("Word", c, sp, false); + allPathsForCaps("Word.", c, sp, false); + allPathsForCaps("Word ", c | w, sp, false); + allPathsForCaps("Word. ", c | w | s, sp, false); + allPathsForCaps("Word..", c, sp, false); + allPathsForCaps("Word.. ", c | w | s, sp, false); + allPathsForCaps("Word... ", c | w | s, sp, false); + allPathsForCaps("Word ... ", c | w | s, sp, false); + allPathsForCaps("Word . ", c | w, sp, false); + allPathsForCaps("In the U.S ", c | w, sp, false); + allPathsForCaps("In the U.S. ", c | w, sp, false); + allPathsForCaps("Some stuff (e.g. ", c | w, sp, false); + allPathsForCaps("In the U.S.. ", c | w | s, sp, false); + allPathsForCaps("\"Word.\" ", c | w | s, sp, false); + allPathsForCaps("\"Word\". ", c | w | s, sp, false); + allPathsForCaps("\"Word\" ", c | w, sp, false); // Test for phantom space - allPathsForCaps("Word", c | w, sv, true); - allPathsForCaps("Word.", c | w | s, sv, true); + allPathsForCaps("Word", c | w, sp, true); + allPathsForCaps("Word.", c | w | s, sp, true); // Tests after some whitespace - allPathsForCaps("Word\n", c | w | s, sv, false); - allPathsForCaps("Word\n", c | w | s, sv, true); - allPathsForCaps("Word\n ", c | w | s, sv, true); - allPathsForCaps("Word.\n", c | w | s, sv, false); - allPathsForCaps("Word.\n", c | w | s, sv, true); - allPathsForCaps("Word.\n ", c | w | s, sv, true); + allPathsForCaps("Word\n", c | w | s, sp, false); + allPathsForCaps("Word\n", c | w | s, sp, true); + allPathsForCaps("Word\n ", c | w | s, sp, true); + allPathsForCaps("Word.\n", c | w | s, sp, false); + allPathsForCaps("Word.\n", c | w | s, sp, true); + allPathsForCaps("Word.\n ", c | w | s, sp, true); + + sp = job.runInLocale(res, Locale.FRENCH); + allPathsForCaps("\"Word.\" ", c | w, sp, false); + allPathsForCaps("\"Word\". ", c | w | s, sp, false); + allPathsForCaps("\"Word\" ", c | w, sp, false); + + // Test special case for German. German does not capitalize at the start of a + // line when the previous line starts with a comma. It does in other cases. + sp = job.runInLocale(res, Locale.GERMAN); + allPathsForCaps("Liebe Sara,\n", c | w, sp, false); + allPathsForCaps("Liebe Sara,\n", c | w, sp, true); + allPathsForCaps("Liebe Sara, \n ", c | w, sp, false); + allPathsForCaps("Liebe Sara \n ", c | w | s, sp, false); + allPathsForCaps("Liebe Sara.\n ", c | w | s, sp, false); + sp = job.runInLocale(res, Locale.ENGLISH); + allPathsForCaps("Liebe Sara,\n", c | w | s, sp, false); + allPathsForCaps("Liebe Sara,\n", c | w | s, sp, true); + allPathsForCaps("Liebe Sara, \n ", c | w | s, sp, false); + allPathsForCaps("Liebe Sara \n ", c | w | s, sp, false); + allPathsForCaps("Liebe Sara.\n ", c | w | s, sp, false); - sv = SettingsValues.makeDummySettingsValuesForTest(Locale.FRENCH); - allPathsForCaps("\"Word.\" ", c | w, sv, false); - allPathsForCaps("\"Word\". ", c | w | s, sv, false); - allPathsForCaps("\"Word\" ", c | w, sv, false); + // Test armenian period + sp = job.runInLocale(res, LocaleUtils.constructLocaleFromString("hy_AM")); + assertTrue("Period is not sentence separator in Armenian", + !sp.isSentenceSeparator('.')); + assertTrue("Sentence separator is Armenian period in Armenian", + sp.isSentenceSeparator(0x589)); + // No space : capitalize only if MODE_CHARACTERS + allPathsForCaps("Word", c, sp, false); + allPathsForCaps("Word.", c, sp, false); + // Space, but no armenian period : capitalize if MODE_WORDS but not SENTENCES + allPathsForCaps("Word. ", c | w, sp, false); + // Armenian period : capitalize if MODE_SENTENCES + allPathsForCaps("Word\u0589 ", c | w | s, sp, false); } } diff --git a/tests/src/com/android/inputmethod/latin/utils/DictionaryInfoUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/DictionaryInfoUtilsTests.java new file mode 100644 index 000000000..6e716074c --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/DictionaryInfoUtilsTests.java @@ -0,0 +1,47 @@ +/* + * 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.latin.utils; + +import android.content.res.Resources; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; + +import java.util.Locale; + +@SmallTest +public class DictionaryInfoUtilsTests extends AndroidTestCase { + public void testLooksValidForDictionaryInsertion() { + final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(final Resources res) { + return new SpacingAndPunctuations(res); + } + }; + final Resources res = getContext().getResources(); + final SpacingAndPunctuations sp = job.runInLocale(res, Locale.ENGLISH); + assertTrue(DictionaryInfoUtils.looksValidForDictionaryInsertion("aochaueo", sp)); + assertFalse(DictionaryInfoUtils.looksValidForDictionaryInsertion("", sp)); + assertTrue(DictionaryInfoUtils.looksValidForDictionaryInsertion("ao-ch'aueo", sp)); + assertFalse(DictionaryInfoUtils.looksValidForDictionaryInsertion("2908743256", sp)); + assertTrue(DictionaryInfoUtils.looksValidForDictionaryInsertion("31aochaueo", sp)); + assertFalse(DictionaryInfoUtils.looksValidForDictionaryInsertion("akeo raeoch oerch .", + sp)); + assertFalse(DictionaryInfoUtils.looksValidForDictionaryInsertion("!!!", sp)); + } +} diff --git a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java b/tests/src/com/android/inputmethod/latin/utils/EditDistanceTests.java index 0b7fcbbe8..58312264b 100644 --- a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/EditDistanceTests.java @@ -14,23 +14,13 @@ * limitations under the License. */ -package com.android.inputmethod.latin; +package com.android.inputmethod.latin.utils; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @SmallTest public class EditDistanceTests extends AndroidTestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - /* * dist(kitten, sitting) == 3 * @@ -39,7 +29,7 @@ public class EditDistanceTests extends AndroidTestCase { * sitting */ public void testExample1() { - final int dist = BinaryDictionary.editDistance("kitten", "sitting"); + final int dist = BinaryDictionaryUtils.editDistance("kitten", "sitting"); assertEquals("edit distance between 'kitten' and 'sitting' is 3", 3, dist); } @@ -52,26 +42,26 @@ public class EditDistanceTests extends AndroidTestCase { * S--unday */ public void testExample2() { - final int dist = BinaryDictionary.editDistance("Saturday", "Sunday"); + final int dist = BinaryDictionaryUtils.editDistance("Saturday", "Sunday"); assertEquals("edit distance between 'Saturday' and 'Sunday' is 3", 3, dist); } public void testBothEmpty() { - final int dist = BinaryDictionary.editDistance("", ""); + final int dist = BinaryDictionaryUtils.editDistance("", ""); assertEquals("when both string are empty, no edits are needed", 0, dist); } public void testFirstArgIsEmpty() { - final int dist = BinaryDictionary.editDistance("", "aaaa"); + final int dist = BinaryDictionaryUtils.editDistance("", "aaaa"); assertEquals("when only one string of the arguments is empty," + " the edit distance is the length of the other.", 4, dist); } public void testSecoondArgIsEmpty() { - final int dist = BinaryDictionary.editDistance("aaaa", ""); + final int dist = BinaryDictionaryUtils.editDistance("aaaa", ""); assertEquals("when only one string of the arguments is empty," + " the edit distance is the length of the other.", 4, dist); @@ -80,27 +70,27 @@ public class EditDistanceTests extends AndroidTestCase { public void testSameStrings() { final String arg1 = "The quick brown fox jumps over the lazy dog."; final String arg2 = "The quick brown fox jumps over the lazy dog."; - final int dist = BinaryDictionary.editDistance(arg1, arg2); + final int dist = BinaryDictionaryUtils.editDistance(arg1, arg2); assertEquals("when same strings are passed, distance equals 0.", 0, dist); } public void testSameReference() { final String arg = "The quick brown fox jumps over the lazy dog."; - final int dist = BinaryDictionary.editDistance(arg, arg); + final int dist = BinaryDictionaryUtils.editDistance(arg, arg); assertEquals("when same string references are passed, the distance equals 0.", 0, dist); } public void testNullArg() { try { - BinaryDictionary.editDistance(null, "aaa"); + BinaryDictionaryUtils.editDistance(null, "aaa"); fail("IllegalArgumentException should be thrown."); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { - BinaryDictionary.editDistance("aaa", null); + BinaryDictionaryUtils.editDistance("aaa", null); fail("IllegalArgumentException should be thrown."); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); diff --git a/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java new file mode 100644 index 000000000..ae2623d12 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 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.latin.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Unit tests for ExecutorUtils. + */ +@MediumTest +public class ExecutorUtilsTests extends AndroidTestCase { + private static final String TAG = ExecutorUtilsTests.class.getSimpleName(); + + private static final String TEST_EXECUTOR_ID = "test"; + private static final int NUM_OF_TASKS = 10; + private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500; + + public void testExecute() { + final ExecutorService executor = ExecutorUtils.getExecutor(TEST_EXECUTOR_ID); + final AtomicInteger v = new AtomicInteger(0); + for (int i = 0; i < NUM_OF_TASKS; ++i) { + executor.execute(new Runnable() { + @Override + public void run() { + v.incrementAndGet(); + } + }); + } + try { + executor.awaitTermination(DELAY_FOR_WAITING_TASKS_MILLISECONDS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Log.d(TAG, "Exception while sleeping.", e); + } + + assertEquals(NUM_OF_TASKS, v.get()); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java b/tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java deleted file mode 100644 index 823bd5d7d..000000000 --- a/tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2012 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.latin.utils; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; - -@SmallTest -public class ForgettingCurveTests extends AndroidTestCase { - public void testFcToFreq() { - for (int i = 0; i < Byte.MAX_VALUE; ++i) { - final byte fc = (byte)i; - final int e = UserHistoryForgettingCurveUtils.fcToElapsedTime(fc); - final int c = UserHistoryForgettingCurveUtils.fcToCount(fc); - final int l = UserHistoryForgettingCurveUtils.fcToLevel(fc); - final byte fc2 = UserHistoryForgettingCurveUtils.calcFc(e, c, l); - assertEquals(fc, fc2); - } - byte fc = 0; - int l; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < (UserHistoryForgettingCurveUtils.COUNT_MAX + 1); ++j) { - fc = UserHistoryForgettingCurveUtils.pushCount(fc, true); - } - l = UserHistoryForgettingCurveUtils.fcToLevel(fc); - assertEquals(l, Math.max(1, Math.min(i + 1, 3))); - } - fc = 0; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < (UserHistoryForgettingCurveUtils.COUNT_MAX + 1); ++j) { - fc = UserHistoryForgettingCurveUtils.pushCount(fc, false); - } - l = UserHistoryForgettingCurveUtils.fcToLevel(fc); - assertEquals(l, Math.min(i + 1, 3)); - } - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < (UserHistoryForgettingCurveUtils.ELAPSED_TIME_MAX + 1); ++j) { - fc = UserHistoryForgettingCurveUtils.pushElapsedTime(fc); - } - l = UserHistoryForgettingCurveUtils.fcToLevel(fc); - assertEquals(l, Math.max(0, 2 - i)); - } - } -} diff --git a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java deleted file mode 100644 index e0755483c..000000000 --- a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2013 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.latin.utils; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Unit tests for PrioritizedSerialExecutor. - * TODO: Add more detailed tests to make use of priorities, etc. - */ -@MediumTest -public class PrioritizedSerialExecutorTests extends AndroidTestCase { - private static final String TAG = PrioritizedSerialExecutorTests.class.getSimpleName(); - - private static final int NUM_OF_TASKS = 10; - private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500; - - public void testExecute() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.execute(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(NUM_OF_TASKS, v.get()); - } - - public void testExecutePrioritized() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.executePrioritized(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(NUM_OF_TASKS, v.get()); - } - - public void testExecuteCombined() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.execute(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.executePrioritized(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(2 * NUM_OF_TASKS, v.get()); - } -} diff --git a/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java b/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java index a52041264..a3f2ce586 100644 --- a/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java @@ -19,31 +19,35 @@ package com.android.inputmethod.latin.utils; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.Constants; + import java.util.Locale; @SmallTest public class RecapitalizeStatusTests extends AndroidTestCase { + private static final int[] SPACE = { Constants.CODE_SPACE }; + public void testTrim() { final RecapitalizeStatus status = new RecapitalizeStatus(); - status.initialize(30, 40, "abcdefghij", Locale.ENGLISH, " "); + status.start(30, 40, "abcdefghij", Locale.ENGLISH, SPACE); status.trim(); assertEquals("abcdefghij", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(40, status.getNewCursorEnd()); - status.initialize(30, 44, " abcdefghij", Locale.ENGLISH, " "); + status.start(30, 44, " abcdefghij", Locale.ENGLISH, SPACE); status.trim(); assertEquals("abcdefghij", status.getRecapitalizedString()); assertEquals(34, status.getNewCursorStart()); assertEquals(44, status.getNewCursorEnd()); - status.initialize(30, 40, "abcdefgh ", Locale.ENGLISH, " "); + status.start(30, 40, "abcdefgh ", Locale.ENGLISH, SPACE); status.trim(); assertEquals("abcdefgh", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(38, status.getNewCursorEnd()); - status.initialize(30, 45, " abcdefghij ", Locale.ENGLISH, " "); + status.start(30, 45, " abcdefghij ", Locale.ENGLISH, SPACE); status.trim(); assertEquals("abcdefghij", status.getRecapitalizedString()); assertEquals(33, status.getNewCursorStart()); @@ -52,7 +56,7 @@ public class RecapitalizeStatusTests extends AndroidTestCase { public void testRotate() { final RecapitalizeStatus status = new RecapitalizeStatus(); - status.initialize(29, 40, "abcd efghij", Locale.ENGLISH, " "); + status.start(29, 40, "abcd efghij", Locale.ENGLISH, SPACE); status.rotate(); assertEquals("Abcd Efghij", status.getRecapitalizedString()); assertEquals(29, status.getNewCursorStart()); @@ -64,7 +68,7 @@ public class RecapitalizeStatusTests extends AndroidTestCase { status.rotate(); assertEquals("Abcd Efghij", status.getRecapitalizedString()); - status.initialize(29, 40, "Abcd Efghij", Locale.ENGLISH, " "); + status.start(29, 40, "Abcd Efghij", Locale.ENGLISH, SPACE); status.rotate(); assertEquals("ABCD EFGHIJ", status.getRecapitalizedString()); assertEquals(29, status.getNewCursorStart()); @@ -76,7 +80,7 @@ public class RecapitalizeStatusTests extends AndroidTestCase { status.rotate(); assertEquals("ABCD EFGHIJ", status.getRecapitalizedString()); - status.initialize(29, 40, "ABCD EFGHIJ", Locale.ENGLISH, " "); + status.start(29, 40, "ABCD EFGHIJ", Locale.ENGLISH, SPACE); status.rotate(); assertEquals("abcd efghij", status.getRecapitalizedString()); assertEquals(29, status.getNewCursorStart()); @@ -88,7 +92,7 @@ public class RecapitalizeStatusTests extends AndroidTestCase { status.rotate(); assertEquals("abcd efghij", status.getRecapitalizedString()); - status.initialize(29, 39, "AbCDefghij", Locale.ENGLISH, " "); + status.start(29, 39, "AbCDefghij", Locale.ENGLISH, SPACE); status.rotate(); assertEquals("abcdefghij", status.getRecapitalizedString()); assertEquals(29, status.getNewCursorStart()); @@ -102,7 +106,7 @@ public class RecapitalizeStatusTests extends AndroidTestCase { status.rotate(); assertEquals("abcdefghij", status.getRecapitalizedString()); - status.initialize(29, 40, "Abcd efghij", Locale.ENGLISH, " "); + status.start(29, 40, "Abcd efghij", Locale.ENGLISH, SPACE); status.rotate(); assertEquals("abcd efghij", status.getRecapitalizedString()); assertEquals(29, status.getNewCursorStart()); @@ -116,7 +120,8 @@ public class RecapitalizeStatusTests extends AndroidTestCase { status.rotate(); assertEquals("abcd efghij", status.getRecapitalizedString()); - status.initialize(30, 34, "grüß", Locale.GERMAN, " "); status.rotate(); + status.start(30, 34, "grüß", Locale.GERMAN, SPACE); + status.rotate(); assertEquals("Grüß", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(34, status.getNewCursorEnd()); @@ -133,7 +138,8 @@ public class RecapitalizeStatusTests extends AndroidTestCase { assertEquals(30, status.getNewCursorStart()); assertEquals(34, status.getNewCursorEnd()); - status.initialize(30, 33, "œuf", Locale.FRENCH, " "); status.rotate(); + status.start(30, 33, "œuf", Locale.FRENCH, SPACE); + status.rotate(); assertEquals("Œuf", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(33, status.getNewCursorEnd()); @@ -150,7 +156,8 @@ public class RecapitalizeStatusTests extends AndroidTestCase { assertEquals(30, status.getNewCursorStart()); assertEquals(33, status.getNewCursorEnd()); - status.initialize(30, 33, "œUf", Locale.FRENCH, " "); status.rotate(); + status.start(30, 33, "œUf", Locale.FRENCH, SPACE); + status.rotate(); assertEquals("œuf", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(33, status.getNewCursorEnd()); @@ -171,7 +178,8 @@ public class RecapitalizeStatusTests extends AndroidTestCase { assertEquals(30, status.getNewCursorStart()); assertEquals(33, status.getNewCursorEnd()); - status.initialize(30, 35, "école", Locale.FRENCH, " "); status.rotate(); + status.start(30, 35, "école", Locale.FRENCH, SPACE); + status.rotate(); assertEquals("École", status.getRecapitalizedString()); assertEquals(30, status.getNewCursorStart()); assertEquals(35, status.getNewCursorEnd()); diff --git a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java index cad80d5ce..8f58e6873 100644 --- a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java @@ -39,7 +39,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { int[] array2 = null, array3 = null; final int limit = DEFAULT_CAPACITY * 2 + 10; for (int i = 0; i < limit; i++) { - src.add(i); + final int value = i; + src.add(value); assertEquals("length after add " + i, i + 1, src.getLength()); if (i == DEFAULT_CAPACITY) { array2 = src.getPrimitiveArray(); @@ -56,7 +57,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { } } for (int i = 0; i < limit; i++) { - assertEquals("value at " + i, i, src.get(i)); + final int value = i; + assertEquals("value at " + i, value, src.get(i)); } } @@ -64,11 +66,13 @@ public class ResizableIntArrayTests extends AndroidTestCase { final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY); final int limit = DEFAULT_CAPACITY * 10, step = DEFAULT_CAPACITY * 2; for (int i = 0; i < limit; i += step) { - src.add(i, i); + final int value = i; + src.addAt(i, value); assertEquals("length after add at " + i, i + 1, src.getLength()); } for (int i = 0; i < limit; i += step) { - assertEquals("value at " + i, i, src.get(i)); + final int value = i; + assertEquals("value at " + i, value, src.get(i)); } } @@ -88,9 +92,10 @@ public class ResizableIntArrayTests extends AndroidTestCase { } final int index = DEFAULT_CAPACITY / 2; - src.add(index, 100); + final int valueAddAt = 100; + src.addAt(index, valueAddAt); assertEquals("legth after add at " + index, index + 1, src.getLength()); - assertEquals("value after add at " + index, 100, src.get(index)); + assertEquals("value after add at " + index, valueAddAt, src.get(index)); assertEquals("value after add at 0", 0, src.get(0)); try { final int value = src.get(src.getLength()); @@ -104,7 +109,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY); final int[] array = src.getPrimitiveArray(); for (int i = 0; i < DEFAULT_CAPACITY; i++) { - src.add(i); + final int value = i; + src.add(value); assertEquals("length after add " + i, i + 1, src.getLength()); } @@ -116,7 +122,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { int[] array3 = null; for (int i = 0; i < DEFAULT_CAPACITY; i++) { - src.add(i); + final int value = i; + src.add(value); assertEquals("length after add " + i, i + 1, src.getLength()); if (i == smallerLength) { array3 = src.getPrimitiveArray(); @@ -133,7 +140,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY); final int[] array = src.getPrimitiveArray(); for (int i = 0; i < DEFAULT_CAPACITY; i++) { - src.add(i); + final int value = i; + src.add(value); assertEquals("length after add " + i, i + 1, src.getLength()); } @@ -144,11 +152,11 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertNotSame("array after larger setLength", array, array2); assertEquals("array length after larger setLength", largerLength, array2.length); for (int i = 0; i < largerLength; i++) { - final int v = src.get(i); + final int value = i; if (i < DEFAULT_CAPACITY) { - assertEquals("value at " + i, i, v); + assertEquals("value at " + i, value, src.get(i)); } else { - assertEquals("value at " + i, 0, v); + assertEquals("value at " + i, 0, src.get(i)); } } @@ -159,7 +167,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertSame("array after smaller setLength", array2, array3); assertEquals("array length after smaller setLength", largerLength, array3.length); for (int i = 0; i < smallerLength; i++) { - assertEquals("value at " + i, i, src.get(i)); + final int value = i; + assertEquals("value at " + i, value, src.get(i)); } } @@ -167,7 +176,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY); final int limit = DEFAULT_CAPACITY * 2 + 10; for (int i = 0; i < limit; i++) { - src.add(i); + final int value = i; + src.add(value); } final ResizableIntArray dst = new ResizableIntArray(DEFAULT_CAPACITY); @@ -179,7 +189,8 @@ public class ResizableIntArrayTests extends AndroidTestCase { public void testCopy() { final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY); for (int i = 0; i < DEFAULT_CAPACITY; i++) { - src.add(i); + final int value = i; + src.add(value); } final ResizableIntArray dst = new ResizableIntArray(DEFAULT_CAPACITY); @@ -204,119 +215,126 @@ public class ResizableIntArrayTests extends AndroidTestCase { } public void testAppend() { - final int srcLen = DEFAULT_CAPACITY; - final ResizableIntArray src = new ResizableIntArray(srcLen); - for (int i = 0; i < srcLen; i++) { - src.add(i); + final int srcLength = DEFAULT_CAPACITY; + final ResizableIntArray src = new ResizableIntArray(srcLength); + for (int i = 0; i < srcLength; i++) { + final int value = i; + src.add(value); } final ResizableIntArray dst = new ResizableIntArray(DEFAULT_CAPACITY * 2); final int[] array = dst.getPrimitiveArray(); - final int dstLen = DEFAULT_CAPACITY / 2; - for (int i = 0; i < dstLen; i++) { + final int dstLength = DEFAULT_CAPACITY / 2; + for (int i = 0; i < dstLength; i++) { final int value = -i - 1; dst.add(value); } final ResizableIntArray dstCopy = new ResizableIntArray(dst.getLength()); dstCopy.copy(dst); - dst.append(src, 0, 0); - assertEquals("length after append zero", dstLen, dst.getLength()); + final int startPos = 0; + dst.append(src, startPos, 0 /* length */); + assertEquals("length after append zero", dstLength, dst.getLength()); assertSame("array after append zero", array, dst.getPrimitiveArray()); - assertIntArrayEquals("values after append zero", - dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); + assertIntArrayEquals("values after append zero", dstCopy.getPrimitiveArray(), startPos, + dst.getPrimitiveArray(), startPos, dstLength); - dst.append(src, 0, srcLen); - assertEquals("length after append", dstLen + srcLen, dst.getLength()); + dst.append(src, startPos, srcLength); + assertEquals("length after append", dstLength + srcLength, dst.getLength()); assertSame("array after append", array, dst.getPrimitiveArray()); assertTrue("primitive length after append", - dst.getPrimitiveArray().length >= dstLen + srcLen); - assertIntArrayEquals("original values after append", - dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); - assertIntArrayEquals("appended values after append", - src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen); + dst.getPrimitiveArray().length >= dstLength + srcLength); + assertIntArrayEquals("original values after append", dstCopy.getPrimitiveArray(), startPos, + dst.getPrimitiveArray(), startPos, dstLength); + assertIntArrayEquals("appended values after append", src.getPrimitiveArray(), startPos, + dst.getPrimitiveArray(), dstLength, srcLength); - dst.append(src, 0, srcLen); - assertEquals("length after 2nd append", dstLen + srcLen * 2, dst.getLength()); + dst.append(src, startPos, srcLength); + assertEquals("length after 2nd append", dstLength + srcLength * 2, dst.getLength()); assertNotSame("array after 2nd append", array, dst.getPrimitiveArray()); assertTrue("primitive length after 2nd append", - dst.getPrimitiveArray().length >= dstLen + srcLen * 2); + dst.getPrimitiveArray().length >= dstLength + srcLength * 2); assertIntArrayEquals("original values after 2nd append", - dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); + dstCopy.getPrimitiveArray(), startPos, dst.getPrimitiveArray(), startPos, + dstLength); assertIntArrayEquals("appended values after 2nd append", - src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen); + src.getPrimitiveArray(), startPos, dst.getPrimitiveArray(), dstLength, + srcLength); assertIntArrayEquals("appended values after 2nd append", - src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen + srcLen, srcLen); + src.getPrimitiveArray(), startPos, dst.getPrimitiveArray(), dstLength + srcLength, + srcLength); } public void testFill() { - final int srcLen = DEFAULT_CAPACITY; - final ResizableIntArray src = new ResizableIntArray(srcLen); - for (int i = 0; i < srcLen; i++) { - src.add(i); + final int srcLength = DEFAULT_CAPACITY; + final ResizableIntArray src = new ResizableIntArray(srcLength); + for (int i = 0; i < srcLength; i++) { + final int value = i; + src.add(value); } final int[] array = src.getPrimitiveArray(); - final int startPos = srcLen / 3; - final int length = srcLen / 3; + final int startPos = srcLength / 3; + final int length = srcLength / 3; final int endPos = startPos + length; assertTrue(startPos >= 1); - final int value = 123; + final int fillValue = 123; try { - src.fill(value, -1, length); + src.fill(fillValue, -1 /* startPos */, length); fail("fill from -1 shouldn't succeed"); } catch (IllegalArgumentException e) { // success } try { - src.fill(value, startPos, -1); + src.fill(fillValue, startPos, -1 /* length */); fail("fill negative length shouldn't succeed"); } catch (IllegalArgumentException e) { // success } - src.fill(value, startPos, length); - assertEquals("length after fill", srcLen, src.getLength()); + src.fill(fillValue, startPos, length); + assertEquals("length after fill", srcLength, src.getLength()); assertSame("array after fill", array, src.getPrimitiveArray()); - for (int i = 0; i < srcLen; i++) { - final int v = src.get(i); + for (int i = 0; i < srcLength; i++) { + final int value = i; if (i >= startPos && i < endPos) { - assertEquals("new values after fill at " + i, value, v); + assertEquals("new values after fill at " + i, fillValue, src.get(i)); } else { - assertEquals("unmodified values after fill at " + i, i, v); + assertEquals("unmodified values after fill at " + i, value, src.get(i)); } } - final int length2 = srcLen * 2 - startPos; + final int length2 = srcLength * 2 - startPos; final int largeEnd = startPos + length2; - assertTrue(largeEnd > srcLen); - final int value2 = 456; - src.fill(value2, startPos, length2); + assertTrue(largeEnd > srcLength); + final int fillValue2 = 456; + src.fill(fillValue2, startPos, length2); assertEquals("length after large fill", largeEnd, src.getLength()); assertNotSame("array after large fill", array, src.getPrimitiveArray()); for (int i = 0; i < largeEnd; i++) { - final int v = src.get(i); + final int value = i; if (i >= startPos && i < largeEnd) { - assertEquals("new values after large fill at " + i, value2, v); + assertEquals("new values after large fill at " + i, fillValue2, src.get(i)); } else { - assertEquals("unmodified values after large fill at " + i, i, v); + assertEquals("unmodified values after large fill at " + i, value, src.get(i)); } } final int startPos2 = largeEnd + length2; final int endPos2 = startPos2 + length2; - final int value3 = 789; - src.fill(value3, startPos2, length2); + final int fillValue3 = 789; + src.fill(fillValue3, startPos2, length2); assertEquals("length after disjoint fill", endPos2, src.getLength()); for (int i = 0; i < endPos2; i++) { - final int v = src.get(i); + final int value = i; if (i >= startPos2 && i < endPos2) { - assertEquals("new values after disjoint fill at " + i, value3, v); + assertEquals("new values after disjoint fill at " + i, fillValue3, src.get(i)); } else if (i >= startPos && i < largeEnd) { - assertEquals("unmodified values after disjoint fill at " + i, value2, v); + assertEquals("unmodified values after disjoint fill at " + i, + fillValue2, src.get(i)); } else if (i < startPos) { - assertEquals("unmodified values after disjoint fill at " + i, i, v); + assertEquals("unmodified values after disjoint fill at " + i, value, src.get(i)); } else { - assertEquals("gap values after disjoint fill at " + i, 0, v); + assertEquals("gap values after disjoint fill at " + i, 0, src.get(i)); } } } @@ -346,12 +364,14 @@ public class ResizableIntArrayTests extends AndroidTestCase { final int limit = DEFAULT_CAPACITY * 10; final int shiftAmount = 20; for (int i = 0; i < limit; ++i) { - src.add(i, i); + final int value = i; + src.addAt(i, value); assertEquals("length after add at " + i, i + 1, src.getLength()); } src.shift(shiftAmount); for (int i = 0; i < limit - shiftAmount; ++i) { - assertEquals("value at " + i, i + shiftAmount, src.get(i)); + final int oldValue = i + shiftAmount; + assertEquals("value at " + i, oldValue, src.get(i)); } } } diff --git a/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java index 1ae22e307..8e764e40f 100644 --- a/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java @@ -19,48 +19,15 @@ package com.android.inputmethod.latin.utils; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; -import com.android.inputmethod.latin.utils.ResourceUtils.DeviceOverridePatternSyntaxError; - import java.util.HashMap; @SmallTest public class ResourceUtilsTests extends AndroidTestCase { - public void testFindDefaultConstant() { - final String[] nullArray = null; - final String[] emptyArray = {}; - final String[] array = { - "HARDWARE=grouper,0.3", - "HARDWARE=mako,0.4", - ",defaultValue1", - "HARDWARE=manta,0.2", - ",defaultValue2", - }; - - try { - assertNull(ResourceUtils.findDefaultConstant(nullArray)); - assertNull(ResourceUtils.findDefaultConstant(emptyArray)); - assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1"); - } catch (final DeviceOverridePatternSyntaxError e) { - fail(e.getMessage()); - } - - final String[] errorArray = { - "HARDWARE=grouper,0.3", - "no_comma" - }; - try { - final String defaultValue = ResourceUtils.findDefaultConstant(errorArray); - fail("exception should be thrown: defaultValue=" + defaultValue); - } catch (final DeviceOverridePatternSyntaxError e) { - assertEquals("Array element has no comma: no_comma", e.getMessage()); - } - } - public void testFindConstantForKeyValuePairsSimple() { - final HashMap<String,String> anyKeyValue = CollectionUtils.newHashMap(); + final HashMap<String,String> anyKeyValue = new HashMap<>(); anyKeyValue.put("anyKey", "anyValue"); final HashMap<String,String> nullKeyValue = null; - final HashMap<String,String> emptyKeyValue = CollectionUtils.newHashMap(); + final HashMap<String,String> emptyKeyValue = new HashMap<>(); final String[] nullArray = null; assertNull(ResourceUtils.findConstantForKeyValuePairs(anyKeyValue, nullArray)); @@ -81,7 +48,7 @@ public class ResourceUtilsTests extends AndroidTestCase { "HARDWARE=mako,0.5", }; - final HashMap<String,String> keyValues = CollectionUtils.newHashMap(); + final HashMap<String,String> keyValues = new HashMap<>(); keyValues.put(HARDWARE_KEY, "grouper"); assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "mako"); @@ -121,7 +88,7 @@ public class ResourceUtilsTests extends AndroidTestCase { "HARDWARE=mantaray:MODEL=Nexus 10:MANUFACTURER=samsung,0.2" }; - final HashMap<String,String> keyValues = CollectionUtils.newHashMap(); + final HashMap<String,String> keyValues = new HashMap<>(); keyValues.put(HARDWARE_KEY, "grouper"); keyValues.put(MODEL_KEY, "Nexus 7"); keyValues.put(MANUFACTURER_KEY, "asus"); @@ -159,7 +126,7 @@ public class ResourceUtilsTests extends AndroidTestCase { "HARDWARE=manta.*:MODEL=Nexus 10:MANUFACTURER=samsung,0.2" }; - final HashMap<String,String> keyValues = CollectionUtils.newHashMap(); + final HashMap<String,String> keyValues = new HashMap<>(); keyValues.put(HARDWARE_KEY, "grouper"); keyValues.put(MODEL_KEY, "Nexus 7"); keyValues.put(MANUFACTURER_KEY, "asus"); diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java new file mode 100644 index 000000000..fdde34251 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2011 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.latin.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.RichInputMethodManager; + +import java.util.ArrayList; +import java.util.Locale; + +@SmallTest +public class SpacebarLanguagetUtilsTests extends AndroidTestCase { + // All input method subtypes of LatinIME. + private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<>(); + + private RichInputMethodManager mRichImm; + private Resources mRes; + + InputMethodSubtype EN_US; + InputMethodSubtype EN_GB; + InputMethodSubtype ES_US; + InputMethodSubtype FR; + InputMethodSubtype FR_CA; + InputMethodSubtype FR_CH; + InputMethodSubtype DE; + InputMethodSubtype DE_CH; + InputMethodSubtype ZZ; + InputMethodSubtype DE_QWERTY; + InputMethodSubtype FR_QWERTZ; + InputMethodSubtype EN_US_AZERTY; + InputMethodSubtype EN_UK_DVORAK; + InputMethodSubtype ES_US_COLEMAK; + InputMethodSubtype ZZ_AZERTY; + InputMethodSubtype ZZ_PC; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getContext(); + RichInputMethodManager.init(context); + mRichImm = RichInputMethodManager.getInstance(); + mRes = context.getResources(); + SubtypeLocaleUtils.init(context); + + final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme(); + final int subtypeCount = imi.getSubtypeCount(); + for (int index = 0; index < subtypeCount; index++) { + final InputMethodSubtype subtype = imi.getSubtypeAt(index); + mSubtypesList.add(subtype); + } + + EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.US.toString(), "qwerty"); + EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.UK.toString(), "qwerty"); + ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "es_US", "spanish"); + FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.FRENCH.toString(), "azerty"); + FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.CANADA_FRENCH.toString(), "qwerty"); + FR_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "fr_CH", "swiss"); + DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.GERMAN.toString(), "qwertz"); + DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "de_CH", "swiss"); + ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); + DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.GERMAN.toString(), "qwerty"); + FR_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.FRENCH.toString(), "qwertz"); + EN_US_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "azerty"); + EN_UK_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.UK.toString(), "dvorak"); + ES_US_COLEMAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "es_US", "colemak"); + ZZ_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + ZZ_PC = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty"); + } + + public void testAllFullDisplayNameForSpacebar() { + for (final InputMethodSubtype subtype : mSubtypesList) { + final String subtypeName = SubtypeLocaleUtils + .getSubtypeDisplayNameInSystemLocale(subtype); + final String spacebarText = SpacebarLanguageUtils.getFullDisplayName(subtype); + final String languageName = SubtypeLocaleUtils + .getSubtypeLocaleDisplayName(subtype.getLocale()); + if (SubtypeLocaleUtils.isNoLanguage(subtype)) { + assertFalse(subtypeName, spacebarText.contains(languageName)); + } else { + assertTrue(subtypeName, spacebarText.contains(languageName)); + } + } + } + + public void testAllMiddleDisplayNameForSpacebar() { + for (final InputMethodSubtype subtype : mSubtypesList) { + final String subtypeName = SubtypeLocaleUtils + .getSubtypeDisplayNameInSystemLocale(subtype); + final String spacebarText = SpacebarLanguageUtils.getMiddleDisplayName(subtype); + if (SubtypeLocaleUtils.isNoLanguage(subtype)) { + assertEquals(subtypeName, + SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype), spacebarText); + } else { + final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + assertEquals(subtypeName, + SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()), + spacebarText); + } + } + } + + // InputMethodSubtype's display name for spacebar text in its locale. + // isAdditionalSubtype (T=true, F=false) + // locale layout | Middle Full + // ------ ------- - --------- ---------------------- + // en_US qwerty F English English (US) exception + // en_GB qwerty F English English (UK) exception + // es_US spanish F Español Español (EE.UU.) exception + // fr azerty F Français Français + // fr_CA qwerty F Français Français (Canada) + // fr_CH swiss F Français Français (Suisse) + // de qwertz F Deutsch Deutsch + // de_CH swiss F Deutsch Deutsch (Schweiz) + // zz qwerty F QWERTY QWERTY + // fr qwertz T Français Français + // de qwerty T Deutsch Deutsch + // en_US azerty T English English (US) + // zz azerty T AZERTY AZERTY + + private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() { + @Override + protected Void job(final Resources res) { + assertEquals("en_US", "English (US)", + SpacebarLanguageUtils.getFullDisplayName(EN_US)); + assertEquals("en_GB", "English (UK)", + SpacebarLanguageUtils.getFullDisplayName(EN_GB)); + assertEquals("es_US", "Español (EE.UU.)", + SpacebarLanguageUtils.getFullDisplayName(ES_US)); + assertEquals("fr", "Français", + SpacebarLanguageUtils.getFullDisplayName(FR)); + assertEquals("fr_CA", "Français (Canada)", + SpacebarLanguageUtils.getFullDisplayName(FR_CA)); + assertEquals("fr_CH", "Français (Suisse)", + SpacebarLanguageUtils.getFullDisplayName(FR_CH)); + assertEquals("de", "Deutsch", + SpacebarLanguageUtils.getFullDisplayName(DE)); + assertEquals("de_CH", "Deutsch (Schweiz)", + SpacebarLanguageUtils.getFullDisplayName(DE_CH)); + assertEquals("zz", "QWERTY", + SpacebarLanguageUtils.getFullDisplayName(ZZ)); + + assertEquals("en_US", "English", + SpacebarLanguageUtils.getMiddleDisplayName(EN_US)); + assertEquals("en_GB", "English", + SpacebarLanguageUtils.getMiddleDisplayName(EN_GB)); + assertEquals("es_US", "Español", + SpacebarLanguageUtils.getMiddleDisplayName(ES_US)); + assertEquals("fr", "Français", + SpacebarLanguageUtils.getMiddleDisplayName(FR)); + assertEquals("fr_CA", "Français", + SpacebarLanguageUtils.getMiddleDisplayName(FR_CA)); + assertEquals("fr_CH", "Français", + SpacebarLanguageUtils.getMiddleDisplayName(FR_CH)); + assertEquals("de", "Deutsch", + SpacebarLanguageUtils.getMiddleDisplayName(DE)); + assertEquals("de_CH", "Deutsch", + SpacebarLanguageUtils.getMiddleDisplayName(DE_CH)); + assertEquals("zz", "QWERTY", + SpacebarLanguageUtils.getMiddleDisplayName(ZZ)); + return null; + } + }; + + private final RunInLocale<Void> testsAdditionalSubtypesForSpacebar = new RunInLocale<Void>() { + @Override + protected Void job(final Resources res) { + assertEquals("fr qwertz", "Français", + SpacebarLanguageUtils.getFullDisplayName(FR_QWERTZ)); + assertEquals("de qwerty", "Deutsch", + SpacebarLanguageUtils.getFullDisplayName(DE_QWERTY)); + assertEquals("en_US azerty", "English (US)", + SpacebarLanguageUtils.getFullDisplayName(EN_US_AZERTY)); + assertEquals("en_UK dvorak", "English (UK)", + SpacebarLanguageUtils.getFullDisplayName(EN_UK_DVORAK)); + assertEquals("es_US colemak", "Español (EE.UU.)", + SpacebarLanguageUtils.getFullDisplayName(ES_US_COLEMAK)); + assertEquals("zz azerty", "AZERTY", + SpacebarLanguageUtils.getFullDisplayName(ZZ_AZERTY)); + assertEquals("zz pc", "PC", + SpacebarLanguageUtils.getFullDisplayName(ZZ_PC)); + + assertEquals("fr qwertz", "Français", + SpacebarLanguageUtils.getMiddleDisplayName(FR_QWERTZ)); + assertEquals("de qwerty", "Deutsch", + SpacebarLanguageUtils.getMiddleDisplayName(DE_QWERTY)); + assertEquals("en_US azerty", "English", + SpacebarLanguageUtils.getMiddleDisplayName(EN_US_AZERTY)); + assertEquals("en_UK dvorak", "English", + SpacebarLanguageUtils.getMiddleDisplayName(EN_UK_DVORAK)); + assertEquals("es_US colemak", "Español", + SpacebarLanguageUtils.getMiddleDisplayName(ES_US_COLEMAK)); + assertEquals("zz azerty", "AZERTY", + SpacebarLanguageUtils.getMiddleDisplayName(ZZ_AZERTY)); + assertEquals("zz pc", "PC", + SpacebarLanguageUtils.getMiddleDisplayName(ZZ_PC)); + return null; + } + }; + + public void testPredefinedSubtypesForSpacebarInEnglish() { + testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH); + } + + public void testAdditionalSubtypeForSpacebarInEnglish() { + testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH); + } + + public void testPredefinedSubtypesForSpacebarInFrench() { + testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH); + } + + public void testAdditionalSubtypeForSpacebarInFrench() { + testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java index 4e396a1cf..4448a6baf 100644 --- a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java @@ -16,17 +16,17 @@ package com.android.inputmethod.latin.utils; -import com.android.inputmethod.latin.settings.SettingsValues; - import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.Constants; + import java.util.Arrays; import java.util.List; import java.util.Locale; @SmallTest -public class StringUtilsTests extends AndroidTestCase { +public class StringAndJsonUtilsTests extends AndroidTestCase { public void testContainsInArray() { assertFalse("empty array", StringUtils.containsInArray("key", new String[0])); assertFalse("not in 1 element", StringUtils.containsInArray("key", new String[] { @@ -44,7 +44,7 @@ public class StringUtilsTests extends AndroidTestCase { })); } - public void testContainsInExtraValues() { + public void testContainsInCommaSplittableText() { assertFalse("null", StringUtils.containsInCommaSplittableText("key", null)); assertFalse("empty", StringUtils.containsInCommaSplittableText("key", "")); assertFalse("not in 1 element", @@ -56,28 +56,7 @@ public class StringUtilsTests extends AndroidTestCase { assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key")); } - public void testAppendToExtraValuesIfNotExists() { - assertEquals("null", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", null)); - assertEquals("empty", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "")); - - assertEquals("not in 1 element", "key1,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1")); - assertEquals("not in 2 elements", "key1,key2,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key2")); - - assertEquals("in 1 element", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key")); - assertEquals("in 2 elements at position 1", "key,key2", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key,key2")); - assertEquals("in 2 elements at position 2", "key1,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key")); - assertEquals("in 3 elements at position 2", "key1,key,key3", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3")); - } - - public void testRemoveFromExtraValuesIfExists() { + public void testRemoveFromCommaSplittableTextIfExists() { assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null)); assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", "")); @@ -187,54 +166,47 @@ public class StringUtilsTests extends AndroidTestCase { assertTrue(StringUtils.isIdenticalAfterDowncase("")); } - public void testLooksValidForDictionaryInsertion() { - final SettingsValues settings = - SettingsValues.makeDummySettingsValuesForTest(Locale.ENGLISH); - assertTrue(StringUtils.looksValidForDictionaryInsertion("aochaueo", settings)); - assertFalse(StringUtils.looksValidForDictionaryInsertion("", settings)); - assertTrue(StringUtils.looksValidForDictionaryInsertion("ao-ch'aueo", settings)); - assertFalse(StringUtils.looksValidForDictionaryInsertion("2908743256", settings)); - assertTrue(StringUtils.looksValidForDictionaryInsertion("31aochaueo", settings)); - assertFalse(StringUtils.looksValidForDictionaryInsertion("akeo raeoch oerch .", settings)); - assertFalse(StringUtils.looksValidForDictionaryInsertion("!!!", settings)); - } - - private static void checkCapitalize(final String src, final String dst, final String separators, - final Locale locale) { - assertEquals(dst, StringUtils.capitalizeEachWord(src, separators, locale)); + private static void checkCapitalize(final String src, final String dst, + final int[] sortedSeparators, final Locale locale) { + assertEquals(dst, StringUtils.capitalizeEachWord(src, sortedSeparators, locale)); assert(src.equals(dst) - == StringUtils.isIdenticalAfterCapitalizeEachWord(src, separators)); + == StringUtils.isIdenticalAfterCapitalizeEachWord(src, sortedSeparators)); } + private static final int[] SPACE = { Constants.CODE_SPACE }; + private static final int[] SPACE_PERIOD = StringUtils.toSortedCodePointArray(" ."); + private static final int[] SENTENCE_SEPARATORS = + StringUtils.toSortedCodePointArray(" \n.!?*()&"); + private static final int[] WORD_SEPARATORS = StringUtils.toSortedCodePointArray(" \n.!?*,();&"); + public void testCapitalizeEachWord() { - checkCapitalize("", "", " ", Locale.ENGLISH); - checkCapitalize("test", "Test", " ", Locale.ENGLISH); - checkCapitalize(" test", " Test", " ", Locale.ENGLISH); - checkCapitalize("Test", "Test", " ", Locale.ENGLISH); - checkCapitalize(" Test", " Test", " ", Locale.ENGLISH); - checkCapitalize(".Test", ".test", " ", Locale.ENGLISH); - checkCapitalize(".Test", ".Test", " .", Locale.ENGLISH); - checkCapitalize(".Test", ".Test", ". ", Locale.ENGLISH); - checkCapitalize("test and retest", "Test And Retest", " .", Locale.ENGLISH); - checkCapitalize("Test and retest", "Test And Retest", " .", Locale.ENGLISH); - checkCapitalize("Test And Retest", "Test And Retest", " .", Locale.ENGLISH); - checkCapitalize("Test And.Retest ", "Test And.Retest ", " .", Locale.ENGLISH); - checkCapitalize("Test And.retest ", "Test And.Retest ", " .", Locale.ENGLISH); - checkCapitalize("Test And.retest ", "Test And.retest ", " ", Locale.ENGLISH); - checkCapitalize("Test And.Retest ", "Test And.retest ", " ", Locale.ENGLISH); - checkCapitalize("test and ietest", "Test And İetest", " .", new Locale("tr")); - checkCapitalize("test and ietest", "Test And Ietest", " .", Locale.ENGLISH); - checkCapitalize("Test&Retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH); - checkCapitalize("Test&retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH); - checkCapitalize("test&Retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH); + checkCapitalize("", "", SPACE, Locale.ENGLISH); + checkCapitalize("test", "Test", SPACE, Locale.ENGLISH); + checkCapitalize(" test", " Test", SPACE, Locale.ENGLISH); + checkCapitalize("Test", "Test", SPACE, Locale.ENGLISH); + checkCapitalize(" Test", " Test", SPACE, Locale.ENGLISH); + checkCapitalize(".Test", ".test", SPACE, Locale.ENGLISH); + checkCapitalize(".Test", ".Test", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("test and retest", "Test And Retest", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test and retest", "Test And Retest", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test And Retest", "Test And Retest", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test And.Retest ", "Test And.Retest ", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test And.retest ", "Test And.Retest ", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test And.retest ", "Test And.retest ", SPACE, Locale.ENGLISH); + checkCapitalize("Test And.Retest ", "Test And.retest ", SPACE, Locale.ENGLISH); + checkCapitalize("test and ietest", "Test And İetest", SPACE_PERIOD, new Locale("tr")); + checkCapitalize("test and ietest", "Test And Ietest", SPACE_PERIOD, Locale.ENGLISH); + checkCapitalize("Test&Retest", "Test&Retest", SENTENCE_SEPARATORS, Locale.ENGLISH); + checkCapitalize("Test&retest", "Test&Retest", SENTENCE_SEPARATORS, Locale.ENGLISH); + checkCapitalize("test&Retest", "Test&Retest", SENTENCE_SEPARATORS, Locale.ENGLISH); checkCapitalize("rest\nrecreation! And in the end...", - "Rest\nRecreation! And In The End...", " \n.!?*,();&", Locale.ENGLISH); + "Rest\nRecreation! And In The End...", WORD_SEPARATORS, Locale.ENGLISH); checkCapitalize("lorem ipsum dolor sit amet", "Lorem Ipsum Dolor Sit Amet", - " \n.,!?*()&;", Locale.ENGLISH); + WORD_SEPARATORS, Locale.ENGLISH); checkCapitalize("Lorem!Ipsum (Dolor) Sit * Amet", "Lorem!Ipsum (Dolor) Sit * Amet", - " \n,.;!?*()&", Locale.ENGLISH); + WORD_SEPARATORS, Locale.ENGLISH); checkCapitalize("Lorem!Ipsum (dolor) Sit * Amet", "Lorem!Ipsum (Dolor) Sit * Amet", - " \n,.;!?*()&", Locale.ENGLISH); + WORD_SEPARATORS, Locale.ENGLISH); } public void testLooksLikeURL() { @@ -271,13 +243,87 @@ public class StringUtilsTests extends AndroidTestCase { assertTrue(bytesStr.equals(bytesStr2)); } - public void testJsonStringUtils() { + public void testJsonUtils() { final Object[] objs = new Object[] { 1, "aaa", "bbb", 3 }; final List<Object> objArray = Arrays.asList(objs); - final String str = StringUtils.listToJsonStr(objArray); - final List<Object> newObjArray = StringUtils.jsonStrToList(str); + final String str = JsonUtils.listToJsonStr(objArray); + final List<Object> newObjArray = JsonUtils.jsonStrToList(str); for (int i = 0; i < objs.length; ++i) { assertEquals(objs[i], newObjArray.get(i)); } } + + public void testToCodePointArray() { + final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh\u0000\u2002\u2003\u3000xx"; + final int[] EXPECTED_RESULT = new int[] { 'a', 'b', 'c', 'd', 'e', 0x286D7, 'f', 'g', 'h', + 0, 0x2002, 0x2003, 0x3000, 'x', 'x'}; + final int[] codePointArray = StringUtils.toCodePointArray(STR_WITH_SUPPLEMENTARY_CHAR, 0, + STR_WITH_SUPPLEMENTARY_CHAR.length()); + assertEquals("toCodePointArray, size matches", codePointArray.length, + EXPECTED_RESULT.length); + for (int i = 0; i < EXPECTED_RESULT.length; ++i) { + assertEquals("toCodePointArray position " + i, codePointArray[i], EXPECTED_RESULT[i]); + } + } + + public void testCopyCodePointsAndReturnCodePointCount() { + final String STR_WITH_SUPPLEMENTARY_CHAR = "AbcDE\uD861\uDED7fGh\u0000\u2002\u3000あx"; + final int[] EXPECTED_RESULT = new int[] { 'A', 'b', 'c', 'D', 'E', 0x286D7, + 'f', 'G', 'h', 0, 0x2002, 0x3000, 'あ', 'x'}; + final int[] EXPECTED_RESULT_DOWNCASE = new int[] { 'a', 'b', 'c', 'd', 'e', 0x286D7, + 'f', 'g', 'h', 0, 0x2002, 0x3000, 'あ', 'x'}; + + int[] codePointArray = new int[50]; + int codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray, + STR_WITH_SUPPLEMENTARY_CHAR, 0, + STR_WITH_SUPPLEMENTARY_CHAR.length(), false /* downCase */); + assertEquals("copyCodePointsAndReturnCodePointCount, size matches", codePointCount, + EXPECTED_RESULT.length); + for (int i = 0; i < codePointCount; ++i) { + assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i], + EXPECTED_RESULT[i]); + } + + codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray, + STR_WITH_SUPPLEMENTARY_CHAR, 0, + STR_WITH_SUPPLEMENTARY_CHAR.length(), true /* downCase */); + assertEquals("copyCodePointsAndReturnCodePointCount downcase, size matches", codePointCount, + EXPECTED_RESULT_DOWNCASE.length); + for (int i = 0; i < codePointCount; ++i) { + assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i], + EXPECTED_RESULT_DOWNCASE[i]); + } + + final int JAVA_CHAR_COUNT = 8; + final int CODEPOINT_COUNT = 7; + codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray, + STR_WITH_SUPPLEMENTARY_CHAR, 0, JAVA_CHAR_COUNT, false /* downCase */); + assertEquals("copyCodePointsAndReturnCodePointCount, size matches", codePointCount, + CODEPOINT_COUNT); + for (int i = 0; i < codePointCount; ++i) { + assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i], + EXPECTED_RESULT[i]); + } + + boolean exceptionHappened = false; + codePointArray = new int[5]; + try { + codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray, + STR_WITH_SUPPLEMENTARY_CHAR, 0, JAVA_CHAR_COUNT, false /* downCase */); + } catch (ArrayIndexOutOfBoundsException e) { + exceptionHappened = true; + } + assertTrue("copyCodePointsAndReturnCodePointCount throws when array is too small", + exceptionHappened); + } + + public void testGetTrailingSingleQuotesCount() { + assertEquals(0, StringUtils.getTrailingSingleQuotesCount("")); + assertEquals(1, StringUtils.getTrailingSingleQuotesCount("'")); + assertEquals(5, StringUtils.getTrailingSingleQuotesCount("'''''")); + assertEquals(0, StringUtils.getTrailingSingleQuotesCount("a")); + assertEquals(0, StringUtils.getTrailingSingleQuotesCount("'this")); + assertEquals(1, StringUtils.getTrailingSingleQuotesCount("'word'")); + assertEquals(0, StringUtils.getTrailingSingleQuotesCount("I'm")); + } } diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java index 856b2dbda..ce3df7dd6 100644 --- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java @@ -20,9 +20,9 @@ import android.content.Context; import android.content.res.Resources; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import java.util.ArrayList; @@ -30,8 +30,8 @@ import java.util.Locale; @SmallTest public class SubtypeLocaleUtilsTests extends AndroidTestCase { - // Locale to subtypes list. - private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList(); + // All input method subtypes of LatinIME. + private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<>(); private RichInputMethodManager mRichImm; private Resources mRes; @@ -41,7 +41,9 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { InputMethodSubtype ES_US; InputMethodSubtype FR; InputMethodSubtype FR_CA; + InputMethodSubtype FR_CH; InputMethodSubtype DE; + InputMethodSubtype DE_CH; InputMethodSubtype ZZ; InputMethodSubtype DE_QWERTY; InputMethodSubtype FR_QWERTZ; @@ -60,6 +62,13 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { mRes = context.getResources(); SubtypeLocaleUtils.init(context); + final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme(); + final int subtypeCount = imi.getSubtypeCount(); + for (int index = 0; index < subtypeCount; index++) { + final InputMethodSubtype subtype = imi.getSubtypeAt(index); + mSubtypesList.add(subtype); + } + EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( Locale.US.toString(), "qwerty"); EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( @@ -70,37 +79,41 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { Locale.FRENCH.toString(), "azerty"); FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( Locale.CANADA_FRENCH.toString(), "qwerty"); + FR_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "fr_CH", "swiss"); DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( Locale.GERMAN.toString(), "qwertz"); + DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "de_CH", "swiss"); ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); - DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.GERMAN.toString(), "qwerty", null); - FR_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.FRENCH.toString(), "qwertz", null); - EN_US_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.US.toString(), "azerty", null); - EN_UK_DVORAK = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.UK.toString(), "dvorak", null); - ES_US_COLEMAK = AdditionalSubtypeUtils.createAdditionalSubtype( - "es_US", "colemak", null); - ZZ_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "azerty", null); - ZZ_PC = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty", null); - + DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.GERMAN.toString(), "qwerty"); + FR_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.FRENCH.toString(), "qwertz"); + EN_US_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "azerty"); + EN_UK_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.UK.toString(), "dvorak"); + ES_US_COLEMAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "es_US", "colemak"); + ZZ_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + ZZ_PC = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty"); } public void testAllFullDisplayName() { for (final InputMethodSubtype subtype : mSubtypesList) { - final String subtypeName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); + final String subtypeName = SubtypeLocaleUtils + .getSubtypeDisplayNameInSystemLocale(subtype); if (SubtypeLocaleUtils.isNoLanguage(subtype)) { - final String noLanguage = mRes.getString(R.string.subtype_no_language); - assertTrue(subtypeName, subtypeName.contains(noLanguage)); + final String layoutName = SubtypeLocaleUtils + .getKeyboardLayoutSetDisplayName(subtype); + assertTrue(subtypeName, subtypeName.contains(layoutName)); } else { - final String languageName = - SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale()); + final String languageName = SubtypeLocaleUtils + .getSubtypeLocaleDisplayNameInSystemLocale(subtype.getLocale()); assertTrue(subtypeName, subtypeName.contains(languageName)); } } @@ -110,10 +123,23 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { assertEquals("en_US", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_US)); assertEquals("en_GB", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_GB)); assertEquals("es_US", "spanish", SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US)); - assertEquals("fr ", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR)); + assertEquals("fr", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR)); assertEquals("fr_CA", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CA)); - assertEquals("de ", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE)); - assertEquals("zz ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ)); + assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH)); + assertEquals("de", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE)); + assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH)); + assertEquals("zz", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ)); + + assertEquals("de qwerty", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_QWERTY)); + assertEquals("fr qwertz", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_QWERTZ)); + assertEquals("en_US azerty", "azerty", + SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_US_AZERTY)); + assertEquals("en_UK dvorak", "dvorak", + SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_UK_DVORAK)); + assertEquals("es_US colemak", "colemak", + SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US_COLEMAK)); + assertEquals("zz azerty", "azerty", + SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ_AZERTY)); } // InputMethodSubtype's display name in system locale (en_US). @@ -125,7 +151,9 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { // es_US spanish F Spanish (US) exception // fr azerty F French // fr_CA qwerty F French (Canada) + // fr_CH swiss F French (Switzerland) // de qwertz F German + // de_CH swiss F German (Switzerland) // zz qwerty F Alphabet (QWERTY) // fr qwertz T French (QWERTZ) // de qwerty T German (QWERTY) @@ -144,13 +172,17 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB)); assertEquals("es_US", "Spanish (US)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US)); - assertEquals("fr ", "French", + assertEquals("fr", "French", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR)); assertEquals("fr_CA", "French (Canada)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA)); - assertEquals("de ", "German", + assertEquals("fr_CH", "French (Switzerland)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH)); + assertEquals("de", "German", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE)); - assertEquals("zz ", "Alphabet (QWERTY)", + assertEquals("de_CH", "German (Switzerland)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH)); + assertEquals("zz", "Alphabet (QWERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ)); return null; } @@ -162,17 +194,19 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { final RunInLocale<Void> tests = new RunInLocale<Void>() { @Override protected Void job(final Resources res) { - assertEquals("fr qwertz", "French (QWERTZ)", + assertEquals("fr qwertz", "French (QWERTZ)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ)); - assertEquals("de qwerty", "German (QWERTY)", + assertEquals("de qwerty", "German (QWERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY)); assertEquals("en_US azerty", "English (US) (AZERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY)); - assertEquals("en_UK dvorak", "English (UK) (Dvorak)", + assertEquals("en_UK dvorak","English (UK) (Dvorak)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK)); - assertEquals("es_US colemak","Spanish (US) (Colemak)", + assertEquals("es_US colemak", "Spanish (US) (Colemak)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK)); - assertEquals("zz pc", "Alphabet (PC)", + assertEquals("zz azerty", "Alphabet (AZERTY)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY)); + assertEquals("zz pc", "Alphabet (PC)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC)); return null; } @@ -189,14 +223,16 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { // es_US spanish F Espagnol (États-Unis) exception // fr azerty F Français // fr_CA qwerty F Français (Canada) + // fr_CH swiss F Français (Suisse) // de qwertz F Allemand - // zz qwerty F Aucune langue (QWERTY) + // de_CH swiss F Allemand (Suisse) + // zz qwerty F Alphabet latin (QWERTY) // fr qwertz T Français (QWERTZ) // de qwerty T Allemand (QWERTY) // en_US azerty T Anglais (États-Unis) (AZERTY) exception // en_UK dvorak T Anglais (Royaume-Uni) (Dvorak) exception // es_US colemak T Espagnol (États-Unis) (Colemak) exception - // zz pc T Alphabet (PC) + // zz pc T Alphabet latin (PC) public void testPredefinedSubtypesInFrenchSystemLocale() { final RunInLocale<Void> tests = new RunInLocale<Void>() { @@ -208,13 +244,17 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB)); assertEquals("es_US", "Espagnol (États-Unis)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US)); - assertEquals("fr ", "Français", + assertEquals("fr", "Français", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR)); assertEquals("fr_CA", "Français (Canada)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA)); - assertEquals("de ", "Allemand", + assertEquals("fr_CH", "Français (Suisse)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH)); + assertEquals("de", "Allemand", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE)); - assertEquals("zz ", "Alphabet latin (QWERTY)", + assertEquals("de_CH", "Allemand (Suisse)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH)); + assertEquals("zz", "Alphabet latin (QWERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ)); return null; } @@ -226,17 +266,19 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { final RunInLocale<Void> tests = new RunInLocale<Void>() { @Override protected Void job(final Resources res) { - assertEquals("fr qwertz", "Français (QWERTZ)", + assertEquals("fr qwertz", "Français (QWERTZ)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ)); - assertEquals("de qwerty", "Allemand (QWERTY)", + assertEquals("de qwerty", "Allemand (QWERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY)); assertEquals("en_US azerty", "Anglais (États-Unis) (AZERTY)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY)); assertEquals("en_UK dvorak", "Anglais (Royaume-Uni) (Dvorak)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK)); - assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)", + assertEquals("es_US colemak", "Espagnol (États-Unis) (Colemak)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK)); - assertEquals("zz pc", "Alphabet latin (PC)", + assertEquals("zz azerty", "Alphabet latin (AZERTY)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY)); + assertEquals("zz pc", "Alphabet latin (PC)", SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC)); return null; } @@ -244,144 +286,26 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { tests.runInLocale(mRes, Locale.FRENCH); } - public void testAllFullDisplayNameForSpacebar() { - for (final InputMethodSubtype subtype : mSubtypesList) { - final String subtypeName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); - final String spacebarText = SubtypeLocaleUtils.getFullDisplayName(subtype); - final String languageName = - SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale()); - if (SubtypeLocaleUtils.isNoLanguage(subtype)) { - assertFalse(subtypeName, spacebarText.contains(languageName)); - } else { - assertTrue(subtypeName, spacebarText.contains(languageName)); - } - } - } - - public void testAllMiddleDisplayNameForSpacebar() { - for (final InputMethodSubtype subtype : mSubtypesList) { - final String subtypeName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); - final String spacebarText = SubtypeLocaleUtils.getMiddleDisplayName(subtype); - if (SubtypeLocaleUtils.isNoLanguage(subtype)) { - assertEquals(subtypeName, - SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype), spacebarText); - } else { - assertEquals(subtypeName, - SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale()), - spacebarText); - } - } - } + public void testIsRtlLanguage() { + // Known Right-to-Left language subtypes. + final InputMethodSubtype ARABIC = mRichImm + .findSubtypeByLocaleAndKeyboardLayoutSet("ar", "arabic"); + assertNotNull("Arabic", ARABIC); + final InputMethodSubtype FARSI = mRichImm + .findSubtypeByLocaleAndKeyboardLayoutSet("fa", "farsi"); + assertNotNull("Farsi", FARSI); + final InputMethodSubtype HEBREW = mRichImm + .findSubtypeByLocaleAndKeyboardLayoutSet("iw", "hebrew"); + assertNotNull("Hebrew", HEBREW); - public void testAllShortDisplayNameForSpacebar() { for (final InputMethodSubtype subtype : mSubtypesList) { - final String subtypeName = - SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); - final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); - final String spacebarText = SubtypeLocaleUtils.getShortDisplayName(subtype); - final String languageCode = StringUtils.capitalizeFirstCodePoint( - locale.getLanguage(), locale); - if (SubtypeLocaleUtils.isNoLanguage(subtype)) { - assertEquals(subtypeName, "", spacebarText); + final String subtypeName = SubtypeLocaleUtils + .getSubtypeDisplayNameInSystemLocale(subtype); + if (subtype.equals(ARABIC) || subtype.equals(FARSI) || subtype.equals(HEBREW)) { + assertTrue(subtypeName, SubtypeLocaleUtils.isRtlLanguage(subtype)); } else { - assertEquals(subtypeName, languageCode, spacebarText); + assertFalse(subtypeName, SubtypeLocaleUtils.isRtlLanguage(subtype)); } } } - - // InputMethodSubtype's display name for spacebar text in its locale. - // isAdditionalSubtype (T=true, F=false) - // locale layout | Short Middle Full - // ------ ------- - ---- --------- ---------------------- - // en_US qwerty F En English English (US) exception - // en_GB qwerty F En English English (UK) exception - // es_US spanish F Es Español Español (EE.UU.) exception - // fr azerty F Fr Français Français - // fr_CA qwerty F Fr Français Français (Canada) - // de qwertz F De Deutsch Deutsch - // zz qwerty F QWERTY QWERTY - // fr qwertz T Fr Français Français - // de qwerty T De Deutsch Deutsch - // en_US azerty T En English English (US) - // zz azerty T AZERTY AZERTY - - private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() { - @Override - protected Void job(final Resources res) { - assertEquals("en_US", "English (US)", SubtypeLocaleUtils.getFullDisplayName(EN_US)); - assertEquals("en_GB", "English (UK)", SubtypeLocaleUtils.getFullDisplayName(EN_GB)); - assertEquals("es_US", "Español (EE.UU.)", - SubtypeLocaleUtils.getFullDisplayName(ES_US)); - assertEquals("fr ", "Français", SubtypeLocaleUtils.getFullDisplayName(FR)); - assertEquals("fr_CA", "Français (Canada)", - SubtypeLocaleUtils.getFullDisplayName(FR_CA)); - assertEquals("de ", "Deutsch", SubtypeLocaleUtils.getFullDisplayName(DE)); - assertEquals("zz ", "QWERTY", SubtypeLocaleUtils.getFullDisplayName(ZZ)); - - assertEquals("en_US", "English", SubtypeLocaleUtils.getMiddleDisplayName(EN_US)); - assertEquals("en_GB", "English", SubtypeLocaleUtils.getMiddleDisplayName(EN_GB)); - assertEquals("es_US", "Español", SubtypeLocaleUtils.getMiddleDisplayName(ES_US)); - assertEquals("fr ", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR)); - assertEquals("fr_CA", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CA)); - assertEquals("de ", "Deutsch", SubtypeLocaleUtils.getMiddleDisplayName(DE)); - assertEquals("zz ", "QWERTY", SubtypeLocaleUtils.getMiddleDisplayName(ZZ)); - - assertEquals("en_US", "En", SubtypeLocaleUtils.getShortDisplayName(EN_US)); - assertEquals("en_GB", "En", SubtypeLocaleUtils.getShortDisplayName(EN_GB)); - assertEquals("es_US", "Es", SubtypeLocaleUtils.getShortDisplayName(ES_US)); - assertEquals("fr ", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR)); - assertEquals("fr_CA", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CA)); - assertEquals("de ", "De", SubtypeLocaleUtils.getShortDisplayName(DE)); - assertEquals("zz ", "", SubtypeLocaleUtils.getShortDisplayName(ZZ)); - return null; - } - }; - - private final RunInLocale<Void> testsAdditionalSubtypesForSpacebar = new RunInLocale<Void>() { - @Override - protected Void job(final Resources res) { - assertEquals("fr qwertz", "Français", - SubtypeLocaleUtils.getFullDisplayName(FR_QWERTZ)); - assertEquals("de qwerty", "Deutsch", - SubtypeLocaleUtils.getFullDisplayName(DE_QWERTY)); - assertEquals("en_US azerty", "English (US)", - SubtypeLocaleUtils.getFullDisplayName(EN_US_AZERTY)); - assertEquals("zz azerty", "AZERTY", - SubtypeLocaleUtils.getFullDisplayName(ZZ_AZERTY)); - - assertEquals("fr qwertz", "Français", - SubtypeLocaleUtils.getMiddleDisplayName(FR_QWERTZ)); - assertEquals("de qwerty", "Deutsch", - SubtypeLocaleUtils.getMiddleDisplayName(DE_QWERTY)); - assertEquals("en_US azerty", "English", - SubtypeLocaleUtils.getMiddleDisplayName(EN_US_AZERTY)); - assertEquals("zz azerty", "AZERTY", - SubtypeLocaleUtils.getMiddleDisplayName(ZZ_AZERTY)); - - assertEquals("fr qwertz", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_QWERTZ)); - assertEquals("de qwerty", "De", SubtypeLocaleUtils.getShortDisplayName(DE_QWERTY)); - assertEquals("en_US azerty", "En", - SubtypeLocaleUtils.getShortDisplayName(EN_US_AZERTY)); - assertEquals("zz azerty", "", SubtypeLocaleUtils.getShortDisplayName(ZZ_AZERTY)); - return null; - } - }; - - public void testPredefinedSubtypesForSpacebarInEnglish() { - testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH); - } - - public void testAdditionalSubtypeForSpacebarInEnglish() { - testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH); - } - - public void testPredefinedSubtypesForSpacebarInFrench() { - testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH); - } - - public void testAdditionalSubtypeForSpacebarInFrench() { - testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH); - } } diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java deleted file mode 100644 index 1944fd332..000000000 --- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2012 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.latin.utils; - -import android.content.Context; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.util.Log; - -import com.android.inputmethod.latin.makedict.DictDecoder; -import com.android.inputmethod.latin.makedict.DictEncoder; -import com.android.inputmethod.latin.makedict.FormatSpec; -import com.android.inputmethod.latin.makedict.FusionDictionary; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.Ver3DictDecoder; -import com.android.inputmethod.latin.makedict.Ver3DictEncoder; -import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList; -import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface; -import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; - -/** - * Unit tests for UserHistoryDictIOUtils - */ -@LargeTest -public class UserHistoryDictIOUtilsTests extends AndroidTestCase - implements BigramDictionaryInterface { - - private static final String TAG = UserHistoryDictIOUtilsTests.class.getSimpleName(); - private static final int UNIGRAM_FREQUENCY = 50; - private static final int BIGRAM_FREQUENCY = 100; - private static final ArrayList<String> NOT_HAVE_BIGRAM = new ArrayList<String>(); - private static final FormatSpec.FormatOptions FORMAT_OPTIONS = new FormatSpec.FormatOptions(2); - private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; - - /** - * Return same frequency for all words and bigrams - */ - @Override - public int getFrequency(String word1, String word2) { - if (word1 == null) return UNIGRAM_FREQUENCY; - return BIGRAM_FREQUENCY; - } - - // Utilities for Testing - - private void addWord(final String word, - final HashMap<String, ArrayList<String> > addedWords) { - if (!addedWords.containsKey(word)) { - addedWords.put(word, new ArrayList<String>()); - } - } - - private void addBigram(final String word1, final String word2, - final HashMap<String, ArrayList<String> > addedWords) { - addWord(word1, addedWords); - addWord(word2, addedWords); - addedWords.get(word1).add(word2); - } - - private void addBigramToBigramList(final String word1, final String word2, - final HashMap<String, ArrayList<String> > addedWords, - final UserHistoryDictionaryBigramList bigramList) { - bigramList.addBigram(null, word1); - bigramList.addBigram(word1, word2); - - addBigram(word1, word2, addedWords); - } - - private void checkWordInFusionDict(final FusionDictionary dict, final String word, - final ArrayList<String> expectedBigrams) { - final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, word); - assertNotNull(ptNode); - assertTrue(ptNode.isTerminal()); - - for (final String bigram : expectedBigrams) { - assertNotNull(ptNode.getBigram(bigram)); - } - } - - private void checkWordsInFusionDict(final FusionDictionary dict, - final HashMap<String, ArrayList<String> > bigrams) { - for (final String word : bigrams.keySet()) { - if (bigrams.containsKey(word)) { - checkWordInFusionDict(dict, word, bigrams.get(word)); - } else { - checkWordInFusionDict(dict, word, NOT_HAVE_BIGRAM); - } - } - } - - private void checkWordInBigramList( - final UserHistoryDictionaryBigramList bigramList, final String word, - final ArrayList<String> expectedBigrams) { - // check unigram - final HashMap<String,Byte> unigramMap = bigramList.getBigrams(null); - assertTrue(unigramMap.containsKey(word)); - - // check bigrams - final ArrayList<String> actualBigrams = new ArrayList<String>( - bigramList.getBigrams(word).keySet()); - - Collections.sort(expectedBigrams); - Collections.sort(actualBigrams); - assertEquals(expectedBigrams, actualBigrams); - } - - private void checkWordsInBigramList(final UserHistoryDictionaryBigramList bigramList, - final HashMap<String, ArrayList<String> > addedWords) { - for (final String word : addedWords.keySet()) { - if (addedWords.containsKey(word)) { - checkWordInBigramList(bigramList, word, addedWords.get(word)); - } else { - checkWordInBigramList(bigramList, word, NOT_HAVE_BIGRAM); - } - } - } - - private void writeDictToFile(final File file, - final UserHistoryDictionaryBigramList bigramList) { - final DictEncoder dictEncoder = new Ver3DictEncoder(file); - UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, bigramList, FORMAT_OPTIONS); - } - - private void readDictFromFile(final File file, final OnAddWordListener listener) { - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY); - try { - dictDecoder.openDictBuffer(); - } catch (FileNotFoundException e) { - Log.e(TAG, "file not found", e); - } catch (IOException e) { - Log.e(TAG, "IOException", e); - } - UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener); - } - - public void testGenerateFusionDictionary() { - final UserHistoryDictionaryBigramList originalList = new UserHistoryDictionaryBigramList(); - - final HashMap<String, ArrayList<String> > addedWords = - new HashMap<String, ArrayList<String>>(); - addBigramToBigramList("this", "is", addedWords, originalList); - addBigramToBigramList("this", "was", addedWords, originalList); - addBigramToBigramList("hello", "world", addedWords, originalList); - - final FusionDictionary fusionDict = - UserHistoryDictIOUtils.constructFusionDictionary(this, originalList); - - checkWordsInFusionDict(fusionDict, addedWords); - } - - public void testReadAndWrite() { - final Context context = getContext(); - - File file = null; - try { - file = File.createTempFile("testReadAndWrite", TEST_DICT_FILE_EXTENSION, - getContext().getCacheDir()); - } catch (IOException e) { - Log.d(TAG, "IOException while creating a temporary file", e); - } - assertNotNull(file); - - // make original dictionary - final UserHistoryDictionaryBigramList originalList = new UserHistoryDictionaryBigramList(); - final HashMap<String, ArrayList<String>> addedWords = CollectionUtils.newHashMap(); - addBigramToBigramList("this" , "is" , addedWords, originalList); - addBigramToBigramList("this" , "was" , addedWords, originalList); - addBigramToBigramList("is" , "not" , addedWords, originalList); - addBigramToBigramList("hello", "world", addedWords, originalList); - - // write to file - writeDictToFile(file, originalList); - - // make result dict. - final UserHistoryDictionaryBigramList resultList = new UserHistoryDictionaryBigramList(); - final OnAddWordListener listener = new OnAddWordListener() { - @Override - public void setUnigram(final String word, final String shortcutTarget, - final int frequency, final int shortcutFreq) { - Log.d(TAG, "in: setUnigram: " + word + "," + frequency); - resultList.addBigram(null, word, (byte)frequency); - } - @Override - public void setBigram(final String word1, final String word2, final int frequency) { - Log.d(TAG, "in: setBigram: " + word1 + "," + word2 + "," + frequency); - resultList.addBigram(word1, word2, (byte)frequency); - } - }; - - // load from file - readDictFromFile(file, listener); - checkWordsInBigramList(resultList, addedWords); - - // add new bigram - addBigramToBigramList("hello", "java", addedWords, resultList); - - // rewrite - writeDictToFile(file, resultList); - final UserHistoryDictionaryBigramList resultList2 = new UserHistoryDictionaryBigramList(); - final OnAddWordListener listener2 = new OnAddWordListener() { - @Override - public void setUnigram(final String word, final String shortcutTarget, - final int frequency, final int shortcutFreq) { - Log.d(TAG, "in: setUnigram: " + word + "," + frequency); - resultList2.addBigram(null, word, (byte)frequency); - } - @Override - public void setBigram(final String word1, final String word2, final int frequency) { - Log.d(TAG, "in: setBigram: " + word1 + "," + word2 + "," + frequency); - resultList2.addBigram(word1, word2, (byte)frequency); - } - }; - - // load from file - readDictFromFile(file, listener2); - checkWordsInBigramList(resultList2, addedWords); - } -} diff --git a/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java b/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java deleted file mode 100644 index 28a9f3d5c..000000000 --- a/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2013 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.research; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.JsonReader; - -import com.android.inputmethod.research.MotionEventReader.ReplayData; - -import java.io.IOException; -import java.io.StringReader; - -@SmallTest -public class MotionEventReaderTests extends AndroidTestCase { - private MotionEventReader mMotionEventReader = new MotionEventReader(); - private ReplayData mReplayData; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mReplayData = new ReplayData(); - } - - private JsonReader jsonReaderForString(final String s) { - return new JsonReader(new StringReader(s)); - } - - public void testTopLevelDataVariant() { - final JsonReader jsonReader = jsonReaderForString( - "{" - + "\"_ct\": 1359590400000," - + "\"_ut\": 4381933," - + "\"_ty\": \"MotionEvent\"," - + "\"action\": \"UP\"," - + "\"isLoggingRelated\": false," - + "\"x\": 100.0," - + "\"y\": 200.0" - + "}" - ); - try { - mMotionEventReader.readLogStatement(jsonReader, mReplayData); - } catch (IOException e) { - e.printStackTrace(); - fail("IOException thrown"); - } - assertEquals("x set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); - assertEquals("y set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); - assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); - assertEquals("only one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); - } - - public void testNestedDataVariant() { - final JsonReader jsonReader = jsonReaderForString( - "{" - + " \"_ct\": 135959040000," - + " \"_ut\": 4382702," - + " \"_ty\": \"MotionEvent\"," - + " \"action\": \"MOVE\"," - + " \"isLoggingRelated\": false," - + " \"motionEvent\": {" - + " \"pointerIds\": [" - + " 0" - + " ]," - + " \"xyt\": [" - + " {" - + " \"t\": 4382551," - + " \"d\": [" - + " {" - + " \"x\": 100.0," - + " \"y\": 200.0," - + " \"toma\": 999.0," - + " \"tomi\": 999.0," - + " \"o\": 0.0" - + " }" - + " ]" - + " }," - + " {" - + " \"t\": 4382559," - + " \"d\": [" - + " {" - + " \"x\": 300.0," - + " \"y\": 400.0," - + " \"toma\": 999.0," - + " \"tomi\": 999.0," - + " \"o\": 0.0" - + " }" - + " ]" - + " }" - + " ]" - + " }" - + "}" - ); - try { - mMotionEventReader.readLogStatement(jsonReader, mReplayData); - } catch (IOException e) { - e.printStackTrace(); - fail("IOException thrown"); - } - assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); - assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); - assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].x, 300); - assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].y, 400); - assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); - assertEquals("two MotionEvents", mReplayData.mPointerCoordsArrays.size(), 2); - } - - public void testNestedDataVariantMultiPointer() { - final JsonReader jsonReader = jsonReaderForString( - "{" - + " \"_ct\": 135959040000," - + " \"_ut\": 4382702," - + " \"_ty\": \"MotionEvent\"," - + " \"action\": \"MOVE\"," - + " \"isLoggingRelated\": false," - + " \"motionEvent\": {" - + " \"pointerIds\": [" - + " 1" - + " ]," - + " \"xyt\": [" - + " {" - + " \"t\": 4382551," - + " \"d\": [" - + " {" - + " \"x\": 100.0," - + " \"y\": 200.0," - + " \"toma\": 999.0," - + " \"tomi\": 999.0," - + " \"o\": 0.0" - + " }," - + " {" - + " \"x\": 300.0," - + " \"y\": 400.0," - + " \"toma\": 999.0," - + " \"tomi\": 999.0," - + " \"o\": 0.0" - + " }" - + " ]" - + " }" - + " ]" - + " }" - + "}" - ); - try { - mMotionEventReader.readLogStatement(jsonReader, mReplayData); - } catch (IOException e) { - e.printStackTrace(); - fail("IOException thrown"); - } - assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); - assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); - assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].x, 300); - assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].y, 400); - assertEquals("two pointers", mReplayData.mPointerCoordsArrays.get(0).length, 2); - assertEquals("one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); - } -} |