Merge "Change default decaying parameters."
diff --git a/java/res/xml-sw600dp/key_space_5kw.xml b/java/res/xml-sw600dp/key_space_5kw.xml
index 86af89f..71ae5fd 100644
--- a/java/res/xml-sw600dp/key_space_5kw.xml
+++ b/java/res/xml-sw600dp/key_space_5kw.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="true"
         >
             <Key
@@ -35,7 +35,7 @@
                 latin:keyStyle="zwnjKeyStyle" />
         </case>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="false"
         >
             <Key
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 94327f9..05cb293 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -50,10 +50,10 @@
     hr: Croatian/qwertz
     hu: Hungarian/qwertz
     hy_AM: Armenian (Armenia) Phonetic/armenian_phonetic
-    in: Indonesian/qwerty    # "id" is official language code of Indonesian.
+    in: Indonesian/qwerty    # "id" is the official language code of Indonesian.
     is: Icelandic/qwerty
     it: Italian/qwerty
-    iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
+    iw: Hebrew/hebrew        # "he" is the official language code of Hebrew.
     ka_GE: Georgian (Georgia)/georgian
     kk: Kazakh/east_slavic
     km_KH: Khmer (Cambodia)/khmer
@@ -65,8 +65,8 @@
     mn_MN: Mongolian (Mongolia)/mongolian
     ms_MY: Malay (Malaysia)/qwerty
     nb: Norwegian Bokmål/nordic
-    ne_NP: Nepali (Nepal) Romanized/nepali_romanized)
-    ne_NP: Nepali (Nepal) Traditional/nepali_traditional)
+    ne_NP: Nepali (Nepal) Romanized/nepali_romanized
+    ne_NP: Nepali (Nepal) Traditional/nepali_traditional
     nl: Dutch/qwerty
     nl_BE: Dutch (Belgium)/azerty
     pl: Polish/qwerty
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index 52987d5..ae86397 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -43,19 +43,19 @@
             final int resultingCodePoint =
                     KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
             if (0 == resultingCodePoint) {
-                // We can't combine both characters. We need to commit the dead key as a committable
+                // We can't combine both characters. We need to commit the dead key as a separate
                 // character, and the next char too unless it's a space (because as a special case,
                 // dead key + space should result in only the dead key being committed - that's
                 // how dead keys work).
                 // If the event is a space, we should commit the dead char alone, but if it's
                 // not, we need to commit both.
-                return Event.createCommittableEvent(deadCodePoint,
+                return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
                         Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */);
             } else {
                 // We could combine the characters.
-                return Event.createCommittableEvent(resultingCodePoint, null /* next */);
+                return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
+                        null /* next */);
             }
         }
     }
-
 }
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 1f3320e..ed487e1 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.event;
 
+import com.android.inputmethod.latin.Constants;
+
 /**
  * Class representing a generic input event as handled by Latin IME.
  *
@@ -33,61 +35,137 @@
     // but probably a bit too much
     // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
     final public static int EVENT_NOT_HANDLED = 0;
-    // A character that is already final, for example pressing an alphabetic character on a
-    // hardware qwerty keyboard.
-    final public static int EVENT_COMMITTABLE = 1;
-    // A dead key, which means a character that should combine with what is coming next. Examples
-    // include the "^" character on an azerty keyboard which combines with "e" to make "ê", or
-    // AltGr+' on a dvorak international keyboard which combines with "e" to make "é". This is
-    // true regardless of the language or combining mode, and should be seen as a property of the
-    // key - a dead key followed by another key with which it can combine should be regarded as if
-    // the keyboard actually had such a key.
-    final public static int EVENT_DEAD = 2;
+    // A key press that is part of input, for example pressing an alphabetic character on a
+    // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
+    // through combination.
+    final public static int EVENT_INPUT_KEYPRESS = 1;
     // A toggle event is triggered by a key that affects the previous character. An example would
     // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
     // repeated presses.
-    final public static int EVENT_TOGGLE = 3;
+    final public static int EVENT_TOGGLE = 2;
     // A mode event instructs the combiner to change modes. The canonical example would be the
     // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
     // if handled at the combiner level.
-    final public static int EVENT_MODE_KEY = 4;
+    final public static int EVENT_MODE_KEY = 3;
+    // An event corresponding to a gesture.
+    final public static int EVENT_GESTURE = 4;
 
-    final private static int NOT_A_CODE_POINT = 0;
+    // 0 is a valid code point, so we use -1 here.
+    final public static int NOT_A_CODE_POINT = -1;
+    // -1 is a valid key code, so we use 0 here.
+    final public static int NOT_A_KEY_CODE = 0;
+
+    final private static int FLAG_NONE = 0;
+    // This event is a dead character, usually input by a dead key. Examples include dead-acute
+    // or dead-abovering.
+    final private static int FLAG_DEAD = 0x1;
 
     final private int mType; // The type of event - one of the constants above
     // The code point associated with the event, if relevant. This is a unicode code point, and
     // has nothing to do with other representations of the key. It is only relevant if this event
-    // is the right type: COMMITTABLE or DEAD or TOGGLE, but for a mode key like hankaku/zenkaku or
-    // ctrl, there is no code point associated so this should be NOT_A_CODE_POINT to avoid
-    // unintentional use of its value when it's not relevant.
+    // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
+    // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
+    // it's not relevant.
     final public int mCodePoint;
+
+    // The key code associated with the event, if relevant. This is relevant whenever this event
+    // has been triggered by a key press, but not for a gesture for example. This has conceptually
+    // no link to the code point, although keys that enter a straight code point may often set
+    // this to be equal to mCodePoint for convenience. If this is not a key, this must contain
+    // NOT_A_KEY_CODE.
+    final public int mKeyCode;
+
+    // Coordinates of the touch event, if relevant. If useful, we may want to replace this with
+    // a MotionEvent or something in the future. This is only relevant when the keypress is from
+    // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
+    // future or some other awesome sauce.
+    final public int mX;
+    final public int mY;
+
+    // Some flags that can't go into the key code. It's a bit field of FLAG_*
+    final private int mFlags;
+
     // The next event, if any. Null if there is no next event yet.
     final public Event mNextEvent;
 
     // This method is private - to create a new event, use one of the create* utility methods.
-    private Event(final int type, final int codePoint, final Event next) {
+    private Event(final int type, final int codePoint, final int keyCode, final int x, final int y,
+            final int flags, final Event next) {
         mType = type;
         mCodePoint = codePoint;
+        mKeyCode = keyCode;
+        mX = x;
+        mY = y;
+        mFlags = flags;
         mNextEvent = next;
     }
 
-    public static Event createDeadEvent(final int codePoint, final Event next) {
-        return new Event(EVENT_DEAD, codePoint, next);
+    public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
+            final int x, final int y) {
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode, x, y, FLAG_NONE, null);
     }
 
-    public static Event createCommittableEvent(final int codePoint, final Event next) {
-        return new Event(EVENT_COMMITTABLE, codePoint, next);
+    public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
+            final Event next) {
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
+                Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+                FLAG_NONE, next);
+    }
+
+    // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
+    public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
+        // TODO: add an argument or something if we ever create a software layout with dead keys.
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
+                Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
+                FLAG_DEAD, next);
+    }
+
+    /**
+     * Create an input event with nothing but a code point. This is the most basic possible input
+     * event; it contains no information on many things the IME requires to function correctly,
+     * so avoid using it unless really nothing is known about this input.
+     * @param codePoint the code point.
+     * @return an event for this code point.
+     */
+    public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
+        // TODO: should we have a different type of event for this? After all, it's not a key press.
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, NOT_A_KEY_CODE,
+                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, FLAG_NONE, null /* next */);
+    }
+
+    /**
+     * Creates an input event with a code point and x, y coordinates. This is typically used when
+     * resuming a previously-typed word, when the coordinates are still known.
+     * @param codePoint the code point to input.
+     * @param x the X coordinate.
+     * @param y the Y coordinate.
+     * @return an event for this code point and coordinates.
+     */
+    public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
+            final int x, final int y) {
+        // TODO: should we have a different type of event for this? After all, it's not a key press.
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, NOT_A_KEY_CODE, x, y, FLAG_NONE,
+                null /* next */);
     }
 
     public static Event createNotHandledEvent() {
-        return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, null);
+        return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, FLAG_NONE, null);
     }
 
-    public boolean isCommittable() {
-        return EVENT_COMMITTABLE == mType;
-    }
-
+    // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
     public boolean isDead() {
-        return EVENT_DEAD == mType;
+        return 0 != (FLAG_DEAD & mFlags);
+    }
+
+    // Returns whether this is a fake key press from the suggestion strip. This happens with
+    // punctuation signs selected from the suggestion strip.
+    public boolean isSuggestionStripPress() {
+        return EVENT_INPUT_KEYPRESS == mType && Constants.SUGGESTION_STRIP_COORDINATE == mX;
+    }
+
+    // TODO: remove this method - we should not have to test this
+    public boolean isCommittable() {
+        return EVENT_INPUT_KEYPRESS == mType || EVENT_MODE_KEY == mType || EVENT_TOGGLE == mType;
     }
 }
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index 720d074..da6780e 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -47,27 +47,33 @@
         // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
         final int keyCode = keyEvent.getKeyCode();
         if (KeyEvent.KEYCODE_DEL == keyCode) {
-            return Event.createCommittableEvent(Constants.CODE_DELETE, null /* next */);
+            return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE,
+                    null /* next */);
         }
         if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode
                 || KeyEvent.KEYCODE_ENTER == keyCode) {
             if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) {
                 // A dead key.
                 return Event.createDeadEvent(
-                        codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, null /* next */);
+                        codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode,
+                        null /* next */);
             }
             if (KeyEvent.KEYCODE_ENTER == keyCode) {
                 // The Enter key. If the Shift key is not being pressed, this should send a
                 // CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the
                 // Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let
                 // Latin IME decide what to do with it.
-                return Event.createCommittableEvent(keyEvent.isShiftPressed()
-                        ? Constants.CODE_SHIFT_ENTER : Constants.CODE_ENTER,
-                        null /* next */);
+                if (keyEvent.isShiftPressed()) {
+                    return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT,
+                            Constants.CODE_SHIFT_ENTER, null /* next */);
+                } else {
+                    return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode,
+                            null /* next */);
+                }
             }
-            // If not Enter, then we have a committable character. This should be committed
-            // right away, taking into account the current state.
-            return Event.createCommittableEvent(codePointAndFlags, null /* next */);
+            // If not Enter, then this is just a regular keypress event for a normal character
+            // that can be committed right away, taking into account the current state.
+            return Event.createHardwareKeypressEvent(keyCode, codePointAndFlags, null /* next */);
         }
         return Event.createNotHandledEvent();
     }
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
index 3f709a6..2e9014f 100644
--- a/java/src/com/android/inputmethod/event/InputTransaction.java
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -33,11 +33,7 @@
 
     // Initial conditions
     public final SettingsValues mSettingsValues;
