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