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