-    // If the key inserts a code point, mKeyCode is always equal to the code points. Otherwise,
-    // it's always a code that may not be a code point, typically a negative number.
-    public final int mKeyCode;
-    public final int mX; // Pressed x-coordinate, or one of Constants.*_COORDINATE
-    public final int mY; // Pressed y-coordinate, or one of Constants.*_COORDINATE
+    public final Event mEvent;
     public final long mTimestamp;
     public final int mSpaceState;
     public final int mShiftState;
@@ -45,13 +41,10 @@
     // Outputs
     private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
 
-    public InputTransaction(final SettingsValues settingsValues, final int keyCode,
-            final int x, final int y, final long timestamp, final int spaceState,
-            final int shiftState) {
+    public InputTransaction(final SettingsValues settingsValues, final Event event,
+            final long timestamp, final int spaceState, final int shiftState) {
         mSettingsValues = settingsValues;
-        mKeyCode = keyCode;
-        mX = x;
-        mY = y;
+        mEvent = event;
         mTimestamp = timestamp;
         mSpaceState = spaceState;
         mShiftState = shiftState;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 79d088f..6c9b5ad 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -34,29 +34,47 @@
     public static final int ICON_UNDEFINED = 0;
     private static final int ATTR_UNDEFINED = 0;
 
+    private static final String NAME_UNDEFINED = "undefined";
+    public static final String NAME_SHIFT_KEY = "shift_key";
+    public static final String NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted";
+    public static final String NAME_DELETE_KEY = "delete_key";
+    public static final String NAME_SETTINGS_KEY = "settings_key";
+    public static final String NAME_SPACE_KEY = "space_key";
+    public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
+    public static final String NAME_ENTER_KEY = "enter_key";
+    public static final String NAME_SEARCH_KEY = "search_key";
+    public static final String NAME_TAB_KEY = "tab_key";
+    public static final String NANE_TAB_KEY_PREVIEW = "tab_key_preview";
+    public static final String NAME_SHORTCUT_KEY = "shortcut_key";
+    public static final String NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled";
+    public static final String NAME_LANGUAGE_SWITCH_KEY = "language_switch_key";
+    public static final String NAME_ZWNJ_KEY = "zwnj_key";
+    public static final String NAME_ZWJ_KEY = "zwj_key";
+    public static final String NAME_EMOJI_KEY = "emoji_key";
+
     private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
 
     // Icon name to icon id map.
     private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
 
     private static final Object[] NAMES_AND_ATTR_IDS = {
-        "undefined",                    ATTR_UNDEFINED,
-        "shift_key",                    R.styleable.Keyboard_iconShiftKey,
-        "delete_key",                   R.styleable.Keyboard_iconDeleteKey,
-        "settings_key",                 R.styleable.Keyboard_iconSettingsKey,
-        "space_key",                    R.styleable.Keyboard_iconSpaceKey,
-        "enter_key",                    R.styleable.Keyboard_iconEnterKey,
-        "search_key",                   R.styleable.Keyboard_iconSearchKey,
-        "tab_key",                      R.styleable.Keyboard_iconTabKey,
-        "shortcut_key",                 R.styleable.Keyboard_iconShortcutKey,
-        "space_key_for_number_layout",  R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
-        "shift_key_shifted",            R.styleable.Keyboard_iconShiftKeyShifted,
-        "shortcut_key_disabled",        R.styleable.Keyboard_iconShortcutKeyDisabled,
-        "tab_key_preview",              R.styleable.Keyboard_iconTabKeyPreview,
-        "language_switch_key",          R.styleable.Keyboard_iconLanguageSwitchKey,
-        "zwnj_key",                     R.styleable.Keyboard_iconZwnjKey,
-        "zwj_key",                      R.styleable.Keyboard_iconZwjKey,
-        "emoji_key",                    R.styleable.Keyboard_iconEmojiKey,
+        NAME_UNDEFINED,                   ATTR_UNDEFINED,
+        NAME_SHIFT_KEY,                   R.styleable.Keyboard_iconShiftKey,
+        NAME_DELETE_KEY,                  R.styleable.Keyboard_iconDeleteKey,
+        NAME_SETTINGS_KEY,                R.styleable.Keyboard_iconSettingsKey,
+        NAME_SPACE_KEY,                   R.styleable.Keyboard_iconSpaceKey,
+        NAME_ENTER_KEY,                   R.styleable.Keyboard_iconEnterKey,
+        NAME_SEARCH_KEY,                  R.styleable.Keyboard_iconSearchKey,
+        NAME_TAB_KEY,                     R.styleable.Keyboard_iconTabKey,
+        NAME_SHORTCUT_KEY,                R.styleable.Keyboard_iconShortcutKey,
+        NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
+        NAME_SHIFT_KEY_SHIFTED,           R.styleable.Keyboard_iconShiftKeyShifted,
+        NAME_SHORTCUT_KEY_DISABLED,       R.styleable.Keyboard_iconShortcutKeyDisabled,
+        NANE_TAB_KEY_PREVIEW,             R.styleable.Keyboard_iconTabKeyPreview,
+        NAME_LANGUAGE_SWITCH_KEY,         R.styleable.Keyboard_iconLanguageSwitchKey,
+        NAME_ZWNJ_KEY,                    R.styleable.Keyboard_iconZwnjKey,
+        NAME_ZWJ_KEY,                     R.styleable.Keyboard_iconZwjKey,
+        NAME_EMOJI_KEY,                   R.styleable.Keyboard_iconEmojiKey,
     };
 
     private static int NUM_ICONS = NAMES_AND_ATTR_IDS.length / 2;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 8546ceb..2a16ab5 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -18,6 +18,10 @@
 
 import android.text.TextUtils;
 
+import com.android.inputmethod.event.Event;
+
+import java.util.ArrayList;
+
 /**
  * This class encapsulates data about a word previously composed, but that has been
  * committed already. This is used for resuming suggestion, and cancel auto-correction.
@@ -41,6 +45,7 @@
     public static final String NOT_A_SEPARATOR = "";
 
     public final int[] mPrimaryKeyCodes;
+    public final ArrayList<Event> mEvents;
     public final String mTypedWord;
     public final CharSequence mCommittedWord;
     public final String mSeparatorString;
@@ -52,19 +57,21 @@
     private boolean mActive;
 
     public static final LastComposedWord NOT_A_COMPOSED_WORD =
-            new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null,
-            WordComposer.CAPS_MODE_OFF);
+            new LastComposedWord(null, new ArrayList<Event>(), null, "", "",
+            NOT_A_SEPARATOR, null, WordComposer.CAPS_MODE_OFF);
 
     // Warning: this is using the passed objects as is and fully expects them to be
     // immutable. Do not fiddle with their contents after you passed them to this constructor.
-    public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
-            final String typedWord, final CharSequence committedWord, final String separatorString,
+    public LastComposedWord(final int[] primaryKeyCodes, final ArrayList<Event> events,
+            final InputPointers inputPointers, final String typedWord,
+            final CharSequence committedWord, final String separatorString,
             final String prevWord, final int capitalizedMode) {
         mPrimaryKeyCodes = primaryKeyCodes;
         if (inputPointers != null) {
             mInputPointers.copy(inputPointers);
         }
         mTypedWord = typedWord;
+        mEvents = new ArrayList<Event>(events);
         mCommittedWord = committedWord;
         mSeparatorString = separatorString;
         mActive = true;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index a9e5480..222e735 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -59,6 +59,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.event.Event;
 import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
@@ -1266,8 +1267,9 @@
             mSubtypeSwitcher.switchToShortcutIME(this);
             // Still call the *#onCodeInput methods for readability.
         }
+        final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY);
         final InputTransaction completeInputTransaction =
-                mInputLogic.onCodeInput(mSettings.getCurrent(), codeToSend, keyX, keyY,
+                mInputLogic.onCodeInput(mSettings.getCurrent(), event,
                         mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
         switch (completeInputTransaction.getRequiredShiftUpdate()) {
             case InputTransaction.SHIFT_UPDATE_LATER:
@@ -1281,6 +1283,22 @@
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
+    // A helper method to split the code point and the key code. Ultimately, they should not be
+    // squashed into the same variable, and this method should be removed.
+    private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
+             final int keyY) {
+        final int keyCode;
+        final int codePoint;
+        if (keyCodeOrCodePoint <= 0) {
+            keyCode = keyCodeOrCodePoint;
+            codePoint = Event.NOT_A_CODE_POINT;
+        } else {
+            keyCode = Event.NOT_A_KEY_CODE;
+            codePoint = keyCodeOrCodePoint;
+        }
+        return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY);
+    }
+
     // Called from PointerTracker through the KeyboardActionListener interface
     @Override
     public void onTextInput(final String rawText) {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 1259769..2ac11aa 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,10 +16,14 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.event.Event;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 
 /**
  * A place to store the currently composing word with information such as adjacent key codes as well
@@ -41,6 +45,8 @@
     // and mCodePointSize can go past that. If mCodePointSize is greater than MAX_WORD_LENGTH,
     // this just does not contain the associated code points past MAX_WORD_LENGTH.
     private int[] mPrimaryKeyCodes;
+    // The list of events that served to compose this string.
+    private final ArrayList<Event> mEvents;
     private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
     // This is the typed word, as a StringBuilder. This has the same contents as mPrimaryKeyCodes
     // but under a StringBuilder representation for ease of use, depending on what is more useful
@@ -82,6 +88,7 @@
 
     public WordComposer() {
         mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
+        mEvents = CollectionUtils.newArrayList();
         mTypedWord = new StringBuilder(MAX_WORD_LENGTH);
         mAutoCorrection = null;
         mTrailingSingleQuotesCount = 0;
@@ -95,6 +102,7 @@
 
     public WordComposer(final WordComposer source) {
         mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
+        mEvents = new ArrayList<Event>(source.mEvents);
         mTypedWord = new StringBuilder(source.mTypedWord);
         mInputPointers.copy(source.mInputPointers);
         mCapsCount = source.mCapsCount;
@@ -115,6 +123,7 @@
      */
     public void reset() {
         mTypedWord.setLength(0);
+        mEvents.clear();
         mAutoCorrection = null;
         mCapsCount = 0;
         mDigitsCount = 0;
@@ -170,11 +179,16 @@
     }
 
     /**
-     * Add a new keystroke, with the pressed key's code point with the touch point coordinates.
+     * Add a new event for a key stroke, with the pressed key's code point with the touch point
+     * coordinates.
      */
-    public void add(final int primaryCode, final int keyX, final int keyY) {
+    public void add(final Event event) {
+        final int primaryCode = event.mCodePoint;
+        final int keyX = event.mX;
+        final int keyY = event.mY;
         final int newIndex = size();
         mTypedWord.appendCodePoint(primaryCode);
+        mEvents.add(event);
         refreshSize();
         mCursorPositionWithinWord = mCodePointSize;
         if (newIndex < MAX_WORD_LENGTH) {
@@ -202,6 +216,7 @@
 
     public void setCursorPositionWithinWord(final int posWithinWord) {
         mCursorPositionWithinWord = posWithinWord;
+        // TODO: compute where that puts us inside the events
     }
 
     public boolean isCursorFrontOrMiddleOfComposingWord() {
@@ -268,7 +283,7 @@
             final int codePoint = Character.codePointAt(word, i);
             // We don't want to override the batch input points that are held in mInputPointers
             // (See {@link #add(int,int,int)}).
-            add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+            add(Event.createEventForCodePointFromUnknownSource(codePoint));
         }
     }
 
@@ -285,8 +300,9 @@
         reset();
         final int length = codePoints.length;
         for (int i = 0; i < length; ++i) {
-            add(codePoints[i], CoordinateUtils.xFromArray(coordinates, i),
-                    CoordinateUtils.yFromArray(coordinates, i));
+            add(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i],
+                    CoordinateUtils.xFromArray(coordinates, i),
+                    CoordinateUtils.yFromArray(coordinates, i)));
         }
         mIsResumed = true;
         mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
@@ -305,6 +321,8 @@
                         "In WordComposer: mCodes and mTypedWords have non-matching lengths");
             }
             final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
