/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.latin; import android.Manifest; import android.content.Context; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.util.Log; import com.android.inputmethod.annotations.ExternallyReferenced; import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.permissions.PermissionsUtil; import com.android.inputmethod.latin.personalization.AccountUtils; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.annotation.Nullable; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary implements ContactsChangedListener { private static final String TAG = ContactsBinaryDictionary.class.getSimpleName(); private static final String NAME = "contacts"; private static final boolean DEBUG = false; private static final boolean DEBUG_DUMP = false; /** * Whether to use "firstname lastname" in bigram predictions. */ private final boolean mUseFirstLastBigrams; private final ContactsManager mContactsManager; protected ContactsBinaryDictionary(final Context context, final Locale locale, final File dictFile, final String name) { super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS, dictFile); mUseFirstLastBigrams = ContactsDictionaryUtils.useFirstLastBigramsForLocale(locale); mContactsManager = new ContactsManager(context); mContactsManager.registerForUpdates(this /* listener */); reloadDictionaryIfRequired(); } // Note: This method is called by {@link DictionaryFacilitator} using Java reflection. @ExternallyReferenced public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale, final File dictFile, final String dictNamePrefix, @Nullable final String account) { return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME); } @Override public synchronized void close() { mContactsManager.close(); super.close(); } /** * Typically called whenever the dictionary is created for the first time or * recreated when we think that there are updates to the dictionary. * This is called asynchronously. */ @Override public void loadInitialContentsLocked() { loadDeviceAccountsEmailAddressesLocked(); loadDictionaryForUriLocked(ContactsContract.Profile.CONTENT_URI); // TODO: Switch this URL to the newer ContactsContract too loadDictionaryForUriLocked(Contacts.CONTENT_URI); } /** * Loads device accounts to the dictionary. */ private void loadDeviceAccountsEmailAddressesLocked() { final List accountVocabulary = AccountUtils.getDeviceAccountsEmailAddresses(mContext); if (accountVocabulary == null || accountVocabulary.isEmpty()) { return; } for (String word : accountVocabulary) { if (DEBUG) { Log.d(TAG, "loadAccountVocabulary: " + word); } runGCIfRequiredLocked(true /* mindsBlockByGC */); addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */, false /* isPossiblyOffensive */, BinaryDictionary.NOT_A_VALID_TIMESTAMP); } } /** * Loads data within content providers to the dictionary. */ private void loadDictionaryForUriLocked(final Uri uri) { if (!PermissionsUtil.checkAllPermissionsGranted( mContext, Manifest.permission.READ_CONTACTS)) { Log.i(TAG, "No permission to read contacts. Not loading the Dictionary."); } final ArrayList validNames = mContactsManager.getValidNames(uri); for (final String name : validNames) { addNameLocked(name); } if (uri.equals(Contacts.CONTENT_URI)) { // Since we were able to add content successfully, update the local // state of the manager. mContactsManager.updateLocalState(validNames); } } /** * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their * bigrams depending on locale. */ private void addNameLocked(final String name) { int len = StringUtils.codePointCount(name); NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext( BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM); // TODO: Better tokenization for non-Latin writing systems for (int i = 0; i < len; i++) { if (Character.isLetter(name.codePointAt(i))) { int end = ContactsDictionaryUtils.getWordEndPosition(name, len, i); String word = name.substring(i, end); if (DEBUG_DUMP) { Log.d(TAG, "addName word = " + word); } i = end - 1; // Don't add single letter words, possibly confuses // capitalization of i. final int wordLen = StringUtils.codePointCount(word); if (wordLen <= MAX_WORD_LENGTH && wordLen > 1) { if (DEBUG) { Log.d(TAG, "addName " + name + ", " + word + ", " + ngramContext); } runGCIfRequiredLocked(true /* mindsBlockByGC */); addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */, false /* isPossiblyOffensive */, BinaryDictionary.NOT_A_VALID_TIMESTAMP); if (ngramContext.isValid() && mUseFirstLastBigrams) { runGCIfRequiredLocked(true /* mindsBlockByGC */); addNgramEntryLocked(ngramContext, word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS_BIGRAM, BinaryDictionary.NOT_A_VALID_TIMESTAMP); } ngramContext = ngramContext.getNextNgramContext( new NgramContext.WordInfo(word)); } } } } @Override public void onContactsChange() { setNeedsToRecreate(); } }