Merge "Use DictionaryFacilitatorLruCache for personalization."
diff --git a/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
index 95df202..9cfcc3d 100644
--- a/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
+++ b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -16,22 +16,12 @@
 
 package com.android.inputmethod.latin.utils;
 
-import android.content.Context;
-
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.settings.SettingsValues;
 
 public final class StatsUtils {
-    public static void init(final Context context) {
-    }
 
     public static void onCreate(final SettingsValues settingsValues,
             RichInputMethodManager richImm) {
     }
-
-    public static void onLoadSettings(final SettingsValues settingsValues) {
-    }
-
-    public static void onDestroy() {
-    }
 }
diff --git a/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java
new file mode 100644
index 0000000..120b105
--- /dev/null
+++ b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtilsManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Context;
+
+import com.android.inputmethod.latin.settings.SettingsValues;
+
+public class StatsUtilsManager {
+
+    private static final StatsUtilsManager sInstance = new StatsUtilsManager();
+
+    /**
+     * @return the singleton instance of {@link StatsUtilsManager}.
+     */
+    public static StatsUtilsManager getInstance() {
+        return sInstance;
+    }
+
+    public void onCreate(final Context context) {
+    }
+
+    public void onLoadSettings(final SettingsValues settingsValues) {
+    }
+
+    public void onDestroy() {
+    }
+}
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index d42b4e9..5453d51 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -82,4 +82,6 @@
     <color name="setup_text_action">@android:color/holo_blue_light</color>
     <color name="setup_step_background">@android:color/background_light</color>
     <color name="setup_welcome_video_margin_color">#FFCCCCCC</color>
+    <!-- Accent color for the notification. We need to match this to the OS build -->
+    <color name="notification_accent_color">#FF607D8B</color>
 </resources>
