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