Home | History | Annotate | Download | only in latin
      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.inputmethod.latin;
     18 
     19 import android.content.ContentProviderClient;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.database.ContentObserver;
     24 import android.database.Cursor;
     25 import android.provider.UserDictionary.Words;
     26 import android.text.TextUtils;
     27 
     28 import java.util.Arrays;
     29 
     30 /**
     31  * An expandable dictionary that stores the words in the user unigram dictionary.
     32  *
     33  * Largely a copy of UserDictionary, will replace that class in the future.
     34  */
     35 public class UserBinaryDictionary extends ExpandableBinaryDictionary {
     36 
     37     // TODO: use Words.SHORTCUT when it's public in the SDK
     38     final static String SHORTCUT = "shortcut";
     39     private static final String[] PROJECTION_QUERY = {
     40         Words.WORD,
     41         SHORTCUT,
     42         Words.FREQUENCY,
     43     };
     44 
     45     private static final String NAME = "userunigram";
     46 
     47     // This is not exported by the framework so we pretty much have to write it here verbatim
     48     private static final String ACTION_USER_DICTIONARY_INSERT =
     49             "com.android.settings.USER_DICTIONARY_INSERT";
     50 
     51     private ContentObserver mObserver;
     52     final private String mLocale;
     53     final private boolean mAlsoUseMoreRestrictiveLocales;
     54 
     55     public UserBinaryDictionary(final Context context, final String locale) {
     56         this(context, locale, false);
     57     }
     58 
     59     public UserBinaryDictionary(final Context context, final String locale,
     60             final boolean alsoUseMoreRestrictiveLocales) {
     61         super(context, getFilenameWithLocale(NAME, locale), Suggest.DIC_USER);
     62         if (null == locale) throw new NullPointerException(); // Catch the error earlier
     63         mLocale = locale;
     64         mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
     65         // Perform a managed query. The Activity will handle closing and re-querying the cursor
     66         // when needed.
     67         ContentResolver cres = context.getContentResolver();
     68 
     69         mObserver = new ContentObserver(null) {
     70             @Override
     71             public void onChange(boolean self) {
     72                 setRequiresReload(true);
     73             }
     74         };
     75         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
     76 
     77         loadDictionary();
     78     }
     79 
     80     @Override
     81     public synchronized void close() {
     82         if (mObserver != null) {
     83             mContext.getContentResolver().unregisterContentObserver(mObserver);
     84             mObserver = null;
     85         }
     86         super.close();
     87     }
     88 
     89     @Override
     90     public void loadDictionaryAsync() {
     91         // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"],
     92         // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3.
     93         // This is correct for locale processing.
     94         // For this example, we'll look at the "en_US_POSIX" case.
     95         final String[] localeElements =
     96                 TextUtils.isEmpty(mLocale) ? new String[] {} : mLocale.split("_", 3);
     97         final int length = localeElements.length;
     98 
     99         final StringBuilder request = new StringBuilder("(locale is NULL)");
    100         String localeSoFar = "";
    101         // At start, localeElements = ["en", "US", "POSIX"] ; localeSoFar = "" ;
    102         // and request = "(locale is NULL)"
    103         for (int i = 0; i < length; ++i) {
    104             // i | localeSoFar    | localeElements
    105             // 0 | ""             | ["en", "US", "POSIX"]
    106             // 1 | "en_"          | ["en", "US", "POSIX"]
    107             // 2 | "en_US_"       | ["en", "en_US", "POSIX"]
    108             localeElements[i] = localeSoFar + localeElements[i];
    109             localeSoFar = localeElements[i] + "_";
    110             // i | request
    111             // 0 | "(locale is NULL)"
    112             // 1 | "(locale is NULL) or (locale=?)"
    113             // 2 | "(locale is NULL) or (locale=?) or (locale=?)"
    114             request.append(" or (locale=?)");
    115         }
    116         // At the end, localeElements = ["en", "en_US", "en_US_POSIX"]; localeSoFar = en_US_POSIX_"
    117         // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)"
    118 
    119         final String[] requestArguments;
    120         // If length == 3, we already have all the arguments we need (common prefix is meaningless
    121         // inside variants
    122         if (mAlsoUseMoreRestrictiveLocales && length < 3) {
    123             request.append(" or (locale like ?)");
    124             // The following creates an array with one more (null) position
    125             final String[] localeElementsWithMoreRestrictiveLocalesIncluded =
    126                     Arrays.copyOf(localeElements, length + 1);
    127             localeElementsWithMoreRestrictiveLocalesIncluded[length] =
    128                     localeElements[length - 1] + "_%";
    129             requestArguments = localeElementsWithMoreRestrictiveLocalesIncluded;
    130             // If for example localeElements = ["en"]
    131             // then requestArguments = ["en", "en_%"]
    132             // and request = (locale is NULL) or (locale=?) or (locale like ?)
    133             // If localeElements = ["en", "en_US"]
    134             // then requestArguments = ["en", "en_US", "en_US_%"]
    135         } else {
    136             requestArguments = localeElements;
    137         }
    138         final Cursor cursor = mContext.getContentResolver().query(
    139             Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
    140         try {
    141             addWords(cursor);
    142         } finally {
    143             if (null != cursor) cursor.close();
    144         }
    145     }
    146 
    147     public boolean isEnabled() {
    148         final ContentResolver cr = mContext.getContentResolver();
    149         final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
    150         if (client != null) {
    151             client.release();
    152             return true;
    153         } else {
    154             return false;
    155         }
    156     }
    157 
    158     /**
    159      * Adds a word to the user dictionary and makes it persistent.
    160      *
    161      * This will call upon the system interface to do the actual work through the intent readied by
    162      * the system to this effect.
    163      *
    164      * @param word the word to add. If the word is capitalized, then the dictionary will
    165      * recognize it as a capitalized word when searched.
    166      * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
    167      * the highest.
    168      * @TODO use a higher or float range for frequency
    169      */
    170     public synchronized void addWordToUserDictionary(final String word, final int frequency) {
    171         // TODO: do something for the UI. With the following, any sufficiently long word will
    172         // look like it will go to the user dictionary but it won't.
    173         // Safeguard against adding long words. Can cause stack overflow.
    174         if (word.length() >= MAX_WORD_LENGTH) return;
    175 
    176         // TODO: Add an argument to the intent to specify the frequency.
    177         Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
    178         intent.putExtra(Words.WORD, word);
    179         intent.putExtra(Words.LOCALE, mLocale);
    180         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    181         mContext.startActivity(intent);
    182     }
    183 
    184     private void addWords(Cursor cursor) {
    185         clearFusionDictionary();
    186         if (cursor == null) return;
    187         if (cursor.moveToFirst()) {
    188             final int indexWord = cursor.getColumnIndex(Words.WORD);
    189             final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
    190             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
    191             while (!cursor.isAfterLast()) {
    192                 String word = cursor.getString(indexWord);
    193                 String shortcut = cursor.getString(indexShortcut);
    194                 int frequency = cursor.getInt(indexFrequency);
    195                 // Safeguard against adding really long words.
    196                 if (word.length() < MAX_WORD_LENGTH) {
    197                     super.addWord(word, null, frequency);
    198                 }
    199                 if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
    200                     super.addWord(shortcut, word, frequency);
    201                 }
    202                 cursor.moveToNext();
    203             }
    204         }
    205     }
    206 
    207     @Override
    208     protected boolean hasContentChanged() {
    209         return true;
    210     }
    211 }
    212