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