Bring back shortcuts and add to dictionary UI

For JB and lower devices, the UI is surfaced by the IME.
Bug: 22200135
Change-Id: Icca08500ee0683e2ceb5357b0bc430cd1712220e
diff --git a/java/res/layout/user_dictionary_add_word_fullscreen.xml b/java/res/layout/user_dictionary_add_word_fullscreen.xml
index cbdfba6..baaaf7e 100644
--- a/java/res/layout/user_dictionary_add_word_fullscreen.xml
+++ b/java/res/layout/user_dictionary_add_word_fullscreen.xml
@@ -44,6 +44,26 @@
         android:columnCount="2" >
 
         <TextView
+            android:id="@+id/user_dictionary_add_shortcut_label"
+            style="?android:attr/textAppearanceSmall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start|center_vertical"
+            android:text="@string/user_dict_settings_add_shortcut_option_name" />
+
+        <EditText
+            android:id="@+id/user_dictionary_add_shortcut"
+            android:layout_width="wrap_content"
+            android:layout_gravity="fill_horizontal|center_vertical"
+            android:layout_marginBottom="8dip"
+            android:layout_marginStart="8dip"
+            android:layout_marginTop="8dip"
+            android:hint="@string/user_dict_settings_add_shortcut_hint"
+            android:imeOptions="flagNoFullscreen"
+            android:inputType="textNoSuggestions"
+            android:maxLength="@integer/config_user_dictionary_max_word_length" />
+
+        <TextView
             android:id="@+id/user_dictionary_add_locale_label"
             style="?android:attr/textAppearanceSmall"
             android:layout_width="wrap_content"
@@ -62,4 +82,4 @@
             android:visibility="gone" />
     </GridLayout>
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
new file mode 100644
index 0000000..3633d66
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.provider.UserDictionary;
+
+import java.util.Locale;
+
+public final class UserDictionaryCompatUtils {
+    @SuppressWarnings("deprecation")
+    public static void addWord(final Context context, final String word,
+            final int freq, final String shortcut, final Locale locale) {
+        if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            addWordWithShortcut(context, word, freq, shortcut, locale);
+            return;
+        }
+        // Fall back to the pre-JellyBean method.
+        final Locale currentLocale = context.getResources().getConfiguration().locale;
+        final int localeType = currentLocale.equals(locale)
+                ? UserDictionary.Words.LOCALE_TYPE_CURRENT : UserDictionary.Words.LOCALE_TYPE_ALL;
+        UserDictionary.Words.addWord(context, word, freq, localeType);
+    }
+
+    // {@link UserDictionary.Words#addWord(Context,String,int,String,Locale)} was introduced
+    // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    private static void addWordWithShortcut(final Context context, final String word,
+            final int freq, final String shortcut, final Locale locale) {
+        UserDictionary.Words.addWord(context, word, freq, shortcut, locale);
+    }
+}
+
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index cb615f3..58e6a25 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.widget.EditText;
 
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.LocaleUtils;
 