diff --git a/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java b/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java
new file mode 100644
index 0000000..eb18007
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/NotificationCompatUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.app.Notification;
+import android.os.Build;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class NotificationCompatUtils {
+    // Note that TextInfo.getCharSequence() is supposed to be available in API level 21 and later.
+    private static final Method METHOD_setColor =
+            CompatUtils.getMethod(Notification.Builder.class, "setColor", int.class);
+    private static final Method METHOD_setVisibility =
+            CompatUtils.getMethod(Notification.Builder.class, "setVisibility", int.class);
+    private static final Method METHOD_setCategory =
+            CompatUtils.getMethod(Notification.Builder.class, "setCategory", String.class);
+    private static final Method METHOD_setPriority =
+            CompatUtils.getMethod(Notification.Builder.class, "setPriority", int.class);
+    private static final Method METHOD_build =
+            CompatUtils.getMethod(Notification.Builder.class, "build");
+    private static final Field FIELD_VISIBILITY_SECRET =
+            CompatUtils.getField(Notification.class, "VISIBILITY_SECRET");
+    private static final int VISIBILITY_SECRET = null == FIELD_VISIBILITY_SECRET ? 0
+            : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+                    FIELD_VISIBILITY_SECRET);
+    private static final Field FIELD_CATEGORY_RECOMMENDATION =
+            CompatUtils.getField(Notification.class, "CATEGORY_RECOMMENDATION");
+    private static final String CATEGORY_RECOMMENDATION = null == FIELD_CATEGORY_RECOMMENDATION ? ""
+            : (String) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+                    FIELD_CATEGORY_RECOMMENDATION);
+    private static final Field FIELD_PRIORITY_LOW =
+            CompatUtils.getField(Notification.class, "PRIORITY_LOW");
+    private static final int PRIORITY_LOW = null == FIELD_PRIORITY_LOW ? 0
+            : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+                    FIELD_PRIORITY_LOW);
+
+    private NotificationCompatUtils() {
+        // This class is non-instantiable.
+    }
+
+    // Sets the accent color
+    public static void setColor(final Notification.Builder builder, final int color) {
+        CompatUtils.invoke(builder, null, METHOD_setColor, color);
+    }
+
+    public static void setVisibilityToSecret(final Notification.Builder builder) {
+        CompatUtils.invoke(builder, null, METHOD_setVisibility, VISIBILITY_SECRET);
+    }
+
+    public static void setCategoryToRecommendation(final Notification.Builder builder) {
+        CompatUtils.invoke(builder, null, METHOD_setCategory, CATEGORY_RECOMMENDATION);
+    }
+
+    public static void setPriorityToLow(final Notification.Builder builder) {
+        CompatUtils.invoke(builder, null, METHOD_setPriority, PRIORITY_LOW);
+    }
+
+    public static Notification build(final Notification.Builder builder) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            // #build was added in API level 16, JELLY_BEAN
+            return (Notification) CompatUtils.invoke(builder, null, METHOD_build);
+        } else {
+            // #getNotification was deprecated in API level 16, JELLY_BEAN
+            return builder.getNotification();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 95a0942..6fbca44 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -31,12 +31,14 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.net.ConnectivityManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
+import com.android.inputmethod.compat.NotificationCompatUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
@@ -858,7 +860,7 @@
         final String language = (null == locale ? "" : locale.getDisplayLanguage());
         final String titleFormat = context.getString(R.string.dict_available_notification_title);
         final String notificationTitle = String.format(titleFormat, language);
-        final Notification notification = new Notification.Builder(context)
+        final Notification.Builder builder = new Notification.Builder(context)
                 .setAutoCancel(true)
                 .setContentIntent(notificationIntent)
                 .setContentTitle(notificationTitle)
@@ -866,8 +868,13 @@
                 .setTicker(notificationTitle)
                 .setOngoing(false)
                 .setOnlyAlertOnce(true)
-                .setSmallIcon(R.drawable.ic_notify_dictionary)
-                .getNotification();
+                .setSmallIcon(R.drawable.ic_notify_dictionary);
+        NotificationCompatUtils.setColor(builder,
+                context.getResources().getColor(R.color.notification_accent_color));
+        NotificationCompatUtils.setPriorityToLow(builder);
+        NotificationCompatUtils.setVisibilityToSecret(builder);
+        NotificationCompatUtils.setCategoryToRecommendation(builder);
+        final Notification notification = NotificationCompatUtils.build(builder);
         notificationManager.notify(DICT_AVAILABLE_NOTIFICATION_ID, notification);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d3dde6f..a05b82c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -93,6 +93,7 @@
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
 import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.StatsUtilsManager;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 
@@ -157,6 +158,7 @@
     private final SubtypeSwitcher mSubtypeSwitcher;
     private final SubtypeState mSubtypeState = new SubtypeState();
     private final SpecialKeyDetector mSpecialKeyDetector;
+    private StatsUtilsManager mStatsUtilsManager;
 
     // Object for reacting to adding/removing a dictionary pack.
     private final BroadcastReceiver mDictionaryPackInstallReceiver =
@@ -537,6 +539,7 @@
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mSpecialKeyDetector = new SpecialKeyDetector(this);
+        mStatsUtilsManager = StatsUtilsManager.getInstance();
         mIsHardwareAcceleratedDrawingEnabled =
                 InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
         Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
@@ -552,8 +555,6 @@
         KeyboardSwitcher.init(this);
         AudioAndHapticFeedbackManager.init(this);
         AccessibilityUtils.init(this);
-        StatsUtils.init(this);
-
         super.onCreate();
 
         mHandler.onCreate();
@@ -585,7 +586,7 @@
         registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
 
         DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-
+        mStatsUtilsManager.onCreate(this /* context */);
         StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
     }
 
@@ -608,7 +609,7 @@
         mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
                 true /* allowsImplicitlySelectedSubtypes */));
         refreshPersonalizationDictionarySession(currentSettingsValues);
