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