@@ -46,7 +47,10 @@
 public class UserDictionaryAddWordContents {
     public static final String EXTRA_MODE = "mode";
     public static final String EXTRA_WORD = "word";
+    public static final String EXTRA_SHORTCUT = "shortcut";
     public static final String EXTRA_LOCALE = "locale";
+    public static final String EXTRA_ORIGINAL_WORD = "originalWord";
+    public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut";
 
     public static final int MODE_EDIT = 0;
     public static final int MODE_INSERT = 1;
@@ -59,12 +63,20 @@
 
     private final int mMode; // Either MODE_EDIT or MODE_INSERT
     private final EditText mWordEditText;
+    private final EditText mShortcutEditText;
     private String mLocale;
     private final String mOldWord;
+    private final String mOldShortcut;
     private String mSavedWord;
+    private String mSavedShortcut;
 
     /* 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);
@@ -72,6 +84,17 @@
             // it's too long to be edited.
             mWordEditText.setSelection(mWordEditText.getText().length());
         }
+        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);
         updateLocale(args.getString(EXTRA_LOCALE));
@@ -80,8 +103,10 @@
     /* package */ UserDictionaryAddWordContents(final View view,
             final UserDictionaryAddWordContents oldInstanceToBeEdited) {
         mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
+        mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
         mMode = MODE_EDIT;
         mOldWord = oldInstanceToBeEdited.mSavedWord;
+        mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
         updateLocale(mLocale);
     }
 
@@ -93,6 +118,13 @@
 
     /* package */ void saveStateIntoBundle(final Bundle outState) {
         outState.putString(EXTRA_WORD, mWordEditText.getText().toString());
+        outState.putString(EXTRA_ORIGINAL_WORD, mOldWord);
+        if (null != mShortcutEditText) {
+            outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString());
+        }
+        if (null != mOldShortcut) {
+            outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut);
+        }
         outState.putString(EXTRA_LOCALE, mLocale);
     }
 
@@ -100,7 +132,7 @@
         if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
             // Mode edit: remove the old entry.
             final ContentResolver resolver = context.getContentResolver();
-            UserDictionarySettings.deleteWord(mOldWord, resolver);
+            UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
         }
         // If we are in add mode, nothing was added, so we don't need to do anything.
     }
@@ -111,31 +143,50 @@
         final ContentResolver resolver = context.getContentResolver();
         if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
             // Mode edit: remove the old entry.
-            UserDictionarySettings.deleteWord(mOldWord, resolver);
+            UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
         }
         final String newWord = mWordEditText.getText().toString();
+        final String newShortcut;
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+            newShortcut = null;
+        } else if (null == mShortcutEditText) {
+            newShortcut = null;
+        } else {
+            final String tmpShortcut = mShortcutEditText.getText().toString();
+            if (TextUtils.isEmpty(tmpShortcut)) {
+                newShortcut = null;
+            } else {
+                newShortcut = tmpShortcut;
+            }
+        }
         if (TextUtils.isEmpty(newWord)) {
             // If the word is somehow empty, don't insert it.
             return CODE_CANCEL;
         }
         mSavedWord = newWord;
-        // If the word already exists in the database, then we should not insert.
-        if (hasWord(newWord, context)) {
+        mSavedShortcut = newShortcut;
+        // If there is no shortcut, and the word already exists in the database, then we
+        // should not insert, because either A. the word exists with no shortcut, in which
+        // case the exact same thing we want to insert is already there, or B. the word
+        // exists with at least one shortcut, in which case it has priority on our word.
+        if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
             return CODE_ALREADY_PRESENT;
         }
 
-        // Disallow duplicates. If the same word is defined, remove it.
-        UserDictionarySettings.deleteWord(newWord, resolver);
+        // Disallow duplicates. If the same word with no shortcut is defined, remove it; if
+        // the same word with the same shortcut is defined, remove it; but we don't mind if
+        // there is the same word with a different, non-empty shortcut.
+        UserDictionarySettings.deleteWord(newWord, null, resolver);
+        if (!TextUtils.isEmpty(newShortcut)) {
+            // If newShortcut is empty we just deleted this, no need to do it again
+            UserDictionarySettings.deleteWord(newWord, newShortcut, resolver);
+        }
 
         // 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'.
-        final Locale locale = TextUtils.isEmpty(mLocale) ?
-                null : LocaleUtils.constructLocaleFromString(mLocale);
-        final Locale currentLocale = context.getResources().getConfiguration().locale;
-        final boolean useCurrentLocale = currentLocale.equals(locale);
-        UserDictionary.Words.addWord(context, newWord.toString(),
-                FREQUENCY_FOR_USER_DICTIONARY_ADDS, null /* shortcut */,
-                useCurrentLocale ? Locale.getDefault() : null);
+        UserDictionaryCompatUtils.addWord(context, newWord.toString(),
+                FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ?
+                        null : LocaleUtils.constructLocaleFromString(mLocale));
 
         return CODE_WORD_ADDED;
     }