-        StatsUtils.onLoadSettings(currentSettingsValues);
+        mStatsUtilsManager.onLoadSettings(currentSettingsValues);
     }
 
     private void refreshPersonalizationDictionarySession(
@@ -698,7 +699,7 @@
         unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
         unregisterReceiver(mDictionaryPackInstallReceiver);
         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
-        StatsUtils.onDestroy();
+        mStatsUtilsManager.onDestroy();
         super.onDestroy();
     }
 
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 018a34d..082e1e2 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -121,10 +121,12 @@
 
 LATIN_IME_CORE_TEST_FILES := \
     defines_test.cpp \
-    suggest/core/layout/normal_distribution_2d_test.cpp \
+    suggest/core/dicnode/dic_node_pool_test.cpp \
     suggest/core/dictionary/bloom_filter_test.cpp \
+    suggest/core/layout/normal_distribution_2d_test.cpp \
     suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp \
     suggest/policyimpl/dictionary/structure/v4/content/probability_entry_test.cpp \
+    suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table_test.cpp \
     suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer_test.cpp \
     suggest/policyimpl/dictionary/utils/byte_array_utils_test.cpp \
     suggest/policyimpl/dictionary/utils/sparse_table_test.cpp \
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.cpp
index cf238ee..2bdf077 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.cpp
@@ -34,7 +34,7 @@
 bool TerminalPositionLookupTable::setTerminalPtNodePosition(
         const int terminalId, const int terminalPtNodePos) {
     if (terminalId < 0) {
-        return NOT_A_DICT_POS;
+        return false;
     }
     while (terminalId >= mSize) {
         // Write new entry.
diff --git a/native/jni/tests/suggest/core/dicnode/dic_node_pool_test.cpp b/native/jni/tests/suggest/core/dicnode/dic_node_pool_test.cpp
new file mode 100644
index 0000000..854efdf
--- /dev/null
+++ b/native/jni/tests/suggest/core/dicnode/dic_node_pool_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dicnode/dic_node_pool.h"
+
+#include <gtest/gtest.h>
+
+namespace latinime {
+namespace {
+
+TEST(DicNodePoolTest, TestGet) {
+    static const int CAPACITY = 10;
+    DicNodePool dicNodePool(CAPACITY);
+
+    for (int i = 0; i < CAPACITY; ++i) {
+        EXPECT_NE(nullptr, dicNodePool.getInstance());
+    }
+    EXPECT_EQ(nullptr, dicNodePool.getInstance());
+}
+
+TEST(DicNodePoolTest, TestPlaceBack) {
+    static const int CAPACITY = 1;
+    DicNodePool dicNodePool(CAPACITY);
+
+    DicNode *const dicNode = dicNodePool.getInstance();
+    EXPECT_NE(nullptr, dicNode);
+    EXPECT_EQ(nullptr, dicNodePool.getInstance());
+    dicNodePool.placeBackInstance(dicNode);
+    EXPECT_EQ(dicNode, dicNodePool.getInstance());
+}
+
+TEST(DicNodePoolTest, TestReset) {
+    static const int CAPACITY_SMALL = 2;
+    static const int CAPACITY_LARGE = 10;
+    DicNodePool dicNodePool(CAPACITY_SMALL);
+
+    for (int i = 0; i < CAPACITY_SMALL; ++i) {
+        EXPECT_NE(nullptr, dicNodePool.getInstance());
+    }
+    EXPECT_EQ(nullptr, dicNodePool.getInstance());
+
+    dicNodePool.reset(CAPACITY_LARGE);
+    for (int i = 0; i < CAPACITY_LARGE; ++i) {
+        EXPECT_NE(nullptr, dicNodePool.getInstance());
+    }
+    EXPECT_EQ(nullptr, dicNodePool.getInstance());
+
+    dicNodePool.reset(CAPACITY_SMALL);
+    for (int i = 0; i < CAPACITY_SMALL; ++i) {
+        EXPECT_NE(nullptr, dicNodePool.getInstance());
+    }
+    EXPECT_EQ(nullptr, dicNodePool.getInstance());
+}
+
+}  // namespace
+}  // namespace latinime
diff --git a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table_test.cpp
new file mode 100644
index 0000000..23b9c55
--- /dev/null
+++ b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table_test.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+
+namespace latinime {
+namespace {
+
+TEST(TerminalPositionLookupTableTest, TestGetFromEmptyTable) {
+    TerminalPositionLookupTable lookupTable;
+
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(0));
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(-1));
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(
+            Ver4DictConstants::NOT_A_TERMINAL_ID));
+}
+
+TEST(TerminalPositionLookupTableTest, TestSetAndGet) {
+    TerminalPositionLookupTable lookupTable;
+
+    EXPECT_TRUE(lookupTable.setTerminalPtNodePosition(10, 100));
+    EXPECT_EQ(100, lookupTable.getTerminalPtNodePosition(10));
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(9));
+    EXPECT_TRUE(lookupTable.setTerminalPtNodePosition(9, 200));
+    EXPECT_EQ(200, lookupTable.getTerminalPtNodePosition(9));
+    EXPECT_TRUE(lookupTable.setTerminalPtNodePosition(10, 300));
+    EXPECT_EQ(300, lookupTable.getTerminalPtNodePosition(10));
+    EXPECT_FALSE(lookupTable.setTerminalPtNodePosition(-1, 400));
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(-1));
+    EXPECT_FALSE(lookupTable.setTerminalPtNodePosition(Ver4DictConstants::NOT_A_TERMINAL_ID, 500));
+    EXPECT_EQ(NOT_A_DICT_POS, lookupTable.getTerminalPtNodePosition(
+            Ver4DictConstants::NOT_A_TERMINAL_ID));
+}
+
+TEST(TerminalPositionLookupTableTest, TestGC) {
+    TerminalPositionLookupTable lookupTable;
+
+    const std::vector<int> terminalIds = { 10, 20, 30 };
+    const std::vector<int> terminalPositions = { 100, 200, 300 };
+
+    for (size_t i = 0; i < terminalIds.size(); ++i) {
+        EXPECT_TRUE(lookupTable.setTerminalPtNodePosition(terminalIds[i], terminalPositions[i]));
+    }
+
+    TerminalPositionLookupTable::TerminalIdMap terminalIdMap;
+    EXPECT_TRUE(lookupTable.runGCTerminalIds(&terminalIdMap));
+
+    for (size_t i = 0; i < terminalIds.size(); ++i) {
+        EXPECT_EQ(static_cast<int>(i), terminalIdMap[terminalIds[i]])
+                << "Terminal id (" << terminalIds[i] << ") should be changed to " << i;
+        EXPECT_EQ(terminalPositions[i], lookupTable.getTerminalPtNodePosition(i));
+    }
+}
+
+}  // namespace
+}  // namespace latinime
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
index 96f9255..545b422 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
@@ -18,34 +18,33 @@
 
 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.RichInputMethodManager;
 import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
