UserDictionarySettings.java revision dcf8ba9eb233b4dadd1e198b7ec6ba413c6583c1
1/** 2 * Copyright (C) 2009 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17package com.android.settings; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.ListFragment; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.database.Cursor; 27import android.os.Bundle; 28import android.provider.UserDictionary; 29import android.text.InputType; 30import android.util.Log; 31import android.view.LayoutInflater; 32import android.view.Menu; 33import android.view.MenuInflater; 34import android.view.MenuItem; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.WindowManager; 38import android.widget.AlphabetIndexer; 39import android.widget.EditText; 40import android.widget.ImageView; 41import android.widget.ListAdapter; 42import android.widget.ListView; 43import android.widget.SectionIndexer; 44import android.widget.SimpleCursorAdapter; 45import android.widget.TextView; 46 47import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; 48 49import java.util.Locale; 50 51public class UserDictionarySettings extends ListFragment implements DialogCreatable { 52 private static final String TAG = "UserDictionarySettings"; 53 54 private static final String INSTANCE_KEY_DIALOG_EDITING_WORD = "DIALOG_EDITING_WORD"; 55 private static final String INSTANCE_KEY_ADDED_WORD = "DIALOG_ADDED_WORD"; 56 57 private static final String[] QUERY_PROJECTION = { 58 UserDictionary.Words._ID, UserDictionary.Words.WORD 59 }; 60 61 private static final int INDEX_ID = 0; 62 private static final int INDEX_WORD = 1; 63 64 // Either the locale is empty (means the word is applicable to all locales) 65 // or the word equals our current locale 66 private static final String QUERY_SELECTION = 67 UserDictionary.Words.LOCALE + "=?"; 68 private static final String QUERY_SELECTION_ALL_LOCALES = 69 UserDictionary.Words.LOCALE + " is null"; 70 71 private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?"; 72 73 private static final String EXTRA_WORD = "word"; 74 75 private static final int OPTIONS_MENU_ADD = Menu.FIRST; 76 77 private static final int DIALOG_ADD_OR_EDIT = 0; 78 79 private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; 80 81 /** The word being edited in the dialog (null means the user is adding a word). */ 82 private String mDialogEditingWord; 83 84 private Cursor mCursor; 85 86 protected String mLocale; 87 88 private boolean mAddedWordAlready; 89 private boolean mAutoReturn; 90 91 private SettingsDialogFragment mDialogFragment; 92 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 super.onCreate(savedInstanceState); 96 } 97 98 @Override 99 public void onActivityCreated(Bundle savedInstanceState) { 100 super.onActivityCreated(savedInstanceState); 101 102 final Intent intent = getActivity().getIntent(); 103 final String localeFromIntent = 104 null == intent ? null : intent.getStringExtra("locale"); 105 106 final Bundle arguments = getArguments(); 107 final String localeFromArguments = 108 null == arguments ? null : arguments.getString("locale"); 109 110 final String locale; 111 if (null != localeFromArguments) { 112 locale = localeFromArguments; 113 } else if (null != localeFromIntent) { 114 locale = localeFromIntent; 115 } else { 116 locale = null; 117 } 118 119 mLocale = locale; 120 mCursor = createCursor(locale); 121 TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); 122 emptyView.setText(R.string.user_dict_settings_empty_text); 123 124 final ListView listView = getListView(); 125 listView.setAdapter(createAdapter()); 126 listView.setFastScrollEnabled(true); 127 listView.setEmptyView(emptyView); 128 129 setHasOptionsMenu(true); 130 131 if (savedInstanceState != null) { 132 mDialogEditingWord = savedInstanceState.getString(INSTANCE_KEY_DIALOG_EDITING_WORD); 133 mAddedWordAlready = savedInstanceState.getBoolean(INSTANCE_KEY_ADDED_WORD, false); 134 } 135 } 136 137 @Override 138 public void onResume() { 139 super.onResume(); 140 final Intent intent = getActivity().getIntent(); 141 if (!mAddedWordAlready 142 && intent.getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) { 143 final String word = intent.getStringExtra(EXTRA_WORD); 144 mAutoReturn = true; 145 if (word != null) { 146 showAddOrEditDialog(word); 147 } 148 } 149 } 150 151 @Override 152 public void onSaveInstanceState(Bundle outState) { 153 super.onSaveInstanceState(outState); 154 outState.putString(INSTANCE_KEY_DIALOG_EDITING_WORD, mDialogEditingWord); 155 outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready); 156 } 157 158 private Cursor createCursor(final String locale) { 159 // Locale can be any of: 160 // - The string representation of a locale, as returned by Locale#toString() 161 // - The empty string. This means we want a cursor returning words valid for all locales. 162 // - null. This means we want a cursor for the current locale, whatever this is. 163 // Note that this contrasts with the data inside the database, where NULL means "all 164 // locales" and there should never be an empty string. The confusion is called by the 165 // historical use of null for "all locales". 166 // TODO: it should be easy to make this more readable by making the special values 167 // human-readable, like "all_locales" and "current_locales" strings, provided they 168 // can be guaranteed not to match locales that may exist. 169 if ("".equals(locale)) { 170 // Case-insensitive sort 171 return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, 172 QUERY_SELECTION_ALL_LOCALES, null, 173 "UPPER(" + UserDictionary.Words.WORD + ")"); 174 } else { 175 final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); 176 return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, 177 QUERY_SELECTION, new String[] { queryLocale }, 178 "UPPER(" + UserDictionary.Words.WORD + ")"); 179 } 180 } 181 182 private ListAdapter createAdapter() { 183 return new MyAdapter(getActivity(), 184 R.layout.user_dictionary_item, mCursor, 185 new String[] { UserDictionary.Words.WORD, UserDictionary.Words._ID }, 186 new int[] { android.R.id.text1, R.id.delete_button }, this); 187 } 188 189 @Override 190 public void onListItemClick(ListView l, View v, int position, long id) { 191 String word = getWord(position); 192 if (word != null) { 193 showAddOrEditDialog(word); 194 } 195 } 196 197 @Override 198 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 199 MenuItem actionItem = 200 menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title) 201 .setIcon(R.drawable.ic_menu_add); 202 actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | 203 MenuItem.SHOW_AS_ACTION_WITH_TEXT); 204 } 205 206 @Override 207 public boolean onOptionsItemSelected(MenuItem item) { 208 showAddOrEditDialog(null); 209 return true; 210 } 211 212 private void showAddOrEditDialog(String editingWord) { 213 mDialogEditingWord = editingWord; 214 showDialog(DIALOG_ADD_OR_EDIT); 215 } 216 217 private String getWord(int position) { 218 if (null == mCursor) return null; 219 mCursor.moveToPosition(position); 220 // Handle a possible race-condition 221 if (mCursor.isAfterLast()) return null; 222 223 return mCursor.getString( 224 mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); 225 } 226 227 @Override 228 public Dialog onCreateDialog(int id) { 229 final Activity activity = getActivity(); 230 final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); 231 final LayoutInflater inflater = LayoutInflater.from(dialogBuilder.getContext()); 232 final View content = inflater.inflate(R.layout.dialog_edittext, null); 233 final EditText editText = (EditText) content.findViewById(R.id.edittext); 234 editText.setText(mDialogEditingWord); 235 // No prediction in soft keyboard mode. TODO: Create a better way to disable prediction 236 editText.setInputType(InputType.TYPE_CLASS_TEXT 237 | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); 238 239 AlertDialog dialog = dialogBuilder 240 .setTitle(mDialogEditingWord != null 241 ? R.string.user_dict_settings_edit_dialog_title 242 : R.string.user_dict_settings_add_dialog_title) 243 .setView(content) 244 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 245 public void onClick(DialogInterface dialog, int which) { 246 onAddOrEditFinished(editText.getText().toString()); 247 if (mAutoReturn) activity.onBackPressed(); 248 }}) 249 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 250 public void onClick(DialogInterface dialog, int which) { 251 if (mAutoReturn) activity.onBackPressed(); 252 }}) 253 .create(); 254 dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | 255 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 256 return dialog; 257 } 258 259 private void showDialog(int dialogId) { 260 if (mDialogFragment != null) { 261 Log.e(TAG, "Old dialog fragment not null!"); 262 } 263 mDialogFragment = new SettingsDialogFragment(this, dialogId); 264 mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); 265 } 266 267 private void onAddOrEditFinished(String word) { 268 if (mDialogEditingWord != null) { 269 // The user was editing a word, so do a delete/add 270 deleteWord(mDialogEditingWord); 271 } 272 273 // Disallow duplicates 274 deleteWord(word); 275 276 // TODO: present UI for picking whether to add word to all locales, or current. 277 if (null == mLocale) { 278 // Null means insert with the default system locale. 279 UserDictionary.Words.addWord(getActivity(), word.toString(), 280 FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); 281 } else if ("".equals(mLocale)) { 282 // Empty string means insert for all languages. 283 UserDictionary.Words.addWord(getActivity(), word.toString(), 284 FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_ALL); 285 } else { 286 // TODO: fix the framework so that it can accept a locale when we add a word 287 // to the user dictionary instead of querying the system locale. 288 final Locale prevLocale = Locale.getDefault(); 289 Locale.setDefault(Utils.createLocaleFromString(mLocale)); 290 UserDictionary.Words.addWord(getActivity(), word.toString(), 291 FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); 292 Locale.setDefault(prevLocale); 293 } 294 if (null != mCursor && !mCursor.requery()) { 295 throw new IllegalStateException("can't requery on already-closed cursor."); 296 } 297 mAddedWordAlready = true; 298 } 299 300 private void deleteWord(String word) { 301 getActivity().getContentResolver().delete( 302 UserDictionary.Words.CONTENT_URI, DELETE_SELECTION, new String[] { word }); 303 } 304 305 private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer, 306 View.OnClickListener { 307 308 private AlphabetIndexer mIndexer; 309 private UserDictionarySettings mSettings; 310 311 private ViewBinder mViewBinder = new ViewBinder() { 312 313 public boolean setViewValue(View v, Cursor c, int columnIndex) { 314 if (v instanceof ImageView && columnIndex == INDEX_ID) { 315 v.setOnClickListener(MyAdapter.this); 316 v.setTag(c.getString(INDEX_WORD)); 317 return true; 318 } 319 320 return false; 321 } 322 }; 323 324 public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to, 325 UserDictionarySettings settings) { 326 super(context, layout, c, from, to); 327 328 mSettings = settings; 329 if (null != c) { 330 final String alphabet = context.getString( 331 com.android.internal.R.string.fast_scroll_alphabet); 332 final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); 333 mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); 334 } 335 setViewBinder(mViewBinder); 336 } 337 338 public int getPositionForSection(int section) { 339 return null == mIndexer ? 0 : mIndexer.getPositionForSection(section); 340 } 341 342 public int getSectionForPosition(int position) { 343 return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position); 344 } 345 346 public Object[] getSections() { 347 return null == mIndexer ? null : mIndexer.getSections(); 348 } 349 350 public void onClick(View v) { 351 mSettings.deleteWord((String) v.getTag()); 352 } 353 } 354} 355