Merge "[ZF2] Read settings for profanity filtering."
diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz
index 253c515..86f9506 100644
--- a/dictionaries/ru_wordlist.combined.gz
+++ b/dictionaries/ru_wordlist.combined.gz
Binary files differ
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 17d11c0..2273394 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -29,13 +29,13 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
 
-    <application android:label="@string/aosp_android_keyboard_ime_name"
+    <application android:label="@string/english_ime_name"
             android:icon="@mipmap/ic_ime_settings"
             android:killAfterRestore="false"
             android:supportsRtl="true">
 
         <service android:name="LatinIME"
-                android:label="@string/aosp_android_keyboard_ime_name"
+                android:label="@string/english_ime_name"
                 android:permission="android.permission.BIND_INPUT_METHOD">
             <intent-filter>
                 <action android:name="android.view.InputMethod" />
@@ -44,7 +44,7 @@
         </service>
 
         <service android:name=".spellcheck.AndroidSpellCheckerService"
-                 android:label="@string/aosp_spell_checker_service_name"
+                 android:label="@string/spell_checker_service_name"
                  android:permission="android.permission.BIND_TEXT_SERVICE">
             <intent-filter>
                 <action android:name="android.service.textservice.SpellCheckerService" />
@@ -53,7 +53,7 @@
         </service>
 
         <activity android:name=".setup.SetupActivity"
-                android:label="@string/aosp_android_keyboard_ime_name"
+                android:label="@string/english_ime_name"
                 android:icon="@drawable/ic_setup_wizard">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -69,7 +69,7 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name="SettingsActivity" android:label="@string/aosp_android_keyboard_ime_settings"
+        <activity android:name="SettingsActivity" android:label="@string/english_ime_settings"
                   android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -77,7 +77,7 @@
         </activity>
 
         <activity android:name="com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsActivity"
-                  android:label="@string/aosp_android_spell_checker_service_settings">
+                  android:label="@string/android_spell_checker_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
diff --git a/java/res/layout/user_dictionary_item.xml b/java/res/layout/user_dictionary_item.xml
index 3062ed8..56bad77 100644
--- a/java/res/layout/user_dictionary_item.xml
+++ b/java/res/layout/user_dictionary_item.xml
@@ -13,40 +13,39 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:background="?android:attr/selectableItemBackground"
     android:gravity="center_vertical"
-    android:paddingEnd="?android:attr/scrollbarSize"
-    android:background="?android:attr/selectableItemBackground" >
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/scrollbarSize" >
 
-  <RelativeLayout android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:layout_marginStart="15dip"
-                  android:layout_marginEnd="6dip"
-                  android:layout_marginTop="6dip"
-                  android:layout_marginBottom="6dip"
-                  android:layout_weight="1">
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="6dip"
+        android:layout_weight="1" >
 
-    <TextView android:id="@+android:id/text1"
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:singleLine="true"
-              android:textAppearance="?android:attr/textAppearanceMedium"
-              android:ellipsize="marquee"
-              android:fadingEdge="horizontal" />
+        <TextView
+            android:id="@+android:id/text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
 
-    <TextView android:id="@+android:id/text2"
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:layout_below="@android:id/text1"
-              android:layout_alignStart="@android:id/text1"
-              android:textAppearance="?android:attr/textAppearanceSmall"
-              android:textColor="?android:attr/textColorSecondary"
-              android:maxLines="1" />
-
-  </RelativeLayout>
+        <TextView
+            android:id="@+android:id/text2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/text1"
+            android:layout_below="@android:id/text1"
+            android:maxLines="1"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:visibility="gone" />
+    </RelativeLayout>
 
 </LinearLayout>