+import java.util.Locale;
+
 @MediumTest
-public class KeyboardLayoutSetActionLabelKlpTests extends KeyboardLayoutSetTestsBase {
+public class KeyboardLayoutSetActionLabelKlpTests extends KeyboardLayoutSetActionLabelLxxTests {
     @Override
     protected int getKeyboardThemeForTests() {
         return KeyboardTheme.THEME_ID_KLP;
     }
 
-    private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet,
-            final int elementId, final CharSequence label, final int iconId) {
-        final Keyboard keyboard = layoutSet.getKeyboard(elementId);
-        final Key enterKey = keyboard.getKey(Constants.CODE_ENTER);
-        assertNotNull(tag + " enter key on " + keyboard.mId, enterKey);
-        assertEquals(tag + " enter label " + enterKey, label, enterKey.getLabel());
-        assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId());
+    protected void doTestActionKeyLabel(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final int labelResId) {
+        final Locale labelLocale = subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)
+                ? null : SubtypeLocaleUtils.getSubtypeLocale(subtype);
+        doTestActionKeyLabel(tag, subtype, actionId, labelLocale, labelResId);
     }
 
-    protected void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
-            final int actionId, final int labelResId) {
+    protected void doTestActionKeyLabel(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final Locale labelLocale, final int labelResId) {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.imeOptions = actionId;
         final RunInLocale<String> job = new RunInLocale<String>() {
@@ -54,138 +53,120 @@
                 return res.getString(labelResId);
             }
         };
-        final Resources res = getContext().getResources();
-        final String label;
-        if (subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
-            // Using system locale.
-            label = res.getString(labelResId);
-        } else {
-            label = job.runInLocale(res, SubtypeLocaleUtils.getSubtypeLocale(subtype));
-        }
-        doTestActionLabel(tag, subtype, editorInfo, label);
+        final String label = job.runInLocale(getContext().getResources(), labelLocale);
+        doTestActionKeyLabel(tag, subtype, editorInfo, label);
     }
 
