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
552e8aa0600293875c620ba7b650010cb30ec023c1Tom Ouyang    /** The locale for this contacts dictionary. Controls name bigram predictions. */
562e8aa0600293875c620ba7b650010cb30ec023c1Tom Ouyang    public final Locale mLocale;
572e8aa0600293875c620ba7b650010cb30ec023c1Tom Ouyang
5818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private ContentObserver mObserver;
5918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
6018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    /**
6118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * Whether to use "firstname lastname" in bigram predictions.
6218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     */
6318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private final boolean mUseFirstLastBigrams;
6418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
6505efe576f976f5fa280f8d523f2935c15cbb9bd1Jean Chalard    public ContactsBinaryDictionary(final Context context, Locale locale) {
66d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS);
672e8aa0600293875c620ba7b650010cb30ec023c1Tom Ouyang        mLocale = locale;
6818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
6918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        registerObserver(context);
7018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
7118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // Load the current binary dictionary from internal storage. If no binary dictionary exists,
7218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // loadDictionary will start a new thread to generate one asynchronously.
7318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        loadDictionary();
7418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
7518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
7618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private synchronized void registerObserver(final Context context) {
7718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // Perform a managed query. The Activity will handle closing and requerying the cursor
7818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // when needed.
7918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (mObserver != null) return;
8018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        ContentResolver cres = context.getContentResolver();
8118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
8218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                new ContentObserver(null) {
8318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    @Override
8418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    public void onChange(boolean self) {
8518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        setRequiresReload(true);
8618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
8718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                });
8818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
8918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
9018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public void reopen(final Context context) {
9118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        registerObserver(context);
9218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
9318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
9418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    @Override
9518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public synchronized void close() {
9618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (mObserver != null) {
9718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            mContext.getContentResolver().unregisterContentObserver(mObserver);
9818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            mObserver = null;
9918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
10018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        super.close();
10118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
10218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
10318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    @Override
10418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    public void loadDictionaryAsync() {
10518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        try {
10618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            Cursor cursor = mContext.getContentResolver()
10718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
10818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            if (cursor != null) {
10918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                try {
11018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    if (cursor.moveToFirst()) {
1114d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        sContactCountAtLastRebuild = getContactCount();
11218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        addWords(cursor);
11318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
11418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                } finally {
11518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    cursor.close();
11618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                }
11718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
11818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        } catch (IllegalStateException e) {
11918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            Log.e(TAG, "Contacts DB is having problems");
12018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
12118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
12218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
12318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private boolean useFirstLastBigramsForLocale(Locale locale) {
12418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // TODO: Add firstname/lastname bigram rules for other languages.
12518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
12618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            return true;
12718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
12818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        return false;
12918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
13018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
13118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private void addWords(Cursor cursor) {
13218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        clearFusionDictionary();
1334d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int count = 0;
1344d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
13518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            String name = cursor.getString(INDEX_NAME);
1364d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (isValidName(name)) {
13718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                addName(name);
1384d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                ++count;
13918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
14018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            cursor.moveToNext();
14118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
14218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
14318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang
1444d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private int getContactCount() {
1454d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // TODO: consider switching to a rawQuery("select count(*)...") on the database if
1464d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // performance is a bottleneck.
1474d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final Cursor cursor = mContext.getContentResolver().query(
1484d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null);
1494d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (cursor != null) {
1502798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            try {
1512798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang                return cursor.getCount();
1522798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            } finally {
1532798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang                cursor.close();
1542798c85c0f77fdf4f12eccfe241f84ddec3de994Tom Ouyang            }
1554d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
1564d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return 0;
1574d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
1584d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
15918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    /**
16018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their
16118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     * bigrams depending on locale.
16218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang     */
16318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    private void addName(String name) {
164e55c23e4b0b8d9d66349a3b275d0fa1540d7450aKen Wakasa        int len = StringUtils.codePointCount(name);
16518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        String prevWord = null;
16618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        // TODO: Better tokenization for non-Latin writing systems
16718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        for (int i = 0; i < len; i++) {
16818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            if (Character.isLetter(name.codePointAt(i))) {
1694d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                int end = getWordEndPosition(name, len, i);
1704d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                String word = name.substring(i, end);
1714d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                i = end - 1;
17218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                // Don't add single letter words, possibly confuses
17318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                // capitalization of i.
174e55c23e4b0b8d9d66349a3b275d0fa1540d7450aKen Wakasa                final int wordLen = StringUtils.codePointCount(word);
17518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
176f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang                    super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
17718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    if (!TextUtils.isEmpty(prevWord)) {
17818222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        if (mUseFirstLastBigrams) {
17918222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                            super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
18018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                        }
18118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    }
18218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                    prevWord = word;
18318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang                }
18418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang            }
18518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang        }
18618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang    }
1874d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
1884d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /**
1894d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Returns the index of the last letter in the word, starting from position startIndex.
1904d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     */
1914d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static int getWordEndPosition(String string, int len, int startIndex) {
1924d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int end;
1934d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        int cp = 0;
1944d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
1954d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            cp = string.codePointAt(end);
1964d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE
1974d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    || Character.isLetter(cp))) {
1984d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                break;
1994d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2004d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2014d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return end;
2024d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2034d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2044d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    @Override
2054d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    protected boolean hasContentChanged() {
2064d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final long startTime = SystemClock.uptimeMillis();
2074d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final int contactCount = getContactCount();
2084d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (contactCount > MAX_CONTACT_COUNT) {
2094d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // If there are too many contacts then return false. In this rare case it is impossible
2104d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // to include all of them anyways and the cost of rebuilding the dictionary is too high.
2114d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
2124d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return false;
2134d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2144d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (contactCount != sContactCountAtLastRebuild) {
2151ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            if (DEBUG) {
2161ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang                Log.d(TAG, "Contact count changed: " + sContactCountAtLastRebuild + " to "
2171ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang                        + contactCount);
2181ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            }
2194d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return true;
2204d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2214d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // Check all contacts since it's not possible to find out which names have changed.
2224d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // This is needed because it's possible to receive extraneous onChange events even when no
2234d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        // name has changed.
2244d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        Cursor cursor = mContext.getContentResolver().query(
2254d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                Contacts.CONTENT_URI, PROJECTION, null, null, null);
2264d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (cursor != null) {
2274d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            try {
2284d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                if (cursor.moveToFirst()) {
2294d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    while (!cursor.isAfterLast()) {
2304d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        String name = cursor.getString(INDEX_NAME);
2314d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (isValidName(name) && !isNameInDictionary(name)) {
2324d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            if (DEBUG) {
2334d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                                Log.d(TAG, "Contact name missing: " + name + " (runtime = "
2344d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                                        + (SystemClock.uptimeMillis() - startTime) + " ms)");
2354d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            }
2364d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return true;
2374d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2384d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        cursor.moveToNext();
2394d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    }
2404d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                }
2414d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            } finally {
2424d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                cursor.close();
2434d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2444d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2454d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (DEBUG) {
2464d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
2474d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    + " ms)");
2484d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2494d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return false;
2504d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2514d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2524d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private static boolean isValidName(String name) {
2534d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (name != null && -1 == name.indexOf('@')) {
2544d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            return true;
2554d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2564d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return false;
2574d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
2584d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
2594d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /**
2604d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Checks if the words in a name are in the current binary dictionary.
2614d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     */
2624d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    private boolean isNameInDictionary(String name) {
263e55c23e4b0b8d9d66349a3b275d0fa1540d7450aKen Wakasa        int len = StringUtils.codePointCount(name);
2644d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        String prevWord = null;
2654d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        for (int i = 0; i < len; i++) {
2664d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            if (Character.isLetter(name.codePointAt(i))) {
2674d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                int end = getWordEndPosition(name, len, i);
2684d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                String word = name.substring(i, end);
2694d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                i = end - 1;
270e55c23e4b0b8d9d66349a3b275d0fa1540d7450aKen Wakasa                final int wordLen = StringUtils.codePointCount(word);
2714d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
2724d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
2734d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (!super.isValidBigramLocked(prevWord, word)) {
2744d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return false;
2754d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2764d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    } else {
2774d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        if (!super.isValidWordLocked(word)) {
2784d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                            return false;
2794d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                        }
2804d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    }
2814d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                    prevWord = word;
2824d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                }
2834d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang            }
2844d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        }
2854d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return true;
2864d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
28718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang}
288