Merge "Remove unused MoreKeysPanel argument from Controller interface"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0b07694..44353ba 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -78,7 +78,6 @@
 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
-import com.android.inputmethod.latin.utils.CompletionInfoUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
@@ -189,8 +188,9 @@
             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
             switch (msg.what) {
             case MSG_UPDATE_SUGGESTION_STRIP:
+                cancelUpdateSuggestionStrip();
                 latinIme.mInputLogic.performUpdateSuggestionStripSync(
-                        latinIme.mSettings.getCurrent(), this /* handler */);
+                        latinIme.mSettings.getCurrent());
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
@@ -551,7 +551,7 @@
             // Create Suggest instance with the new dictionary facilitator.
             replaceSuggest(new Suggest(oldSuggest, dictionaryFacilitator));
         } else if (oldSuggest == null) {
-            initSuggestForLocale(oldSuggest, locale);
+            initSuggest();
         }
     }
 
@@ -950,8 +950,7 @@
         // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
         // TODO: find a better way to simulate actual execution.
         if (isInputViewShown() &&
-                mInputLogic.onUpdateSelection(mSettings.getCurrent(), oldSelStart, oldSelEnd,
-                        newSelStart, newSelEnd, composingSpanStart, composingSpanEnd)) {
+                mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) {
             mKeyboardSwitcher.updateShiftState();
         }
 
@@ -1044,18 +1043,6 @@
         }
     }
 