+            // TODO: with events and composition, this is absolutely not necessarily true.
+            mEvents.remove(mEvents.size() - 1);
             if (Character.isSupplementaryCodePoint(lastChar)) {
                 mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
             } else {
@@ -445,7 +463,7 @@
         // the last composed word to ensure this does not happen.
         final int[] primaryKeyCodes = mPrimaryKeyCodes;
         mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
-        final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
+        final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes, mEvents,
                 mInputPointers, mTypedWord.toString(), committedWord, separatorString,
                 prevWord, mCapitalizedMode);
         mInputPointers.reset();
@@ -458,6 +476,7 @@
         mIsBatchMode = false;
         mPreviousWordForSuggestion = committedWord.toString();
         mTypedWord.setLength(0);
+        mEvents.clear();
         mCodePointSize = 0;
         mTrailingSingleQuotesCount = 0;
         mIsFirstCharCapitalized = false;
@@ -480,6 +499,8 @@
     public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
             final String previousWord) {
         mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
+        mEvents.clear();
+        Collections.copy(mEvents, lastComposedWord.mEvents);
         mInputPointers.set(lastComposedWord.mInputPointers);
         mTypedWord.setLength(0);
         mTypedWord.append(lastComposedWord.mTypedWord);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index dd9d6e8..cb55aa0 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -27,6 +27,7 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.event.Event;
 import com.android.inputmethod.event.EventInterpreter;
 import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
@@ -205,9 +206,9 @@
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
             final int primaryCode = suggestion.charAt(0);
-            onCodeInput(settingsValues, primaryCode,
-                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
-                    keyboardSwitcher.getKeyboardShiftMode(), handler);
+            final Event event = Event.createSoftwareKeypressEvent(primaryCode, Event.NOT_A_KEY_CODE,
+                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+            onCodeInput(settingsValues, event, keyboardSwitcher.getKeyboardShiftMode(), handler);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
@@ -354,25 +355,26 @@
      * the entry point for gesture input; see the onBatchInput* family of functions for this.
      *
      * @param settingsValues the current settings values.
-     * @param code the code to handle. It may be a code point, or an internal key code.
-     * @param x the x-coordinate where the user pressed the key, or NOT_A_COORDINATE.
-     * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE.
+     * @param event the event to handle.
      * @param keyboardShiftMode the current shift mode of the keyboard, as returned by
      *     {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
      * @return the complete transaction object
      */
-    public InputTransaction onCodeInput(final SettingsValues settingsValues, final int code,
-            final int x, final int y, final int keyboardShiftMode,
+    public InputTransaction onCodeInput(final SettingsValues settingsValues, final Event event,
+            final int keyboardShiftMode,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
-        final InputTransaction inputTransaction = new InputTransaction(settingsValues, code, x, y,
+        // TODO: rework the following to not squash the keycode and the code point into the same
+        // var because it's confusing. Instead the switch() should handle this in a readable manner.
+        final int code =
+                Event.NOT_A_CODE_POINT == event.mCodePoint ? event.mKeyCode : event.mCodePoint;
+        final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
                 SystemClock.uptimeMillis(), mSpaceState,
                 getActualCapsMode(settingsValues, keyboardShiftMode));
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_onCodeInput(inputTransaction.mKeyCode,
-                    inputTransaction.mX, inputTransaction.mY);
+            ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
         }
-        if (inputTransaction.mKeyCode != Constants.CODE_DELETE
+        if (event.mKeyCode != Constants.CODE_DELETE
                 || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
             mDeleteCount = 0;
         }
@@ -383,97 +385,104 @@
         }
 
         // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
-        if (inputTransaction.mKeyCode != Constants.CODE_SPACE) {
+        if (event.mCodePoint != Constants.CODE_SPACE) {
             handler.cancelDoubleSpacePeriodTimer();
         }
 
         boolean didAutoCorrect = false;
-        switch (inputTransaction.mKeyCode) {
-        case Constants.CODE_DELETE:
-            handleBackspace(inputTransaction, handler);
-            LatinImeLogger.logOnDelete(inputTransaction.mX, inputTransaction.mY);
-            break;
-        case Constants.CODE_SHIFT:
-            performRecapitalization(inputTransaction.mSettingsValues);
-            inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
-            break;
-        case Constants.CODE_CAPSLOCK:
-            // Note: Changing keyboard to shift lock state is handled in
-            // {@link KeyboardSwitcher#onCodeInput(int)}.
-            break;
-        case Constants.CODE_SYMBOL_SHIFT:
-            // Note: Calling back to the keyboard on the symbol Shift key is handled in
-            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
-            break;
-        case Constants.CODE_SWITCH_ALPHA_SYMBOL:
-            // Note: Calling back to the keyboard on symbol key is handled in
-            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
-            break;
-        case Constants.CODE_SETTINGS:
-            onSettingsKeyPressed();
-            break;
-        case Constants.CODE_SHORTCUT:
-            // We need to switch to the shortcut IME. This is handled by LatinIME since the
-            // input logic has no business with IME switching.
-            break;
-        case Constants.CODE_ACTION_NEXT:
-            performEditorAction(EditorInfo.IME_ACTION_NEXT);
-            break;
-        case Constants.CODE_ACTION_PREVIOUS:
-            performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
-            break;
-        case Constants.CODE_LANGUAGE_SWITCH:
-            handleLanguageSwitchKey();
-            break;
-        case Constants.CODE_EMOJI:
-            // Note: Switching emoji keyboard is being handled in
-            // {@link KeyboardState#onCodeInput(int,int)}.
-            break;
-        case Constants.CODE_ENTER:
-            final EditorInfo editorInfo = getCurrentInputEditorInfo();
-            final int imeOptionsActionId =
-                    InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
-            if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
-                // Either we have an actionLabel and we should performEditorAction with actionId
-                // regardless of its value.
-                performEditorAction(editorInfo.actionId);
-            } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
-                // We didn't have an actionLabel, but we had another action to execute.
-                // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
-                // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
-                // means there should be an action and the app didn't bother to set a specific
-                // code for it - presumably it only handles one. It does not have to be treated
-                // in any specific way: anything that is not IME_ACTION_NONE should be sent to
-                // performEditorAction.
-                performEditorAction(imeOptionsActionId);
-            } else {
-                // No action label, and the action from imeOptions is NONE: this is a regular
-                // enter key that should input a carriage return.
-                didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
+        if (Event.NOT_A_KEY_CODE != event.mKeyCode) {
+            // A special key, like delete, shift, emoji, or the settings key.
+            switch (event.mKeyCode) {
+            case Constants.CODE_DELETE:
+                handleBackspace(inputTransaction, handler);
+                LatinImeLogger.logOnDelete(event.mX, event.mY);
+                break;
+            case Constants.CODE_SHIFT:
+                performRecapitalization(inputTransaction.mSettingsValues);
+                inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+                break;
+            case Constants.CODE_CAPSLOCK:
+                // Note: Changing keyboard to shift lock state is handled in
+                // {@link KeyboardSwitcher#onCodeInput(int)}.
+                break;
+            case Constants.CODE_SYMBOL_SHIFT:
+                // Note: Calling back to the keyboard on the symbol Shift key is handled in
+                // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+                break;
+            case Constants.CODE_SWITCH_ALPHA_SYMBOL:
+                // Note: Calling back to the keyboard on symbol key is handled in
+                // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+                break;
+            case Constants.CODE_SETTINGS:
+                onSettingsKeyPressed();
+                break;
+            case Constants.CODE_SHORTCUT:
+                // We need to switch to the shortcut IME. This is handled by LatinIME since the
+                // input logic has no business with IME switching.
+                break;
+            case Constants.CODE_ACTION_NEXT:
+                performEditorAction(EditorInfo.IME_ACTION_NEXT);
+                break;
+            case Constants.CODE_ACTION_PREVIOUS:
+                performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+                break;
+            case Constants.CODE_LANGUAGE_SWITCH:
+                handleLanguageSwitchKey();
+                break;
+            case Constants.CODE_EMOJI:
+                // Note: Switching emoji keyboard is being handled in
+                // {@link KeyboardState#onCodeInput(int,int)}.
+                break;
+            case Constants.CODE_ALPHA_FROM_EMOJI:
+                // Note: Switching back from Emoji keyboard to the main keyboard is being
+                // handled in {@link KeyboardState#onCodeInput(int,int)}.
+                break;
+            case Constants.CODE_SHIFT_ENTER:
+                // TODO: remove this object
+                final InputTransaction tmpTransaction = new InputTransaction(
+                        inputTransaction.mSettingsValues, inputTransaction.mEvent,
+                        inputTransaction.mTimestamp, inputTransaction.mSpaceState,
+                        inputTransaction.mShiftState);
+                didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
+                break;
+            default:
+                throw new RuntimeException("Unknown key code : " + event.mKeyCode);
             }
-            break;
-        case Constants.CODE_SHIFT_ENTER:
-            // TODO: remove this object
-            final InputTransaction tmpTransaction = new InputTransaction(
-                    inputTransaction.mSettingsValues, inputTransaction.mKeyCode,
-                    inputTransaction.mX, inputTransaction.mY, inputTransaction.mTimestamp,
-                    inputTransaction.mSpaceState, inputTransaction.mShiftState);
-            didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
-            break;
-        case Constants.CODE_ALPHA_FROM_EMOJI:
-            // Note: Switching back from Emoji keyboard to the main keyboard is being handled in
-            // {@link KeyboardState#onCodeInput(int,int)}.
-            break;
-        default:
-            didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
-            break;
+        } else {
+            switch (event.mCodePoint) {
+            case Constants.CODE_ENTER:
+                final EditorInfo editorInfo = getCurrentInputEditorInfo();
+                final int imeOptionsActionId =
+                        InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+                if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+                    // Either we have an actionLabel and we should performEditorAction with
+                    // actionId regardless of its value.
+                    performEditorAction(editorInfo.actionId);
+                } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+                    // We didn't have an actionLabel, but we had another action to execute.
+                    // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+                    // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+                    // means there should be an action and the app didn't bother to set a specific
+                    // code for it - presumably it only handles one. It does not have to be treated
+                    // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+                    // performEditorAction.
+                    performEditorAction(imeOptionsActionId);
+                } else {
+                    // No action label, and the action from imeOptions is NONE: this is a regular
+                    // enter key that should input a carriage return.
+                    didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
+                }
+                break;
+            default:
+                didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
+                break;
+            }
         }
-        // Reset after any single keystroke, except shift, capslock, and symbol-shift
-        if (!didAutoCorrect && inputTransaction.mKeyCode != Constants.CODE_SHIFT
-                && inputTransaction.mKeyCode != Constants.CODE_CAPSLOCK
-                && inputTransaction.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
+        if (!didAutoCorrect && event.mKeyCode != Constants.CODE_SHIFT
+                && event.mKeyCode != Constants.CODE_CAPSLOCK
+                && event.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
-        if (Constants.CODE_DELETE != inputTransaction.mKeyCode) {
+        if (Constants.CODE_DELETE != event.mKeyCode) {
             mEnteredText = null;
         }
         mConnection.endBatchEdit();
@@ -627,15 +636,16 @@
     private boolean handleNonSpecialCharacter(final InputTransaction inputTransaction,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
+        final int codePoint = inputTransaction.mEvent.mCodePoint;
         mSpaceState = SpaceState.NONE;
         final boolean didAutoCorrect;
-        if (inputTransaction.mSettingsValues.isWordSeparator(inputTransaction.mKeyCode)
-                || Character.getType(inputTransaction.mKeyCode) == Character.OTHER_SYMBOL) {
+        if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
+                || Character.getType(codePoint) == Character.OTHER_SYMBOL) {
             didAutoCorrect = handleSeparator(inputTransaction,
-                    Constants.SUGGESTION_STRIP_COORDINATE == inputTransaction.mX, handler);
+                    inputTransaction.mEvent.isSuggestionStripPress(), handler);
             if (inputTransaction.mSettingsValues.mIsInternal) {
-                LatinImeLoggerUtils.onSeparator((char)inputTransaction.mKeyCode,
-                        inputTransaction.mX, inputTransaction.mY);
+                LatinImeLoggerUtils.onSeparator((char)codePoint,
+                        inputTransaction.mEvent.mX, inputTransaction.mEvent.mY);
             }
         } else {
             didAutoCorrect = false;
@@ -669,6 +679,7 @@
             final InputTransaction inputTransaction,
             // TODO: Remove this argument
             final LatinIME.UIHandler handler) {
+        final int codePoint = inputTransaction.mEvent.mCodePoint;
         // TODO: refactor this method to stop flipping isComposingWord around all the time, and
         // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
         // which has the same name as other handle* methods but is not the same.
@@ -677,7 +688,7 @@
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
         // See onStartBatchInput() to see how to do it.
         if (SpaceState.PHANTOM == inputTransaction.mSpaceState
-                && !settingsValues.isWordConnector(inputTransaction.mKeyCode)) {
+                && !settingsValues.isWordConnector(codePoint)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -699,7 +710,7 @@
         if (!isComposingWord
         // We only start composing if this is a word code point. Essentially that means it's a
         // a letter or a word connector.
-                && settingsValues.isWordCodePoint(inputTransaction.mKeyCode)
+                && settingsValues.isWordCodePoint(codePoint)
         // We never go into composing state if suggestions are not requested.
                 && settingsValues.isSuggestionsRequested() &&
         // In languages with spaces, we only start composing a word when we are not already
@@ -710,8 +721,8 @@
             // the character is a single quote or a dash. The idea here is, single quote and dash
             // are not separators and they should be treated as normal characters, except in the
             // first position where they should not start composing a word.
-            isComposingWord = (Constants.CODE_SINGLE_QUOTE != inputTransaction.mKeyCode
-                    && Constants.CODE_DASH != inputTransaction.mKeyCode);
+            isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
+                    && Constants.CODE_DASH != codePoint);
             // Here we don't need to reset the last composed word. It will be reset
             // when we commit this one, if we ever do; if on the other hand we backspace
             // it entirely and resume suggestions on the previous word, we'd like to still
@@ -719,7 +730,7 @@
             resetComposingState(false /* alsoResetLastComposedWord */);
         }
         if (isComposingWord) {
-            mWordComposer.add(inputTransaction.mKeyCode, inputTransaction.mX, inputTransaction.mY);
+            mWordComposer.add(inputTransaction.mEvent);
             // If it's the first letter, make note of auto-caps state
             if (mWordComposer.size() == 1) {
                 // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
@@ -732,9 +743,9 @@
                     mWordComposer.getTypedWord()), 1);
         } else {
             final boolean swapWeakSpace = maybeStripSpace(inputTransaction,
-                    Constants.SUGGESTION_STRIP_COORDINATE == inputTransaction.mX);
+                    inputTransaction.mEvent.isSuggestionStripPress());
 
-            sendKeyCodePoint(settingsValues, inputTransaction.mKeyCode);
+            sendKeyCodePoint(settingsValues, codePoint);
 
             if (swapWeakSpace) {
                 swapSwapperAndSpace(inputTransaction);
@@ -745,8 +756,8 @@
         }
         handler.postUpdateSuggestionStrip();
         if (settingsValues.mIsInternal) {
-            LatinImeLoggerUtils.onNonSeparator((char)inputTransaction.mKeyCode, inputTransaction.mX,
-                    inputTransaction.mY);
+            LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX,
+                    inputTransaction.mEvent.mY);
         }
     }
 
@@ -760,9 +771,10 @@
             final boolean isFromSuggestionStrip,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
+        final int codePoint = inputTransaction.mEvent.mCodePoint;
         boolean didAutoCorrect = false;
         // We avoid sending spaces in languages without spaces if we were composing.
-        final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == inputTransaction.mKeyCode
+        final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
                 && !inputTransaction.mSettingsValues.mSpacingAndPunctuations
                         .mCurrentLanguageHasSpaces
                 && mWordComposer.isComposingWord();
@@ -776,46 +788,44 @@
         if (mWordComposer.isComposingWord()) {
             if (inputTransaction.mSettingsValues.mCorrectionEnabled) {
                 final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
-                        : StringUtils.newSingleCodePointString(inputTransaction.mKeyCode);
+                        : StringUtils.newSingleCodePointString(codePoint);
                 commitCurrentAutoCorrection(inputTransaction.mSettingsValues, separator, handler);
                 didAutoCorrect = true;
             } else {
                 commitTyped(inputTransaction.mSettingsValues,
-                        StringUtils.newSingleCodePointString(inputTransaction.mKeyCode));
+                        StringUtils.newSingleCodePointString(codePoint));
             }
         }
 
         final boolean swapWeakSpace = maybeStripSpace(inputTransaction, isFromSuggestionStrip);
 
-        final boolean isInsideDoubleQuoteOrAfterDigit =
-                Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode
+        final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint
                 && mConnection.isInsideDoubleQuoteOrAfterDigit();
 
         final boolean needsPrecedingSpace;
         if (SpaceState.PHANTOM != inputTransaction.mSpaceState) {
             needsPrecedingSpace = false;
-        } else if (Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode) {
+        } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
             // Double quotes behave like they are usually preceded by space iff we are
             // not inside a double quote or after a digit.
             needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
         } else {
             needsPrecedingSpace = inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
-                    inputTransaction.mKeyCode);
+                    codePoint);
         }
 
         if (needsPrecedingSpace) {
             promotePhantomSpace(inputTransaction.mSettingsValues);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_handleSeparator(inputTransaction.mKeyCode,
-                    mWordComposer.isComposingWord());
+            ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
         }
 
         if (!shouldAvoidSendingCode) {
-            sendKeyCodePoint(inputTransaction.mSettingsValues, inputTransaction.mKeyCode);
+            sendKeyCodePoint(inputTransaction.mSettingsValues, codePoint);
         }
 