diff --git a/java/res/raw/empty.dict b/java/res/raw/empty.dict
index da1bf96..80ce066 100644
--- a/java/res/raw/empty.dict
+++ b/java/res/raw/empty.dict
@@ -1 +1 @@
-x±
\ No newline at end of file
+›Á:þ
\ No newline at end of file
diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict
index 9fd6133..9bb7805 100644
--- a/java/res/raw/main_ru.dict
+++ b/java/res/raw/main_ru.dict
Binary files differ
diff --git a/java/res/values/strings-appname.xml b/java/res/values/strings-appname.xml
new file mode 100644
index 0000000..46d8c44
--- /dev/null
+++ b/java/res/values/strings-appname.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <!-- Application name for opensource Android keyboard. AOSP(Android Open Source Project) should not be translated.
+         This resource should be copied from msgid="8250992613616792321" -->
+    <string name="english_ime_name">Android Keyboard (AOSP)</string>
+
+    <!-- Name of Android spell checker service. AOSP(Android Open Source Project) should not be translated.
+         This resource should be copied from msgid="511950477199948048" -->
+    <string name="spell_checker_service_name">Android Spell Checker (AOSP)</string>
+
+    <!-- Title for Android Keyboard settings screen. AOSP(Android Open Source Project) should not be translated.
+         This resource should be copied from msgid="423615877174850267" -->
+    <string name="english_ime_settings">Android Keyboard Settings (AOSP)</string>
+
+    <!-- Title for the spell checking service settings screen. AOSP(Android Open Source Project) should not be translated.
+         This resource should be copied from msgid="2970535894327288421" -->
+    <string name="android_spell_checker_settings">Android Spell Checker Settings (AOSP)</string>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 1483939..9bf4ddd 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -18,18 +18,6 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Application name for opensource Android keyboard. AOSP(Android Open Source Project) should not be translated. -->
-    <string name="aosp_android_keyboard_ime_name">Android Keyboard (AOSP)</string>
-
-    <!-- Title for Android Keyboard settings screen. AOSP(Android Open Source Project) should not be translated. -->
-    <string name="aosp_android_keyboard_ime_settings">Android Keyboard Settings (AOSP)</string>
-
-    <!-- Name of Android spell checker service. AOSP(Android Open Source Project) should not be translated. -->
-    <string name="aosp_spell_checker_service_name">Android Spell Checker (AOSP)</string>
-
-    <!-- Title for the spell checking service settings screen. AOSP(Android Open Source Project) should not be translated. -->
-    <string name="aosp_android_spell_checker_service_settings">Android Spell Checker Settings (AOSP)</string>
-
     <!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] -->
     <string name="english_ime_input_options">Input options</string>
 
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 436e080..dad7e20 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -94,8 +94,10 @@
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
         <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
         <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
+        <!-- Remove animations for now because it could drain a non-negligible amount of battery while typing.
         <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
+        -->
         <!-- Common attributes of MainKeyboardView for gesture typing detection and recognition -->
         <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
         <item name="gestureStaticTimeThresholdAfterFastTyping">@integer/config_gesture_static_time_threshold_after_fast_typing</item>
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
index ff6561c..a0d7641 100644
--- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -28,6 +28,7 @@
     private static final Method METHOD_addWord = CompatUtils.getMethod(Words.class, "addWord",
             Context.class, String.class, Integer.TYPE, String.class, Locale.class);
 
+    @SuppressWarnings("deprecation")
     public static void addWord(final Context context, final String word, final int freq,
             final String shortcut, final Locale locale) {
         if (hasNewerAddWord()) {
@@ -39,13 +40,18 @@
             if (null == locale) {
                 localeType = Words.LOCALE_TYPE_ALL;
             } else {
-                localeType = Words.LOCALE_TYPE_CURRENT;
+                final Locale currentLocale = context.getResources().getConfiguration().locale;
+                if (locale.equals(currentLocale)) {
+                    localeType = Words.LOCALE_TYPE_CURRENT;
+                } else {
+                    localeType = Words.LOCALE_TYPE_ALL;
+                }
             }
             Words.addWord(context, word, freq, localeType);
         }
     }
 
-    private static final boolean hasNewerAddWord() {
+    public static final boolean hasNewerAddWord() {
         return null != METHOD_addWord;
     }
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 196c211..391a15c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -16,9 +16,12 @@
 
 package com.android.inputmethod.dictionarypack;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.widget.Button;
 import android.widget.FrameLayout;
 
@@ -115,22 +118,35 @@
     }
 
     private void animateButtonPosition(final int oldStatus, final int newStatus) {
-        animateButton(getButton(oldStatus), ANIMATION_OUT);
-        animateButton(getButton(newStatus), ANIMATION_IN);
+        final View oldButton = getButton(oldStatus);
+        final View newButton = getButton(newStatus);
+        if (null != oldButton && null != newButton) {
+            // Transition between two buttons : animate out, then in
+            animateButton(oldButton, ANIMATION_OUT).setListener(
+                    new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(final Animator animation) {
+                            animateButton(newButton, ANIMATION_IN);
+                        }
+                    });
+        } else if (null != oldButton) {
+            animateButton(oldButton, ANIMATION_OUT);
+        } else if (null != newButton) {
+            animateButton(newButton, ANIMATION_IN);
+        }
     }
 
     public void setInternalOnClickListener(final OnClickListener listener) {
         mOnClickListener = listener;
     }
 