@@ -232,3 +283,4 @@
         return mLocale;
     }
 }
+
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
new file mode 100644
index 0000000..a8781d7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.userdictionary;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.LocaleRenderer;
+import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker.LocationChangedListener;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+// Caveat: This class is basically taken from
+// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
+// in order to deal with some devices that have issues with the user dictionary handling
+
+/**
+ * Fragment to add a word/shortcut to the user dictionary.
+ *
+ * As opposed to the UserDictionaryActivity, this is only invoked within Settings
+ * from the UserDictionarySettings.
+ */
+public class UserDictionaryAddWordFragment extends Fragment
+        implements AdapterView.OnItemSelectedListener, LocationChangedListener {
+
+    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;
+    private boolean mIsDeleting = false;
+
+    @Override
+    public void onActivityCreated(final Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setHasOptionsMenu(true);
+        getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary);
+        // Keep the instance so that we remember mContents when configuration changes (eg rotation)
+        setRetainInstance(true);
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+            final Bundle savedState) {
+        mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
+        mIsDeleting = false;
+        // If we have a non-null mContents object, it's the old value before a configuration
+        // change (eg rotation) so we need to use its values. Otherwise, read from the arguments.
+        if (null == mContents) {
+            mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
+        } else {
+            // We create a new mContents object to account for the new situation : a word has
+            // been added to the user dictionary when we started rotating, and we are now editing
+            // it. That means in particular if the word undergoes any change, the old version should
+            // be updated, so the mContents object needs to switch to EDIT mode if it was in
+            // INSERT mode.
+            mContents = new UserDictionaryAddWordContents(mRootView,
+                    mContents /* oldInstanceToBeEdited */);
+        }
+        getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(
+                getActivity(), mContents.getCurrentUserDictionaryLocale()));
+        return mRootView;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+        final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
+                R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add);
+        actionItemAdd.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0,
+                R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete);
+        actionItemDelete.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+    }
+
+    /**
+     * Callback for the framework when a menu option is pressed.
+     *
+     * @param item 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;
+            getActivity().onBackPressed();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // We are being shown: display the word
+        updateSpinner();
+    }
+
+    private void updateSpinner() {
+        final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity());
+
+        final Spinner localeSpinner =
+                (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale);
+        final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
+                getActivity(), android.R.layout.simple_spinner_item, localesList);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        localeSpinner.setAdapter(adapter);
+        localeSpinner.setOnItemSelectedListener(this);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        // We are being hidden: commit changes to the user dictionary, unless we were deleting it
+        if (!mIsDeleting) {
+            mContents.apply(getActivity(), null);
+        }
+    }
+
+    @Override
+    public void onItemSelected(final AdapterView<?> parent, final View view, final int pos,
+            final long id) {
+        final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos);
+        if (locale.isMoreLanguages()) {
+            PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity();
+            preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(), true);
+        } else {
+            mContents.updateLocale(locale.getLocaleString());
+        }
+    }
+
+    @Override
+    public void onNothingSelected(final AdapterView<?> parent) {
+        // I'm not sure we can come here, but if we do, that's the right thing to do.
+        final Bundle args = getArguments();
+        mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE));
+    }
+
+    // Called by the locale picker
+    @Override
+    public void onLocaleSelected(final Locale locale) {
+        mContents.updateLocale(locale.toString());
+        getActivity().onBackPressed();
+    }
+}
+
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 57347ce..f7940e3 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
-import android.os.Build;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
@@ -75,7 +74,7 @@
         } finally {
             cursor.close();
         }
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
             // For ICS, we need to show "For all languages" in case that the keyboard locale
             // is different from the system locale
             localeSet.add("");
@@ -163,3 +162,4 @@
         createUserDictSettings(getPreferenceScreen());
     }
 }
+
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index bd35723..d9339d9 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -16,12 +16,6 @@
 
 package com.android.inputmethod.latin.userdictionary;
 
-import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_LOCALE;
-import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_MODE;
-import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.EXTRA_WORD;
-import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.MODE_EDIT;
-import static com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.MODE_INSERT;
-
 import com.android.inputmethod.latin.R;
 
 import android.app.ListFragment;
@@ -31,7 +25,7 @@
 import android.database.Cursor;
 import android.os.Build;
 import android.os.Bundle;
-import android.provider.UserDictionary.Words;
+import android.provider.UserDictionary;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -54,8 +48,62 @@
 
 public class UserDictionarySettings extends ListFragment {
 
+    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 =
+            UserDictionary.Words.LOCALE + "=?";
+    private static final String QUERY_SELECTION_ALL_LOCALES =
+            UserDictionary.Words.LOCALE + " is null";
+
+    private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD
+            + "=? AND " + UserDictionary.Words.SHORTCUT + "=?";
+    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;
+
     private Cursor mCursor;
-    private String mLocale;
+
+    protected String mLocale;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -112,6 +160,19 @@
                 UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
     }
 
+    @Override
+    public void onResume() {
+        super.onResume();
+        ListAdapter adapter = getListView().getAdapter();
+        if (adapter != null && adapter instanceof MyAdapter) {
+            // The list view is forced refreshed here. This allows the changes done 
+            // in UserDictionaryAddWordFragment (update/delete/insert) to be seen when 
+            // user goes back to this view. 
+            MyAdapter listAdapter = (MyAdapter) adapter;
+            listAdapter.notifyDataSetChanged();
+        }
+    }
+
     @SuppressWarnings("deprecation")
     private Cursor createCursor(final String locale) {
         // Locale can be any of:
@@ -124,60 +185,54 @@
         // TODO: it should be easy to make this more readable by making the special values
         // human-readable, like "all_locales" and "current_locales" strings, provided they
         // can be guaranteed not to match locales that may exist.
-        if (TextUtils.isEmpty(locale)) {
+        if ("".equals(locale)) {
             // Case-insensitive sort
-            return getActivity().managedQuery(
-                    Words.CONTENT_URI,
-                    new String[] { Words._ID, Words.WORD },
-                    Words.LOCALE + " is null",
-                    null,
-                    "UPPER(" + Words.WORD + ")");
+            return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+                    QUERY_SELECTION_ALL_LOCALES, null,
+                    "UPPER(" + UserDictionary.Words.WORD + ")");
         }
-        return getActivity().managedQuery(
-                Words.CONTENT_URI,
-                new String[] { Words._ID, Words.WORD },
-                Words.LOCALE + "=?",
-                new String[] { locale },
-                "UPPER(" + Words.WORD + ")");
+        final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
+        return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+                QUERY_SELECTION, new String[] { queryLocale },
+                "UPPER(" + UserDictionary.Words.WORD + ")");
     }
 
     private ListAdapter createAdapter() {
-        return new MyAdapter(
-                getActivity(),
-                R.layout.user_dictionary_item,
-                mCursor,
-                new String[] { Words.WORD },
-                new int[] { android.R.id.text1 });
+        return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor,
+                ADAPTER_FROM, ADAPTER_TO);
     }
 
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
         final String word = getWord(position);
+        final String shortcut = getShortcut(position);
         if (word != null) {
-            showAddOrEditDialog(word);
+            showAddOrEditDialog(word, shortcut);
         }
     }
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+        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.
+                // for an entry. This new "locale"-aware API has been added in conjunction
+                // with the shortcut API.
                 return;
             }
         }
-        menu.add(0, Menu.FIRST, 0, R.string.user_dict_settings_add_menu_title)
-                .setIcon(R.drawable.ic_menu_add)
-                .setShowAsAction(
-                        MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+        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);
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == Menu.FIRST) {
-            showAddOrEditDialog(null);
+        if (item.getItemId() == OPTIONS_MENU_ADD) {
+            showAddOrEditDialog(null, null);
             return true;
         }
         return false;
@@ -186,13 +241,20 @@
     /**
      * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit.
      * @param editingWord the word to edit, or null if it's an add.
+     * @param editingShortcut the shortcut for this entry, or null if none.
      */
