• 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.Map;
21 
22 import android.app.SearchManager;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Intent;
26 import android.database.CharArrayBuffer;
27 import android.database.ContentObserver;
28 import android.database.CrossProcessCursor;
29 import android.database.Cursor;
30 import android.database.CursorWindow;
31 import android.database.DataSetObserver;
32 import android.database.sqlite.SQLiteException;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.text.TextUtils;
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         String mQuery;
96 
SuggestionsCursor(Cursor cursor, String query)97         public SuggestionsCursor(Cursor cursor, String query) {
98             mDatabaseCursor = cursor;
99             mQuery = query;
100 
101             mColumnCount = cursor.getColumnCount();
102             try {
103                 computeRows();
104             } catch (SQLiteException ex) {
105                 // This can happen if the user enters -n (anything starting with -).
106                 // sqlite3/fts3 can't handle it.  Google for "logic error or missing database fts3"
107                 // for commentary on it.
108                 mRows.clear(); // assume no results
109             }
110         }
111 
getCount()112         public int getCount() {
113             return mRows.size();
114         }
115 
116         private class Row {
117             private String mSnippet;
118             private int mRowNumber;
119 
Row(int row, String snippet)120             public Row(int row, String snippet) {
121                 mSnippet = snippet.trim();
122                 mRowNumber = row;
123             }
getSnippet()124             public String getSnippet() {
125                 return mSnippet;
126             }
127         }
128 
129         /*
130          * Compute rows for rows in the cursor.  The cursor can contain duplicates which
131          * are filtered out in the while loop.  Using DISTINCT on the result of the
132          * FTS3 snippet function does not work so we do it here in the code.
133          */
computeRows()134         private void computeRows() {
135             int snippetColumn = mDatabaseCursor.getColumnIndex("snippet");
136 
137             int count = mDatabaseCursor.getCount();
138             String previousSnippet = null;
139 
140             for (int i = 0; i < count; i++) {
141                 mDatabaseCursor.moveToPosition(i);
142                 String snippet = mDatabaseCursor.getString(snippetColumn);
143                 if (!TextUtils.equals(previousSnippet, snippet)) {
144                     mRows.add(new Row(i, snippet));
145                     previousSnippet = snippet;
146                 }
147             }
148         }
149 
computeOffsets(String offsetsString)150         private int [] computeOffsets(String offsetsString) {
151             String [] vals = offsetsString.split(" ");
152 
153             int [] retvals = new int[vals.length];
154             for (int i = retvals.length-1; i >= 0; i--) {
155                 retvals[i] = Integer.parseInt(vals[i]);
156             }
157             return retvals;
158         }
159 
fillWindow(int position, CursorWindow window)160         public void fillWindow(int position, CursorWindow window) {
161             int count = getCount();
162             if (position < 0 || position > count + 1) {
163                 return;
164             }
165             window.acquireReference();
166             try {
167                 int oldpos = getPosition();
168                 int pos = position;
169                 window.clear();
170                 window.setStartPosition(position);
171                 int columnNum = getColumnCount();
172                 window.setNumColumns(columnNum);
173                 while (moveToPosition(pos) && window.allocRow()) {
174                     for (int i = 0; i < columnNum; i++) {
175                         String field = getString(i);
176                         if (field != null) {
177                             if (!window.putString(field, pos, i)) {
178                                 window.freeLastRow();
179                                 break;
180                             }
181                         } else {
182                             if (!window.putNull(pos, i)) {
183                                 window.freeLastRow();
184                                 break;
185                             }
186                         }
187                     }
188                     ++pos;
189                 }
190                 moveToPosition(oldpos);
191             } catch (IllegalStateException e){
192                 // simply ignore it
193             } finally {
194                 window.releaseReference();
195             }
196         }
197 
getWindow()198         public CursorWindow getWindow() {
199             return null;
200         }
201 
onMove(int oldPosition, int newPosition)202         public boolean onMove(int oldPosition, int newPosition) {
203             return ((CrossProcessCursor)mDatabaseCursor).onMove(oldPosition, newPosition);
204         }
205 
206         /*
207          * These "virtual columns" are columns which don't exist in the underlying
208          * database cursor but are exported by this cursor.  For example, we compute
209          * a "word" by taking the substring of the full row text in the words table
210          * using the provided offsets.
211          */
212         private String [] mVirtualColumns = new String [] {
213                 SearchManager.SUGGEST_COLUMN_INTENT_DATA,
214                 SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
215                 SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
216                 SearchManager.SUGGEST_COLUMN_TEXT_1,
217             };
218 
219         // Cursor column offsets for the above virtual columns.
220         // These columns exist after the natural columns in the
221         // database cursor.  So, for example, the column called
222         // SUGGEST_COLUMN_TEXT_1 comes 3 after mDatabaseCursor.getColumnCount().
223         private final int INTENT_DATA_COLUMN = 0;
224         private final int INTENT_ACTION_COLUMN = 1;
225         private final int INTENT_EXTRA_DATA_COLUMN = 2;
226         private final int INTENT_TEXT_COLUMN = 3;
227 
228 
getColumnCount()229         public int getColumnCount() {
230             return mColumnCount + mVirtualColumns.length;
231         }
232 
getColumnIndex(String columnName)233         public int getColumnIndex(String columnName) {
234             for (int i = 0; i < mVirtualColumns.length; i++) {
235                 if (mVirtualColumns[i].equals(columnName)) {
236                     return mColumnCount + i;
237                 }
238             }
239             return mDatabaseCursor.getColumnIndex(columnName);
240         }
241 
getColumnNames()242         public String [] getColumnNames() {
243             String [] x = mDatabaseCursor.getColumnNames();
244             String [] y = new String [x.length + mVirtualColumns.length];
245 
246             for (int i = 0; i < x.length; i++) {
247                 y[i] = x[i];
248             }
249 
250             for (int i = 0; i < mVirtualColumns.length; i++) {
251                 y[x.length + i] = mVirtualColumns[i];
252             }
253 
254             return y;
255         }
256 
moveToPosition(int position)257         public boolean moveToPosition(int position) {
258             if (position >= 0 && position < mRows.size()) {
259                 mCurrentRow = position;
260                 mDatabaseCursor.moveToPosition(mRows.get(position).mRowNumber);
261                 return true;
262             } else {
263                 return false;
264             }
265         }
266 
move(int offset)267         public boolean move(int offset) {
268             return moveToPosition(mCurrentRow + offset);
269         }
270 
moveToFirst()271         public boolean moveToFirst() {
272             return moveToPosition(0);
273         }
274 
moveToLast()275         public boolean moveToLast() {
276             return moveToPosition(mRows.size() - 1);
277         }
278 
moveToNext()279         public boolean moveToNext() {
280             return moveToPosition(mCurrentRow + 1);
281         }
282 
moveToPrevious()283         public boolean moveToPrevious() {
284             return moveToPosition(mCurrentRow - 1);
285         }
286 
getString(int column)287         public String getString(int column) {
288             // if we're returning one of the columns in the underlying database column
289             // then do so here
290             if (column < mColumnCount) {
291                 return mDatabaseCursor.getString(column);
292             }
293 
294             // otherwise we're returning one of the synthetic columns.
295             // the constants like INTENT_DATA_COLUMN are offsets relative to
296             // mColumnCount.
297             Row row = mRows.get(mCurrentRow);
298             switch (column - mColumnCount) {
299                 case INTENT_DATA_COLUMN:
300                     Uri.Builder b = Uri.parse("content://mms-sms/search").buildUpon();
301                     b = b.appendQueryParameter("pattern", row.getSnippet());
302                     Uri u = b.build();
303                     return u.toString();
304                 case INTENT_ACTION_COLUMN:
305                     return Intent.ACTION_SEARCH;
306                 case INTENT_EXTRA_DATA_COLUMN:
307                     return row.getSnippet();
308                 case INTENT_TEXT_COLUMN:
309                     return row.getSnippet();
310                 default:
311                     return null;
312             }
313         }
314 
close()315         public void close() {
316             mDatabaseCursor.close();
317         }
318 
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)319         public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
320             mDatabaseCursor.copyStringToBuffer(columnIndex, buffer);
321         }
322 
deactivate()323         public void deactivate() {
324             mDatabaseCursor.deactivate();
325         }
326 
getBlob(int columnIndex)327         public byte[] getBlob(int columnIndex) {
328             return null;
329         }
330 
getColumnIndexOrThrow(String columnName)331         public int getColumnIndexOrThrow(String columnName)
332                 throws IllegalArgumentException {
333             return 0;
334         }
335 
getColumnName(int columnIndex)336         public String getColumnName(int columnIndex) {
337             return null;
338         }
339 
getDouble(int columnIndex)340         public double getDouble(int columnIndex) {
341             return 0;
342         }
343 
getExtras()344         public Bundle getExtras() {
345             return Bundle.EMPTY;
346         }
347 
getFloat(int columnIndex)348         public float getFloat(int columnIndex) {
349             return 0;
350         }
351 
getInt(int columnIndex)352         public int getInt(int columnIndex) {
353             return 0;
354         }
355 
getLong(int columnIndex)356         public long getLong(int columnIndex) {
357             return 0;
358         }
359 
getPosition()360         public int getPosition() {
361             return mCurrentRow;
362         }
363 
getShort(int columnIndex)364         public short getShort(int columnIndex) {
365             return 0;
366         }
367 
getWantsAllOnMoveCalls()368         public boolean getWantsAllOnMoveCalls() {
369             return false;
370         }
371 
isAfterLast()372         public boolean isAfterLast() {
373             return mCurrentRow >= mRows.size();
374         }
375 
isBeforeFirst()376         public boolean isBeforeFirst() {
377             return mCurrentRow < 0;
378         }
379 
isClosed()380         public boolean isClosed() {
381             return mDatabaseCursor.isClosed();
382         }
383 
isFirst()384         public boolean isFirst() {
385             return mCurrentRow == 0;
386         }
387 
isLast()388         public boolean isLast() {
389             return mCurrentRow == mRows.size() - 1;
390         }
391 
getType(int columnIndex)392         public int getType(int columnIndex) {
393             throw new UnsupportedOperationException();  // TODO revisit
394         }
395 
isNull(int columnIndex)396         public boolean isNull(int columnIndex) {
397             return false;  // TODO revisit
398         }
399 
registerContentObserver(ContentObserver observer)400         public void registerContentObserver(ContentObserver observer) {
401             mDatabaseCursor.registerContentObserver(observer);
402         }
403 
registerDataSetObserver(DataSetObserver observer)404         public void registerDataSetObserver(DataSetObserver observer) {
405             mDatabaseCursor.registerDataSetObserver(observer);
406         }
407 
requery()408         public boolean requery() {
409             return false;
410         }
411 
respond(Bundle extras)412         public Bundle respond(Bundle extras) {
413             return mDatabaseCursor.respond(extras);
414         }
415 
setNotificationUri(ContentResolver cr, Uri uri)416         public void setNotificationUri(ContentResolver cr, Uri uri) {
417             mDatabaseCursor.setNotificationUri(cr, uri);
418         }
419 
unregisterContentObserver(ContentObserver observer)420         public void unregisterContentObserver(ContentObserver observer) {
421             mDatabaseCursor.unregisterContentObserver(observer);
422         }
423 
unregisterDataSetObserver(DataSetObserver observer)424         public void unregisterDataSetObserver(DataSetObserver observer) {
425             mDatabaseCursor.unregisterDataSetObserver(observer);
426         }
427     }
428 }
429