-    private void animateButton(final View button, final int direction) {
-        if (null == button) return;
+    private ViewPropertyAnimator animateButton(final View button, final int direction) {
         final float outerX = getWidth();
         final float innerX = button.getX() - button.getTranslationX();
         if (ANIMATION_IN == direction) {
-            button.animate().translationX(0);
+            return button.animate().translationX(0);
         } else {
-            button.animate().translationX(outerX - innerX);
+            return button.animate().translationX(outerX - innerX);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index 8975d69..de3711c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -44,6 +44,12 @@
         return state.mOpen;
     }
 
+    public int getStatus(final String wordlistId) {
+        final State state = mWordlistToState.get(wordlistId);
+        if (null == state) return MetadataDbHelper.STATUS_UNKNOWN;
+        return state.mStatus;
+    }
+
     public void setOpen(final String wordlistId, final int status) {
         final State newState;
         final State state = mWordlistToState.get(wordlistId);
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 2d15bed..1cf9196 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -191,8 +191,20 @@
         ((ViewGroup)view).setLayoutTransition(null);
         final ButtonSwitcher buttonSwitcher =
                 (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
-        buttonSwitcher.setStatusAndUpdateVisuals(mInterfaceState.isOpen(mWordlistId) ?
-                getButtonSwitcherStatus(mStatus) : ButtonSwitcher.STATUS_NO_BUTTON);
+        if (mInterfaceState.isOpen(mWordlistId)) {
+            // The button is open.
+            final int previousStatus = mInterfaceState.getStatus(mWordlistId);
+            buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(previousStatus));
+            if (previousStatus != mStatus) {
+                // We come here if the status has changed since last time. We need to animate
+                // the transition.
+                buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
+                mInterfaceState.setOpen(mWordlistId, mStatus);
+            }
+        } else {
+            // The button is closed.
+            buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
+        }
         buttonSwitcher.setInternalOnClickListener(mActionButtonClickHandler);
         view.setOnClickListener(mPreferenceClickHandler);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index d74644d..8d4beec 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -333,6 +333,10 @@
 
         private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
                 final ObjectAnimator animatorToStart) {
+            if (animatorToCancel == null || animatorToStart == null) {
+                // TODO: Stop using null as a no-operation animator.
+                return;
+            }
             float startFraction = 0.0f;
             if (animatorToCancel.isStarted()) {
                 animatorToCancel.cancel();
@@ -581,6 +585,7 @@
 
     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
         if (resId == 0) {
+            // TODO: Stop returning null.
             return null;
         }
         final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
@@ -1054,6 +1059,7 @@
 
     @Override
     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
+        locatePreviewPlacerView();
         if (isShowingMoreKeysPanel()) {
             onDismissMoreKeysPanel();
         }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index c78064b..830cae9 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -25,7 +25,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.media.AudioManager;
-import android.os.Build;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
@@ -46,7 +45,7 @@
 
 public final class SettingsFragment extends InputMethodSettingsFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final boolean DBG_USE_INTERNAL_USER_SETTINGS = false;
+    private static final boolean DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS = false;
 
     private ListPreference mVoicePreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
@@ -202,13 +201,8 @@
         final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
         final ResolveInfo ri = context.getPackageManager().resolveActivity(
                 editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
-        if (DBG_USE_INTERNAL_USER_SETTINGS || ri == null) {
-            // TODO: Support ICS
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                updateUserDictionaryPreference(editPersonalDictionary);
-            } else {
-                removePreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY, getPreferenceScreen());
-            }
+        if (DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS || ri == null) {
+            updateUserDictionaryPreference(editPersonalDictionary);
         }
 
         if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index 578787d..29ee63d 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -236,8 +236,7 @@
         final Intent intent = new Intent();
         intent.setClass(this, SettingsActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                | Intent.FLAG_ACTIVITY_CLEAR_TOP
-                | Intent.FLAG_ACTIVITY_NO_HISTORY);
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         startActivity(intent);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index f0dc526..2b6fda3 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin.userdictionary;
 
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 
@@ -68,18 +69,28 @@
     /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
         mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
         mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+            mShortcutEditText.setVisibility(View.GONE);
+            view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE);
+        }
         final String word = args.getString(EXTRA_WORD);
         if (null != word) {
             mWordEditText.setText(word);
             mWordEditText.setSelection(word.length());
         }
-        final String shortcut = args.getString(EXTRA_SHORTCUT);
-        if (null != shortcut && null != mShortcutEditText) {
-            mShortcutEditText.setText(shortcut);
+        final String shortcut;
+        if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+            shortcut = args.getString(EXTRA_SHORTCUT);
+            if (null != shortcut && null != mShortcutEditText) {
+                mShortcutEditText.setText(shortcut);
+            }
+            mOldShortcut = args.getString(EXTRA_SHORTCUT);
+        } else {
+            shortcut = null;
+            mOldShortcut = null;
         }
         mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT
         mOldWord = args.getString(EXTRA_WORD);
-        mOldShortcut = args.getString(EXTRA_SHORTCUT);
         updateLocale(args.getString(EXTRA_LOCALE));
     }
 
@@ -110,7 +121,8 @@
         // If we are in add mode, nothing was added, so we don't need to do anything.
     }
 
