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