UserDictionarySettings.java revision f58090d5224fa03e8f4d8a980306952686a152f0
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 com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.app.Dialog;
24import android.app.ListFragment;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.database.Cursor;
29import android.os.Bundle;
30import android.provider.UserDictionary;
31import android.text.InputType;
32import android.util.Log;
33import android.view.ContextMenu;
34import android.view.ContextMenu.ContextMenuInfo;
35import android.view.LayoutInflater;
36import android.view.Menu;
37import android.view.MenuInflater;
38import android.view.MenuItem;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.WindowManager;
42import android.widget.AdapterView.AdapterContextMenuInfo;
43import android.widget.AlphabetIndexer;
44import android.widget.EditText;
45import android.widget.ListAdapter;
46import android.widget.ListView;
47import android.widget.SectionIndexer;
48import android.widget.SimpleCursorAdapter;
49import android.widget.TextView;
50
51import java.util.Locale;
52
53public class UserDictionarySettings extends ListFragment implements DialogCreatable {
54    private static final String TAG = "UserDictionarySettings";
55
56    private static final String INSTANCE_KEY_DIALOG_EDITING_WORD = "DIALOG_EDITING_WORD";
57    private static final String INSTANCE_KEY_ADDED_WORD = "DIALOG_ADDED_WORD";
58
59    private static final String[] QUERY_PROJECTION = {
60        UserDictionary.Words._ID, UserDictionary.Words.WORD
61    };
62
63    // Either the locale is empty (means the word is applicable to all locales)
64    // or the word equals our current locale
65    private static final String QUERY_SELECTION = UserDictionary.Words.LOCALE + "=? OR "
66            + UserDictionary.Words.LOCALE + " is null";
67
68    private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?";
69
70    private static final String EXTRA_WORD = "word";
71
72    private static final int CONTEXT_MENU_EDIT = Menu.FIRST;
73    private static final int CONTEXT_MENU_DELETE = Menu.FIRST + 1;
74
75    private static final int OPTIONS_MENU_ADD = Menu.FIRST;
76
77    private static final int DIALOG_ADD_OR_EDIT = 0;
78
79    /** The word being edited in the dialog (null means the user is adding a word). */
80    private String mDialogEditingWord;
81
82    private View mView;
83    private Cursor mCursor;
84
85    private boolean mAddedWordAlready;
86    private boolean mAutoReturn;
87
88    private SettingsDialogFragment mDialogFragment;
89
90    @Override
91    public void onCreate(Bundle savedInstanceState) {
92        super.onCreate(savedInstanceState);
93    }
94
95    @Override
96    public View onCreateView(LayoutInflater inflater, ViewGroup container,
97            Bundle savedInstanceState) {
98        mView = inflater.inflate(R.layout.list_content_with_empty_view, container, false);
99        return mView;
100    }
101
102    @Override
103    public void onActivityCreated(Bundle savedInstanceState) {
104        super.onActivityCreated(savedInstanceState);
105
106        mCursor = createCursor();
107        TextView emptyView = (TextView)mView.findViewById(R.id.empty);
108        emptyView.setText(R.string.user_dict_settings_empty_text);
109
110        final ListView listView = getListView();
111        listView.setAdapter(createAdapter());
112        listView.setFastScrollEnabled(true);
113        listView.setEmptyView(emptyView);
114
115        registerForContextMenu(listView);
116        setHasOptionsMenu(true);
117
118        if (savedInstanceState != null) {
119            mDialogEditingWord = savedInstanceState.getString(INSTANCE_KEY_DIALOG_EDITING_WORD);
120            mAddedWordAlready = savedInstanceState.getBoolean(INSTANCE_KEY_ADDED_WORD, false);
121        }
122    }
123
124    @Override
125    public void onResume() {
126        super.onResume();
127        final Intent intent = getActivity().getIntent();
128        if (!mAddedWordAlready
129                && intent.getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) {
130            final String word = intent.getStringExtra(EXTRA_WORD);
131            mAutoReturn = true;
132            if (word != null) {
133                showAddOrEditDialog(word);
134            }
135        }
136    }
137
138    @Override
139    public void onSaveInstanceState(Bundle outState) {
140        super.onSaveInstanceState(outState);
141        outState.putString(INSTANCE_KEY_DIALOG_EDITING_WORD, mDialogEditingWord);
142        outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready);
143    }
144
145    private Cursor createCursor() {
146        String currentLocale = Locale.getDefault().toString();
147        // Case-insensitive sort
148        return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
149                QUERY_SELECTION, new String[] { currentLocale },
150                "UPPER(" + UserDictionary.Words.WORD + ")");
151    }
152
153    private ListAdapter createAdapter() {
154        return new MyAdapter(getActivity(),
155                android.R.layout.simple_list_item_1, mCursor,
156                new String[] { UserDictionary.Words.WORD },
157                new int[] { android.R.id.text1 });
158    }
159
160    @Override
161    public void onListItemClick(ListView l, View v, int position, long id) {
162        getActivity().openContextMenu(v);
163    }
164
165    @Override
166    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
167        if (!(menuInfo instanceof AdapterContextMenuInfo)) return;
168
169        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
170        menu.setHeaderTitle(getWord(adapterMenuInfo.position));
171        menu.add(0, CONTEXT_MENU_EDIT, 0,
172                R.string.user_dict_settings_context_menu_edit_title);
173        menu.add(0, CONTEXT_MENU_DELETE, 0,
174                R.string.user_dict_settings_context_menu_delete_title);
175    }
176
177    @Override
178    public boolean onContextItemSelected(MenuItem item) {
179        ContextMenuInfo menuInfo = item.getMenuInfo();
180        if (!(menuInfo instanceof AdapterContextMenuInfo)) return false;
181
182        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
183        String word = getWord(adapterMenuInfo.position);
184        if (word == null) return true;
185
186        switch (item.getItemId()) {
187            case CONTEXT_MENU_DELETE:
188                deleteWord(word);
189                return true;
190
191            case CONTEXT_MENU_EDIT:
192                showAddOrEditDialog(word);
193                return true;
194        }
195
196        return false;
197    }
198
199    @Override
200    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
201        menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
202                .setIcon(R.drawable.ic_menu_add);
203    }
204
205    @Override
206    public boolean onOptionsItemSelected(MenuItem item) {
207        showAddOrEditDialog(null);
208        return true;
209    }
210
211    private void showAddOrEditDialog(String editingWord) {
212        mDialogEditingWord = editingWord;
213        showDialog(DIALOG_ADD_OR_EDIT);
214    }
215
216    private String getWord(int position) {
217        mCursor.moveToPosition(position);
218        // Handle a possible race-condition
219        if (mCursor.isAfterLast()) return null;
220
221        return mCursor.getString(
222                mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
223    }
224
225    @Override
226    public Dialog onCreateDialog(int id) {
227        final Activity activity = getActivity();
228        final View content = activity.getLayoutInflater().inflate(R.layout.dialog_edittext, null);
229        final EditText editText = (EditText) content.findViewById(R.id.edittext);
230        editText.setText(mDialogEditingWord);
231        // No prediction in soft keyboard mode. TODO: Create a better way to disable prediction
232        editText.setInputType(InputType.TYPE_CLASS_TEXT
233                | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
234
235        AlertDialog dialog =  new AlertDialog.Builder(activity)
236                .setTitle(mDialogEditingWord != null
237                        ? R.string.user_dict_settings_edit_dialog_title
238                        : R.string.user_dict_settings_add_dialog_title)
239                .setView(content)
240                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
241                    public void onClick(DialogInterface dialog, int which) {
242                        onAddOrEditFinished(editText.getText().toString());
243                        if (mAutoReturn) activity.onBackPressed();
244                    }})
245                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
246                    public void onClick(DialogInterface dialog, int which) {
247                        if (mAutoReturn) activity.onBackPressed();
248                    }})
249                .create();
250        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
251                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
252        return dialog;
253    }
254
255    private void showDialog(int dialogId) {
256        if (mDialogFragment != null) {
257            Log.e(TAG, "Old dialog fragment not null!");
258        }
259        mDialogFragment = new SettingsDialogFragment(this, dialogId);
260        mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId));
261    }
262
263    private void onAddOrEditFinished(String word) {
264        if (mDialogEditingWord != null) {
265            // The user was editing a word, so do a delete/add
266            deleteWord(mDialogEditingWord);
267        }
268
269        // Disallow duplicates
270        deleteWord(word);
271
272        // TODO: present UI for picking whether to add word to all locales, or current.
273        UserDictionary.Words.addWord(getActivity(), word.toString(),
274                250, UserDictionary.Words.LOCALE_TYPE_ALL);
275        if (!mCursor.requery()) {
276            throw new IllegalStateException("can't requery on already-closed cursor.");
277        }
278        mAddedWordAlready = true;
279    }
280
281    private void deleteWord(String word) {
282        getActivity().getContentResolver().delete(
283                UserDictionary.Words.CONTENT_URI, DELETE_SELECTION, new String[] { word });
284    }
285
286    private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
287        private AlphabetIndexer mIndexer;
288
289        public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
290            super(context, layout, c, from, to);
291
292            int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
293            String alphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
294            mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
295        }
296
297        public int getPositionForSection(int section) {
298            return mIndexer.getPositionForSection(section);
299        }
300
301        public int getSectionForPosition(int position) {
302            return mIndexer.getSectionForPosition(position);
303        }
304
305        public Object[] getSections() {
306            return mIndexer.getSections();
307        }
308    }
309}
310