ContactsBinaryDictionary.java revision 1ed017ef0e271ed3f3c212def6cc6ba95b14e780
118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang/*
218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * Copyright (C) 2012 The Android Open Source Project
318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang *
418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * in compliance with the License. You may obtain a copy of the License at
618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang *
718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * http://www.apache.org/licenses/LICENSE-2.0
818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang *
918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * Unless required by applicable law or agreed to in writing, software distributed under the License
1018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
1118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * or implied. See the License for the specific language governing permissions and limitations under
1218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang * the License.
1318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang */
1418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
1518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangpackage com.android.inputmethod.latin;
1618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
1718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.content.ContentResolver;
1818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.content.Context;
1918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.database.ContentObserver;
2018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.database.Cursor;
214d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyangimport android.os.SystemClock;
2218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.provider.BaseColumns;
2318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.provider.ContactsContract.Contacts;
2418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.text.TextUtils;
2518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport android.util.Log;
2618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
2718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport com.android.inputmethod.keyboard.Keyboard;
2818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
2918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport java.util.Locale;
3018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
3118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangpublic class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
3218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
3318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME,};
344d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID};
3518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
3618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
3718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final String NAME = "contacts";
3818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
394d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static boolean DEBUG = false;
404d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
4118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    /**
4218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * Frequency for contacts information into the dictionary
4318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     */
4418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final int FREQUENCY_FOR_CONTACTS = 40;
4518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
4618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
474d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /** The maximum number of contacts that this dictionary supports. */
484d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static final int MAX_CONTACT_COUNT = 10000;
494d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
5018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private static final int INDEX_NAME = 1;
5118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
524d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /** The number of contacts in the most recent dictionary rebuild. */
534d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    static private int sContactCountAtLastRebuild = 0;
544d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
5518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private ContentObserver mObserver;
5618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
5718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    /**
5818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * Whether to use "firstname lastname" in bigram predictions.
5918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     */
6018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private final boolean mUseFirstLastBigrams;
6118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
6218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public ContactsBinaryDictionary(final Context context, final int dicTypeId, Locale locale) {
63f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang        super(context, getFilenameWithLocale(NAME, locale.toString()), dicTypeId);
6418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
6518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        registerObserver(context);
6618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
6718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // Load the current binary dictionary from internal storage. If no binary dictionary exists,
6818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // loadDictionary will start a new thread to generate one asynchronously.
6918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        loadDictionary();
7018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
7118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
7218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private synchronized void registerObserver(final Context context) {
7318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // Perform a managed query. The Activity will handle closing and requerying the cursor
7418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // when needed.
7518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (mObserver != null) return;
7618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        ContentResolver cres = context.getContentResolver();
7718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
7818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                new ContentObserver(null) {
7918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    @Override
8018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    public void onChange(boolean self) {
8118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        setRequiresReload(true);
8218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
8318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                });
8418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
8518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
8618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public void reopen(final Context context) {
8718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        registerObserver(context);
8818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
8918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
9018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    @Override
9118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public synchronized void close() {
9218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (mObserver != null) {
9318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            mContext.getContentResolver().unregisterContentObserver(mObserver);
9418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            mObserver = null;
9518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
9618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        super.close();
9718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
9818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
9918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    @Override
10018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public void loadDictionaryAsync() {
10118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        try {
10218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            Cursor cursor = mContext.getContentResolver()
10318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
10418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            if (cursor != null) {
10518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                try {
10618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    if (cursor.moveToFirst()) {
1074d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        sContactCountAtLastRebuild = getContactCount();
10818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        addWords(cursor);
10918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
11018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                } finally {
11118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    cursor.close();
11218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                }
11318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
11418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        } catch (IllegalStateException e) {
11518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            Log.e(TAG, "Contacts DB is having problems");
11618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
11718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
11818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
11918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    @Override
12018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
12118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            final WordCallback callback) {
12218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        super.getBigrams(codes, previousWord, callback);
12318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
12418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
12518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private boolean useFirstLastBigramsForLocale(Locale locale) {
12618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // TODO: Add firstname/lastname bigram rules for other languages.
12718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
12818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            return true;
12918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
13018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        return false;
13118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
13218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
13318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private void addWords(Cursor cursor) {
13418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        clearFusionDictionary();
1354d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int count = 0;
1364d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
13718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            String name = cursor.getString(INDEX_NAME);
1384d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (isValidName(name)) {
13918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                addName(name);
1404d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                ++count;
14118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
14218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            cursor.moveToNext();
14318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
14418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
14518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
1464d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private int getContactCount() {
1474d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // TODO: consider switching to a rawQuery("select count(*)...") on the database if
1484d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // performance is a bottleneck.
1494d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final Cursor cursor = mContext.getContentResolver().query(
1504d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null);
1514d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (cursor != null) {
1522798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            try {
1532798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang                return cursor.getCount();
1542798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            } finally {
1552798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang                cursor.close();
1562798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            }
1574d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
1584d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return 0;
1594d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
1604d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
16118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    /**
16218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their
16318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * bigrams depending on locale.
16418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     */
16518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private void addName(String name) {
16618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        int len = name.codePointCount(0, name.length());
16718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        String prevWord = null;
16818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // TODO: Better tokenization for non-Latin writing systems
16918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        for (int i = 0; i < len; i++) {
17018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            if (Character.isLetter(name.codePointAt(i))) {
1714d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                int end = getWordEndPosition(name, len, i);
1724d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                String word = name.substring(i, end);
1734d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                i = end - 1;
17418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                // Don't add single letter words, possibly confuses
17518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                // capitalization of i.
17618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                final int wordLen = word.codePointCount(0, word.length());
17718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
178f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang                    super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
17918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    if (!TextUtils.isEmpty(prevWord)) {
18018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        if (mUseFirstLastBigrams) {
18118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                            super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
18218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        }
18318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
18418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    prevWord = word;
18518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                }
18618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
18718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
18818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
1894d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
1904d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /**
1914d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Returns the index of the last letter in the word, starting from position startIndex.
1924d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     */
1934d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static int getWordEndPosition(String string, int len, int startIndex) {
1944d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int end;
1954d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int cp = 0;
1964d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
1974d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            cp = string.codePointAt(end);
1984d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE
1994d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    || Character.isLetter(cp))) {
2004d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                break;
2014d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2024d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2034d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return end;
2044d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2054d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2064d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    @Override
2074d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    protected boolean hasContentChanged() {
2084d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final long startTime = SystemClock.uptimeMillis();
2094d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final int contactCount = getContactCount();
2104d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (contactCount > MAX_CONTACT_COUNT) {
2114d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // If there are too many contacts then return false. In this rare case it is impossible
2124d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // to include all of them anyways and the cost of rebuilding the dictionary is too high.
2134d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
2144d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return false;
2154d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2164d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (contactCount != sContactCountAtLastRebuild) {
2171ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            if (DEBUG) {
2181ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang                Log.d(TAG, "Contact count changed: " + sContactCountAtLastRebuild + " to "
2191ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang                        + contactCount);
2201ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            }
2214d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return true;
2224d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2234d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // Check all contacts since it's not possible to find out which names have changed.
2244d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // This is needed because it's possible to receive extraneous onChange events even when no
2254d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // name has changed.
2264d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        Cursor cursor = mContext.getContentResolver().query(
2274d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                Contacts.CONTENT_URI, PROJECTION, null, null, null);
2284d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (cursor != null) {
2294d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            try {
2304d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                if (cursor.moveToFirst()) {
2314d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    while (!cursor.isAfterLast()) {
2324d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        String name = cursor.getString(INDEX_NAME);
2334d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (isValidName(name) && !isNameInDictionary(name)) {
2344d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            if (DEBUG) {
2354d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                                Log.d(TAG, "Contact name missing: " + name + " (runtime = "
2364d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                                        + (SystemClock.uptimeMillis() - startTime) + " ms)");
2374d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            }
2384d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return true;
2394d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2404d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        cursor.moveToNext();
2414d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    }
2424d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                }
2434d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            } finally {
2444d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                cursor.close();
2454d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2464d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2474d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (DEBUG) {
2484d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
2494d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    + " ms)");
2504d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2514d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return false;
2524d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2534d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2544d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static boolean isValidName(String name) {
2554d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (name != null && -1 == name.indexOf('@')) {
2564d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return true;
2574d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2584d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return false;
2594d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2604d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2614d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /**
2624d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Checks if the words in a name are in the current binary dictionary.
2634d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     */
2644d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private boolean isNameInDictionary(String name) {
2654d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int len = name.codePointCount(0, name.length());
2664d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        String prevWord = null;
2674d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        for (int i = 0; i < len; i++) {
2684d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (Character.isLetter(name.codePointAt(i))) {
2694d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                int end = getWordEndPosition(name, len, i);
2704d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                String word = name.substring(i, end);
2714d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                i = end - 1;
2724d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                final int wordLen = word.codePointCount(0, word.length());
2734d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
2744d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
2754d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (!super.isValidBigramLocked(prevWord, word)) {
2764d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return false;
2774d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2784d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    } else {
2794d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (!super.isValidWordLocked(word)) {
2804d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return false;
2814d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2824d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    }
2834d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    prevWord = word;
2844d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                }
2854d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2864d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2874d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return true;
2884d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
28918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang}
290