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