-    /* package */ int apply(final Context context, final Bundle outParameters) {
+    /* package */
+    int apply(final Context context, final Bundle outParameters) {
         if (null != outParameters) saveStateIntoBundle(outParameters);
         final ContentResolver resolver = context.getContentResolver();
         if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
@@ -119,7 +131,9 @@
         }
         final String newWord = mWordEditText.getText().toString();
         final String newShortcut;
-        if (null == mShortcutEditText) {
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+            newShortcut = null;
+        } else if (null == mShortcutEditText) {
             newShortcut = null;
         } else {
             final String tmpShortcut = mShortcutEditText.getText().toString();
@@ -150,9 +164,9 @@
 
         // In this class we use the empty string to represent 'all locales' and mLocale cannot
         // be null. However the addWord method takes null to mean 'all locales'.
-        UserDictionary.Words.addWord(context, newWord.toString(),
-                FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut,
-                TextUtils.isEmpty(mLocale) ? null : LocaleUtils.constructLocaleFromString(mLocale));
+        UserDictionaryCompatUtils.addWord(context, newWord.toString(),
+                FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ?
+                        null : LocaleUtils.constructLocaleFromString(mLocale));
 
         return CODE_WORD_ADDED;
     }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 7970a36..5f4c446 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -49,7 +49,8 @@
 public class UserDictionaryAddWordFragment extends Fragment
         implements AdapterView.OnItemSelectedListener, LocationChangedListener {
 
-    private static final int OPTIONS_MENU_DELETE = Menu.FIRST;
+    private static final int OPTIONS_MENU_ADD = Menu.FIRST;
+    private static final int OPTIONS_MENU_DELETE = Menu.FIRST + 1;
 
     private UserDictionaryAddWordContents mContents;
     private View mRootView;
@@ -73,21 +74,29 @@
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        MenuItem actionItem = menu.add(0, OPTIONS_MENU_DELETE, 0,
+        final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0,
                 R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete);
-        actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
-                MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        actionItemDelete.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
+                R.string.user_dict_settings_delete).setIcon(R.drawable.ic_menu_add);
+        actionItemAdd.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
     }
 
     /**
      * Callback for the framework when a menu option is pressed.
      *
-     * This class only supports the delete menu item.
      * @param MenuItem the item that was pressed
      * @return false to allow normal menu processing to proceed, true to consume it here
      */
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == OPTIONS_MENU_ADD) {
+            // added the entry in "onPause"
+            getActivity().onBackPressed();
+            return true;
+        }
         if (item.getItemId() == OPTIONS_MENU_DELETE) {
             mContents.delete(getActivity());
             mIsDeleting = true;
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 2d147aa..6e64882 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -27,6 +27,7 @@
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
 import android.provider.UserDictionary;
+import android.text.TextUtils;
 
 import java.util.Locale;
 import java.util.TreeSet;
@@ -52,16 +53,26 @@
                 new String[] { UserDictionary.Words.LOCALE },
                 null, null, null);
         final TreeSet<String> localeList = new TreeSet<String>();
