UserDictionaryAddWordContents.java revision a79ba8a3d6dbdee777f156449c436fd3a4a57feb
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin.userdictionary;
18
19import com.android.inputmethod.latin.LocaleUtils;
20import com.android.inputmethod.latin.R;
21
22import android.app.Activity;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.database.Cursor;
26import android.os.Bundle;
27import android.provider.UserDictionary;
28import android.text.TextUtils;
29import android.view.View;
30import android.widget.EditText;
31
32import java.util.ArrayList;
33import java.util.Locale;
34import java.util.TreeSet;
35
36// Caveat: This class is basically taken from
37// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
38// in order to deal with some devices that have issues with the user dictionary handling
39
40/**
41 * A container class to factor common code to UserDictionaryAddWordFragment
42 * and UserDictionaryAddWordActivity.
43 */
44public class UserDictionaryAddWordContents {
45    public static final String EXTRA_MODE = "mode";
46    public static final String EXTRA_WORD = "word";
47    public static final String EXTRA_SHORTCUT = "shortcut";
48    public static final String EXTRA_LOCALE = "locale";
49    public static final String EXTRA_ORIGINAL_WORD = "originalWord";
50    public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut";
51
52    public static final int MODE_EDIT = 0;
53    public static final int MODE_INSERT = 1;
54
55    /* package */ static final int CODE_WORD_ADDED = 0;
56    /* package */ static final int CODE_CANCEL = 1;
57    /* package */ static final int CODE_ALREADY_PRESENT = 2;
58
59    private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250;
60
61    private final int mMode; // Either MODE_EDIT or MODE_INSERT
62    private final EditText mWordEditText;
63    private final EditText mShortcutEditText;
64    private String mLocale;
65    private final String mOldWord;
66    private final String mOldShortcut;
67
68    /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
69        mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
70        mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
71        final String word = args.getString(EXTRA_WORD);
72        if (null != word) {
73            mWordEditText.setText(word);
74            mWordEditText.setSelection(word.length());
75        }
76        final String shortcut = args.getString(EXTRA_SHORTCUT);
77        if (null != shortcut && null != mShortcutEditText) {
78            mShortcutEditText.setText(shortcut);
79        }
80        mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT
81        mOldWord = args.getString(EXTRA_WORD);
82        mOldShortcut = args.getString(EXTRA_SHORTCUT);
83        updateLocale(args.getString(EXTRA_LOCALE));
84    }
85
86    // locale may be null, this means default locale
87    // It may also be the empty string, which means "all locales"
88    /* package */ void updateLocale(final String locale) {
89        mLocale = null == locale ? Locale.getDefault().toString() : locale;
90    }
91
92    /* package */ void saveStateIntoBundle(final Bundle outState) {
93        outState.putString(EXTRA_WORD, mWordEditText.getText().toString());
94        outState.putString(EXTRA_ORIGINAL_WORD, mOldWord);
95        if (null != mShortcutEditText) {
96            outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString());
97        }
98        if (null != mOldShortcut) {
99            outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut);
100        }
101        outState.putString(EXTRA_LOCALE, mLocale);
102    }
103
104    /* package */ void delete(final Context context) {
105        if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
106            // Mode edit: remove the old entry.
107            final ContentResolver resolver = context.getContentResolver();
108            UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
109        }
110        // If we are in add mode, nothing was added, so we don't need to do anything.
111    }
112
113    /* package */ int apply(final Context context, final Bundle outParameters) {
114        if (null != outParameters) saveStateIntoBundle(outParameters);
115        final ContentResolver resolver = context.getContentResolver();
116        if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
117            // Mode edit: remove the old entry.
118            UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
119        }
120        final String newWord = mWordEditText.getText().toString();
121        final String newShortcut;
122        if (null == mShortcutEditText) {
123            newShortcut = null;
124        } else {
125            final String tmpShortcut = mShortcutEditText.getText().toString();
126            if (TextUtils.isEmpty(tmpShortcut)) {
127                newShortcut = null;
128            } else {
129                newShortcut = tmpShortcut;
130            }
131        }
132        if (TextUtils.isEmpty(newWord)) {
133            // If the word is somehow empty, don't insert it.
134            return CODE_CANCEL;
135        }
136        // If there is no shortcut, and the word already exists in the database, then we
137        // should not insert, because either A. the word exists with no shortcut, in which
138        // case the exact same thing we want to insert is already there, or B. the word
139        // exists with at least one shortcut, in which case it has priority on our word.
140        if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT;
141
142        // Disallow duplicates. If the same word with no shortcut is defined, remove it; if
143        // the same word with the same shortcut is defined, remove it; but we don't mind if
144        // there is the same word with a different, non-empty shortcut.
145        UserDictionarySettings.deleteWord(newWord, null, resolver);
146        if (!TextUtils.isEmpty(newShortcut)) {
147            // If newShortcut is empty we just deleted this, no need to do it again
148            UserDictionarySettings.deleteWord(newWord, newShortcut, resolver);
149        }
150
151        // In this class we use the empty string to represent 'all locales' and mLocale cannot
152        // be null. However the addWord method takes null to mean 'all locales'.
153        UserDictionary.Words.addWord(context, newWord.toString(),
154                FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut,
155                TextUtils.isEmpty(mLocale) ? null : LocaleUtils.constructLocaleFromString(mLocale));
156
157        return CODE_WORD_ADDED;
158    }
159
160    private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD };
161    private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD
162            + "=? AND " + UserDictionary.Words.LOCALE + "=?";
163    private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD
164            + "=? AND " + UserDictionary.Words.LOCALE + " is null";
165    private boolean hasWord(final String word, final Context context) {
166        final Cursor cursor;
167        // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't
168        // be null at all (it's ensured by the updateLocale method).
169        if ("".equals(mLocale)) {
170            cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
171                      HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES,
172                      new String[] { word }, null /* sort order */);
173        } else {
174            cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
175                      HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE,
176                      new String[] { word, mLocale }, null /* sort order */);
177        }
178        try {
179            if (null == cursor) return false;
180            return cursor.getCount() > 0;
181        } finally {
182            if (null != cursor) cursor.close();
183        }
184    }
185
186    public static class LocaleRenderer {
187        private final String mLocaleString;
188        private final String mDescription;
189        // LocaleString may NOT be null.
190        public LocaleRenderer(final Context context, final String localeString) {
191            mLocaleString = localeString;
192            if (null == localeString) {
193                mDescription = context.getString(R.string.user_dict_settings_more_languages);
194            } else if ("".equals(localeString)) {
195                mDescription = context.getString(R.string.user_dict_settings_all_languages);
196            } else {
197                mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName();
198            }
199        }
200        @Override
201        public String toString() {
202            return mDescription;
203        }
204        public String getLocaleString() {
205            return mLocaleString;
206        }
207        // "More languages..." is null ; "All languages" is the empty string.
208        public boolean isMoreLanguages() {
209            return null == mLocaleString;
210        }
211    }
212
213    private static void addLocaleDisplayNameToList(final Context context,
214            final ArrayList<LocaleRenderer> list, final String locale) {
215        if (null != locale) {
216            list.add(new LocaleRenderer(context, locale));
217        }
218    }
219
220    // Helper method to get the list of locales to display for this word
221    public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) {
222        final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity);
223        // Remove our locale if it's in, because we're always gonna put it at the top
224        locales.remove(mLocale); // mLocale may not be null
225        final String systemLocale = Locale.getDefault().toString();
226        // The system locale should be inside. We want it at the 2nd spot.
227        locales.remove(systemLocale); // system locale may not be null
228        locales.remove(""); // Remove the empty string if it's there
229        final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>();
230        // Add the passed locale, then the system locale at the top of the list. Add an
231        // "all languages" entry at the bottom of the list.
232        addLocaleDisplayNameToList(activity, localesList, mLocale);
233        if (!systemLocale.equals(mLocale)) {
234            addLocaleDisplayNameToList(activity, localesList, systemLocale);
235        }
236        for (final String l : locales) {
237            // TODO: sort in unicode order
238            addLocaleDisplayNameToList(activity, localesList, l);
239        }
240        if (!"".equals(mLocale)) {
241            // If mLocale is "", then we already inserted the "all languages" item, so don't do it
242            addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages
243        }
244        localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
245        return localesList;
246    }
247}
248