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