1f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov/* 2f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * Copyright (C) 2011 The Android Open Source Project 3f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * 4f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * Licensed under the Apache License, Version 2.0 (the "License"); 5f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * you may not use this file except in compliance with the License. 6f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * You may obtain a copy of the License at 7f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * 8f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * http://www.apache.org/licenses/LICENSE-2.0 9f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * 10f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * Unless required by applicable law or agreed to in writing, software 11f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * distributed under the License is distributed on an "AS IS" BASIS, 12f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * See the License for the specific language governing permissions and 14f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * limitations under the License 15f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov */ 16f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovpackage com.android.providers.contacts; 17f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 18f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; 19f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; 20f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns; 21f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport com.android.providers.contacts.ContactsDatabaseHelper.Tables; 22f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 23f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.content.ContentValues; 24f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.database.Cursor; 25f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.database.sqlite.SQLiteDatabase; 2605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikovimport android.os.SystemClock; 27197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Email; 28197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Nickname; 29197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Organization; 30197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 31f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.provider.ContactsContract.Data; 3205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikovimport android.provider.ContactsContract.ProviderStatus; 33f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.text.TextUtils; 34f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.util.Log; 35f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 36f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport java.util.HashSet; 37f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport java.util.Set; 38f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onukiimport java.util.regex.Pattern; 39f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 40f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov/** 41f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * Maintains a search index for comprehensive contact search. 42f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov */ 43f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovpublic class SearchIndexManager { 44f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private static final String TAG = "ContactsFTS"; 45f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 46197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov public static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index"; 4705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private static final int SEARCH_INDEX_VERSION = 1; 4805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 49f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private static final class ContactIndexQuery { 50f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final String[] COLUMNS = { 51f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.CONTACT_ID, 52f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov MimetypesColumns.MIMETYPE, 53f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, 54f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, 55f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA12, Data.DATA13, Data.DATA14 56f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov }; 57f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 58f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int MIMETYPE = 1; 59f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 60f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 61f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static class IndexBuilder { 62f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_SPACE = 0; 63f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_PARENTHESES = 1; 64f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_SLASH = 2; 65f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_COMMA = 3; 66f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 67f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbContent = new StringBuilder(); 68155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov private StringBuilder mSbName = new StringBuilder(); 69f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbTokens = new StringBuilder(); 70f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbElementContent = new StringBuilder(); 71f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private HashSet<String> mUniqueElements = new HashSet<String>(); 72f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private Cursor mCursor; 73f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 74f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov void setCursor(Cursor cursor) { 75f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov this.mCursor = cursor; 76f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 77f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 78f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov void reset() { 79f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.setLength(0); 80f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbTokens.setLength(0); 81155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mSbName.setLength(0); 82f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbElementContent.setLength(0); 83f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mUniqueElements.clear(); 84f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 85f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 86f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String getContent() { 87f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return mSbContent.length() == 0 ? null : mSbContent.toString(); 88f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 89f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 90155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov public String getName() { 91155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return mSbName.length() == 0 ? null : mSbName.toString(); 92155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 93155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 94f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String getTokens() { 95f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return mSbTokens.length() == 0 ? null : mSbTokens.toString(); 96f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 97f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 98eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public String getString(String columnName) { 99eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov return mCursor.getString(mCursor.getColumnIndex(columnName)); 100eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 101eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 1026d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov public int getInt(String columnName) { 1036d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov return mCursor.getInt(mCursor.getColumnIndex(columnName)); 1046d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov } 1056d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov 106f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov @Override 107f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String toString() { 108155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return "Content: " + mSbContent + "\n Name: " + mSbTokens + "\n Tokens: " + mSbTokens; 109f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 110f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 111f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void commit() { 112f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (mSbElementContent.length() != 0) { 113eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov String content = mSbElementContent.toString().replace('\n', ' '); 114f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (!mUniqueElements.contains(content)) { 115f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (mSbContent.length() != 0) { 116f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.append('\n'); 117f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 118f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.append(content); 119f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mUniqueElements.add(content); 120f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 121eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.setLength(0); 122f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 123f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 124f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 125f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void appendContentFromColumn(String columnName) { 126f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov appendContentFromColumn(columnName, SEPARATOR_SPACE); 127f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 128f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 129f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void appendContentFromColumn(String columnName, int format) { 130eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov appendContent(getString(columnName), format); 131eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 132eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 133eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public void appendContent(String value) { 134eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov appendContent(value, SEPARATOR_SPACE); 135f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 136f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 137f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void appendContent(String value, int format) { 138f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (TextUtils.isEmpty(value)) { 139f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return; 140f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 141f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 142f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov switch (format) { 143f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_SPACE: 144eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 145eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(' '); 146f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 147eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(value); 148f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 149f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 150f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_SLASH: 151eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append('/').append(value); 152f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 153f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 154f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_PARENTHESES: 155eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 156eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(' '); 157f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 158eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append('(').append(value).append(')'); 159f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 160f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 161f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_COMMA: 162eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 163eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(", "); 164f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 165eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(value); 166f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 167f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 168f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 169eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 170eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public void appendToken(String token) { 171f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov if (TextUtils.isEmpty(token)) { 172f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov return; 173f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov } 174f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov 175eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbTokens.length() != 0) { 176eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbTokens.append(' '); 177eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 178eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbTokens.append(token); 179eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 180155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 181f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki private static final Pattern PATTERN_HYPHEN = Pattern.compile("\\-"); 182f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki 183155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov public void appendName(String name) { 184155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov if (TextUtils.isEmpty(name)) { 185155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return; 186155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 187f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki if (name.indexOf('-') < 0) { 188f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki // Common case -- no hyphens in it. 189f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki appendNameInternal(name); 190f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } else { 191f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki // In order to make hyphenated names searchable, let's split names with '-'. 192f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki for (String namePart : PATTERN_HYPHEN.split(name)) { 193f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki if (!TextUtils.isEmpty(namePart)) { 194f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki appendNameInternal(namePart); 195f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 196f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 197f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 198f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 199155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 200f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki private void appendNameInternal(String name) { 201155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov if (mSbName.length() != 0) { 202155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mSbName.append(' '); 203155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 204d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann mSbName.append(NameNormalizer.normalize(name)); 205155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 206f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 207f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 208f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private final ContactsProvider2 mContactsProvider; 209f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private final ContactsDatabaseHelper mDbHelper; 210f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSb = new StringBuilder(); 211f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private IndexBuilder mIndexBuilder = new IndexBuilder(); 212f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private ContentValues mValues = new ContentValues(); 213f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private String[] mSelectionArgs1 = new String[1]; 214f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 215f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public SearchIndexManager(ContactsProvider2 contactsProvider) { 216f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov this.mContactsProvider = contactsProvider; 217f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mDbHelper = (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper(); 218f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 219f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 22005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov public void updateIndex() { 22105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov if (getSearchIndexVersion() == SEARCH_INDEX_VERSION) { 22205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov return; 22305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 22405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov SQLiteDatabase db = mDbHelper.getWritableDatabase(); 22505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.beginTransaction(); 22605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov try { 22705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov if (getSearchIndexVersion() != SEARCH_INDEX_VERSION) { 22805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov rebuildIndex(db); 22905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov setSearchIndexVersion(SEARCH_INDEX_VERSION); 23005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.setTransactionSuccessful(); 23105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 23205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } finally { 23305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.endTransaction(); 23405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 23505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 23605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 23705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private void rebuildIndex(SQLiteDatabase db) { 23805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mContactsProvider.setProviderStatus(ProviderStatus.STATUS_UPGRADING); 23905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov long start = SystemClock.currentThreadTimeMillis(); 24005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov int count = 0; 24105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov try { 24205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mDbHelper.createSearchIndexTable(db); 2438f79445f450755858a88e8a5a0e14d81a0b9ff87Daisuke Miyakawa count = buildIndex(db, null, false); 24405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } finally { 24505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mContactsProvider.setProviderStatus(ProviderStatus.STATUS_NORMAL); 24605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 24705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov long end = SystemClock.currentThreadTimeMillis(); 24805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov Log.i(TAG, "Rebuild contact search index in " + (end - start) + "ms, " 24905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov + count + " contacts"); 25005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 25105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 25205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 253bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov public void updateIndexForRawContacts(Set<Long> contactIds, Set<Long> rawContactIds) { 254f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSb.setLength(0); 255bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append("("); 256bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!contactIds.isEmpty()) { 257bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(Data.CONTACT_ID + " IN ("); 258bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov for (Long contactId : contactIds) { 259bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(contactId).append(","); 260bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 261bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.setLength(mSb.length() - 1); 262bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(')'); 263bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 264bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov 265bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!rawContactIds.isEmpty()) { 266bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!contactIds.isEmpty()) { 267bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(" OR "); 268bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 269bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(Data.RAW_CONTACT_ID + " IN ("); 270bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov for (Long rawContactId : rawContactIds) { 271bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(rawContactId).append(","); 272bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 273bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.setLength(mSb.length() - 1); 274bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov mSb.append(')'); 275f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 276f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 2778f79445f450755858a88e8a5a0e14d81a0b9ff87Daisuke Miyakawa mSb.append(")"); 27805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov buildIndex(mDbHelper.getWritableDatabase(), mSb.toString(), true); 27905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 28005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 28105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private int buildIndex(SQLiteDatabase db, String selection, boolean replace) { 282197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.setLength(0); 283197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(Data.CONTACT_ID + ", "); 284197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append("(CASE WHEN " + DataColumns.MIMETYPE_ID + "="); 285197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Nickname.CONTENT_ITEM_TYPE)); 286197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -4 "); 287197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 288197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Organization.CONTENT_ITEM_TYPE)); 289197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -3 "); 290197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 291197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE)); 292197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -2"); 293197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 294197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE)); 295197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -1"); 296174f7d319b987aa2aeeb6f2563f4b939acb8d791Dmitri Plotnikov mSb.append(" ELSE " + DataColumns.MIMETYPE_ID); 297197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" END), " + Data.IS_SUPER_PRIMARY + ", " + DataColumns.CONCRETE_ID); 298197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov 29905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov int count = 0; 300197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov Cursor cursor = db.query(Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS, ContactIndexQuery.COLUMNS, 301197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov selection, null, null, null, mSb.toString()); 302f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.setCursor(cursor); 303f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.reset(); 304f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov try { 305f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov long currentContactId = -1; 306f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov while (cursor.moveToNext()) { 307f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov long contactId = cursor.getLong(0); 308f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (contactId != currentContactId) { 309f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (currentContactId != -1) { 31005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov saveContactIndex(db, currentContactId, mIndexBuilder, replace); 31105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov count++; 312f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 313f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov currentContactId = contactId; 314f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.reset(); 315f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 316f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov String mimetype = cursor.getString(ContactIndexQuery.MIMETYPE); 317f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov DataRowHandler dataRowHandler = mContactsProvider.getDataRowHandler(mimetype); 318f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (dataRowHandler.hasSearchableData()) { 319f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov dataRowHandler.appendSearchableData(mIndexBuilder); 320f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.commit(); 321f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 322f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 323f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (currentContactId != -1) { 32405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov saveContactIndex(db, currentContactId, mIndexBuilder, replace); 32505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov count++; 326f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 327f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } finally { 328f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov cursor.close(); 329f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 33005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov return count; 331f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 332f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 33305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private void saveContactIndex( 33405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov SQLiteDatabase db, long contactId, IndexBuilder builder, boolean replace) { 335f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.clear(); 336f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.put(SearchIndexColumns.CONTENT, builder.getContent()); 337155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mValues.put(SearchIndexColumns.NAME, builder.getName()); 338f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.put(SearchIndexColumns.TOKENS, builder.getTokens()); 33905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov if (replace) { 34005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mSelectionArgs1[0] = String.valueOf(contactId); 34105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov int count = db.update(Tables.SEARCH_INDEX, mValues, 34205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", mSelectionArgs1); 34305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov if (count == 0) { 34405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mValues.put(SearchIndexColumns.CONTACT_ID, contactId); 34505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.insert(Tables.SEARCH_INDEX, null, mValues); 34605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 34705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } else { 348f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.put(SearchIndexColumns.CONTACT_ID, contactId); 349f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov db.insert(Tables.SEARCH_INDEX, null, mValues); 350f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 351f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 35205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private int getSearchIndexVersion() { 35305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov return Integer.parseInt(mDbHelper.getProperty(PROPERTY_SEARCH_INDEX_VERSION, "0")); 35405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 35505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 35605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private void setSearchIndexVersion(int version) { 35705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mDbHelper.setProperty(PROPERTY_SEARCH_INDEX_VERSION, String.valueOf(version)); 35805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 359d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 360d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 361d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Tokenizes the query and normalizes/hex encodes each token. The tokenizer uses the same 362d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * rules as SQLite's "simple" tokenizer. Each token is added to the retokenizer and then 363d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * returned as a String. 364d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * @see FtsQueryBuilder#UNSCOPED_NORMALIZING 365d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * @see FtsQueryBuilder#SCOPED_NAME_NORMALIZING 366d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 367d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static String getFtsMatchQuery(String query, FtsQueryBuilder ftsQueryBuilder) { 368d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // SQLite's "simple" tokenizer uses the following rules to detect characters: 369d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // - Unicode codepoints >= 128: Everything 370d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // - Unicode codepoints < 128: Alphanumeric and "_" 371d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // Everything else is a separator of tokens 372d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann int tokenStart = -1; 373d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final StringBuilder result = new StringBuilder(); 374d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann for (int i = 0; i <= query.length(); i++) { 375d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final boolean isChar; 376d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (i == query.length()) { 377d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann isChar = false; 378d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } else { 379d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final char ch = query.charAt(i); 380d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (ch >= 128) { 381d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann isChar = true; 382d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } else { 383d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann isChar = Character.isLetterOrDigit(ch) || ch == '_'; 384d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 385d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 386d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (isChar) { 387d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (tokenStart == -1) { 388d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann tokenStart = i; 389d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 390d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } else { 391d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (tokenStart != -1) { 392d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final String token = query.substring(tokenStart, i); 393d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann ftsQueryBuilder.addToken(result, token); 394d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann tokenStart = -1; 395d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 396d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 397d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 398d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann return result.toString(); 399d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 400d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 401d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static abstract class FtsQueryBuilder { 402d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public abstract void addToken(StringBuilder builder, String token); 403d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 404d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** Normalizes and space-concatenates each token. Example: "a1b2c1* a2b3c2*" */ 405d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static final FtsQueryBuilder UNSCOPED_NORMALIZING = new UnscopedNormalizingBuilder(); 406d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 407d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 408d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Scopes each token to a column and normalizes the name. 409d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Example: "content:foo* name:a1b2c1* tokens:foo* content:bar* name:a2b3c2* tokens:bar*" 410d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 411d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static final FtsQueryBuilder SCOPED_NAME_NORMALIZING = 412d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann new ScopedNameNormalizingBuilder(); 413d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 414d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 415d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Scopes each token to a the content column and also for name with normalization. 416d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Also adds a user-defined expression to each token. This allows common criteria to be 417d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * concatenated to each token. 418d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Example (commonCriteria=" OR tokens:123*"): 419d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * "content:650* OR name:1A1B1C* OR tokens:123* content:2A2B2C* OR name:foo* OR tokens:123*" 420d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 421d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static FtsQueryBuilder getDigitsQueryBuilder(final String commonCriteria) { 422d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann return new FtsQueryBuilder() { 423d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 424d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 425d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 426d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 427d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("content:"); 428d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 429d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("* "); 430d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 431d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final String normalizedToken = NameNormalizer.normalize(token); 432d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (!TextUtils.isEmpty(normalizedToken)) { 433d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR name:"); 434d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(normalizedToken); 435d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 436d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 437d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 438d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(commonCriteria); 439d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 440d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann }; 441d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 442d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 443d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 444d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann private static class UnscopedNormalizingBuilder extends FtsQueryBuilder { 445d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 446d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 447d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 448d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 449d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // the token could be empty (if the search query was "_"). we should still emit it 450d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // here, as we otherwise risk to end up with an empty MATCH-expression MATCH "" 451d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(NameNormalizer.normalize(token)); 452d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 453d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 454d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 455d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 456d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann private static class ScopedNameNormalizingBuilder extends FtsQueryBuilder { 457d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 458d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 459d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 460d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 461d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("content:"); 462d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 463d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 464d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 465d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final String normalizedToken = NameNormalizer.normalize(token); 466d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (!TextUtils.isEmpty(normalizedToken)) { 467d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR name:"); 468d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(normalizedToken); 469d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 470d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 471d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 472d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR tokens:"); 473d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 474d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("*"); 475d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 476d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 477f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov} 478