-    protected void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
-            final EditorInfo editorInfo, final CharSequence label) {
-        // Test text layouts.
-        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
-        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        // Test phone number layouts.
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        // Test normal number layout.
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-        // Test number password layouts.
-        editorInfo.inputType =
-                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
-        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
-        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER,
-                label, KeyboardIconsSet.ICON_UNDEFINED);
-    }
-
-    protected void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
-            final int actionId, final String iconName) {
-        final int iconId = KeyboardIconsSet.getIconId(iconName);
-        final EditorInfo editorInfo = new EditorInfo();
-        editorInfo.imeOptions = actionId;
-        // Test text layouts.
-        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
-        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, null /* label */, iconId);
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, null /* label */, iconId);
-        doTestActionKey(
-                tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, null /* label */, iconId);
-        // Test phone number layouts.
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, null /* label */, iconId);
-        doTestActionKey(
-                tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, null /* label */, iconId);
-        // Test normal number layout.
-        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
-        // Test number password layout.
-        editorInfo.inputType =
-                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
-        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
-        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
-    }
-
+    @Override
     public void testActionUnspecified() {
-        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
-            final String tag = "unspecifiled "
-                    + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED,
-                    KeyboardIconsSet.NAME_ENTER_KEY);
-        }
+        super.testActionUnspecified();
     }
 
+    @Override
     public void testActionNone() {
-        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
-            final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE,
-                    KeyboardIconsSet.NAME_ENTER_KEY);
-        }
+        super.testActionNone();
     }
 
+    @Override
     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);
+            doTestActionKeyLabel(tag, subtype, EditorInfo.IME_ACTION_GO, R.string.label_go_key);
         }
     }
 
+    @Override
     public void testActionSearch() {
-        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
-            final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH,
-                    KeyboardIconsSet.NAME_SEARCH_KEY);
-        }
+        super.testActionSearch();
     }
 
+    @Override
     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);
+            doTestActionKeyLabel(tag, subtype, EditorInfo.IME_ACTION_SEND, R.string.label_send_key);
         }
     }
 
+    @Override
     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);
+            doTestActionKeyLabel(tag, subtype, EditorInfo.IME_ACTION_NEXT, R.string.label_next_key);
         }
     }
 
+    @Override
     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);
+            doTestActionKeyLabel(tag, subtype, EditorInfo.IME_ACTION_DONE, R.string.label_done_key);
         }
     }
 
+    @Override
     public void testActionPrevious() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionLabel(
+            doTestActionKeyLabel(
                     tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, R.string.label_previous_key);
         }
     }
 
