UserDictionaryAddWordContents.java revision 23a3fd5bfacc54fd3841bb080460c91be4006051
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.latin.userdictionary; 18 19import android.app.Activity; 20import android.content.ContentResolver; 21import android.content.Context; 22import android.database.Cursor; 23import android.os.Bundle; 24import android.provider.UserDictionary; 25import android.text.TextUtils; 26import android.view.View; 27import android.widget.EditText; 28 29import com.android.inputmethod.compat.UserDictionaryCompatUtils; 30import com.android.inputmethod.latin.R; 31import com.android.inputmethod.latin.utils.LocaleUtils; 32 33import java.util.ArrayList; 34import java.util.Locale; 35import java.util.TreeSet; 36 37// Caveat: This class is basically taken from 38// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java 39// in order to deal with some devices that have issues with the user dictionary handling 40 41/** 42 * A container class to factor common code to UserDictionaryAddWordFragment 43 * and UserDictionaryAddWordActivity. 44 */ 45public class UserDictionaryAddWordContents { 46 public static final String EXTRA_MODE = "mode"; 47 public static final String EXTRA_WORD = "word"; 48 public static final String EXTRA_SHORTCUT = "shortcut"; 49 public static final String EXTRA_LOCALE = "locale"; 50 public static final String EXTRA_ORIGINAL_WORD = "originalWord"; 51 public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut"; 52 53 public static final int MODE_EDIT = 0; 54 public static final int MODE_INSERT = 1; 55 56 /* package */ static final int CODE_WORD_ADDED = 0; 57 /* package */ static final int CODE_CANCEL = 1; 58 /* package */ static final int CODE_ALREADY_PRESENT = 2; 59 60 private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; 61 62 private final int mMode; // Either MODE_EDIT or MODE_INSERT 63 private final EditText mWordEditText; 64 private final EditText mShortcutEditText; 65 private String mLocale; 66 private final String mOldWord; 67 private final String mOldShortcut; 68 private String mSavedWord; 69 private String mSavedShortcut; 70 71 /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) { 72 mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); 73 mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); 74 if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { 75 mShortcutEditText.setVisibility(View.GONE); 76 view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE); 77 } 78 final String word = args.getString(EXTRA_WORD); 79 if (null != word) { 80 mWordEditText.setText(word); 81 // Use getText in case the edit text modified the text we set. This happens when 82 // it's too long to be edited. 83 mWordEditText.setSelection(mWordEditText.getText().length()); 84 } 85 final String shortcut; 86 if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { 87 shortcut = args.getString(EXTRA_SHORTCUT); 88 if (null != shortcut && null != mShortcutEditText) { 89 mShortcutEditText.setText(shortcut); 90 } 91 mOldShortcut = args.getString(EXTRA_SHORTCUT); 92 } else { 93 shortcut = null; 94 mOldShortcut = null; 95 } 96 mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT 97 mOldWord = args.getString(EXTRA_WORD); 98 updateLocale(args.getString(EXTRA_LOCALE)); 99 } 100 101 /* package */ UserDictionaryAddWordContents(final View view, 102 final UserDictionaryAddWordContents oldInstanceToBeEdited) { 103 mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); 104 mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); 105 mMode = MODE_EDIT; 106 mOldWord = oldInstanceToBeEdited.mSavedWord; 107 mOldShortcut = oldInstanceToBeEdited.mSavedShortcut; 108 updateLocale(mLocale); 109 } 110 111 // locale may be null, this means default locale 112 // It may also be the empty string, which means "all locales" 113 /* package */ void updateLocale(final String locale) { 114 mLocale = null == locale ? Locale.getDefault().toString() : locale; 115 } 116 117 /* package */ void saveStateIntoBundle(final Bundle outState) { 118 outState.putString(EXTRA_WORD, mWordEditText.getText().toString()); 119 outState.putString(EXTRA_ORIGINAL_WORD, mOldWord); 120 if (null != mShortcutEditText) { 121 outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString()); 122 } 123 if (null != mOldShortcut) { 124 outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut); 125 } 126 outState.putString(EXTRA_LOCALE, mLocale); 127 } 128 129 /* package */ void delete(final Context context) { 130 if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { 131 // Mode edit: remove the old entry. 132 final ContentResolver resolver = context.getContentResolver(); 133 UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); 134 } 135 // If we are in add mode, nothing was added, so we don't need to do anything. 136 } 137 138 /* package */ 139 int apply(final Context context, final Bundle outParameters) { 140 if (null != outParameters) saveStateIntoBundle(outParameters); 141 final ContentResolver resolver = context.getContentResolver(); 142 if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { 143 // Mode edit: remove the old entry. 144 UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); 145 } 146 final String newWord = mWordEditText.getText().toString(); 147 final String newShortcut; 148 if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) { 149 newShortcut = null; 150 } else if (null == mShortcutEditText) { 151 newShortcut = null; 152 } else { 153 final String tmpShortcut = mShortcutEditText.getText().toString(); 154 if (TextUtils.isEmpty(tmpShortcut)) { 155 newShortcut = null; 156 } else { 157 newShortcut = tmpShortcut; 158 } 159 } 160 if (TextUtils.isEmpty(newWord)) { 161 // If the word is somehow empty, don't insert it. 162 return CODE_CANCEL; 163 } 164 mSavedWord = newWord; 165 mSavedShortcut = newShortcut; 166 // If there is no shortcut, and the word already exists in the database, then we 167 // should not insert, because either A. the word exists with no shortcut, in which 168 // case the exact same thing we want to insert is already there, or B. the word 169 // exists with at least one shortcut, in which case it has priority on our word. 170 if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT; 171 172 // Disallow duplicates. If the same word with no shortcut is defined, remove it; if 173 // the same word with the same shortcut is defined, remove it; but we don't mind if 174 // there is the same word with a different, non-empty shortcut. 175 UserDictionarySettings.deleteWord(newWord, null, resolver); 176 if (!TextUtils.isEmpty(newShortcut)) { 177 // If newShortcut is empty we just deleted this, no need to do it again 178 UserDictionarySettings.deleteWord(newWord, newShortcut, resolver); 179 } 180 181 // In this class we use the empty string to represent 'all locales' and mLocale cannot 182 // be null. However the addWord method takes null to mean 'all locales'. 183 UserDictionaryCompatUtils.addWord(context, newWord.toString(), 184 FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ? 185 null : LocaleUtils.constructLocaleFromString(mLocale)); 186 187 return CODE_WORD_ADDED; 188 } 189 190 private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD }; 191 private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD 192 + "=? AND " + UserDictionary.Words.LOCALE + "=?"; 193 private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD 194 + "=? AND " + UserDictionary.Words.LOCALE + " is null"; 195 private boolean hasWord(final String word, final Context context) { 196 final Cursor cursor; 197 // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't 198 // be null at all (it's ensured by the updateLocale method). 199 if ("".equals(mLocale)) { 200 cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, 201 HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES, 202 new String[] { word }, null /* sort order */); 203 } else { 204 cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, 205 HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE, 206 new String[] { word, mLocale }, null /* sort order */); 207 } 208 try { 209 if (null == cursor) return false; 210 return cursor.getCount() > 0; 211 } finally { 212 if (null != cursor) cursor.close(); 213 } 214 } 215 216 public static class LocaleRenderer { 217 private final String mLocaleString; 218 private final String mDescription; 219 // LocaleString may NOT be null. 220 public LocaleRenderer(final Context context, final String localeString) { 221 mLocaleString = localeString; 222 if (null == localeString) { 223 mDescription = context.getString(R.string.user_dict_settings_more_languages); 224 } else if ("".equals(localeString)) { 225 mDescription = context.getString(R.string.user_dict_settings_all_languages); 226 } else { 227 mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName(); 228 } 229 } 230 @Override 231 public String toString() { 232 return mDescription; 233 } 234 public String getLocaleString() { 235 return mLocaleString; 236 } 237 // "More languages..." is null ; "All languages" is the empty string. 238 public boolean isMoreLanguages() { 239 return null == mLocaleString; 240 } 241 } 242 243 private static void addLocaleDisplayNameToList(final Context context, 244 final ArrayList<LocaleRenderer> list, final String locale) { 245 if (null != locale) { 246 list.add(new LocaleRenderer(context, locale)); 247 } 248 } 249 250 // Helper method to get the list of locales to display for this word 251 public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) { 252 final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity); 253 // Remove our locale if it's in, because we're always gonna put it at the top 254 locales.remove(mLocale); // mLocale may not be null 255 final String systemLocale = Locale.getDefault().toString(); 256 // The system locale should be inside. We want it at the 2nd spot. 257 locales.remove(systemLocale); // system locale may not be null 258 locales.remove(""); // Remove the empty string if it's there 259 final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>(); 260 // Add the passed locale, then the system locale at the top of the list. Add an 261 // "all languages" entry at the bottom of the list. 262 addLocaleDisplayNameToList(activity, localesList, mLocale); 263 if (!systemLocale.equals(mLocale)) { 264 addLocaleDisplayNameToList(activity, localesList, systemLocale); 265 } 266 for (final String l : locales) { 267 // TODO: sort in unicode order 268 addLocaleDisplayNameToList(activity, localesList, l); 269 } 270 if (!"".equals(mLocale)) { 271 // If mLocale is "", then we already inserted the "all languages" item, so don't do it 272 addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages 273 } 274 localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale 275 return localesList; 276 } 277} 278