• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.mms;
18 
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.Map;
22 
23 import android.app.SearchManager;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Intent;
27 import android.database.CharArrayBuffer;
28 import android.database.ContentObserver;
29 import android.database.CrossProcessCursor;
30 import android.database.Cursor;
31 import android.database.CursorWindow;
32 import android.database.DataSetObserver;
33 import android.database.sqlite.SQLiteException;
34 import android.net.Uri;
35 import android.os.Bundle;
36 
37 /**
38  * Suggestions provider for mms.  Queries the "words" table to provide possible word suggestions.
39  */
40 public class SuggestionsProvider extends android.content.ContentProvider {
41 
42     final static String AUTHORITY = "com.android.mms.SuggestionsProvider";
43 //    final static int MODE = DATABASE_MODE_QUERIES + DATABASE_MODE_2LINES;
44 
SuggestionsProvider()45     public SuggestionsProvider() {
46         super();
47     }
48 
49     @Override
delete(Uri uri, String selection, String[] selectionArgs)50     public int delete(Uri uri, String selection, String[] selectionArgs) {
51         return 0;
52     }
53 
54     @Override
getType(Uri uri)55     public String getType(Uri uri) {
56         return null;
57     }
58 
59     @Override
insert(Uri uri, ContentValues values)60     public Uri insert(Uri uri, ContentValues values) {
61         return null;
62     }
63 
64     @Override
onCreate()65     public boolean onCreate() {
66         return true;
67     }
68 
69     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)70     public Cursor query(Uri uri, String[] projection, String selection,
71             String[] selectionArgs, String sortOrder) {
72         Uri u = Uri.parse(String.format(
73                 "content://mms-sms/searchSuggest?pattern=%s",
74                 selectionArgs[0]));
75         Cursor c = getContext().getContentResolver().query(
76                 u,
77                 null,
78                 null,
79                 null,
80                 null);
81 
82         return new SuggestionsCursor(c, selectionArgs[0]);
83     }
84 
85     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)86     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
87         return 0;
88     }
89 
90     private class SuggestionsCursor implements CrossProcessCursor {
91         Cursor mDatabaseCursor;
92         int mColumnCount;
93         int mCurrentRow;
94         ArrayList<Row> mRows = new ArrayList<Row>();
95 
SuggestionsCursor(Cursor cursor, String query)96         public SuggestionsCursor(Cursor cursor, String query) {
97             mDatabaseCursor = cursor;
98 
99             mColumnCount = cursor.getColumnCount();
100             try {
101                 computeRows();
102             } catch (SQLiteException ex) {
103                 // This can happen if the user enters -n (anything starting with -).
104                 // sqlite3/fts3 can't handle it.  Google for "logic error or missing database fts3"
105                 // for commentary on it.
106                 mRows.clear(); // assume no results
107             }
108         }
109 
getCount()110         public int getCount() {
111             return mRows.size();
112         }
113 
114         private class Row {
Row(int row, String text, int startOffset, int endOffset)115             public Row(int row, String text, int startOffset, int endOffset) {
116                 mText = text;
117                 mRowNumber = row;
118                 mStartOffset = startOffset;
119                 mEndOffset = endOffset;
120             }
121             String mText;
122             int mRowNumber;
123             int mStartOffset;
124             int mEndOffset;
125 
getWord()126             public String getWord() {
127                 return mText.substring(mStartOffset, mEndOffset);
128             }
129         }
130 
computeRows()131         private void computeRows() {
132             HashSet<String> got = new HashSet<String>();
133 
134             int textColumn = mDatabaseCursor.getColumnIndex("index_text");
135             int offsetsColumn = mDatabaseCursor.getColumnIndex("offsets(words)");
136 
137             int count = mDatabaseCursor.getCount();
138             for (int i = 0; i < count; i++) {
139                 mDatabaseCursor.moveToPosition(i);
140                 String message = mDatabaseCursor.getString(textColumn);
141 
142                 int [] offsets = computeOffsets(mDatabaseCursor.getString(offsetsColumn));
143                 for (int j = 0; j < offsets.length; j += 4) {
144 //                  int columnNumber = offsets[j+0];
145 //                  int termNumber   = offsets[j+1];
146                     int startOffset  = offsets[j+2];
147                     int length       = offsets[j+3];
148                     int endOffset = startOffset + length;
149                     String candidate = message.substring(startOffset, endOffset);
150                     String key = candidate.toLowerCase();
151                     if (got.contains(key)) {
152                         continue;
153                     }
154                     got.add(key);
155                     mRows.add(new Row(i, message, startOffset, endOffset));
156                 }
157             }
158         }
159 
computeOffsets(String offsetsString)160         private int [] computeOffsets(String offsetsString) {
161             String [] vals = offsetsString.split(" ");
162 
163             int [] retvals = new int[vals.length];
164             for (int i = retvals.length-1; i >= 0; i--) {
165                 retvals[i] = Integer.parseInt(vals[i]);
166             }
167             return retvals;
168         }
169 
fillWindow(int position, CursorWindow window)170         public void fillWindow(int position, CursorWindow window) {
171             int count = getCount();
172             if (position < 0 || position > count + 1) {
173                 return;
174             }
175             window.acquireReference();
176             try {
177                 int oldpos = getPosition();
178                 int pos = position;
179                 window.clear();
180                 window.setStartPosition(position);
181                 int columnNum = getColumnCount();
182                 window.setNumColumns(columnNum);
183                 while (moveToPosition(pos) && window.allocRow()) {
184                     for (int i = 0; i < columnNum; i++) {
185                         String field = getString(i);
186                         if (field != null) {
187                             if (!window.putString(field, pos, i)) {
188                                 window.freeLastRow();
189                                 break;
190                             }
191                         } else {
192                             if (!window.putNull(pos, i)) {
193                                 window.freeLastRow();
194                                 break;
195                             }
196                         }
197                     }
198                     ++pos;
199                 }
200                 moveToPosition(oldpos);
201             } catch (IllegalStateException e){
202                 // simply ignore it
203             } finally {
204                 window.releaseReference();
205             }
206         }
207 
getWindow()208         public CursorWindow getWindow() {
209 //          return ((CrossProcessCursor)mCursor).getWindow();
210             CursorWindow window = new CursorWindow(false);
211             return window;
212         }
213 
onMove(int oldPosition, int newPosition)214         public boolean onMove(int oldPosition, int newPosition) {
215             return ((CrossProcessCursor)mDatabaseCursor).onMove(oldPosition, newPosition);
216         }
217 
218         /*
219          * These "virtual columns" are columns which don't exist in the underlying
220          * database cursor but are exported by this cursor.  For example, we compute
221          * a "word" by taking the substring of the full row text in the words table
222          * using the provided offsets.
223          */
224         private String [] mVirtualColumns = new String [] {
225                 SearchManager.SUGGEST_COLUMN_INTENT_DATA,
226                 SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
227                 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
228                 SearchManager.SUGGEST_COLUMN_TEXT_1
229             };
230 
231         // Cursor column offsets for the above virtual columns.
232         // These columns exist after the natural columns in the
233         // database cursor.  So, for example, the column called
234         // SUGGEST_COLUMN_TEXT_1 comes 3 after mDatabaseCursor.getColumnCount().
235         private final int INTENT_DATA_COLUMN = 0;
236         private final int INTENT_ACTION_COLUMN = 1;
237         private final int INTENT_EXTRA_DATA_COLUMN = 2;
238         private final int INTENT_TEXT_COLUMN = 3;
239 
240 
getColumnCount()241         public int getColumnCount() {
242             return mColumnCount + mVirtualColumns.length;
243         }
244 
getColumnIndex(String columnName)245         public int getColumnIndex(String columnName) {
246             for (int i = 0; i < mVirtualColumns.length; i++) {
247                 if (mVirtualColumns[i].equals(columnName)) {
248                     return mColumnCount + i;
249                 }
250             }
251             return mDatabaseCursor.getColumnIndex(columnName);
252         }
253 
getColumnNames()254         public String [] getColumnNames() {
255             String [] x = mDatabaseCursor.getColumnNames();
256             String [] y = new String [x.length + mVirtualColumns.length];
257 
258             for (int i = 0; i < x.length; i++) {
259                 y[i] = x[i];
260             }
261 
262             for (int i = 0; i < mVirtualColumns.length; i++) {
263                 y[x.length + i] = mVirtualColumns[i];
264             }
265 
266             return y;
267         }
268 
moveToPosition(int position)269         public boolean moveToPosition(int position) {
270             if (position >= 0 && position < mRows.size()) {
271                 mCurrentRow = position;
272                 mDatabaseCursor.moveToPosition(mRows.get(position).mRowNumber);
273                 return true;
274             } else {
275                 return false;
276             }
277         }
278 
move(int offset)279         public boolean move(int offset) {
280             return moveToPosition(mCurrentRow + offset);
281         }
282 
moveToFirst()283         public boolean moveToFirst() {
284             return moveToPosition(0);
285         }
286 
moveToLast()287         public boolean moveToLast() {
288             return moveToPosition(mRows.size() - 1);
289         }
290 
moveToNext()291         public boolean moveToNext() {
292             return moveToPosition(mCurrentRow + 1);
293         }
294 
moveToPrevious()295         public boolean moveToPrevious() {
296             return moveToPosition(mCurrentRow - 1);
297         }
298 
getString(int column)299         public String getString(int column) {
300             if (column < mColumnCount) {
301                 return mDatabaseCursor.getString(column);
302             }
303 
304             Row row = mRows.get(mCurrentRow);
305             switch (column - mColumnCount) {
306                 case INTENT_DATA_COLUMN:
307                     Uri u = Uri.parse("content://mms-sms/search").buildUpon().appendQueryParameter("pattern", row.getWord()).build();
308                     return u.toString();
309                 case INTENT_ACTION_COLUMN:
310                     return Intent.ACTION_SEARCH;
311                 case INTENT_EXTRA_DATA_COLUMN:
312                     return getString(getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
313                 case INTENT_TEXT_COLUMN:
314                     return row.getWord();
315                 default:
316                     return null;
317             }
318         }
319 
abortUpdates()320         public void abortUpdates() {
321         }
322 
close()323         public void close() {
324             mDatabaseCursor.close();
325         }
326 
commitUpdates()327         public boolean commitUpdates() {
328             return false;
329         }
330 
commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values)331         public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) {
332             return false;
333         }
334 
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)335         public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
336             mDatabaseCursor.copyStringToBuffer(columnIndex, buffer);
337         }
338 
deactivate()339         public void deactivate() {
340             mDatabaseCursor.deactivate();
341         }
342 
deleteRow()343         public boolean deleteRow() {
344             return false;
345         }
346 
getBlob(int columnIndex)347         public byte[] getBlob(int columnIndex) {
348             return null;
349         }
350 
getColumnIndexOrThrow(String columnName)351         public int getColumnIndexOrThrow(String columnName)
352                 throws IllegalArgumentException {
353             return 0;
354         }
355 
getColumnName(int columnIndex)356         public String getColumnName(int columnIndex) {
357             return null;
358         }
359 
getDouble(int columnIndex)360         public double getDouble(int columnIndex) {
361             return 0;
362         }
363 
getExtras()364         public Bundle getExtras() {
365             return Bundle.EMPTY;
366         }
367 
getFloat(int columnIndex)368         public float getFloat(int columnIndex) {
369             return 0;
370         }
371 
getInt(int columnIndex)372         public int getInt(int columnIndex) {
373             return 0;
374         }
375 
getLong(int columnIndex)376         public long getLong(int columnIndex) {
377             return 0;
378         }
379 
getPosition()380         public int getPosition() {
381             return mCurrentRow;
382         }
383 
getShort(int columnIndex)384         public short getShort(int columnIndex) {
385             return 0;
386         }
387 
getWantsAllOnMoveCalls()388         public boolean getWantsAllOnMoveCalls() {
389             return false;
390         }
391 
hasUpdates()392         public boolean hasUpdates() {
393             return false;
394         }
395 
isAfterLast()396         public boolean isAfterLast() {
397             return mCurrentRow >= mRows.size();
398         }
399 
isBeforeFirst()400         public boolean isBeforeFirst() {
401             return mCurrentRow < 0;
402         }
403 
isClosed()404         public boolean isClosed() {
405             return mDatabaseCursor.isClosed();
406         }
407 
isFirst()408         public boolean isFirst() {
409             return mCurrentRow == 0;
410         }
411 
isLast()412         public boolean isLast() {
413             return mCurrentRow == mRows.size() - 1;
414         }
415 
isNull(int columnIndex)416         public boolean isNull(int columnIndex) {
417             return false;  // TODO revisit
418         }
419 
registerContentObserver(ContentObserver observer)420         public void registerContentObserver(ContentObserver observer) {
421             mDatabaseCursor.registerContentObserver(observer);
422         }
423 
registerDataSetObserver(DataSetObserver observer)424         public void registerDataSetObserver(DataSetObserver observer) {
425             mDatabaseCursor.registerDataSetObserver(observer);
426         }
427 
requery()428         public boolean requery() {
429             return false;
430         }
431 
respond(Bundle extras)432         public Bundle respond(Bundle extras) {
433             return mDatabaseCursor.respond(extras);
434         }
435 
setNotificationUri(ContentResolver cr, Uri uri)436         public void setNotificationUri(ContentResolver cr, Uri uri) {
437             mDatabaseCursor.setNotificationUri(cr, uri);
438         }
439 
supportsUpdates()440         public boolean supportsUpdates() {
441             return false;
442         }
443 
unregisterContentObserver(ContentObserver observer)444         public void unregisterContentObserver(ContentObserver observer) {
445             mDatabaseCursor.unregisterContentObserver(observer);
446         }
447 
unregisterDataSetObserver(DataSetObserver observer)448         public void unregisterDataSetObserver(DataSetObserver observer) {
449             mDatabaseCursor.unregisterDataSetObserver(observer);
450         }
451 
updateBlob(int columnIndex, byte[] value)452         public boolean updateBlob(int columnIndex, byte[] value) {
453             return false;
454         }
455 
updateDouble(int columnIndex, double value)456         public boolean updateDouble(int columnIndex, double value) {
457             return false;
458         }
459 
updateFloat(int columnIndex, float value)460         public boolean updateFloat(int columnIndex, float value) {
461             return false;
462         }
463 
updateInt(int columnIndex, int value)464         public boolean updateInt(int columnIndex, int value) {
465             return false;
466         }
467 
updateLong(int columnIndex, long value)468         public boolean updateLong(int columnIndex, long value) {
469             return false;
470         }
471 
updateShort(int columnIndex, short value)472         public boolean updateShort(int columnIndex, short value) {
473             return false;
474         }
475 
updateString(int columnIndex, String value)476         public boolean updateString(int columnIndex, String value) {
477             return false;
478         }
479 
updateToNull(int columnIndex)480         public boolean updateToNull(int columnIndex) {
481             return false;
482         }
483     }
484 }
485