-        if (Constants.CODE_SPACE == inputTransaction.mKeyCode) {
+        if (Constants.CODE_SPACE == codePoint) {
             if (inputTransaction.mSettingsValues.isSuggestionsRequested()) {
                 if (maybeDoubleSpacePeriod(inputTransaction.mSettingsValues, handler)) {
                     inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
@@ -832,9 +842,8 @@
                 swapSwapperAndSpace(inputTransaction);
                 mSpaceState = SpaceState.SWAP_PUNCTUATION;
             } else if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
-                    && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(
-                            inputTransaction.mKeyCode))
-                    || (Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode
+                    && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint))
+                    || (Constants.CODE_DOUBLE_QUOTE == codePoint
                             && isInsideDoubleQuoteOrAfterDigit)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
                 // stay in phantom space state so that the next keypress has a chance to add the
@@ -1051,7 +1060,8 @@
      */
     private boolean maybeStripSpace(final InputTransaction inputTransaction,
             final boolean isFromSuggestionStrip) {
-        if (Constants.CODE_ENTER == inputTransaction.mKeyCode &&
+        final int codePoint = inputTransaction.mEvent.mCodePoint;
+        if (Constants.CODE_ENTER == codePoint &&
                 SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
             mConnection.removeTrailingSpace();
             return false;
@@ -1059,12 +1069,10 @@
         if ((SpaceState.WEAK == inputTransaction.mSpaceState
                 || SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState)
                 && isFromSuggestionStrip) {
-            if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
-                    inputTransaction.mKeyCode)) {
+            if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(codePoint)) {
                 return false;
             }
-            if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(
-                    inputTransaction.mKeyCode)) {
+            if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint)) {
                 return true;
             }
             mConnection.removeTrailingSpace();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index afa8fe3..c26e223 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -511,7 +511,8 @@
             final String importantNoticeTitle) {
         final TextView titleView = (TextView)importantNoticeStrip.findViewById(
                 R.id.important_notice_title);
-        final int width = stripWidth - titleView.getPaddingLeft() - titleView.getPaddingRight();
+        final int width = titleView.getWidth() - titleView.getPaddingLeft()
+                - titleView.getPaddingRight();
         titleView.setTextColor(mColorAutoCorrect);
         titleView.setText(importantNoticeTitle);
         titleView.setTextScaleX(1.0f); // Reset textScaleX.
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
deleted file mode 100644
index b81061a..0000000
--- a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard.layout;
-
-import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
-import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
-import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
-import com.android.inputmethod.latin.Constants;
-
-import java.util.Locale;
-
-/**
- * The generic upper case alphabet keyboard layout.
- */
-public final class AlphabetShifted extends LayoutBase {
-    public static ExpectedKey[][] getDefaultLayout(final ExpectedKey[][] lowerCaseKeyboard,
-            final Locale locale) {
-        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(lowerCaseKeyboard);
-        builder.toUpperCase(locale);
-        builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY);
-        return builder.build();
-    }
-
-    // Icon id.
-    private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId("shift_key_shifted");
-
-    // Functional key.
-    private static final ExpectedKey SHIFTED_SHIFT_KEY = key(
-            ICON_SHIFTED_SHIFT, Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
new file mode 100644
index 0000000..02a3900
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
@@ -0,0 +1,346 @@
+/*
+ * 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 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) {
+            return isPhone ? joinKeys(key(",", SETTINGS_KEY)) : joinKeys(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(key(","), 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 EuroLayoutCustomizer extends LayoutCustomizer {
+        public EuroLayoutCustomizer(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);
+
+    // 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")));
+
+
+    // 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();
+        final ExpectedKey[] spacebar = joinKeys(
+                customizer.getKeysLeftToSpacebar(isPhone),
+                SPACEBAR,
+                customizer.getKeysRightToSpacebar(isPhone));
+        builder.setKeysOfRow(4, spacebar);
+        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(), SETTINGS_KEY)
+                    .addKeysOnTheRightOfRow(4, EMOJI_KEY);
+
+        }
+        builder.addKeysOnTheLeftOfRow(3, customizer.getLeftShiftKeys(isPhone))
+            .addKeysOnTheRightOfRow(3, customizer.getRightShiftKeys(isPhone));
+        return builder;
+    }
+
+    /**
+     * Get common alphabet layout. This layout doesn't contain any special keys.
+     * @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.
+     * @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.
+     * @param isPhone true if requesting phone's layout.
+     * @param elementId the element id of the requesting keyboard mode.
+     * @return
+     */
+    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/Qwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
index 325b784..f17c59d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
@@ -18,18 +18,23 @@
 
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
-import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
 
 /**
  * The QWERTY alphabet keyboard.
  */
 public final class Qwerty extends LayoutBase {
-    public static final String LAYOUT_NAME = "qwerty";
+    private static final String LAYOUT_NAME = "qwerty";
 
-    public static ExpectedKey[][] getLayout(final boolean isPhone) {
-        return getDefaultAlphabetLayout(ALPHABET_COMMON, isPhone);
+    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(10, 9, 7, 3)
             .setLabelsOfRow(1, "q", "w", "e", "r", "t", "y", "u", "i", "o", "p")
             .setMoreKeysOf("q", "1")
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
index 6fcfa05..ca3c711 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
@@ -16,36 +16,34 @@
 
 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;
-import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
-import com.android.inputmethod.latin.Constants;
 
 /**
  * The symbols keyboard layout.
  */
-public final class Symbols extends LayoutBase {
-    public static ExpectedKey[][] getLayout(final boolean isPhone) {
-        return isPhone ? toPhoneSymbol(SYMBOLS_COMMON) : toTabletSymbols(SYMBOLS_COMMON);
+public class Symbols extends AbstractLayoutBase {
+    private final LayoutCustomizer mCustomizer;
+
+    public Symbols(final LayoutCustomizer customizer) {
+        mCustomizer = customizer;
     }
 
-    public static ExpectedKey[][] getDefaultLayout(final boolean isPhone) {
-        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(getLayout(isPhone));
-        builder.replaceKeyOfLabel(CURRENCY, Symbols.CURRENCY_DOLLAR);
-        builder.replaceKeyOfLabel(DOUBLE_QUOTE,
-                key("\"", join(Symbols.DOUBLE_QUOTES_9LR, Symbols.DOUBLE_ANGLE_QUOTES_LR)));
-        builder.replaceKeyOfLabel(SINGLE_QUOTE,
-                key("'", join(Symbols.SINGLE_QUOTES_9LR, Symbols.SINGLE_ANGLE_QUOTES_LR)));
+    public ExpectedKey[][] getLayout(final boolean isPhone) {
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(isPhone
+                ? toPhoneSymbol(SYMBOLS_COMMON) : toTabletSymbols(SYMBOLS_COMMON));
+        builder.replaceKeyOfLabel(CURRENCY, mCustomizer.getCurrencyKey());
+        builder.replaceKeyOfLabel(DOUBLE_QUOTE, key("\"", joinKeys(
+                mCustomizer.getDoubleQuoteMoreKeys(), mCustomizer.getDoubleAngleQuoteKeys())));
+        builder.replaceKeyOfLabel(SINGLE_QUOTE, key("'", joinKeys(
+                mCustomizer.getSingleQuoteMoreKeys(), mCustomizer.getSingleAngleQuoteKeys())));
         return builder.build();
     }
 
-    // Functional keys.
-    public static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
-    public static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT);
-    public static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT);
-
     // Variations of the "currency" key on the 2nd row.
-    public static final String CURRENCY = "currency";
+    public static final String CURRENCY = "CURRENCY";
     // U+00A2: "¢" CENT SIGN
     // U+00A3: "£" POUND SIGN
     // U+00A5: "¥" YEN SIGN
@@ -63,13 +61,13 @@
             CENT_SIGN, POUND_SIGN, DOLLAR_SIGN, YEN_SIGN, PESO_SIGN);
 
     // Variations of the "double quote" key's "more keys" on the 3rd row.
-    public static final String DOUBLE_QUOTE = "double_quote";
+    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
-    static final ExpectedKey DQUOTE_LEFT = key("\u201C");
-    static final ExpectedKey DQUOTE_RIGHT = key("\u201D");
-    static final ExpectedKey DQUOTE_LOW9 = key("\u201E");
+    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 };
@@ -78,20 +76,17 @@
     // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
     private static final ExpectedKey DAQUOTE_LEFT = key("\u00AB");
     private static final ExpectedKey DAQUOTE_RIGHT = key("\u00BB");
-    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 = { DAQUOTE_LEFT, DAQUOTE_RIGHT };
     public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_RL = { DAQUOTE_RIGHT, DAQUOTE_LEFT };
-    public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_RTL = { DAQUOTE_LEFT_RTL, DAQUOTE_RIGHT_RTL };
 
     // Variations of the "single quote" key's "more keys" on the 3rd row.
-    public static final String SINGLE_QUOTE = "single_quote";
+    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
-    static final ExpectedKey SQUOTE_LEFT = key("\u2018");
-    static final ExpectedKey SQUOTE_RIGHT = key("\u2019");
-    static final ExpectedKey SQUOTE_LOW9 = key("\u201A");
+    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 };
@@ -100,14 +95,11 @@
     // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
     private static final ExpectedKey SAQUOTE_LEFT = key("\u2039");
     private static final ExpectedKey SAQUOTE_RIGHT = key("\u203A");
-    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 = { SAQUOTE_LEFT, SAQUOTE_RIGHT };
     public static ExpectedKey[] SINGLE_ANGLE_QUOTES_RL = { SAQUOTE_RIGHT, SAQUOTE_LEFT };
-    public static ExpectedKey[] SINGLE_ANGLE_QUOTES_RTL = { SAQUOTE_LEFT_RTL, SAQUOTE_RIGHT_RTL };
 
     // Common symbols keyboard layout.
-    public static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 5)
+    private static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 5)
             .setLabelsOfRow(1, "1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
             // U+00B9: "¹" SUPERSCRIPT ONE
             // U+00BD: "½" VULGAR FRACTION ONE HALF
@@ -156,25 +148,56 @@
             .setMoreKeysOf(".", "\u2026")
             .build();
 
-    private static ExpectedKey[][] toPhoneSymbol(final ExpectedKey[][] common) {
+    private ExpectedKey[][] toPhoneSymbol(final ExpectedKey[][] common) {
         return new ExpectedKeyboardBuilder(common)
-                .addKeysOnTheLeftOfRow(3, Symbols.SYMBOLS_SHIFT_KEY)
+                .addKeysOnTheLeftOfRow(3, mCustomizer.getSymbolsShiftKey(true /* isPhone */))
                 .addKeysOnTheRightOfRow(3, DELETE_KEY)
-                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheLeftOfRow(4, mCustomizer.getAlphabetKey())
                 .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY))
                 .build();
     }
 
-    private static ExpectedKey[][] toTabletSymbols(final ExpectedKey[][] common) {
+    private ExpectedKey[][] toTabletSymbols(final ExpectedKey[][] common) {
         return new ExpectedKeyboardBuilder(common)
                 .addKeysOnTheLeftOfRow(3,
                         key("\\"), key("="))
                 .addKeysOnTheRightOfRow(1, DELETE_KEY)
                 .addKeysOnTheRightOfRow(2, ENTER_KEY)
-                .addKeysOnTheLeftOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY)
-                .addKeysOnTheRightOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY)
-                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheLeftOfRow(3, mCustomizer.getSymbolsShiftKey(false /* isPhone */))
+                .addKeysOnTheRightOfRow(3, mCustomizer.getSymbolsShiftKey(false /* isPhone */))
+                .addKeysOnTheLeftOfRow(4, mCustomizer.getAlphabetKey())
                 .addKeysOnTheRightOfRow(4, EMOJI_KEY)
                 .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
index 4d9ae43..f5b78ce 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
@@ -16,31 +16,31 @@
 
 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.LayoutBase;
-import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase;
 
 /**
  * The symbols shifted keyboard layout.
  */
-public final class SymbolsShifted extends LayoutBase {
-    public static ExpectedKey[][] getLayout(final boolean isPhone) {
-        return isPhone ? toPhoneSymbolsShifted(SYMBOLS_SHIFTED_COMMON)
-                : toTabletSymbolsShifted(SYMBOLS_SHIFTED_COMMON);
+public class SymbolsShifted extends AbstractLayoutBase {
+    private final LayoutCustomizer mCustomizer;
+
+    public SymbolsShifted(final LayoutCustomizer customizer) {
+        mCustomizer = customizer;
     }
 
-    public static ExpectedKey[][] getDefaultLayout(final boolean isPhone) {
-        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(getLayout(isPhone));
-        builder.replaceKeyOfLabel(OTHER_CURRENCIES, SymbolsShifted.CURRENCIES_OTHER_THAN_DOLLAR);
+    public ExpectedKey[][] getLayout(final boolean isPhone) {
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(isPhone
+                ? toPhoneSymbolsShifted(SYMBOLS_SHIFTED_COMMON)
+                : toTabletSymbolsShifted(SYMBOLS_SHIFTED_COMMON));
+        builder.replaceKeyOfLabel(OTHER_CURRENCIES, mCustomizer.getOtherCurrencyKeys());
         return builder.build();
     }
 
-    // Functional key.
-    public static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT);
-
     // Variations of the "other currencies" keys on the 2rd row.
-    public static final String OTHER_CURRENCIES = "other_currencies";
+    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
     };
@@ -48,9 +48,13 @@
         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.
-    public static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON =
+    private static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON =
             new ExpectedKeyboardBuilder(10, 1 /* other_currencies */ + 5, 7, 5)
             // U+0060: "`" GRAVE ACCENT
             // U+2022: "•" BULLET
@@ -112,16 +116,16 @@
             .setMoreKeysOf(".", "\u2026")
             .build();
 
-    private static ExpectedKey[][] toPhoneSymbolsShifted(final ExpectedKey[][] common) {
+    private ExpectedKey[][] toPhoneSymbolsShifted(final ExpectedKey[][] common) {
         return new ExpectedKeyboardBuilder(common)
-                .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY)
+                .addKeysOnTheLeftOfRow(3, mCustomizer.getBackToSymbolsKey())
                 .addKeysOnTheRightOfRow(3, DELETE_KEY)
-                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheLeftOfRow(4, mCustomizer.getAlphabetKey())
                 .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY))
                 .build();
     }
 
-    private static ExpectedKey[][] toTabletSymbolsShifted(final ExpectedKey[][] common) {
+    private ExpectedKey[][] toTabletSymbolsShifted(final ExpectedKey[][] common) {
         return new ExpectedKeyboardBuilder(common)
                 // U+00BF: "¿" INVERTED QUESTION MARK
                 // U+00A1: "¡" INVERTED EXCLAMATION MARK
@@ -129,10 +133,38 @@
                         key("\u00A1"), key("\u00BF"))
                 .addKeysOnTheRightOfRow(1, DELETE_KEY)
                 .addKeysOnTheRightOfRow(2, ENTER_KEY)
-                .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY)
-                .addKeysOnTheRightOfRow(3, BACK_TO_SYMBOLS_KEY)
-                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheLeftOfRow(3, mCustomizer.getBackToSymbolsKey())
+                .addKeysOnTheRightOfRow(3, mCustomizer.getBackToSymbolsKey())
+                .addKeysOnTheLeftOfRow(4, mCustomizer.getAlphabetKey())
                 .addKeysOnTheRightOfRow(4, EMOJI_KEY)
                 .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/expected/AbstractLayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
new file mode 100644
index 0000000..d8d66c2
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.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.expected;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * 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 {@link ExpectedKey} object that has the label.
+    public static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the label and the output text.
+    public static ExpectedKey key(final String label, final String outputText,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, outputText, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the label and the output code.
+    public static ExpectedKey key(final String label, final int code,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, code, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the icon and the output code.
+    public static ExpectedKey key(final int iconId, final int code,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(iconId, code, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has new "more keys".
+    public static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(key.getVisual(), key.getOutput(), moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
+    public static ExpectedKey moreKey(final String label) {
+        return ExpectedKey.newInstance(label);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
+    // output text.
+    public static ExpectedKey moreKey(final String label, final String outputText) {
+        return ExpectedKey.newInstance(label, outputText);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
+    // output code.
+    public static ExpectedKey moreKey(final String label, final int code) {
+        return ExpectedKey.newInstance(label, code);
+    }
+
+    // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
+    // {@link ExpectedKey} array, and {@link String}.
+    public static ExpectedKey[] joinKeys(final Object ... keys) {
+        final ArrayList<ExpectedKey> list = CollectionUtils.newArrayList();
+        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(key((String)key));
+            } else {
+                throw new RuntimeException("Unknown expected key type: " + key);
+            }
+        }
+        return list.toArray(new ExpectedKey[list.size()]);
+    }
+
+    // Icon ids.
+    private static final int ICON_DELETE = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_DELETE_KEY);
+    private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_SETTINGS_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 SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
+    public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
+    public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI);
+    public static final ExpectedKey SPACEBAR = key(
+            StringUtils.newSingleCodePointString(Constants.CODE_SPACE));
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
index 57f842b..795bcb2 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -121,6 +121,17 @@
     }
 
     /**
+     * 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>.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setKeysOfRow(final int row, final ExpectedKey ... keys) {
+        setRowAt(row, keys);
+        return this;
+    }
+
+    /**
      * Set the "more keys" of the key that has the specified label.
      * @param label the label of the key to set the "more keys".
      * @param moreKeys the array of labels of the "more keys" to be set.
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
deleted file mode 100644
index 1d242d0..0000000
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard.layout.expected;
-
-import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Base class to create an expected keyboard for unit test.
- */
-public class LayoutBase {
-    // Those helper methods have a lower case name to be readable when defining expected keyboard
-    // layouts.
-
-    // Helper method to create {@link ExpectedKey} object that has the label.
-    public static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) {
-        return ExpectedKey.newInstance(label, moreKeys);
-    }
-
-    // Helper method to create {@link ExpectedKey} object that has the label and the output text.
-    public static ExpectedKey key(final String label, final String outputText,
-            final ExpectedKey ... moreKeys) {
-        return ExpectedKey.newInstance(label, outputText, moreKeys);
-    }
-
-    // Helper method to create {@link ExpectedKey} object that has the label and the output code.
-    public static ExpectedKey key(final String label, final int code,
-            final ExpectedKey ... moreKeys) {
-        return ExpectedKey.newInstance(label, code, moreKeys);
-    }
-
-    // Helper method to create {@link ExpectedKey} object that has the icon and the output code.
-    public static ExpectedKey key(final int iconId, final int code,
-            final ExpectedKey ... moreKeys) {
-        return ExpectedKey.newInstance(iconId, code, moreKeys);
-    }
-
-    // Helper method to create {@link ExpectedKey} object that has new "more keys".
-    public static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) {
-        return ExpectedKey.newInstance(key.getVisual(), key.getOutput(), moreKeys);
-    }
-
-    // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
-    public static ExpectedKey moreKey(final String label) {
-        return ExpectedKey.newInstance(label);
-    }
-
-    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
-    // output text.
-    public static ExpectedKey moreKey(final String label, final String outputText) {
-        return ExpectedKey.newInstance(label, outputText);
-    }
-
-    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
-    // output code.
-    public static ExpectedKey moreKey(final String label, final int code) {
-        return ExpectedKey.newInstance(label, code);
-    }
-
-    // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
-    // {@link ExpectedKey} array, and {@link String}.
-    public static ExpectedKey[] join(final Object ... keys) {
-        final ArrayList<ExpectedKey> list = CollectionUtils.newArrayList();
-        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(key((String)key));
-            } else {
-                throw new RuntimeException("Unknown expected key type: " + key);
-            }
-        }
-        return list.toArray(new ExpectedKey[list.size()]);
-    }
-
-    // Icon ids.
-    private static final int ICON_SHIFT = KeyboardIconsSet.getIconId("shift_key");
-    private static final int ICON_DELETE = KeyboardIconsSet.getIconId("delete_key");
-    private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId("settings_key");
-    private static final int ICON_ENTER = KeyboardIconsSet.getIconId("enter_key");
-    private static final int ICON_EMOJI = KeyboardIconsSet.getIconId("emoji_key");
-
-    // Functional keys.
-    public static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK);
-    public static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT, Constants.CODE_SHIFT);
-    public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE);
-    public static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL);
-    public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
-    public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
-    public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI);
-
-    // Punctuation more keys for phone form factor.
-    public static final String[] PHONE_PUNCTUATION_MORE_KEYS = {
-            ";", "/", "(", ")", "#", "!", ",", "?",
-            "&", "%", "+", "\"", "-", ":", "'", "@"
-    };
-
-    // Punctuation more keys for tablet form factor.
-    public static final String[] TABLET_PUNCTUATION_MORE_KEYS = {
-            ";", "/", "(", ")", "#", "'", ",",
-            "&", "%", "+", "\"", "-", ":", "@"
-    };
-
-    // Helper method to create alphabet layout for phone by adding special function keys except
-    // shift key.
-    private static ExpectedKeyboardBuilder convertToPhoneAlphabetKeyboardBuilder(
-            final ExpectedKey[][] commonLayout) {
-        return new ExpectedKeyboardBuilder(commonLayout)
-                .addKeysOnTheRightOfRow(3, DELETE_KEY)
-                .setLabelsOfRow(4, ",", " ", ".")
-                .setMoreKeysOf(",", SETTINGS_KEY)
-                .setMoreKeysOf(".", PHONE_PUNCTUATION_MORE_KEYS)
-                .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY)
-                .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY));
-    }
-
-    // Helper method to create alphabet layout for tablet by adding special function keys except
-    // shift key.
-    private static ExpectedKeyboardBuilder convertToTabletAlphabetKeyboardBuilder(
-            final ExpectedKey[][] commonLayout) {
-        return new ExpectedKeyboardBuilder(commonLayout)
-                // U+00BF: "¿" INVERTED QUESTION MARK
-                // U+00A1: "¡" INVERTED EXCLAMATION MARK
-                .addKeysOnTheRightOfRow(3,
-                        key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF")))
-                .addKeysOnTheRightOfRow(1, DELETE_KEY)
-                .addKeysOnTheRightOfRow(2, ENTER_KEY)
-                .setLabelsOfRow(4, "/", " ", ",", ".")
-                .setMoreKeysOf(".", TABLET_PUNCTUATION_MORE_KEYS)
-                .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY, SETTINGS_KEY)
-                .addKeysOnTheRightOfRow(4, EMOJI_KEY);
-    }
-
-    // Helper method to create alphabet layout by adding special function keys.
-    public static ExpectedKey[][] getAlphabetLayoutWithoutShiftKeys(
-            final ExpectedKey[][] commonLayout, final boolean isPhone) {
-        return isPhone ? convertToPhoneAlphabetKeyboardBuilder(commonLayout).build()
-                : convertToTabletAlphabetKeyboardBuilder(commonLayout).build();
-    }
-
-    // Helper method to create alphabet layout by adding special function keys.
-    public static ExpectedKey[][] getDefaultAlphabetLayout(final ExpectedKey[][] commonLayout,
-            final boolean isPhone) {
-        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(
-                getAlphabetLayoutWithoutShiftKeys(commonLayout, isPhone));
-        if (isPhone) {
-            builder.addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY));
-        } else {
-            builder.addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY))
-                    .addKeysOnTheRightOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY));
-        }
-        return builder.build();
-    }
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
index 5c51d08..b967844 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
@@ -24,22 +24,20 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.keyboard.KeyboardLayoutSetTestsBase;
-import com.android.inputmethod.keyboard.layout.AlphabetShifted;
-import com.android.inputmethod.keyboard.layout.Symbols;
-import com.android.inputmethod.keyboard.layout.SymbolsShifted;
+import com.android.inputmethod.keyboard.layout.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.ExpectedKeyboardBuilder;
-import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Arrays;
-import java.util.Locale;
 
 /**
  * Base class for keyboard layout unit test.
  */
 abstract class LayoutTestsBase extends KeyboardLayoutSetTestsBase {
+    private LayoutBase mLayout;
     private InputMethodSubtype mSubtype;
     private String mLogTag;
     private KeyboardLayoutSet mKeyboardLayoutSet;
@@ -48,7 +46,8 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        mSubtype = getSubtype(getTestLocale(), getTestKeyboardLayout());
+        mLayout = getLayout();
+        mSubtype = getSubtype(mLayout.getLocale(), mLayout.getName());
         mLogTag = SubtypeLocaleUtils.getSubtypeNameForLogging(mSubtype) + "/"
                 + (isPhone() ? "phone" : "tablet");
         mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */);