-    private void setSuggestionStripShownInternal(final boolean isSuggestionStripVisible) {
-        // TODO: Modify this if we support suggestions with hard keyboard
-        if (!onEvaluateInputViewShown() || !hasSuggestionStripView()) {
-            return;
-        }
-        if (isSuggestionStripVisible) {
-            mSuggestionStripView.setVisibility(View.VISIBLE);
-        } else {
-            mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
-        }
-    }
-
     private int getAdjustedBackingViewHeight() {
         final int currentHeight = mKeyPreviewBackingView.getHeight();
         if (currentHeight > 0) {
@@ -1293,7 +1280,7 @@
 
     @Override
     public void onStartBatchInput() {
-        mInputLogic.onStartBatchInput(mSettings.getCurrent(),  mKeyboardSwitcher, mHandler);
+        mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
     }
 
     @Override
@@ -1303,7 +1290,7 @@
 
     @Override
     public void onEndBatchInput(final InputPointers batchPointers) {
-        mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers);
+        mInputLogic.onEndBatchInput(batchPointers);
     }
 
     @Override
@@ -1381,13 +1368,23 @@
     public void setSuggestedWords(final SuggestedWords suggestedWords,
             final boolean isSuggestionStripVisible) {
         mInputLogic.setSuggestedWords(suggestedWords);
+        // TODO: Modify this when we support suggestions with hard keyboard
         if (!hasSuggestionStripView()) {
             return;
         }
+        mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
+        if (!onEvaluateInputViewShown()) {
+            return;
+        }
+        if (!isSuggestionStripVisible) {
+            mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
+            return;
+        }
+        mSuggestionStripView.setVisibility(View.VISIBLE);
+
         final SettingsValues currentSettings = mSettings.getCurrent();
         final boolean showSuggestions;
-        if (SuggestedWords.EMPTY == suggestedWords
-                || suggestedWords.isPunctuationSuggestions()
+        if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions()
                 || !currentSettings.isSuggestionsRequested()) {
             showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
                     currentSettings.mInputAttributes);
@@ -1398,8 +1395,6 @@
             mSuggestionStripView.setSuggestions(suggestedWords,
                     SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
         }
-        mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
-        setSuggestionStripShownInternal(isSuggestionStripVisible);
     }
 
     // TODO[IL]: Move this out of LatinIME.
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 7be60fd..0e4374a 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -74,7 +74,7 @@
 
     // TODO : make all these fields private as soon as possible.
     // Current space state of the input method. This can be any of the above constants.
-    public int mSpaceState;
+    private int mSpaceState;
     // Never null
     public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     // TODO: mSuggest should be touched by a single thread.
@@ -85,7 +85,7 @@
     public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     public final WordComposer mWordComposer;
     public final RichInputConnection mConnection;
-    public final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
+    private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
 
     private int mDeleteCount;
     private long mLastKeyTime;
@@ -96,7 +96,7 @@
 
     // TODO: This boolean is persistent state and causes large side effects at unexpected times.
     // Find a way to remove it for readability.
-    public boolean mIsAutoCorrectionIndicatorOn;
+    private boolean mIsAutoCorrectionIndicatorOn;
 
     public InputLogic(final LatinIME latinIME,
             final SuggestionStripViewAccessor suggestionStripViewAccessor) {
@@ -289,19 +289,14 @@
      * Consider an update to the cursor position. Evaluate whether this update has happened as
      * part of normal typing or whether it was an explicit cursor move by the user. In any case,
      * do the necessary adjustments.
-     * @param settingsValues the current settings
      * @param oldSelStart old selection start
      * @param oldSelEnd old selection end
      * @param newSelStart new selection start
      * @param newSelEnd new selection end
-     * @param composingSpanStart composing span start
-     * @param composingSpanEnd composing span end
      * @return whether the cursor has moved as a result of user interaction.
      */
-    public boolean onUpdateSelection(final SettingsValues settingsValues,
-            final int oldSelStart, final int oldSelEnd,
-            final int newSelStart, final int newSelEnd,
-            final int composingSpanStart, final int composingSpanEnd) {
+    public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd,
+            final int newSelStart, final int newSelEnd) {
         if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) {
             return false;
         }
@@ -334,8 +329,7 @@
             // we'd have the suggestion strip noticeably janky. To avoid that, we don't clear
             // it here, which means we'll keep outdated suggestions for a split second but the
             // visual result is better.
-            resetEntireInputState(settingsValues, newSelStart, newSelEnd,
-                    false /* clearSuggestionStrip */);
+            resetEntireInputState(newSelStart, newSelEnd, false /* clearSuggestionStrip */);
         } else {
             // resetEntireInputState calls resetCachesUponCursorMove, but forcing the
             // composition to end. But in all cases where we don't reset the entire input
@@ -503,7 +497,7 @@
             if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
                 // If we are in the middle of a recorrection, we need to commit the recorrection
                 // first so that we can insert the batch input at the current cursor position.
-                resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+                resetEntireInputState(mConnection.getExpectedSelectionStart(),
                         mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             } else if (wordComposerSize <= 1) {
                 // We auto-correct the previous (typed, not gestured) string iff it's one character
@@ -584,8 +578,7 @@
         mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
     }
 
-    public void onEndBatchInput(final SettingsValues settingValues,
-            final InputPointers batchPointers) {
+    public void onEndBatchInput(final InputPointers batchPointers) {
         mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
         ++mAutoCommitSequenceNumber;
     }
@@ -657,7 +650,7 @@
                 if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
                     // If we are in the middle of a recorrection, we need to commit the recorrection
                     // first so that we can insert the character at the current cursor position.
-                    resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+                    resetEntireInputState(mConnection.getExpectedSelectionStart(),
                             mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
                 } else {
                     commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR);
@@ -699,7 +692,7 @@
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can insert the character at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             isComposingWord = false;
         }
@@ -781,7 +774,7 @@
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can insert the separator at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
         }
         // isComposingWord() may have changed since we stored wasComposing
@@ -876,7 +869,6 @@
             // TODO: remove these arguments
             final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
         mSpaceState = SpaceState.NONE;
-        final int deleteCountAtStart = mDeleteCount;
         mDeleteCount++;
 
         // In many cases, we may have to put the keyboard in auto-shift state again. However
@@ -887,7 +879,7 @@
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can remove the character at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
         }
@@ -1204,11 +1196,7 @@
                 timeStampInSeconds);
     }
 