-    private void showAddOrEditDialog(final String editingWord) {
+    private void showAddOrEditDialog(final String editingWord, final String editingShortcut) {
         final Bundle args = new Bundle();
-        args.putInt(EXTRA_MODE, editingWord == null ? MODE_INSERT : MODE_EDIT);
-        args.putString(EXTRA_WORD, editingWord);
-        args.putString(EXTRA_LOCALE, mLocale);
-        getActivity();
+        args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord
+                ? UserDictionaryAddWordContents.MODE_INSERT
+                : UserDictionaryAddWordContents.MODE_EDIT);
+        args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
+        args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
+        args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale);
+        android.preference.PreferenceActivity pa =
+                (android.preference.PreferenceActivity)getActivity();
+        pa.startPreferencePanel(UserDictionaryAddWordFragment.class.getName(),
+                args, R.string.user_dict_settings_add_dialog_title, null, null, 0);
     }
 
     private String getWord(final int position) {
@@ -201,11 +263,35 @@
         // Handle a possible race-condition
         if (mCursor.isAfterLast()) return null;
 
-        return mCursor.getString(mCursor.getColumnIndexOrThrow(Words.WORD));
+        return mCursor.getString(
+                mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
     }
 
-    public static void deleteWord(final String word, final ContentResolver resolver) {
-        resolver.delete(Words.CONTENT_URI, Words.WORD + "=?", new String[] { word });
+    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
+        if (mCursor.isAfterLast()) return null;
+
+        return mCursor.getString(
+                mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT));
+    }
+
+    public static void deleteWord(final String word, final String shortcut,
+            final ContentResolver resolver) {
+        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 });
+        } else {
+            resolver.delete(
+                    UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT,
+                    new String[] { word, shortcut });
+        }
     }
 
     private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