+    @Override
     public void testActionCustom() {
-        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
-            final String tag = "custom " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            final CharSequence customLabel = "customLabel";
-            final EditorInfo editorInfo = new EditorInfo();
-            editorInfo.imeOptions = EditorInfo.IME_ACTION_UNSPECIFIED;
-            editorInfo.actionLabel = customLabel;
-            doTestActionLabel(tag, subtype, editorInfo, customLabel);
-        }
+        super.testActionCustom();
+    }
+
+    private void doTestActionLabelInLocale(final InputMethodSubtype subtype,
+            final Locale labelLocale, final Locale systemLocale) {
+        final String tag = "label=" + labelLocale + " system=" + systemLocale
+                + " " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+        final RunInLocale<Void> job = new RunInLocale<Void>() {
+            @Override
+            public Void job(final Resources res) {
+                doTestActionKeyIcon(tag + " unspecified", subtype,
+                        EditorInfo.IME_ACTION_UNSPECIFIED, KeyboardIconsSet.NAME_ENTER_KEY);
+                doTestActionKeyIcon(tag + " none", subtype,
+                        EditorInfo.IME_ACTION_NONE, KeyboardIconsSet.NAME_ENTER_KEY);
+                doTestActionKeyLabel(tag + " go", subtype,
+                        EditorInfo.IME_ACTION_GO, labelLocale, R.string.label_go_key);
+                doTestActionKeyIcon(tag + " search", subtype,
+                        EditorInfo.IME_ACTION_SEARCH, KeyboardIconsSet.NAME_SEARCH_KEY);
+                doTestActionKeyLabel(tag + " send", subtype,
+                        EditorInfo.IME_ACTION_SEND, labelLocale, R.string.label_send_key);
+                doTestActionKeyLabel(tag + " next", subtype,
+                        EditorInfo.IME_ACTION_NEXT, labelLocale, R.string.label_next_key);
+                doTestActionKeyLabel(tag + " done", subtype,
+                        EditorInfo.IME_ACTION_DONE, labelLocale, R.string.label_done_key);
+                doTestActionKeyLabel(tag + " previous", subtype,
+                        EditorInfo.IME_ACTION_PREVIOUS, labelLocale, R.string.label_previous_key);
+                return null;
+            }
+        };
+        job.runInLocale(getContext().getResources(), systemLocale);
+    }
+
+    public void testActionLabelInOtherLocale() {
+        final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
+        final InputMethodSubtype italian = richImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.ITALIAN.toString(), SubtypeLocaleUtils.QWERTY);
+        // An action label should be displayed in subtype's locale regardless of the system locale.
+        doTestActionLabelInLocale(italian, Locale.ITALIAN, Locale.US);
+        doTestActionLabelInLocale(italian, Locale.ITALIAN, Locale.FRENCH);
+        doTestActionLabelInLocale(italian, Locale.ITALIAN, Locale.ITALIAN);
+        doTestActionLabelInLocale(italian, Locale.ITALIAN, Locale.JAPANESE);
+    }
+
+    public void testNoLanguageSubtypeActionLabel() {
+        final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
+        final InputMethodSubtype noLanguage = richImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+        // An action label of no language keyboard should be displayed in the system locale.
+        doTestActionLabelInLocale(noLanguage, Locale.US, Locale.US);
+        // TODO: Uncomment the following test once a bug is fixed.
+        // doTestActionLabelInLocale(noLanguage, Locale.FRENCH, Locale.FRENCH);
+        // doTestActionLabelInLocale(noLanguage, Locale.ITALIAN, Locale.ITALIAN);
+        // doTestActionLabelInLocale(noLanguage, Locale.JAPANESE, Locale.JAPANESE);
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java
index 7747ac5..25da509 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java
@@ -17,30 +17,99 @@
 package com.android.inputmethod.keyboard;
 
 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.utils.SubtypeLocaleUtils;
 
 @MediumTest
-public class KeyboardLayoutSetActionLabelLxxTests extends KeyboardLayoutSetActionLabelKlpTests {
+public class KeyboardLayoutSetActionLabelLxxTests extends KeyboardLayoutSetTestsBase {
     @Override
     protected int getKeyboardThemeForTests() {
         return KeyboardTheme.THEME_ID_LXX_DARK;
     }
 
-    @Override
+    private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet,
+            final int elementId, final CharSequence label, final int iconId) {
+        final Keyboard keyboard = layoutSet.getKeyboard(elementId);
+        final Key enterKey = keyboard.getKey(Constants.CODE_ENTER);
+        assertNotNull(tag + " enter key on " + keyboard.mId, enterKey);
+        assertEquals(tag + " enter label " + enterKey, label, enterKey.getLabel());
+        assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId());
+    }
+
+    protected void doTestActionKeyLabel(final String tag, final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final CharSequence label) {
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test number password layouts.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+    }
+
+    protected void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final String iconName) {
+        final int iconId = KeyboardIconsSet.getIconId(iconName);
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.imeOptions = actionId;
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, null /* label */, iconId);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, null /* label */, iconId);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, null /* label */, iconId);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+        // Test number password layout.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+    }
+
     public void testActionUnspecified() {
-        super.testActionUnspecified();
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "unspecifiled "
+                    + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED,
+                    KeyboardIconsSet.NAME_ENTER_KEY);
+        }
     }
 