-    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
-            // TODO: Remove this argument
-            final LatinIME.UIHandler handler) {
-        handler.cancelUpdateSuggestionStrip();
-
+    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
         // Check if we have a suggestion engine attached.
         if (mSuggest == null || !settingsValues.isSuggestionsRequested()) {
             if (mWordComposer.isComposingWord()) {
@@ -1228,10 +1216,15 @@
                 SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                     @Override
                     public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
-                        final SuggestedWords suggestedWordsWithMaybeOlderSuggestions =
-                                maybeRetrieveOlderSuggestions(mWordComposer.getTypedWord(),
-                                        suggestedWords, mSuggestedWords);
-                        holder.set(suggestedWordsWithMaybeOlderSuggestions);
+                        final String typedWord = mWordComposer.getTypedWord();
+                        // Show new suggestions if we have at least one. Otherwise keep the old
+                        // suggestions with the new typed word. Exception: if the length of the
+                        // typed word is <= 1 (after a deletion typically) we clear old suggestions.
+                        if (suggestedWords.size() > 1 || typedWord.length() <= 1) {
+                            holder.set(suggestedWords);
+                        } else {
+                            holder.set(retrieveOlderSuggestions(typedWord, mSuggestedWords));
+                        }
                     }
                 }
         );
@@ -1621,15 +1614,13 @@
      * This will clear the composing word, reset the last composed word, clear the suggestion
      * strip and tell the input connection about it so that it can refresh its caches.
      *
-     * @param settingsValues the current values of the settings.
      * @param newSelStart the new selection start, in java characters.
      * @param newSelEnd the new selection end, in java characters.
      * @param clearSuggestionStrip whether this method should clear the suggestion strip.
      */
     // TODO: how is this different from startInput ?!
