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 17 package com.android.settings; 18 19 import android.app.ListFragment; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.database.Cursor; 24 import android.os.Bundle; 25 import android.provider.UserDictionary; 26 import android.text.TextUtils; 27 import android.view.LayoutInflater; 28 import android.view.Menu; 29 import android.view.MenuInflater; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.AlphabetIndexer; 34 import android.widget.ListAdapter; 35 import android.widget.ListView; 36 import android.widget.SectionIndexer; 37 import android.widget.SimpleCursorAdapter; 38 import android.widget.TextView; 39 40 import com.android.internal.logging.nano.MetricsProto; 41 import com.android.settings.core.instrumentation.VisibilityLoggerMixin; 42 import com.android.settings.inputmethod.UserDictionaryAddWordContents; 43 import com.android.settings.inputmethod.UserDictionarySettingsUtils; 44 import com.android.settings.core.instrumentation.Instrumentable; 45 46 import java.util.Locale; 47 48 public class UserDictionarySettings extends ListFragment implements Instrumentable { 49 50 private static final String[] QUERY_PROJECTION = { 51 UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT 52 }; 53 54 // The index of the shortcut in the above array. 55 private static final int INDEX_SHORTCUT = 2; 56 57 // Either the locale is empty (means the word is applicable to all locales) 58 // or the word equals our current locale 59 private static final String QUERY_SELECTION = 60 UserDictionary.Words.LOCALE + "=?"; 61 private static final String QUERY_SELECTION_ALL_LOCALES = 62 UserDictionary.Words.LOCALE + " is null"; 63 64 private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD 65 + "=? AND " + UserDictionary.Words.SHORTCUT + "=?"; 66 private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD 67 + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR " 68 + UserDictionary.Words.SHORTCUT + "=''"; 69 70 private static final int OPTIONS_MENU_ADD = Menu.FIRST; 71 72 private final VisibilityLoggerMixin mVisibilityLoggerMixin = 73 new VisibilityLoggerMixin(getMetricsCategory()); 74 75 private Cursor mCursor; 76 protected String mLocale; 77 78 @Override getMetricsCategory()79 public int getMetricsCategory() { 80 return MetricsProto.MetricsEvent.USER_DICTIONARY_SETTINGS; 81 } 82 83 @Override onAttach(Context context)84 public void onAttach(Context context) { 85 super.onAttach(context); 86 mVisibilityLoggerMixin.onAttach(context); 87 } 88 89 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)90 public View onCreateView( 91 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 92 return inflater.inflate( 93 com.android.internal.R.layout.preference_list_fragment, container, false); 94 } 95 96 @Override onActivityCreated(Bundle savedInstanceState)97 public void onActivityCreated(Bundle savedInstanceState) { 98 super.onActivityCreated(savedInstanceState); 99 getActivity().getActionBar().setTitle(R.string.user_dict_settings_title); 100 101 final Intent intent = getActivity().getIntent(); 102 final String localeFromIntent = 103 null == intent ? null : intent.getStringExtra("locale"); 104 105 final Bundle arguments = getArguments(); 106 final String localeFromArguments = 107 null == arguments ? null : arguments.getString("locale"); 108 109 final String locale; 110 if (null != localeFromArguments) { 111 locale = localeFromArguments; 112 } else if (null != localeFromIntent) { 113 locale = localeFromIntent; 114 } else { 115 locale = null; 116 } 117 118 mLocale = locale; 119 mCursor = createCursor(locale); 120 TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); 121 emptyView.setText(R.string.user_dict_settings_empty_text); 122 123 final ListView listView = getListView(); 124 listView.setAdapter(createAdapter()); 125 listView.setFastScrollEnabled(true); 126 listView.setEmptyView(emptyView); 127 128 setHasOptionsMenu(true); 129 // Show the language as a subtitle of the action bar 130 getActivity().getActionBar().setSubtitle( 131 UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale)); 132 } 133 134 @Override onResume()135 public void onResume() { 136 super.onResume(); 137 mVisibilityLoggerMixin.onResume(); 138 } 139 createCursor(final String locale)140 private Cursor createCursor(final String locale) { 141 // Locale can be any of: 142 // - The string representation of a locale, as returned by Locale#toString() 143 // - The empty string. This means we want a cursor returning words valid for all locales. 144 // - null. This means we want a cursor for the current locale, whatever this is. 145 // Note that this contrasts with the data inside the database, where NULL means "all 146 // locales" and there should never be an empty string. The confusion is called by the 147 // historical use of null for "all locales". 148 // TODO: it should be easy to make this more readable by making the special values 149 // human-readable, like "all_locales" and "current_locales" strings, provided they 150 // can be guaranteed not to match locales that may exist. 151 if ("".equals(locale)) { 152 // Case-insensitive sort 153 return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, 154 QUERY_SELECTION_ALL_LOCALES, null, 155 "UPPER(" + UserDictionary.Words.WORD + ")"); 156 } else { 157 final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); 158 return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, 159 QUERY_SELECTION, new String[] { queryLocale }, 160 "UPPER(" + UserDictionary.Words.WORD + ")"); 161 } 162 } 163 createAdapter()164 private ListAdapter createAdapter() { 165 return new MyAdapter(getActivity(), 166 R.layout.user_dictionary_item, mCursor, 167 new String[] { UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT }, 168 new int[] { android.R.id.text1, android.R.id.text2 }, this); 169 } 170 171 @Override onListItemClick(ListView l, View v, int position, long id)172 public void onListItemClick(ListView l, View v, int position, long id) { 173 final String word = getWord(position); 174 final String shortcut = getShortcut(position); 175 if (word != null) { 176 showAddOrEditDialog(word, shortcut); 177 } 178 } 179 180 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)181 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 182 MenuItem actionItem = 183 menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title) 184 .setIcon(R.drawable.ic_menu_add_white); 185 actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | 186 MenuItem.SHOW_AS_ACTION_WITH_TEXT); 187 } 188 189 @Override onOptionsItemSelected(MenuItem item)190 public boolean onOptionsItemSelected(MenuItem item) { 191 if (item.getItemId() == OPTIONS_MENU_ADD) { 192 showAddOrEditDialog(null, null); 193 return true; 194 } 195 return false; 196 } 197 198 @Override onPause()199 public void onPause() { 200 super.onPause(); 201 mVisibilityLoggerMixin.onPause(); 202 } 203 204 /** 205 * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit. 206 * @param editingWord the word to edit, or null if it's an add. 207 * @param editingShortcut the shortcut for this entry, or null if none. 208 */ showAddOrEditDialog(final String editingWord, final String editingShortcut)209 private void showAddOrEditDialog(final String editingWord, final String editingShortcut) { 210 final Bundle args = new Bundle(); 211 args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord 212 ? UserDictionaryAddWordContents.MODE_INSERT 213 : UserDictionaryAddWordContents.MODE_EDIT); 214 args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); 215 args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); 216 args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); 217 SettingsActivity sa = (SettingsActivity) getActivity(); 218 sa.startPreferencePanel(this, 219 com.android.settings.inputmethod.UserDictionaryAddWordFragment.class.getName(), 220 args, R.string.user_dict_settings_add_dialog_title, null, null, 0); 221 } 222 getWord(final int position)223 private String getWord(final int position) { 224 if (null == mCursor) return null; 225 mCursor.moveToPosition(position); 226 // Handle a possible race-condition 227 if (mCursor.isAfterLast()) return null; 228 229 return mCursor.getString( 230 mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); 231 } 232 getShortcut(final int position)233 private String getShortcut(final int position) { 234 if (null == mCursor) return null; 235 mCursor.moveToPosition(position); 236 // Handle a possible race-condition 237 if (mCursor.isAfterLast()) return null; 238 239 return mCursor.getString( 240 mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT)); 241 } 242 deleteWord(final String word, final String shortcut, final ContentResolver resolver)243 public static void deleteWord(final String word, final String shortcut, 244 final ContentResolver resolver) { 245 if (TextUtils.isEmpty(shortcut)) { 246 resolver.delete( 247 UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT, 248 new String[] { word }); 249 } else { 250 resolver.delete( 251 UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT, 252 new String[] { word, shortcut }); 253 } 254 } 255 256 private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer { 257 258 private AlphabetIndexer mIndexer; 259 260 private final ViewBinder mViewBinder = new ViewBinder() { 261 262 @Override 263 public boolean setViewValue(View v, Cursor c, int columnIndex) { 264 if (columnIndex == INDEX_SHORTCUT) { 265 final String shortcut = c.getString(INDEX_SHORTCUT); 266 if (TextUtils.isEmpty(shortcut)) { 267 v.setVisibility(View.GONE); 268 } else { 269 ((TextView)v).setText(shortcut); 270 v.setVisibility(View.VISIBLE); 271 } 272 v.invalidate(); 273 return true; 274 } 275 276 return false; 277 } 278 }; 279 MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to, UserDictionarySettings settings)280 public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to, 281 UserDictionarySettings settings) { 282 super(context, layout, c, from, to); 283 284 if (null != c) { 285 final String alphabet = context.getString( 286 com.android.internal.R.string.fast_scroll_alphabet); 287 final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); 288 mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); 289 } 290 setViewBinder(mViewBinder); 291 } 292 293 @Override getPositionForSection(int section)294 public int getPositionForSection(int section) { 295 return null == mIndexer ? 0 : mIndexer.getPositionForSection(section); 296 } 297 298 @Override getSectionForPosition(int position)299 public int getSectionForPosition(int position) { 300 return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position); 301 } 302 303 @Override getSections()304 public Object[] getSections() { 305 return null == mIndexer ? null : mIndexer.getSections(); 306 } 307 } 308 } 309