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