-    // TODO: remove all references to this in LatinIME and make this private
-    public void resetEntireInputState(final SettingsValues settingsValues,
-            final int newSelStart, final int newSelEnd, final boolean clearSuggestionStrip) {
+    private void resetEntireInputState(final int newSelStart, final int newSelEnd,
+            final boolean clearSuggestionStrip) {
         final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
         if (clearSuggestionStrip) {
@@ -1647,8 +1638,7 @@
      *
      * @param alsoResetLastComposedWord whether to also reset the last composed word.
      */
-    // TODO: remove all references to this in LatinIME and make this private.
-    public void resetComposingState(final boolean alsoResetLastComposedWord) {
+    private void resetComposingState(final boolean alsoResetLastComposedWord) {
         mWordComposer.reset();
         if (alsoResetLastComposedWord) {
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -1656,32 +1646,24 @@
     }
 
     /**
-     * Given a typed word and computed suggested words, return an object that may or may not
-     * contain older suggestions according to the contents of the current suggestions.
+     * Make a {@link com.android.inputmethod.latin.SuggestedWords} object containing a typed word
+     * and obsolete suggestions.
+     * See {@link com.android.inputmethod.latin.SuggestedWords#getTypedWordAndPreviousSuggestions(
+     *      String, com.android.inputmethod.latin.SuggestedWords)}.
      * @param typedWord The typed word as a string.
-     * @param suggestedWords The computed suggested words for this typed word.
-     * @param previousSuggestedWords The previous suggested words.
-     * @return suggestions possibly enriched with older suggestions.
+     * @param previousSuggestedWords The previously suggested words.
+     * @return Obsolete suggestions with the newly typed word.
      */
-    private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord,
-            final SuggestedWords suggestedWords, final SuggestedWords previousSuggestedWords) {
-        // TODO: consolidate this into performUpdateSuggestionStripSync?
-        // We update the suggestion strip only when we have some suggestions to show, i.e. when
-        // the suggestion count is > 1; else, we leave the old suggestions, with the typed word
-        // replaced with the new one. However, when the length of the typed word is 1 or 0 (after
-        // a deletion typically), we do want to remove the old suggestions.
-        if (suggestedWords.size() > 1 || typedWord.length() <= 1) {
-            return suggestedWords;
-        } else {
-            final SuggestedWords oldSuggestedWords =
-                    previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
-                            : previousSuggestedWords;
-            final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
-                    SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
-            return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
-                    false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
-                    true /* isObsoleteSuggestions */, false /* isPrediction */);
-        }
+    private SuggestedWords retrieveOlderSuggestions(final String typedWord,
+            final SuggestedWords previousSuggestedWords) {
+        final SuggestedWords oldSuggestedWords =
+                previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
+                        : previousSuggestedWords;
+        final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+                SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
+        return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
+                false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
+                true /* isObsoleteSuggestions */, false /* isPrediction */);
     }
 
     /**
@@ -1701,9 +1683,8 @@
      * @param text the text on which to maybe apply the span.
      * @return the same text, with the auto-correction underline span if that's appropriate.
      */
-    // TODO: remove all references to this in LatinIME and make this private. Also, shouldn't
-    // this go in some *Utils class instead?
-    public CharSequence getTextWithUnderline(final String text) {
+    // TODO: Shouldn't this go in some *Utils class instead?
+    private CharSequence getTextWithUnderline(final String text) {
         return mIsAutoCorrectionIndicatorOn
                 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text)
                 : text;
@@ -1768,8 +1749,7 @@
      *
      * @param settingsValues the current values of the settings.
      */
-    // TODO: Make this private.
-    public void promotePhantomSpace(final SettingsValues settingsValues) {
+    private void promotePhantomSpace(final SettingsValues settingsValues) {
         if (settingsValues.shouldInsertSpacesAutomatically()
                 && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
                 && !mConnection.textBeforeCursorLooksLikeURL()) {
@@ -1875,7 +1855,8 @@
             final LatinIME.UIHandler handler) {
         // Complete any pending suggestions query first
         if (handler.hasPendingUpdateSuggestions()) {
-            performUpdateSuggestionStripSync(settingsValues, handler);
+            handler.cancelUpdateSuggestionStrip();
+            performUpdateSuggestionStripSync(settingsValues);
         }
         final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
         final String typedWord = mWordComposer.getTypedWord();
@@ -1919,8 +1900,7 @@
      * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_*
      * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none.
      */
-    // TODO: Make this private
-    public void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
+    private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
             final int commitType, final String separatorString) {
         final SuggestedWords suggestedWords = mSuggestedWords;
         final CharSequence chosenWordWithSuggestions =
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
new file mode 100644
index 0000000..06139b8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.res.Resources;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+@MediumTest
+public final class KeyboardLayoutSetActionLabelTests extends KeyboardLayoutSetTestsBase {
+    private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet,
+            final int elementId, final String label, final int iconId) {
+        final Keyboard keyboard = layoutSet.getKeyboard(elementId);
+        final Key enterKey = keyboard.getKey(Constants.CODE_ENTER);
+        assertNotNull(tag + " enter key on " + keyboard.mId, enterKey);
+        assertEquals(tag + " enter label " + enterKey, label, enterKey.getLabel());
+        assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId());
+    }
+
+    private void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final int labelResId) {
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.imeOptions = actionId;
+        final RunInLocale<String> job = new RunInLocale<String>() {
+            @Override
+            protected String job(final Resources res) {
+                return res.getString(labelResId);
+            }
+        };
+        final Resources res = getContext().getResources();
+        final String label;
+        if (subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
+            // Using system locale.
+            label = res.getString(labelResId);
+        } else {
+            label = job.runInLocale(res, SubtypeLocaleUtils.getSubtypeLocale(subtype));
+        }
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test number password layouts.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+    }
+
+    private void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final String iconName) {
+        final int iconId = KeyboardIconsSet.getIconId(iconName);
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.imeOptions = actionId;
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, null /* label */, iconId);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, null /* label */, iconId);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, null /* label */, iconId);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+        // Test number password layout.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+    }
+
+    public void testActionUnspecified() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "unspecifiled "
+                    + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED, "enter_key");
+        }
+    }
+
+    public void testActionNone() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE, "enter_key");
+        }
+    }
+
+    public void testActionGo() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_GO, R.string.label_go_key);
+        }
+    }
+
+    public void testActionSearch() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH, "search_key");
+        }
+    }
+
+    public void testActionSend() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_SEND, R.string.label_send_key);
+        }
+    }
+
+    public void testActionNext() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_NEXT, R.string.label_next_key);
+        }
+    }
+
+    public void testActionDone() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_DONE, R.string.label_done_key);
+        }
+    }
+
+    public void testActionPrevious() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(
+                    tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, R.string.label_previous_key);
+        }
+    }
+}