-    @Override
     public void testActionNone() {
-        super.testActionNone();
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE,
+                    KeyboardIconsSet.NAME_ENTER_KEY);
+        }
     }
 
-    @Override
     public void testActionGo() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
@@ -49,12 +118,14 @@
         }
     }
 
-    @Override
     public void testActionSearch() {
-        super.testActionSearch();
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH,
+                    KeyboardIconsSet.NAME_SEARCH_KEY);
+        }
     }
 
-    @Override
     public void testActionSend() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
@@ -63,7 +134,6 @@
         }
     }
 
-    @Override
     public void testActionNext() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
@@ -72,7 +142,6 @@
         }
     }
 
-    @Override
     public void testActionDone() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
@@ -81,7 +150,6 @@
         }
     }
 
-    @Override
     public void testActionPrevious() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
@@ -90,8 +158,14 @@
         }
     }
 
-    @Override
     public void testActionCustom() {
-        super.testActionCustom();
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "custom " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            final CharSequence customLabel = "customLabel";
+            final EditorInfo editorInfo = new EditorInfo();
+            editorInfo.imeOptions = EditorInfo.IME_ACTION_UNSPECIFIED;
+            editorInfo.actionLabel = customLabel;
+            doTestActionKeyLabel(tag, subtype, editorInfo, customLabel);
+        }
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 0a76a9d..29aa758 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -43,7 +43,6 @@
     private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList = new ArrayList<>();
     private final ArrayList<InputMethodSubtype> mAdditionalSubtypesList = new ArrayList<>();
 
-    private Context mThemeContext;
     private int mScreenMetrics;
 
     protected abstract int getKeyboardThemeForTests();
@@ -51,12 +50,13 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics);
-
         final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById(
                 getKeyboardThemeForTests());
-        mThemeContext = new ContextThemeWrapper(mContext, keyboardTheme.mStyleId);
-        RichInputMethodManager.init(mThemeContext);
+        setContext(new ContextThemeWrapper(getContext(), keyboardTheme.mStyleId));
+
+        final Context context = getContext();
+        mScreenMetrics = context.getResources().getInteger(R.integer.config_screen_metrics);
+        RichInputMethodManager.init(context);
         final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
 
         final InputMethodInfo imi = richImm.getInputMethodInfoOfThisIme();
@@ -122,7 +122,7 @@
     protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
             final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
             final boolean languageSwitchKeyEnabled) {
-        final Context context = mThemeContext;
+        final Context context = getContext();
         final Resources res = context.getResources();
         final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
         final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);