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.Manifest;
     20 import android.content.Context;
     21 import android.net.Uri;
     22 import android.provider.ContactsContract;
     23 import android.provider.ContactsContract.Contacts;
     24 import android.util.Log;
     25 
     26 import com.android.inputmethod.annotations.ExternallyReferenced;
     27 import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
     28 import com.android.inputmethod.latin.common.StringUtils;
     29 import com.android.inputmethod.latin.permissions.PermissionsUtil;
     30 import com.android.inputmethod.latin.personalization.AccountUtils;
     31 
     32 import java.io.File;
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 import java.util.Locale;
     36 
     37 import javax.annotation.Nullable;
     38 
     39 public class ContactsBinaryDictionary extends ExpandableBinaryDictionary
     40         implements ContactsChangedListener {
     41     private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
     42     private static final String NAME = "contacts";
     43 
     44     private static final boolean DEBUG = false;
     45     private static final boolean DEBUG_DUMP = false;
     46 
     47     /**
     48      * Whether to use "firstname lastname" in bigram predictions.
     49      */
     50     private final boolean mUseFirstLastBigrams;
     51     private final ContactsManager mContactsManager;
     52 
     53     protected ContactsBinaryDictionary(final Context context, final Locale locale,
     54             final File dictFile, final String name) {
     55         super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
     56                 dictFile);
     57         mUseFirstLastBigrams = ContactsDictionaryUtils.useFirstLastBigramsForLocale(locale);
     58         mContactsManager = new ContactsManager(context);
     59         mContactsManager.registerForUpdates(this /* listener */);
     60         reloadDictionaryIfRequired();
     61     }
     62 
     63     // Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
     64     @ExternallyReferenced
     65     public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale,
     66             final File dictFile, final String dictNamePrefix, @Nullable final String account) {
     67         return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
     68     }
     69 
     70     @Override
     71     public synchronized void close() {
     72         mContactsManager.close();
     73         super.close();
     74     }
     75 
     76     /**
     77      * Typically called whenever the dictionary is created for the first time or
     78      * recreated when we think that there are updates to the dictionary.
     79      * This is called asynchronously.
     80      */
     81     @Override
     82     public void loadInitialContentsLocked() {
     83         loadDeviceAccountsEmailAddressesLocked();
     84         loadDictionaryForUriLocked(ContactsContract.Profile.CONTENT_URI);
     85         // TODO: Switch this URL to the newer ContactsContract too
     86         loadDictionaryForUriLocked(Contacts.CONTENT_URI);
     87     }
     88 
     89     /**
     90      * Loads device accounts to the dictionary.
     91      */
     92     private void loadDeviceAccountsEmailAddressesLocked() {
     93         final List<String> accountVocabulary =
     94                 AccountUtils.getDeviceAccountsEmailAddresses(mContext);
     95         if (accountVocabulary == null || accountVocabulary.isEmpty()) {
     96             return;
     97         }
     98         for (String word : accountVocabulary) {
     99             if (DEBUG) {
    100                 Log.d(TAG, "loadAccountVocabulary: " + word);
    101             }
    102             runGCIfRequiredLocked(true /* mindsBlockByGC */);
    103             addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS,
    104                     false /* isNotAWord */, false /* isPossiblyOffensive */,
    105                     BinaryDictionary.NOT_A_VALID_TIMESTAMP);
    106         }
    107     }
    108 
    109     /**
    110      * Loads data within content providers to the dictionary.
    111      */
    112     private void loadDictionaryForUriLocked(final Uri uri) {
    113         if (!PermissionsUtil.checkAllPermissionsGranted(
    114                 mContext, Manifest.permission.READ_CONTACTS)) {
    115             Log.i(TAG, "No permission to read contacts. Not loading the Dictionary.");
    116         }
    117 
    118         final ArrayList<String> validNames = mContactsManager.getValidNames(uri);
    119         for (final String name : validNames) {
    120             addNameLocked(name);
    121         }
    122         if (uri.equals(Contacts.CONTENT_URI)) {
    123             // Since we were able to add content successfully, update the local
    124             // state of the manager.
    125             mContactsManager.updateLocalState(validNames);
    126         }
    127     }
    128 
    129     /**
    130      * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their
    131      * bigrams depending on locale.
    132      */
    133     private void addNameLocked(final String name) {
    134         int len = StringUtils.codePointCount(name);
    135         NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext(
    136                 BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM);
    137         // TODO: Better tokenization for non-Latin writing systems
    138         for (int i = 0; i < len; i++) {
    139             if (Character.isLetter(name.codePointAt(i))) {
    140                 int end = ContactsDictionaryUtils.getWordEndPosition(name, len, i);
    141                 String word = name.substring(i, end);
    142                 if (DEBUG_DUMP) {
    143                     Log.d(TAG, "addName word = " + word);
    144                 }
    145                 i = end - 1;
    146                 // Don't add single letter words, possibly confuses
    147                 // capitalization of i.
    148                 final int wordLen = StringUtils.codePointCount(word);
    149                 if (wordLen <= MAX_WORD_LENGTH && wordLen > 1) {
    150                     if (DEBUG) {
    151                         Log.d(TAG, "addName " + name + ", " + word + ", "  + ngramContext);
    152                     }
    153                     runGCIfRequiredLocked(true /* mindsBlockByGC */);
    154                     addUnigramLocked(word,
    155                             ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */,
    156                             false /* isPossiblyOffensive */,
    157                             BinaryDictionary.NOT_A_VALID_TIMESTAMP);
    158                     if (ngramContext.isValid() && mUseFirstLastBigrams) {
    159                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
    160                         addNgramEntryLocked(ngramContext,
    161                                 word,
    162                                 ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS_BIGRAM,
    163                                 BinaryDictionary.NOT_A_VALID_TIMESTAMP);
    164                     }
    165                     ngramContext = ngramContext.getNextNgramContext(
    166                             new NgramContext.WordInfo(word));
    167                 }
    168             }
    169         }
    170     }
    171 
    172     @Override
    173     public void onContactsChange() {
    174         setNeedsToRecreate();
    175     }
    176 }
    177