@@ -59,115 +58,77 @@
 
     // Helper method to create {@link ExpectedKey} object that has the label.
     static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) {
-        return LayoutBase.key(label, moreKeys);
+        return AbstractLayoutBase.key(label, moreKeys);
     }
 
     // Helper method to create {@link ExpectedKey} object that has the label and the output text.
     static ExpectedKey key(final String label, final String outputText,
             final ExpectedKey ... moreKeys) {
-        return LayoutBase.key(label, outputText, moreKeys);
+        return AbstractLayoutBase.key(label, outputText, moreKeys);
     }
 
     // Helper method to create {@link ExpectedKey} object that has new "more keys".
     static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) {
-        return LayoutBase.key(key, moreKeys);
+        return AbstractLayoutBase.key(key, moreKeys);
     }
 
     // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
     static ExpectedKey moreKey(final String label) {
-        return LayoutBase.moreKey(label);
+        return AbstractLayoutBase.moreKey(label);
     }
 
     // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
     // output text.
     static ExpectedKey moreKey(final String label, final String outputText) {
-        return LayoutBase.moreKey(label, outputText);
+        return AbstractLayoutBase.moreKey(label, outputText);
     }
 
     // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
     // {@link ExpectedKey} array, and {@link String}.
-    static ExpectedKey[] join(final Object ... keys) {
-        return LayoutBase.join(keys);
+    static ExpectedKey[] joinKeys(final Object ... keys) {
+        return AbstractLayoutBase.joinKeys(keys);
     }
 
-    // Locale for testing subtype.
-    abstract Locale getTestLocale();
+    // Keyboard layout for testing subtype.
+    abstract LayoutBase getLayout();
 
-    // Keyboard layout name for testing subtype.
-    abstract String getTestKeyboardLayout();
-
-    // Alphabet keyboard for testing subtype.
-    abstract ExpectedKey[][] getAlphabetLayout(final boolean isPhone);
-
-    // Alphabet automatic shifted keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetAutomaticShiftedLayout(final boolean isPhone) {
-        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
-    }
-
-    // Alphabet manual shifted  keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetManualShiftedLayout(final boolean isPhone) {
-        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
-    }
-
-    // Alphabet shift locked keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetShiftLockedLayout(final boolean isPhone) {
-        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
-    }
-
-    // Alphabet shift lock shifted keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetShiftLockShiftedLayout(final boolean isPhone) {
-        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
-    }
-
-    // Symbols keyboard for testing subtype.
-    ExpectedKey[][] getSymbolsLayout(final boolean isPhone) {
-        return Symbols.getDefaultLayout(isPhone);
-    }
-
-    // Symbols shifted keyboard for testing subtype.
-    ExpectedKey[][] getSymbolsShiftedLayout(final boolean isPhone) {
-        return SymbolsShifted.getDefaultLayout(isPhone);
+    ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
+        return builder;
     }
 
     // TODO: Add phone, phone symbols, number, number password layout tests.
 
     public final void testAlphabet() {
-        final int elementId = KeyboardId.ELEMENT_ALPHABET;
-        doKeyboardTests(elementId, getAlphabetLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_ALPHABET);
     }
 
     public final void testAlphabetAutomaticShifted() {
-        final int elementId = KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetAutomaticShiftedLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
     }
 
     public final void testAlphabetManualShifted() {
-        final int elementId = KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetManualShiftedLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
     }
 
     public final void testAlphabetShiftLocked() {
-        final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED;
-        doKeyboardTests(elementId, getAlphabetShiftLockedLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED);
     }
 
     public final void testAlphabetShiftLockShifted() {
-        final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetShiftLockShiftedLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED);
     }
 
     public final void testSymbols() {
-        final int elementId = KeyboardId.ELEMENT_SYMBOLS;
-        doKeyboardTests(elementId, getSymbolsLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_SYMBOLS);
     }
 
     public final void testSymbolsShifted() {
-        final int elementId = KeyboardId.ELEMENT_SYMBOLS_SHIFTED;
-        doKeyboardTests(elementId, getSymbolsShiftedLayout(isPhone()));
+        doKeyboardTests(KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
     }
 
     // Comparing expected keyboard and actual keyboard.
-    private void doKeyboardTests(final int elementId, final ExpectedKey[][] expectedKeyboard) {
+    private void doKeyboardTests(final int elementId) {
+        final ExpectedKey[][] expectedKeyboard = mLayout.getLayout(isPhone(), elementId);
         // Skip test if no keyboard is defined.
         if (expectedKeyboard == null) {
             return;
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 0000000..c98b4a5
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAfrikaans.java
@@ -0,0 +1,101 @@
+/*
+ * 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",
+                            "3", "\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", "7", "\u00FA", "\u00FB", "\u00FC", "\u00F9", "\u016B")
+                    // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+                    // U+0133: "ij" LATIN SMALL LIGATURE IJ
+                    .setMoreKeysOf("y", "6", "\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",
+                            "8", "\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",
+                            "9", "\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/TestsAzerbaijaniAZ.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAzerbaijaniAZ.java
new file mode 100644
index 0000000..84d5a36
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsAzerbaijaniAZ.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 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", "3", "\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", "7", "\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",
+                            "8", "\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",
+                            "9", "\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/TestsDutch.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutch.java
new file mode 100644
index 0000000..e6e3378
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDutch.java
@@ -0,0 +1,108 @@
+/*
+ * 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.EuroLayoutCustomizer;
+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; }
+
+    private static class DutchCustomizer extends EuroLayoutCustomizer {
+        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+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",
+                            "3", "\u00E9", "\u00EB", "\u00EA", "\u00E8", "\u0119", "\u0117",
+                            "\u0113")
+                    // U+0133: "ij" LATIN SMALL LIGATURE IJ
+                    .setMoreKeysOf("y", "6", "\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", "7", "\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",
+                            "8", "\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",
+                            "9", "\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+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+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/TestsEnglishUK.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUK.java
new file mode 100644
index 0000000..4789507
--- /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 EnglishUSCustomizer {
+        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
index fd1a606..991187a 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
@@ -18,8 +18,9 @@
 
 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.ExpectedKey;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
 
 import java.util.Locale;
@@ -28,71 +29,65 @@
  * en_US: English (United States)/qwerty
  */
 @SmallTest
-public final class TestsEnglishUS extends LayoutTestsBase {
-    @Override
-    Locale getTestLocale() {
-        return new Locale("en", "US");
-    }
+public class TestsEnglishUS extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("en", "US");
+    private static final LayoutBase LAYOUT = new Qwerty(new EnglishUSCustomizer(LOCALE));
 
     @Override
-    String getTestKeyboardLayout() {
-        return Qwerty.LAYOUT_NAME;
-    }
+    LayoutBase getLayout() { return LAYOUT; }
 
-    @Override
-    ExpectedKey[][] getAlphabetLayout(final boolean isPhone) {
-        final ExpectedKey[][] keyboard = Qwerty.getLayout(isPhone);
-        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(keyboard);
-        setAccentedLetters(builder);
-        return builder.build();
-    }
+    // TODO: Make this as generic English customizer.
+    static class EnglishUSCustomizer extends LayoutCustomizer {
+        public EnglishUSCustomizer(final Locale locale) { super(locale); }
 
-    static ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
-                // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
-                // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
-                // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
-                // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
-                .setMoreKeysOf("e", "3", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113")
-                // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
-                // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
-                // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-                // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
-                // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
-                .setMoreKeysOf("u", "7", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B")
-                // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
-                // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-                // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
-                // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
-                // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
-                .setMoreKeysOf("i", "8", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC")
-                // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
-                // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
-                // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-                // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
-                // U+0153: "œ" LATIN SMALL LIGATURE OE
-                // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
-                // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
-                // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
-                .setMoreKeysOf("o",
-                        "9", "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8", "\u014D",
-                        "\u00F5")
-                // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
-                // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
-                // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
-                // U+00E6: "æ" LATIN SMALL LETTER AE
-                // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
-                // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
-                // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
-                .setMoreKeysOf("a",
-                        "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5",
-                        "\u0101")
-                // U+00DF: "ß" LATIN SMALL LETTER SHARP S
-                .setMoreKeysOf("s", "\u00DF")
-                // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
-                .setMoreKeysOf("c", "\u00E7")
-                // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-                .setMoreKeysOf("n", "\u00F1");
+        @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", "3", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113")
+                    // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+                    // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+                    // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+                    // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+                    // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+                    .setMoreKeysOf("u", "7", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B")
+                    // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+                    // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+                    // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+                    // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+                    // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+                    .setMoreKeysOf("i", "8", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC")
+                    // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+                    // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+                    // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+                    // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+                    // U+0153: "œ" LATIN SMALL LIGATURE OE
+                    // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+                    // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+                    // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+                    .setMoreKeysOf("o",
+                            "9", "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8",
+                            "\u014D", "\u00F5")
+                    // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+                    // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+                    // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+                    // U+00E6: "æ" LATIN SMALL LETTER AE
+                    // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+                    // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+                    // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+                    .setMoreKeysOf("a",
+                            "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5",
+                            "\u0101")
+                    // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+                    .setMoreKeysOf("s", "\u00DF")
+                    // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+                    .setMoreKeysOf("c", "\u00E7")
+                    // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+                    .setMoreKeysOf("n", "\u00F1");
+        }
     }
 }
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 0000000..d6babc8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsFrenchCA.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+/**
+ * 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 FrenchCACustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    private static class FrenchCACustomizer extends LayoutCustomizer {
+        public FrenchCACustomizer(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", "3", "\u0119", "\u0117",
+                            "\u0113")
+                    // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+                    .setMoreKeysOf("y", "6", "\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
+                    .setMoreKeysOf("u", "\u00F9", "\u00FB", "7", "\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
+                    .setMoreKeysOf("i", "\u00EE", "8", "\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
+                    .setMoreKeysOf("o",
+                            "\u00F4", "\u0153", "9", "\u00F6", "\u00F2", "\u00F3", "\u00F5",
+                            "\u00F8", "\u014D", "\u00BA")
+                    // 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
+                    .setMoreKeysOf("a",
+                            "\u00E0", "\u00E2", "\u00E6", "\u00E1", "\u00E4", "\u00E3", "\u00E5",
+                            "\u0101", "\u00AA")
+                    // 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/TestsGermanQwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanQwerty.java
new file mode 100644
index 0000000..618485d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsGermanQwerty.java
@@ -0,0 +1,102 @@
+/*
+ * 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.EuroLayoutCustomizer;
+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;
+
+/**
+ * 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 GermanQwertyCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    private static class GermanQwertyCustomizer extends EuroLayoutCustomizer {
+        public GermanQwertyCustomizer(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", "3", "\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", "7", "\u00FC", "\u00FB", "\u00F9", "\u00FA", "\u016B")
+                    // 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",
+                            "9", "\u00F6", "\u00F4", "\u00F2", "\u00F3", "\u00F5", "\u0153",
+                            "\u00F8", "\u014D")
+                    // 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")
+                    // 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/TestsIcelandic.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIcelandic.java
new file mode 100644
index 0000000..c10c1a9
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsIcelandic.java
@@ -0,0 +1,108 @@
+/*
+ * 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",
+                            "3", "\u00E9", "\u00EB", "\u00E8", "\u00EA", "\u0119", "\u0117",
+                            "\u0113")
+                    // U+00FE: "þ" LATIN SMALL LETTER THORN
+                    .setMoreKeysOf("t", "5", "\u00FE")
+                    // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+                    // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+                    .setMoreKeysOf("y", "6", "\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", "7", "\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", "8", "\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",
+                            "9", "\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 0000000..9b23bfe
--- /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 0000000..1ba54b1
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsItalian.java
@@ -0,0 +1,95 @@
+/*
+ * 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.EuroLayoutCustomizer;
+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 ItalianCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    private static class ItalianCustomizer extends EuroLayoutCustomizer {
+        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",
+                            "3", "\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", "7", "\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", "8", "\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",
+                            "9", "\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/TestsLatvian.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLatvian.java
new file mode 100644
index 0000000..0f07854
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLatvian.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;
+
+/**
+ * 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",
+                            "3", "\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", "4", "\u0157", "\u0159", "\u0155")
+                    // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+                    // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+                    .setMoreKeysOf("t", "5", "\u0163", "\u0165")
+                    // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+                    // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+                    .setMoreKeysOf("y", "6", "\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",
+                            "7", "\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",
+                            "8", "\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",
+                            "9", "\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 0000000..9094db9
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsLithuanian.java
@@ -0,0 +1,150 @@
+/*
+ * 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",
+                            "3", "\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", "4", "\u0157", "\u0159", "\u0155")
+                    // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+                    // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+                    .setMoreKeysOf("t", "5", "\u0163", "\u0165")
+                    // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+                    // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+                    .setMoreKeysOf("y", "6", "\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",
+                            "7", "\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",
+                            "8", "\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",
+                            "9", "\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/TestsMalayMY.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMalayMY.java
new file mode 100644
index 0000000..9792af9
--- /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/TestsNoLanguage.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguage.java
new file mode 100644
index 0000000..76a3d4d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsNoLanguage.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.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;
+
+/**
+ * zz: QWERTY/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; }
+
+    private static class NoLanguageCustomizer extends LayoutCustomizer {
+        public 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", "2", "\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",
+                            "3", "\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", "4", "\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", "5", "\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", "6", "\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",
+                            "7", "\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",
+                            "8", "\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",
+                            "9", "\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/TestsPolish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPolish.java
new file mode 100644
index 0000000..7d8f629
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPolish.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.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",
+                            "3", "\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",
+                            "9", "\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 0000000..00ee698
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortugueseBR.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;
+
+/**
+ * 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 PortugueseBRCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    // TODO: Make this as generic Portuguese customizer.
+    static class PortugueseBRCustomizer extends LayoutCustomizer {
+        public PortugueseBRCustomizer(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",
+                            "3", "\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", "7", "\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", "8", "\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",
+                            "9", "\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/TestsPortuguesePT.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortuguesePT.java
new file mode 100644
index 0000000..836542e
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsPortuguesePT.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.EuroLayoutCustomizer;
+import com.android.inputmethod.keyboard.layout.Qwerty;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+
+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 EuroLayoutCustomizer {
+        private final PortugueseBRCustomizer mPortugueseCustomizer;
+
+        public PortuguesePTCustomizer(final Locale locale) {
+            super(locale);
+            mPortugueseCustomizer = new PortugueseBRCustomizer(locale);
+        }
+
+        @Override
+        public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
+            return mPortugueseCustomizer.setAccentedLetters(builder);
+        }
+    }
+}
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 0000000..59b328d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsRomanian.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.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", "5", "\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", "8", "\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/TestsSlovak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovak.java
new file mode 100644
index 0000000..3d7825b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSlovak.java
@@ -0,0 +1,156 @@
+/*
+ * 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.EuroLayoutCustomizer;
+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 EuroLayoutCustomizer {
+        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",
+                            "3", "\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", "4", "\u0155", "\u0159", "\u0157")
+                    // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+                    // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+                    .setMoreKeysOf("t", "5", "\u0165", "\u0163")
+                    // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+                    // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+                    .setMoreKeysOf("y", "6", "\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",
+                            "7", "\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",
+                            "8", "\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",
+                            "9", "\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 0000000..88a23ba
--- /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.EuroLayoutCustomizer;
+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 EuroLayoutCustomizer {
+        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/TestsSwahili.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSwahili.java
new file mode 100644
index 0000000..49e1912
--- /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", "3", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113")
+                    // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+                    // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+                    // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+                    // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+                    // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+                    .setMoreKeysOf("u", "7", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B")
+                    // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+                    // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+                    // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+                    // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+                    // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+                    .setMoreKeysOf("i", "8", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC")
+                    // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+                    // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+                    // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+                    // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+                    // U+0153: "œ" LATIN SMALL LIGATURE OE
+                    // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+                    // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+                    // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+                    .setMoreKeysOf("o",
+                            "9", "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8",
+                            "\u014D", "\u00F5")
+                    // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+                    // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+                    // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+                    // U+00E6: "æ" LATIN SMALL LETTER AE
+                    // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+                    // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+                    // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+                    .setMoreKeysOf("a",
+                            "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5",
+                            "\u0101")
+                    // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+                    .setMoreKeysOf("s", "\u00DF")
+                    .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/TestsTurkish.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsTurkish.java
new file mode 100644
index 0000000..ab67ae9
--- /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.EuroLayoutCustomizer;
+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 EuroLayoutCustomizer {
+        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", "7", "\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", "8",
+                            "\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",
+                            "9", "\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/TestsVietnamese.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsVietnamese.java
new file mode 100644
index 0000000..94b0c1f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsVietnamese.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.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.DOLLAR_SIGN, Symbols.CENT_SIGN, Symbols.EURO_SIGN, Symbols.POUND_SIGN,
+                Symbols.YEN_SIGN, Symbols.PESO_SIGN);
+
+        @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",
+                            "3", "\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", "6", "\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",
+                            "7", "\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", "8", "\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",
+                            "9", "\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 0000000..04e89be
--- /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 EnglishUSCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+}
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index 4d44135..b476627 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -948,16 +948,15 @@
             final HashSet<String> bigramWord1s = bigrams.get(word0);
             final WordProperty wordProperty = binaryDictionary.getWordProperty(word0);
             assertEquals(bigramWord1s.size(), wordProperty.mBigrams.size());
-            final int unigramProbability = wordProperty.getProbability();
             for (int j = 0; j < wordProperty.mBigrams.size(); j++) {
                 final String word1 = wordProperty.mBigrams.get(j).mWord;
                 assertTrue(bigramWord1s.contains(word1));
-                final int bigramProbability = wordProperty.mBigrams.get(j).getProbability();
-                final int probability = binaryDictionary.calculateProbability(
-                        unigramProbability, bigramProbability);
-                assertEquals((int)bigramProbabilities.get(new Pair<String, String>(word0, word1)),
-                        probability);
-                assertEquals(wordProperty.mBigrams.get(j).getProbability(), probability);
+                final int bigramProbabilityDelta = bigramProbabilities.get(
+                        new Pair<String, String>(word0, word1));
+                final int unigramProbability = wordProbabilities.get(word1);
+                final int bigramProbablity = binaryDictionary.calculateProbability(
+                        unigramProbability, bigramProbabilityDelta);
+                assertEquals(wordProperty.mBigrams.get(j).getProbability(), bigramProbablity);
             }
         }
     }
@@ -1040,16 +1039,16 @@
             assertEquals((int)wordProbabilitiesToCheckLater.get(word0),
                     wordProperty.mProbabilityInfo.mProbability);
             wordSet.remove(word0);
-            final int unigramProbability = wordProperty.getProbability();
             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 int bigramProbability = wordProperty.mBigrams.get(j).getProbability();
-                final int probability = binaryDictionary.calculateProbability(
-                        unigramProbability, bigramProbability);
+                final int unigramProbability = wordProbabilitiesToCheckLater.get(word1);
                 final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
-                assertEquals((int)bigramProbabilitiesToCheckLater.get(bigram), probability);
+                final int bigramProbabilityDelta = bigramProbabilitiesToCheckLater.get(bigram);
+                final int bigramProbablity = binaryDictionary.calculateProbability(
+                        unigramProbability, bigramProbabilityDelta);
+                assertEquals(wordProperty.mBigrams.get(j).getProbability(), bigramProbablity);
                 bigramSet.remove(bigram);
             }
             token = result.mNextToken;
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index c0a5562..948c03b 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -29,32 +29,34 @@
 LATINIME_LOCAL_DIR := ../..
 LATINIME_BASE_SOURCE_DIRECTORY := $(LATINIME_LOCAL_DIR)/java/src/com/android/inputmethod
 LATINIME_ANNOTATIONS_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/annotations
-LATINIME_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin
-MAKEDICT_CORE_SOURCE_DIRECTORY := $(LATINIME_CORE_SOURCE_DIRECTORY)/makedict
+MAKEDICT_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin/makedict
 
 # Dependencies for Dicttool. Most of these files are needed by BinaryDictionary.java. Note that
 # a significant part of the dependencies are mocked in the compat/ directory, with empty or
 # nearly-empty implementations, for parts that we don't use in Dicttool.
-USED_TARGETTED_UTILS := \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/BinaryDictionary.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/DicTraverseSession.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/Dictionary.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/InputPointers.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/LastComposedWord.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/LatinImeLogger.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/SuggestedWords.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/WordComposer.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/settings/NativeSuggestOptions.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/BinaryDictionaryUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayDictBuffer.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CombinedFormatUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CoordinateUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/FileUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/JniUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/LocaleUtils.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ResizableIntArray.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/StringUtils.java
+LATINIME_SRCS_FOR_DICTTOOL := \
+        event/Event.java \
+        latin/BinaryDictionary.java \
+        latin/DicTraverseSession.java \
+        latin/Dictionary.java \
+        latin/InputPointers.java \
+        latin/LastComposedWord.java \
+        latin/LatinImeLogger.java \
+        latin/SuggestedWords.java \
+        latin/WordComposer.java \
+        latin/settings/NativeSuggestOptions.java \
+        latin/utils/BinaryDictionaryUtils.java \
+        latin/utils/ByteArrayDictBuffer.java \
+        latin/utils/CollectionUtils.java \
+        latin/utils/CombinedFormatUtils.java \
+        latin/utils/CoordinateUtils.java \
+        latin/utils/FileUtils.java \
+        latin/utils/JniUtils.java \
+        latin/utils/LocaleUtils.java \
+        latin/utils/ResizableIntArray.java \
+        latin/utils/StringUtils.java
+USED_TARGETED_SRCS := $(addprefix $(LATINIME_BASE_SOURCE_DIRECTORY)/, \
+        $(LATINIME_SRCS_FOR_DICTTOOL))
 
 DICTTOOL_ONDEVICE_TESTS_DIRECTORY := \
         $(LATINIME_LOCAL_DIR)/tests/src/com/android/inputmethod/latin/makedict/
@@ -68,11 +70,11 @@
 LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \
         $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
         $(LOCAL_ANNOTATIONS_SRC_FILES) \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/Constants.java \
+        $(LATINIME_BASE_SOURCE_DIRECTORY)/latin/Constants.java \
         $(call all-java-files-under, tests) \
         $(call all-java-files-under, $(DICTTOOL_ONDEVICE_TESTS_DIRECTORY)) \
         $(call all-java-files-under, $(DICTTOOL_COMPAT_TESTS_DIRECTORY)) \
-        $(USED_TARGETTED_UTILS)
+        $(USED_TARGETED_SRCS)
 
 LOCAL_JAVA_LIBRARIES := junit
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LATINIME_HOST_NATIVE_LIBNAME)