+        boolean addedAllLocale = false;
         if (null == cursor) {
             // The user dictionary service is not present or disabled. Return null.
             return null;
         } else if (cursor.moveToFirst()) {
             final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
             do {
-                String locale = cursor.getString(columnIndex);
-                localeList.add(null != locale ? locale : "");
+                final String locale = cursor.getString(columnIndex);
+                final boolean allLocale = TextUtils.isEmpty(locale);
+                localeList.add(allLocale ? "" : locale);
+                if (allLocale) {
+                    addedAllLocale = true;
+                }
             } while (cursor.moveToNext());
         }
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED && !addedAllLocale) {
+            // For ICS, we need to show "For all languages" in case that the keyboard locale
+            // is different from the system locale
+            localeList.add("");
+        }
         localeList.add(Locale.getDefault().toString());
         return localeList;
     }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index a250c24..36bc5ba 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
+import android.os.Build;
 import android.os.Bundle;
 import android.provider.UserDictionary;
 import android.text.TextUtils;
@@ -47,13 +48,42 @@
 
 public class UserDictionarySettings extends ListFragment {
 
-    private static final String[] QUERY_PROJECTION = {
-        UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
-    };
+    public static final boolean IS_SHORTCUT_API_SUPPORTED =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
+    private static final String[] QUERY_PROJECTION_SHORTCUT_UNSUPPORTED =
+            { UserDictionary.Words._ID, UserDictionary.Words.WORD};
+    private static final String[] QUERY_PROJECTION_SHORTCUT_SUPPORTED =
+            { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT};
+    private static final String[] QUERY_PROJECTION =
+            IS_SHORTCUT_API_SUPPORTED ?
+                    QUERY_PROJECTION_SHORTCUT_SUPPORTED : QUERY_PROJECTION_SHORTCUT_UNSUPPORTED;
 
     // The index of the shortcut in the above array.
     private static final int INDEX_SHORTCUT = 2;
 
+    private static final String[] ADAPTER_FROM_SHORTCUT_UNSUPPORTED = {
+        UserDictionary.Words.WORD,
+    };
+
+    private static final String[] ADAPTER_FROM_SHORTCUT_SUPPORTED = {
+        UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
+    };
+
+    private static final String[] ADAPTER_FROM = IS_SHORTCUT_API_SUPPORTED ?
+            ADAPTER_FROM_SHORTCUT_SUPPORTED : ADAPTER_FROM_SHORTCUT_UNSUPPORTED;
+
+    private static final int[] ADAPTER_TO_SHORTCUT_UNSUPPORTED = {
+        android.R.id.text1,
+    };
+
+    private static final int[] ADAPTER_TO_SHORTCUT_SUPPORTED = {
+        android.R.id.text1, android.R.id.text2
+    };
+
+    private static final int[] ADAPTER_TO = IS_SHORTCUT_API_SUPPORTED ?
+            ADAPTER_TO_SHORTCUT_SUPPORTED : ADAPTER_TO_SHORTCUT_UNSUPPORTED;
+
     // Either the locale is empty (means the word is applicable to all locales)
     // or the word equals our current locale
     private static final String QUERY_SELECTION =
@@ -66,6 +96,8 @@
     private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD
             + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR "
             + UserDictionary.Words.SHORTCUT + "=''";
+    private static final String DELETE_SELECTION_SHORTCUT_UNSUPPORTED =
+            UserDictionary.Words.WORD + "=?";
 
     private static final int OPTIONS_MENU_ADD = Menu.FIRST;
 
@@ -146,10 +178,8 @@
     }
 
     private ListAdapter createAdapter() {
-        return new MyAdapter(getActivity(),
-                R.layout.user_dictionary_item, mCursor,
-                new String[] { UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT },
-                new int[] { android.R.id.text1, android.R.id.text2 }, this);
+        return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor,
+                ADAPTER_FROM, ADAPTER_TO, this);
     }
 
     @Override
