diff --git a/java/res/drawable-hdpi/ic_menu_add.png b/java/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 000000000..4b68f52ad
Binary files /dev/null and b/java/res/drawable-hdpi/ic_menu_add.png differ
diff --git a/java/res/drawable-mdpi/ic_menu_add.png b/java/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 000000000..15ffadd36
Binary files /dev/null and b/java/res/drawable-mdpi/ic_menu_add.png differ
diff --git a/java/res/drawable-xhdpi/ic_menu_add.png b/java/res/drawable-xhdpi/ic_menu_add.png
new file mode 100644
index 000000000..420510e93
Binary files /dev/null and b/java/res/drawable-xhdpi/ic_menu_add.png differ
diff --git a/java/res/layout/user_dictionary_add_word.xml b/java/res/layout/user_dictionary_add_word.xml
new file mode 100644
index 000000000..bbf9b1b5b
--- /dev/null
+++ b/java/res/layout/user_dictionary_add_word.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/res/layout/user_dictionary_add_word_fullscreen.xml b/java/res/layout/user_dictionary_add_word_fullscreen.xml
new file mode 100644
index 000000000..75e86c509
--- /dev/null
+++ b/java/res/layout/user_dictionary_add_word_fullscreen.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/res/layout/user_dictionary_item.xml b/java/res/layout/user_dictionary_item.xml
new file mode 100644
index 000000000..3062ed89a
--- /dev/null
+++ b/java/res/layout/user_dictionary_item.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/res/layout/user_dictionary_preference_list_fragment.xml b/java/res/layout/user_dictionary_preference_list_fragment.xml
new file mode 100644
index 000000000..40e562c87
--- /dev/null
+++ b/java/res/layout/user_dictionary_preference_list_fragment.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index e9b34aa12..da735cf5a 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -117,4 +117,6 @@
8dp
+
+ 48
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d8a88a8eb..ff7942681 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -562,4 +562,76 @@ Tip: You can download and remove dictionaries by going to <b>Language & i
Version %1$s
+
+
+
+
+ User dictionary
+
+
+ Personal dictionary
+
+
+ Personal dictionaries
+
+ ""
+
+
+ Add
+
+
+ Add to dictionary
+
+
+ Phrase
+
+
+ More options
+
+
+ Less options
+
+
+ OK
+
+
+ Word:
+
+
+ Shortcut:
+
+
+ Language:
+
+
+ Type a word
+
+
+ Optional shortcut
+
+
+ Edit word
+
+
+ Edit
+
+
+ Delete
+
+
+ You don\'t have any words in the user dictionary. Add a word by touching the Add (+) button.
+
+
+ For all languages
+
+
+ More languages…
+
+
+ Delete
+
+
+ \u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 88a27144c..c78064b23 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import android.app.Activity;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +25,7 @@ import android.content.pm.PackageManager;
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;
@@ -31,17 +33,20 @@ import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
-import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
+import java.util.TreeSet;
+
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
+import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
+import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
public final class SettingsFragment extends InputMethodSettingsFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = SettingsFragment.class.getSimpleName();
+ private static final boolean DBG_USE_INTERNAL_USER_SETTINGS = false;
private ListPreference mVoicePreference;
private ListPreference mShowCorrectionSuggestionsPreference;
@@ -197,9 +202,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
final ResolveInfo ri = context.getPackageManager().resolveActivity(
editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
- if (ri == null) {
- // TODO: Set a intent that invokes our own edit personal dictionary activity.
- Log.w(TAG, "No activity that responds to " + editPersonalDictionaryIntent.getAction());
+ 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 (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
@@ -408,4 +417,33 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
});
}
+
+ private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
+ final Activity activity = getActivity();
+ final TreeSet localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
+ if (null == localeList) {
+ // The locale list is null if and only if the user dictionary service is
+ // not present or disabled. In this case we need to remove the preference.
+ getPreferenceScreen().removePreference(userDictionaryPreference);
+ } else if (localeList.size() <= 1) {
+ final Intent intent =
+ new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION);
+ userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
+ userDictionaryPreference.setIntent(intent);
+ userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
+ // If the size of localeList is 0, we don't set the locale parameter in the
+ // extras. This will be interpreted by the UserDictionarySettings class as
+ // meaning "the current locale".
+ // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet()
+ // the locale list always has at least one element, since it always includes the current
+ // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet().
+ if (localeList.size() == 1) {
+ final String locale = (String)localeList.toArray()[0];
+ userDictionaryPreference.getExtras().putString("locale", locale);
+ }
+ } else {
+ userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
+ userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
new file mode 100644
index 000000000..f0dc526e4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -0,0 +1,247 @@
+/*
+ * 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.LocaleUtils;
+import com.android.inputmethod.latin.R;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.UserDictionary;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.EditText;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TreeSet;
+
+// Caveat: This class is basically taken from
+// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
+// in order to deal with some devices that have issues with the user dictionary handling
+
+/**
+ * A container class to factor common code to UserDictionaryAddWordFragment
+ * and UserDictionaryAddWordActivity.
+ */
+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;
+
+ /* package */ static final int CODE_WORD_ADDED = 0;
+ /* package */ static final int CODE_CANCEL = 1;
+ /* package */ static final int CODE_ALREADY_PRESENT = 2;
+
+ private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250;
+
+ 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;
+
+ /* 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);
+ 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);
+ }
+ 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));
+ }
+
+ // locale may be null, this means default locale
+ // It may also be the empty string, which means "all locales"
+ /* package */ void updateLocale(final String locale) {
+ mLocale = null == locale ? Locale.getDefault().toString() : locale;
+ }
+
+ /* 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);
+ }
+
+ /* package */ void delete(final Context context) {
+ if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
+ // Mode edit: remove the old entry.
+ final ContentResolver resolver = context.getContentResolver();
+ UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ }
+ // 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) {
+ if (null != outParameters) saveStateIntoBundle(outParameters);
+ final ContentResolver resolver = context.getContentResolver();
+ if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
+ // Mode edit: remove the old entry.
+ UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ }
+ final String newWord = mWordEditText.getText().toString();
+ final String newShortcut;
+ 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;
+ }
+ // 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 (hasWord(newWord, context)) return CODE_ALREADY_PRESENT;
+
+ // 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'.
+ UserDictionary.Words.addWord(context, newWord.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut,
+ TextUtils.isEmpty(mLocale) ? null : LocaleUtils.constructLocaleFromString(mLocale));
+
+ return CODE_WORD_ADDED;
+ }
+
+ private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD };
+ private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD
+ + "=? AND " + UserDictionary.Words.LOCALE + "=?";
+ private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD
+ + "=? AND " + UserDictionary.Words.LOCALE + " is null";
+ private boolean hasWord(final String word, final Context context) {
+ final Cursor cursor;
+ // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't
+ // be null at all (it's ensured by the updateLocale method).
+ if ("".equals(mLocale)) {
+ cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
+ HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES,
+ new String[] { word }, null /* sort order */);
+ } else {
+ cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
+ HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE,
+ new String[] { word, mLocale }, null /* sort order */);
+ }
+ try {
+ if (null == cursor) return false;
+ return cursor.getCount() > 0;
+ } finally {
+ if (null != cursor) cursor.close();
+ }
+ }
+
+ public static class LocaleRenderer {
+ private final String mLocaleString;
+ private final String mDescription;
+ // LocaleString may NOT be null.
+ public LocaleRenderer(final Context context, final String localeString) {
+ mLocaleString = localeString;
+ if (null == localeString) {
+ mDescription = context.getString(R.string.user_dict_settings_more_languages);
+ } else if ("".equals(localeString)) {
+ mDescription = context.getString(R.string.user_dict_settings_all_languages);
+ } else {
+ mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName();
+ }
+ }
+ @Override
+ public String toString() {
+ return mDescription;
+ }
+ public String getLocaleString() {
+ return mLocaleString;
+ }
+ // "More languages..." is null ; "All languages" is the empty string.
+ public boolean isMoreLanguages() {
+ return null == mLocaleString;
+ }
+ }
+
+ private static void addLocaleDisplayNameToList(final Context context,
+ final ArrayList list, final String locale) {
+ if (null != locale) {
+ list.add(new LocaleRenderer(context, locale));
+ }
+ }
+
+ // Helper method to get the list of locales to display for this word
+ public ArrayList getLocalesList(final Activity activity) {
+ final TreeSet locales = UserDictionaryList.getUserDictionaryLocalesSet(activity);
+ // Remove our locale if it's in, because we're always gonna put it at the top
+ locales.remove(mLocale); // mLocale may not be null
+ final String systemLocale = Locale.getDefault().toString();
+ // The system locale should be inside. We want it at the 2nd spot.
+ locales.remove(systemLocale); // system locale may not be null
+ locales.remove(""); // Remove the empty string if it's there
+ final ArrayList localesList = new ArrayList();
+ // Add the passed locale, then the system locale at the top of the list. Add an
+ // "all languages" entry at the bottom of the list.
+ addLocaleDisplayNameToList(activity, localesList, mLocale);
+ if (!systemLocale.equals(mLocale)) {
+ addLocaleDisplayNameToList(activity, localesList, systemLocale);
+ }
+ for (final String l : locales) {
+ // TODO: sort in unicode order
+ addLocaleDisplayNameToList(activity, localesList, l);
+ }
+ if (!"".equals(mLocale)) {
+ // If mLocale is "", then we already inserted the "all languages" item, so don't do it
+ addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages
+ }
+ localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
+ return localesList;
+ }
+}
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 000000000..7970a365c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -0,0 +1,153 @@
+/*
+ * 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_DELETE = Menu.FIRST;
+
+ private UserDictionaryAddWordContents mContents;
+ private View mRootView;
+ private boolean mIsDeleting = false;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
+ mIsDeleting = false;
+ if (null == mContents) {
+ mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
+ }
+ return mRootView;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ MenuItem actionItem = 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);
+ }
+
+ /**
+ * 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_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 localesList = mContents.getLocalesList(getActivity());
+
+ final Spinner localeSpinner =
+ (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale);
+ final ArrayAdapter 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
new file mode 100644
index 000000000..2d147aada
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -0,0 +1,116 @@
+/*
+ * 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.LocaleUtils;
+import com.android.inputmethod.latin.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.provider.UserDictionary;
+
+import java.util.Locale;
+import java.util.TreeSet;
+
+// Caveat: This class is basically taken from
+// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java
+// in order to deal with some devices that have issues with the user dictionary handling
+
+public class UserDictionaryList extends PreferenceFragment {
+
+ public static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION =
+ "android.settings.USER_DICTIONARY_SETTINGS";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+ }
+
+ public static TreeSet getUserDictionaryLocalesSet(Activity activity) {
+ @SuppressWarnings("deprecation")
+ final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
+ new String[] { UserDictionary.Words.LOCALE },
+ null, null, null);
+ final TreeSet localeList = new TreeSet();
+ 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 : "");
+ } while (cursor.moveToNext());
+ }
+ localeList.add(Locale.getDefault().toString());
+ return localeList;
+ }
+
+ /**
+ * Creates the entries that allow the user to go into the user dictionary for each locale.
+ * @param userDictGroup The group to put the settings in.
+ */
+ protected void createUserDictSettings(PreferenceGroup userDictGroup) {
+ final Activity activity = getActivity();
+ userDictGroup.removeAll();
+ final TreeSet localeList =
+ UserDictionaryList.getUserDictionaryLocalesSet(activity);
+
+ if (localeList.isEmpty()) {
+ userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
+ } else {
+ for (String locale : localeList) {
+ userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
+ }
+ }
+ }
+
+ /**
+ * Create a single User Dictionary Preference object, with its parameters set.
+ * @param locale The locale for which this user dictionary is for.
+ * @return The corresponding preference.
+ */
+ protected Preference createUserDictionaryPreference(String locale, Activity activity) {
+ final Preference newPref = new Preference(getActivity());
+ final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
+ if (null == locale) {
+ newPref.setTitle(Locale.getDefault().getDisplayName());
+ } else {
+ if ("".equals(locale))
+ newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
+ else
+ newPref.setTitle(LocaleUtils.constructLocaleFromString(locale).getDisplayName());
+ intent.putExtra("locale", locale);
+ newPref.getExtras().putString("locale", locale);
+ }
+ newPref.setIntent(intent);
+ newPref.setFragment(UserDictionarySettings.class.getName());
+ return newPref;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ createUserDictSettings(getPreferenceScreen());
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java
new file mode 100644
index 000000000..58d3fb91c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java
@@ -0,0 +1,36 @@
+/*
+ * 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 android.app.Fragment;
+
+import java.util.Locale;
+
+// Caveat: This class is basically taken from
+// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java
+// in order to deal with some devices that have issues with the user dictionary handling
+
+public class UserDictionaryLocalePicker extends Fragment {
+ public UserDictionaryLocalePicker() {
+ super();
+ // TODO: implement
+ }
+
+ public interface LocationChangedListener {
+ public void onLocaleSelected(Locale locale);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
new file mode 100644
index 000000000..a250c246e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -0,0 +1,286 @@
+/**
+ * Copyright (C) 2013 Google Inc.
+ *
+ * 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 android.app.ListFragment;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.UserDictionary;
+import android.text.TextUtils;
+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.AlphabetIndexer;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.Locale;
+
+// Caveat: This class is basically taken from
+// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionarySettings.java
+// in order to deal with some devices that have issues with the user dictionary handling
+
+public class UserDictionarySettings extends ListFragment {
+
+ private static final String[] QUERY_PROJECTION = {
+ UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
+ };
+
+ // The index of the shortcut in the above array.
+ private static final int INDEX_SHORTCUT = 2;
+
+ // 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 int OPTIONS_MENU_ADD = Menu.FIRST;
+
+ private Cursor mCursor;
+
+ protected String mLocale;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(
+ R.layout.user_dictionary_preference_list_fragment, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Intent intent = getActivity().getIntent();
+ final String localeFromIntent =
+ null == intent ? null : intent.getStringExtra("locale");
+
+ final Bundle arguments = getArguments();
+ final String localeFromArguments =
+ null == arguments ? null : arguments.getString("locale");
+
+ final String locale;
+ if (null != localeFromArguments) {
+ locale = localeFromArguments;
+ } else if (null != localeFromIntent) {
+ locale = localeFromIntent;
+ } else {
+ locale = null;
+ }
+
+ mLocale = locale;
+ mCursor = createCursor(locale);
+ TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
+ emptyView.setText(R.string.user_dict_settings_empty_text);
+
+ final ListView listView = getListView();
+ listView.setAdapter(createAdapter());
+ listView.setFastScrollEnabled(true);
+ listView.setEmptyView(emptyView);
+
+ setHasOptionsMenu(true);
+
+ }
+
+ @SuppressWarnings("deprecation")
+ private Cursor createCursor(final String locale) {
+ // Locale can be any of:
+ // - The string representation of a locale, as returned by Locale#toString()
+ // - The empty string. This means we want a cursor returning words valid for all locales.
+ // - null. This means we want a cursor for the current locale, whatever this is.
+ // Note that this contrasts with the data inside the database, where NULL means "all
+ // locales" and there should never be an empty string. The confusion is called by the
+ // historical use of null for "all locales".
+ // 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 ("".equals(locale)) {
+ // Case-insensitive sort
+ return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+ QUERY_SELECTION_ALL_LOCALES, null,
+ "UPPER(" + UserDictionary.Words.WORD + ")");
+ } else {
+ 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[] { UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT },
+ new int[] { android.R.id.text1, android.R.id.text2 }, this);
+ }
+
+ @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, shortcut);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ 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() == OPTIONS_MENU_ADD) {
+ showAddOrEditDialog(null, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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, final String editingShortcut) {
+ final Bundle args = new Bundle();
+ 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) {
+ 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.WORD));
+ }
+
+ private String getShortcut(final int position) {
+ 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 (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 {
+
+ private AlphabetIndexer mIndexer;
+
+ private ViewBinder mViewBinder = new ViewBinder() {
+
+ @Override
+ public boolean setViewValue(View v, Cursor c, int columnIndex) {
+ 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;
+ }
+ };
+
+ @SuppressWarnings("deprecation")
+ public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to,
+ UserDictionarySettings settings) {
+ super(context, layout, c, from, to);
+
+ if (null != c) {
+ final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet);
+ final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
+ mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
+ }
+ setViewBinder(mViewBinder);
+ }
+
+ @Override
+ public int getPositionForSection(int section) {
+ return null == mIndexer ? 0 : mIndexer.getPositionForSection(section);
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position);
+ }
+
+ @Override
+ public Object[] getSections() {
+ return null == mIndexer ? null : mIndexer.getSections();
+ }
+ }
+}