@@ -215,7 +301,22 @@
 
             @Override
             public boolean setViewValue(final View v, final Cursor c, final int columnIndex) {
-                // just let SimpleCursorAdapter set the view values
+                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)) {
+                        v.setVisibility(View.GONE);
+                    } else {
+                        ((TextView)v).setText(shortcut);
+                        v.setVisibility(View.VISIBLE);
+                    }
+                    v.invalidate();
+                    return true;
+                }
+
                 return false;
             }
         };
@@ -226,7 +327,7 @@
 
             if (null != c) {
                 final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet);
-                final int wordColIndex = c.getColumnIndexOrThrow(Words.WORD);
+                final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
                 mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
             }
             setViewBinder(mViewBinder);
@@ -248,3 +349,4 @@
         }
     }
 }
+
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index c87a7c0..147e57b 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -29,6 +29,7 @@
 import com.android.inputmethod.latin.settings.SettingsFragment;
 import com.android.inputmethod.latin.settings.ThemeSettingsFragment;
 import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment;
+import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragment;
 import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
 import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
@@ -51,6 +52,7 @@
         sLatinImeFragments.add(DebugSettingsFragment.class.getName());
         sLatinImeFragments.add(SettingsFragment.class.getName());
         sLatinImeFragments.add(SpellCheckerSettingsFragment.class.getName());
+        sLatinImeFragments.add(UserDictionaryAddWordFragment.class.getName());
         sLatinImeFragments.add(UserDictionaryList.class.getName());
         sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
         sLatinImeFragments.add(UserDictionarySettings.class.getName());