@@ -163,11 +193,20 @@
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+            final Locale systemLocale = getResources().getConfiguration().locale;
+            if (!TextUtils.isEmpty(mLocale) && !mLocale.equals(systemLocale.toString())) {
+                // Hide the add button for ICS because it doesn't support specifying a locale
+                // for an entry. This new "locale"-aware API has been added in conjunction
+                // with the shortcut API.
+                return;
+            }
+        }
         MenuItem actionItem =
                 menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
                 .setIcon(R.drawable.ic_menu_add);
-        actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
-                MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        actionItem.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
     }
 
     @Override
@@ -209,6 +248,7 @@
     }
 
     private String getShortcut(final int position) {
+        if (!IS_SHORTCUT_API_SUPPORTED) return null;
         if (null == mCursor) return null;
         mCursor.moveToPosition(position);
         // Handle a possible race-condition
@@ -220,7 +260,10 @@
 
     public static void deleteWord(final String word, final String shortcut,
             final ContentResolver resolver) {
-        if (TextUtils.isEmpty(shortcut)) {
+        if (!IS_SHORTCUT_API_SUPPORTED) {
+            resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_SHORTCUT_UNSUPPORTED,
+                    new String[] { word });
+        } else if (TextUtils.isEmpty(shortcut)) {
             resolver.delete(
                     UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT,
                     new String[] { word });
@@ -239,6 +282,10 @@
 
             @Override
             public boolean setViewValue(View v, Cursor c, int columnIndex) {
+                if (!IS_SHORTCUT_API_SUPPORTED) {
+                    // just let SimpleCursorAdapter set the view values
+                    return false;
+                }
                 if (columnIndex == INDEX_SHORTCUT) {
                     final String shortcut = c.getString(INDEX_SHORTCUT);
                     if (TextUtils.isEmpty(shortcut)) {
diff --git a/native/jni/src/suggest/core/policy/traversal.h b/native/jni/src/suggest/core/policy/traversal.h
index 02c358a..d3146da 100644
--- a/native/jni/src/suggest/core/policy/traversal.h
+++ b/native/jni/src/suggest/core/policy/traversal.h
@@ -28,7 +28,8 @@
     virtual int getMaxPointerCount() const = 0;
     virtual bool allowsErrorCorrections(const DicNode *const dicNode) const = 0;
     virtual bool isOmission(const DicTraverseSession *const traverseSession,
-            const DicNode *const dicNode, const DicNode *const childDicNode) const = 0;
+            const DicNode *const dicNode, const DicNode *const childDicNode,
+            const bool allowsErrorCorrections) const = 0;
     virtual bool isSpaceSubstitutionTerminal(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const = 0;
     virtual bool isSpaceOmissionTerminal(const DicTraverseSession *const traverseSession,
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index b1a5ff2..4f94a9a 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -296,8 +296,8 @@
                     correctionDicNode.advanceDigraphIndex();
                     processDicNodeAsDigraph(traverseSession, &correctionDicNode);
                 }
-                if (allowsErrorCorrections
-                        && TRAVERSAL->isOmission(traverseSession, &dicNode, childDicNode)) {
+                if (TRAVERSAL->isOmission(traverseSession, &dicNode, childDicNode,
+                        allowsErrorCorrections)) {
                     // TODO: (Gesture) Change weight between omission and substitution errors
                     // TODO: (Gesture) Terminal node should not be handled as omission
                     correctionDicNode.initByCopy(childDicNode);
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 9f83474..fb1fb79 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -43,10 +43,17 @@
     }
 
     AK_FORCE_INLINE bool isOmission(const DicTraverseSession *const traverseSession,
-            const DicNode *const dicNode, const DicNode *const childDicNode) const {
+            const DicNode *const dicNode, const DicNode *const childDicNode,
+            const bool allowsErrorCorrections) const {
         if (!CORRECT_OMISSION) {
             return false;
         }
+        // Note: Always consider intentional omissions (like apostrophes) since they are common.
+        const bool canConsiderOmission =
+                allowsErrorCorrections || childDicNode->canBeIntentionalOmission();
+        if (!canConsiderOmission) {
+            return false;
+        }
         const int inputSize = traverseSession->getInputSize();
         // TODO: Don't refer to isCompletion?
         if (dicNode->isCompletion(inputSize)) {