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 android.content.ContentValues; 19f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.database.Cursor; 20f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.database.sqlite.SQLiteDatabase; 2105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikovimport android.os.SystemClock; 22197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Email; 23197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Nickname; 24197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Organization; 25197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 26f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.provider.ContactsContract.Data; 2705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikovimport android.provider.ContactsContract.ProviderStatus; 287e086471c6317d059af21d292bee964b24613346Makoto Onukiimport android.provider.ContactsContract.RawContacts; 29f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.text.TextUtils; 30f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport android.util.Log; 31f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 3238210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; 3338210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; 3438210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns; 3538210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns; 3638210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.android.providers.contacts.ContactsDatabaseHelper.Tables; 3738210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.google.android.collect.Lists; 3838210445730ee04c351c7cc1b3800cfe23e34325Makoto Onukiimport com.google.common.annotations.VisibleForTesting; 3938210445730ee04c351c7cc1b3800cfe23e34325Makoto Onuki 40116d86ddd67330428f9128613b4886fc0ea66221Makoto Onukiimport java.util.ArrayList; 41f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport java.util.HashSet; 42116d86ddd67330428f9128613b4886fc0ea66221Makoto Onukiimport java.util.List; 43f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovimport java.util.Set; 44f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onukiimport java.util.regex.Pattern; 45f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 46f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov/** 47f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov * Maintains a search index for comprehensive contact search. 48f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov */ 49f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikovpublic class SearchIndexManager { 50f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private static final String TAG = "ContactsFTS"; 51f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 520992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE); 530992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki 54197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov public static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index"; 5505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private static final int SEARCH_INDEX_VERSION = 1; 5605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 57f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private static final class ContactIndexQuery { 58f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final String[] COLUMNS = { 59f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.CONTACT_ID, 60f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov MimetypesColumns.MIMETYPE, 61f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, 62f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, 63f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov Data.DATA12, Data.DATA13, Data.DATA14 64f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov }; 65f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 66f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int MIMETYPE = 1; 67f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 68f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 69f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static class IndexBuilder { 70f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_SPACE = 0; 71f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_PARENTHESES = 1; 72f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_SLASH = 2; 73f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public static final int SEPARATOR_COMMA = 3; 74f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 75f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbContent = new StringBuilder(); 76155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov private StringBuilder mSbName = new StringBuilder(); 77f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbTokens = new StringBuilder(); 78f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSbElementContent = new StringBuilder(); 79f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private HashSet<String> mUniqueElements = new HashSet<String>(); 80f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private Cursor mCursor; 81f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 82f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov void setCursor(Cursor cursor) { 83f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov this.mCursor = cursor; 84f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 85f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 86f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov void reset() { 87f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.setLength(0); 88f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbTokens.setLength(0); 89155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mSbName.setLength(0); 90f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbElementContent.setLength(0); 91f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mUniqueElements.clear(); 92f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 93f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 94f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String getContent() { 95f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return mSbContent.length() == 0 ? null : mSbContent.toString(); 96f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 97f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 98155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov public String getName() { 99155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return mSbName.length() == 0 ? null : mSbName.toString(); 100155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 101155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 102f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String getTokens() { 103f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return mSbTokens.length() == 0 ? null : mSbTokens.toString(); 104f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 105f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 106eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public String getString(String columnName) { 107eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov return mCursor.getString(mCursor.getColumnIndex(columnName)); 108eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 109eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 1106d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov public int getInt(String columnName) { 1116d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov return mCursor.getInt(mCursor.getColumnIndex(columnName)); 1126d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov } 1136d9702cec82fd27a1c3093c64df9dcc22744899aDmitri Plotnikov 114f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov @Override 115f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public String toString() { 116155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return "Content: " + mSbContent + "\n Name: " + mSbTokens + "\n Tokens: " + mSbTokens; 117f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 118f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 119f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void commit() { 120f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (mSbElementContent.length() != 0) { 121eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov String content = mSbElementContent.toString().replace('\n', ' '); 122f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (!mUniqueElements.contains(content)) { 123f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (mSbContent.length() != 0) { 124f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.append('\n'); 125f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 126f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mSbContent.append(content); 127f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mUniqueElements.add(content); 128f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 129eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.setLength(0); 130f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 131f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 132f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 133f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void appendContentFromColumn(String columnName) { 134f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov appendContentFromColumn(columnName, SEPARATOR_SPACE); 135f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 136f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 137f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public void appendContentFromColumn(String columnName, int format) { 138eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov appendContent(getString(columnName), format); 139eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 140eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 141eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public void appendContent(String value) { 142eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov appendContent(value, SEPARATOR_SPACE); 143f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 144f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 145116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki private void appendContent(String value, int format) { 146f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (TextUtils.isEmpty(value)) { 147f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov return; 148f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 149f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 150f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov switch (format) { 151f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_SPACE: 152eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 153eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(' '); 154f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 155eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(value); 156f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 157f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 158f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_SLASH: 159eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append('/').append(value); 160f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 161f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 162f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_PARENTHESES: 163eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 164eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(' '); 165f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 166eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append('(').append(value).append(')'); 167f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 168f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 169f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov case SEPARATOR_COMMA: 170eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbElementContent.length() > 0) { 171eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(", "); 172f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 173eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbElementContent.append(value); 174f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov break; 175f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 176f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 177eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov 178eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov public void appendToken(String token) { 179f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov if (TextUtils.isEmpty(token)) { 180f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov return; 181f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov } 182f482a4e25759d2c144eb41a3de56a342bd473435Dmitri Plotnikov 183eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov if (mSbTokens.length() != 0) { 184eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbTokens.append(' '); 185eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 186eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov mSbTokens.append(token); 187eeeed5669d98897501bf2b18c88579c7effd0955Dmitri Plotnikov } 188155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 189d639ddd61fc7b6f4a7879d583bfd2b595b066265Yorke Lee public void appendNameFromColumn(String columnName) { 190d639ddd61fc7b6f4a7879d583bfd2b595b066265Yorke Lee appendName(getString(columnName)); 191d639ddd61fc7b6f4a7879d583bfd2b595b066265Yorke Lee } 192d639ddd61fc7b6f4a7879d583bfd2b595b066265Yorke Lee 193155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov public void appendName(String name) { 194155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov if (TextUtils.isEmpty(name)) { 195155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov return; 196155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 197116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // First, put the original name. 198116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki appendNameInternal(name); 199116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki 200116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // Then, if the name contains more than one FTS token, put each token into the index 201116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // too. 202116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // 203116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // This is to make names with special characters searchable, such as "double-barrelled" 204116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // "L'Image". 205116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // 206116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // Here's how it works: 207116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // Because we "normalize" names when putting into the index, if we only put 208116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // "double-barrelled", the index will only contain "doublebarrelled". 209116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // Now, if the user searches for "double-barrelled", the searcher tokenizes it into 210116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // two tokens, "double" and "barrelled". The first one matches "doublebarrelled" 211116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // but the second one doesn't (because we only do the prefix match), so 212116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // "doublebarrelled" doesn't match. 213116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // So, here, we put each token in a name into the index too. In the case above, 214116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // we put also "double" and "barrelled". 215116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // With this, queries such as "double-barrelled", "double barrelled", "doublebarrelled" 216116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki // will all match "double-barrelled". 217116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki final List<String> nameParts = splitIntoFtsTokens(name); 218116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki if (nameParts.size() > 1) { 219116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki for (String namePart : nameParts) { 220f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki if (!TextUtils.isEmpty(namePart)) { 221f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki appendNameInternal(namePart); 222f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 223f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 224f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 225f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki } 226155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov 227116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki /** 228116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * Normalize a name and add to {@link #mSbName} 229116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki */ 230f5f038faf7f3ef460e1c11028d467954840e5f6fMakoto Onuki private void appendNameInternal(String name) { 231155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov if (mSbName.length() != 0) { 232155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mSbName.append(' '); 233155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 234d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann mSbName.append(NameNormalizer.normalize(name)); 235155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov } 236f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 237f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 238f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private final ContactsProvider2 mContactsProvider; 239f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private final ContactsDatabaseHelper mDbHelper; 240f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private StringBuilder mSb = new StringBuilder(); 241f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private IndexBuilder mIndexBuilder = new IndexBuilder(); 242f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private ContentValues mValues = new ContentValues(); 243f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov private String[] mSelectionArgs1 = new String[1]; 244f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 245f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov public SearchIndexManager(ContactsProvider2 contactsProvider) { 246f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov this.mContactsProvider = contactsProvider; 247f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mDbHelper = (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper(); 248f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 249f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 250d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki public void updateIndex(boolean force) { 251d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki if (force) { 252d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki setSearchIndexVersion(0); 253d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki } else { 254d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki if (getSearchIndexVersion() == SEARCH_INDEX_VERSION) { 255d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki return; 256d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki } 25705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 25805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov SQLiteDatabase db = mDbHelper.getWritableDatabase(); 25905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.beginTransaction(); 26005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov try { 261d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki // We do a version check again, because the version might have been modified after 262d2f6ad6d50b5570327f8cca3b2d2bdcaec36ea90Makoto Onuki // the first check. We need to do the check again in a transaction to make sure. 26305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov if (getSearchIndexVersion() != SEARCH_INDEX_VERSION) { 26405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov rebuildIndex(db); 26505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov setSearchIndexVersion(SEARCH_INDEX_VERSION); 26605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.setTransactionSuccessful(); 26705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 26805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } finally { 26905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov db.endTransaction(); 27005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 27105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 27205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 27305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private void rebuildIndex(SQLiteDatabase db) { 27495f4fd8bf381ed9319f139febc369ce70dcb022eBrian Attwell mContactsProvider.setProviderStatus(ContactsProvider2.STATUS_UPGRADING); 275565b62f354d8b6aadc760092a7dbf483f8bbbe17Makoto Onuki final long start = SystemClock.elapsedRealtime(); 27605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov int count = 0; 27705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov try { 278f5c0020b87709f9c4c3de66a49c0893e2c2adebbMakoto Onuki mDbHelper.createSearchIndexTable(db, true); 2797e086471c6317d059af21d292bee964b24613346Makoto Onuki count = buildAndInsertIndex(db, null); 28005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } finally { 28195f4fd8bf381ed9319f139febc369ce70dcb022eBrian Attwell mContactsProvider.setProviderStatus(ContactsProvider2.STATUS_NORMAL); 28205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 283565b62f354d8b6aadc760092a7dbf483f8bbbe17Makoto Onuki final long end = SystemClock.elapsedRealtime(); 28405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov Log.i(TAG, "Rebuild contact search index in " + (end - start) + "ms, " 28505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov + count + " contacts"); 28605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 28705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 28805e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 289bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov public void updateIndexForRawContacts(Set<Long> contactIds, Set<Long> rawContactIds) { 2900992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki if (VERBOSE_LOGGING) { 2910992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki Log.v(TAG, "Updating search index for " + contactIds.size() + 2920992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki " contacts / " + rawContactIds.size() + " raw contacts"); 2930992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki } 2940992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki StringBuilder sb = new StringBuilder(); 2950992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki sb.append("("); 296bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!contactIds.isEmpty()) { 2971dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // Select all raw contacts that belong to all contacts in contactIds 2980992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki sb.append(RawContacts.CONTACT_ID + " IN ("); 299de853799fa95c596616e138f3ef2e66b1f9f95a6Yorke Lee sb.append(TextUtils.join(",", contactIds)); 3000992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki sb.append(')'); 301bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 302bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!rawContactIds.isEmpty()) { 303bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov if (!contactIds.isEmpty()) { 3040992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki sb.append(" OR "); 305bd9abbb6b03b4ec1e28ad3fa2fcba5d1eb8609eaDmitri Plotnikov } 3061dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // Select all raw contacts that belong to the same contact as all raw contacts 3071dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // in rawContactIds. For every raw contact in rawContactIds that we are updating 3081dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // the index for, we need to rebuild the search index for all raw contacts belonging 3091dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // to the same contact, because we can only update the search index on a per-contact 3101dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee // basis. 3111dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee sb.append(RawContacts.CONTACT_ID + " IN " + 3121dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee "(SELECT " + RawContacts.CONTACT_ID + " FROM " + Tables.RAW_CONTACTS + 3131dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee " WHERE " + RawContactsColumns.CONCRETE_ID + " IN ("); 314de853799fa95c596616e138f3ef2e66b1f9f95a6Yorke Lee sb.append(TextUtils.join(",", rawContactIds)); 3151dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee sb.append("))"); 316f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 317f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 3180992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki sb.append(")"); 3197e086471c6317d059af21d292bee964b24613346Makoto Onuki 3207e086471c6317d059af21d292bee964b24613346Makoto Onuki // The selection to select raw_contacts. 3210992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki final String rawContactsSelection = sb.toString(); 3227e086471c6317d059af21d292bee964b24613346Makoto Onuki 3237e086471c6317d059af21d292bee964b24613346Makoto Onuki // Remove affected search_index rows. 3247e086471c6317d059af21d292bee964b24613346Makoto Onuki final SQLiteDatabase db = mDbHelper.getWritableDatabase(); 3257e086471c6317d059af21d292bee964b24613346Makoto Onuki final int deleted = db.delete(Tables.SEARCH_INDEX, 3267e086471c6317d059af21d292bee964b24613346Makoto Onuki SearchIndexColumns.CONTACT_ID + " IN (SELECT " + 3277e086471c6317d059af21d292bee964b24613346Makoto Onuki RawContacts.CONTACT_ID + 3287e086471c6317d059af21d292bee964b24613346Makoto Onuki " FROM " + Tables.RAW_CONTACTS + 3297e086471c6317d059af21d292bee964b24613346Makoto Onuki " WHERE " + rawContactsSelection + 3307e086471c6317d059af21d292bee964b24613346Makoto Onuki ")" 3317e086471c6317d059af21d292bee964b24613346Makoto Onuki , null); 3327e086471c6317d059af21d292bee964b24613346Makoto Onuki 3337e086471c6317d059af21d292bee964b24613346Makoto Onuki // Then rebuild index for them. 3340992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki final int count = buildAndInsertIndex(db, rawContactsSelection); 3351dc1cfb821c03a17c8b09265fdfd7260023e6767Yorke Lee 3360992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki if (VERBOSE_LOGGING) { 3370992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki Log.v(TAG, "Updated search index for " + count + " contacts"); 3380992b9d4969ed0eee6e879db94292b635229e2b7Makoto Onuki } 33905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 34005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 3417e086471c6317d059af21d292bee964b24613346Makoto Onuki private int buildAndInsertIndex(SQLiteDatabase db, String selection) { 342197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.setLength(0); 343197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(Data.CONTACT_ID + ", "); 344197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append("(CASE WHEN " + DataColumns.MIMETYPE_ID + "="); 345197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Nickname.CONTENT_ITEM_TYPE)); 346197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -4 "); 347197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 348197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Organization.CONTENT_ITEM_TYPE)); 349197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -3 "); 350197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 351197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE)); 352197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -2"); 353197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" WHEN " + DataColumns.MIMETYPE_ID + "="); 354197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE)); 355197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" THEN -1"); 356174f7d319b987aa2aeeb6f2563f4b939acb8d791Dmitri Plotnikov mSb.append(" ELSE " + DataColumns.MIMETYPE_ID); 357197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov mSb.append(" END), " + Data.IS_SUPER_PRIMARY + ", " + DataColumns.CONCRETE_ID); 358197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov 35905e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov int count = 0; 360197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov Cursor cursor = db.query(Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS, ContactIndexQuery.COLUMNS, 361197411a6cc3f81b94a34ca207f267d43d8548f04Dmitri Plotnikov selection, null, null, null, mSb.toString()); 362f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.setCursor(cursor); 363f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.reset(); 364f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov try { 365f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov long currentContactId = -1; 366f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov while (cursor.moveToNext()) { 367f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov long contactId = cursor.getLong(0); 368f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (contactId != currentContactId) { 369f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (currentContactId != -1) { 3707e086471c6317d059af21d292bee964b24613346Makoto Onuki insertIndexRow(db, currentContactId, mIndexBuilder); 37105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov count++; 372f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 373f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov currentContactId = contactId; 374f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.reset(); 375f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 376f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov String mimetype = cursor.getString(ContactIndexQuery.MIMETYPE); 377f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov DataRowHandler dataRowHandler = mContactsProvider.getDataRowHandler(mimetype); 378f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (dataRowHandler.hasSearchableData()) { 379f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov dataRowHandler.appendSearchableData(mIndexBuilder); 380f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mIndexBuilder.commit(); 381f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 382f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 383f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov if (currentContactId != -1) { 3847e086471c6317d059af21d292bee964b24613346Makoto Onuki insertIndexRow(db, currentContactId, mIndexBuilder); 38505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov count++; 386f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 387f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } finally { 388f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov cursor.close(); 389f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 39005e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov return count; 391f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 392f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov 3937e086471c6317d059af21d292bee964b24613346Makoto Onuki private void insertIndexRow(SQLiteDatabase db, long contactId, IndexBuilder builder) { 394f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.clear(); 395f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.put(SearchIndexColumns.CONTENT, builder.getContent()); 396155accbcb95fc13b984cf0ea8e5498a9c619cbf5Dmitri Plotnikov mValues.put(SearchIndexColumns.NAME, builder.getName()); 397f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov mValues.put(SearchIndexColumns.TOKENS, builder.getTokens()); 3987e086471c6317d059af21d292bee964b24613346Makoto Onuki mValues.put(SearchIndexColumns.CONTACT_ID, contactId); 3997e086471c6317d059af21d292bee964b24613346Makoto Onuki db.insert(Tables.SEARCH_INDEX, null, mValues); 400f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov } 40105e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private int getSearchIndexVersion() { 40205e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov return Integer.parseInt(mDbHelper.getProperty(PROPERTY_SEARCH_INDEX_VERSION, "0")); 40305e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 40405e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov 40505e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov private void setSearchIndexVersion(int version) { 40605e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov mDbHelper.setProperty(PROPERTY_SEARCH_INDEX_VERSION, String.valueOf(version)); 40705e50fbf9809bf04eceec3d2a2753630dc4f9315Dmitri Plotnikov } 408d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 409d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 410116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * Token separator that matches SQLite's "simple" tokenizer. 411116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * - Unicode codepoints >= 128: Everything 412116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * - Unicode codepoints < 128: Alphanumeric and "_" 413116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * - Everything else is a separator of tokens 414116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki */ 415116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki private static final Pattern FTS_TOKEN_SEPARATOR_RE = 416116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki Pattern.compile("[^\u0080-\uffff\\p{Alnum}_]"); 417116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki 418116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki /** 419116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki * Tokenize a string in the way as that of SQLite's "simple" tokenizer. 420116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki */ 421116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki @VisibleForTesting 422116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki static List<String> splitIntoFtsTokens(String s) { 423116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki final ArrayList<String> ret = Lists.newArrayList(); 424116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki for (String token : FTS_TOKEN_SEPARATOR_RE.split(s)) { 425116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki if (!TextUtils.isEmpty(token)) { 426116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki ret.add(token); 427116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki } 428116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki } 429116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki return ret; 430116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki } 431116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki 432116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki /** 433d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Tokenizes the query and normalizes/hex encodes each token. The tokenizer uses the same 434d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * rules as SQLite's "simple" tokenizer. Each token is added to the retokenizer and then 435d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * returned as a String. 436d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * @see FtsQueryBuilder#UNSCOPED_NORMALIZING 437d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * @see FtsQueryBuilder#SCOPED_NAME_NORMALIZING 438d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 439d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static String getFtsMatchQuery(String query, FtsQueryBuilder ftsQueryBuilder) { 440d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final StringBuilder result = new StringBuilder(); 441116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki for (String token : splitIntoFtsTokens(query)) { 442116d86ddd67330428f9128613b4886fc0ea66221Makoto Onuki ftsQueryBuilder.addToken(result, token); 443d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 444d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann return result.toString(); 445d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 446d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 447d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static abstract class FtsQueryBuilder { 448d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public abstract void addToken(StringBuilder builder, String token); 449d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 450d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** Normalizes and space-concatenates each token. Example: "a1b2c1* a2b3c2*" */ 451d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static final FtsQueryBuilder UNSCOPED_NORMALIZING = new UnscopedNormalizingBuilder(); 452d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 453d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 454d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Scopes each token to a column and normalizes the name. 455d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Example: "content:foo* name:a1b2c1* tokens:foo* content:bar* name:a2b3c2* tokens:bar*" 456d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 457d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static final FtsQueryBuilder SCOPED_NAME_NORMALIZING = 458d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann new ScopedNameNormalizingBuilder(); 459d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 460d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann /** 461d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Scopes each token to a the content column and also for name with normalization. 462d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Also adds a user-defined expression to each token. This allows common criteria to be 463d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * concatenated to each token. 464d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * Example (commonCriteria=" OR tokens:123*"): 465d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann * "content:650* OR name:1A1B1C* OR tokens:123* content:2A2B2C* OR name:foo* OR tokens:123*" 466d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann */ 467d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public static FtsQueryBuilder getDigitsQueryBuilder(final String commonCriteria) { 468d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann return new FtsQueryBuilder() { 469d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 470d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 471d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 472d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 473d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("content:"); 474d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 475d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("* "); 476d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 477d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final String normalizedToken = NameNormalizer.normalize(token); 478d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (!TextUtils.isEmpty(normalizedToken)) { 479d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR name:"); 480d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(normalizedToken); 481d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 482d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 483d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 484d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(commonCriteria); 485d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 486d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann }; 487d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 488d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 489d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 490d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann private static class UnscopedNormalizingBuilder extends FtsQueryBuilder { 491d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 492d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 493d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 494d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 495d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // the token could be empty (if the search query was "_"). we should still emit it 496d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann // here, as we otherwise risk to end up with an empty MATCH-expression MATCH "" 497d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(NameNormalizer.normalize(token)); 498d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 499d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 500d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 501d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 502d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann private static class ScopedNameNormalizingBuilder extends FtsQueryBuilder { 503d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann @Override 504d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann public void addToken(StringBuilder builder, String token) { 505d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (builder.length() != 0) builder.append(' '); 506d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 507d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("content:"); 508d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 509d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 510d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 511d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann final String normalizedToken = NameNormalizer.normalize(token); 512d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann if (!TextUtils.isEmpty(normalizedToken)) { 513d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR name:"); 514d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(normalizedToken); 515d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append('*'); 516d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 517d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann 518d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(" OR tokens:"); 519d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append(token); 520d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann builder.append("*"); 521d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 522d1746e09bc7739f3d1449cececc66d5045ada498Daniel Lehmann } 523f262d56495ac4ea30d31bd050efb116bd4bb4235Dmitri Plotnikov} 524