12bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer/*
22bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * Copyright (C) 2009 The Android Open Source Project
32bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer *
42bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * Licensed under the Apache License, Version 2.0 (the "License");
52bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * you may not use this file except in compliance with the License.
62bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * You may obtain a copy of the License at
72bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer *
82bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer *      http://www.apache.org/licenses/LICENSE-2.0
92bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer *
102bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * Unless required by applicable law or agreed to in writing, software
112bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * distributed under the License is distributed on an "AS IS" BASIS,
122bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * See the License for the specific language governing permissions and
142bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer * limitations under the License.
152bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer */
162bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
172bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerpackage com.android.inputmethod.latin;
182bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
192bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerimport android.content.ContentResolver;
202bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerimport android.content.Context;
212bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerimport android.database.ContentObserver;
222bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerimport android.database.Cursor;
23718e813fbdb905b083562c0a6cd087463bacc261Amith Yamasaniimport android.os.SystemClock;
24e26ef1bccddc942fdaeada3409c8e8ff18a35008Tadashi G. Takaokaimport android.provider.BaseColumns;
2541fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikovimport android.provider.ContactsContract.Contacts;
26979f8690967ff5409fe18f5085858ccdb8e0ccf1satokimport android.text.TextUtils;
27979f8690967ff5409fe18f5085858ccdb8e0ccf1satokimport android.util.Log;
282bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
290730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalardimport com.android.inputmethod.keyboard.Keyboard;
300730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard
312bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischerpublic class ContactsDictionary extends ExpandableDictionary {
3241fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
332bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    private static final String[] PROJECTION = {
34e26ef1bccddc942fdaeada3409c8e8ff18a35008Tadashi G. Takaoka        BaseColumns._ID,
3541fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov        Contacts.DISPLAY_NAME,
362bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    };
3741fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
3878c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa    private static final String TAG = "ContactsDictionary";
3978c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa
40979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    /**
41979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * Frequency for contacts information into the dictionary
42979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     */
43dc05c6650e02222686bae9fa05aad9030c11453bJean Chalard    private static final int FREQUENCY_FOR_CONTACTS = 40;
44979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
45979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
462bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    private static final int INDEX_NAME = 1;
4741fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
482bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    private ContentObserver mObserver;
4941fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
50b3f6d58b6ec716c26df38b584eda061265437cf4Amith Yamasani    private long mLastLoadedContacts;
51b3f6d58b6ec716c26df38b584eda061265437cf4Amith Yamasani
5214051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    public ContactsDictionary(final Context context, final int dicTypeId) {
53979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        super(context, dicTypeId);
5414051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        registerObserver(context);
5514051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        loadDictionary();
5614051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
5714051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
5814051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    private synchronized void registerObserver(final Context context) {
592bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        // Perform a managed query. The Activity will handle closing and requerying the cursor
602bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        // when needed.
6114051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        if (mObserver != null) return;
622bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        ContentResolver cres = context.getContentResolver();
63979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        cres.registerContentObserver(
6414051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard                Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
65979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    @Override
66979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    public void onChange(boolean self) {
67979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                        setRequiresReload(true);
68979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    }
69979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                });
7014051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
7114051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
7214051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    public void reopen(final Context context) {
7314051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        registerObserver(context);
742bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
7541fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
76979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    @Override
772bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    public synchronized void close() {
782bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        if (mObserver != null) {
792bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer            getContext().getContentResolver().unregisterContentObserver(mObserver);
802bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer            mObserver = null;
812bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        }
82283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani        super.close();
832bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
8441fc8f4a183a5be8070fe28b7956b1d03a5dd8ceDmitri Plotnikov
85283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani    @Override
86283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani    public void startDictionaryLoadingTaskLocked() {
87718e813fbdb905b083562c0a6cd087463bacc261Amith Yamasani        long now = SystemClock.uptimeMillis();
88b3f6d58b6ec716c26df38b584eda061265437cf4Amith Yamasani        if (mLastLoadedContacts == 0
89b3f6d58b6ec716c26df38b584eda061265437cf4Amith Yamasani                || now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
90283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani            super.startDictionaryLoadingTaskLocked();
91718e813fbdb905b083562c0a6cd087463bacc261Amith Yamasani        }
922bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
932bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
942bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    @Override
95283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani    public void loadDictionaryAsync() {
9678c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa        try {
9778c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            Cursor cursor = getContext().getContentResolver()
9878c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
9978c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            if (cursor != null) {
10078c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                addWords(cursor);
10178c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            }
10278c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa        } catch(IllegalStateException e) {
10378c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            Log.e(TAG, "Contacts DB is having problems");
104718e813fbdb905b083562c0a6cd087463bacc261Amith Yamasani        }
105283a77f633e92ed7dbe96b083c921fc244bbe880Amith Yamasani        mLastLoadedContacts = SystemClock.uptimeMillis();
1062bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
1072bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
1084556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard    @Override
1094556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
1104556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard            final WordCallback callback) {
1114556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard        // Do not return bigrams from Contacts when nothing was typed.
1124556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard        if (codes.size() <= 0) return;
1134556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard        super.getBigrams(codes, previousWord, callback);
1144556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard    }
1154556de4b4540b18d059759c88cd8254ae6a42fa7Jean Chalard
1162bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    private void addWords(Cursor cursor) {
1172bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        clearDictionary();
1182bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
1192bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        final int maxWordLength = getMaxWordLength();
12078c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa        try {
12178c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            if (cursor.moveToFirst()) {
12278c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                while (!cursor.isAfterLast()) {
12378c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                    String name = cursor.getString(INDEX_NAME);
12478c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa
1251ea78d92e6c148f28326f475373fb40e65350909Jean Chalard                    if (name != null && -1 == name.indexOf('@')) {
12678c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                        int len = name.length();
12778c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                        String prevWord = null;
12878c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa
12978c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                        // TODO: Better tokenization for non-Latin writing systems
13078c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                        for (int i = 0; i < len; i++) {
13178c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                            if (Character.isLetter(name.charAt(i))) {
13278c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                int j;
13378c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                for (j = i + 1; j < len; j++) {
13478c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    char c = name.charAt(j);
13578c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa
1360730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard                                    if (!(c == Keyboard.CODE_DASH
1370730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard                                            || c == Keyboard.CODE_SINGLE_QUOTE
1380730bbfbf5e37bbcb5c287aeff71b304c833a36eJean Chalard                                            || Character.isLetter(c))) {
13978c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                        break;
14078c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    }
141e5c7f0981d869806bc2ea7d58379a3138e0a0186Eric Fischer                                }
1422bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
14378c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                String word = name.substring(i, j);
14478c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                i = j - 1;
14578c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa
14678c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                // Safeguard against adding really long words. Stack
14778c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                // may overflow due to recursion
14878c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                // Also don't add single letter words, possibly confuses
14978c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                // capitalization of i.
15078c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                final int wordLen = word.length();
15178c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                if (wordLen < maxWordLength && wordLen > 1) {
15278c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    super.addWord(word, FREQUENCY_FOR_CONTACTS);
15378c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    if (!TextUtils.isEmpty(prevWord)) {
15478c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                        super.setBigram(prevWord, word,
15578c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                                FREQUENCY_FOR_CONTACTS_BIGRAM);
15678c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    }
15778c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                                    prevWord = word;
158979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                                }
159e5c7f0981d869806bc2ea7d58379a3138e0a0186Eric Fischer                            }
1602bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer                        }
1612bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer                    }
16278c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa                    cursor.moveToNext();
1632bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer                }
1642bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer            }
16578c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            cursor.close();
16678c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa        } catch(IllegalStateException e) {
16778c4611719f10b4a53ade54ab5eeb390061737a3Ken Wakasa            Log.e(TAG, "Contacts DB is having problems");
1682bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer        }
1692bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
1702bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer}
171