| /* |
| * Copyright (C) 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mms; |
| |
| import java.util.ArrayList; |
| |
| import android.app.SearchManager; |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Intent; |
| import android.database.CharArrayBuffer; |
| import android.database.ContentObserver; |
| import android.database.CrossProcessCursor; |
| import android.database.Cursor; |
| import android.database.CursorWindow; |
| import android.database.DataSetObserver; |
| import android.database.sqlite.SQLiteException; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.text.TextUtils; |
| |
| /** |
| * Suggestions provider for mms. Queries the "words" table to provide possible word suggestions. |
| */ |
| public class SuggestionsProvider extends android.content.ContentProvider { |
| |
| final static String AUTHORITY = "com.android.mms.SuggestionsProvider"; |
| // final static int MODE = DATABASE_MODE_QUERIES + DATABASE_MODE_2LINES; |
| |
| public SuggestionsProvider() { |
| super(); |
| } |
| |
| @Override |
| public int delete(Uri uri, String selection, String[] selectionArgs) { |
| return 0; |
| } |
| |
| @Override |
| public String getType(Uri uri) { |
| return null; |
| } |
| |
| @Override |
| public Uri insert(Uri uri, ContentValues values) { |
| return null; |
| } |
| |
| @Override |
| public boolean onCreate() { |
| return true; |
| } |
| |
| @Override |
| public Cursor query(Uri uri, String[] projection, String selection, |
| String[] selectionArgs, String sortOrder) { |
| Uri u = Uri.parse(String.format( |
| "content://mms-sms/searchSuggest?pattern=%s", |
| selectionArgs[0])); |
| Cursor c = getContext().getContentResolver().query( |
| u, |
| null, |
| null, |
| null, |
| null); |
| |
| return new SuggestionsCursor(c, selectionArgs[0]); |
| } |
| |
| @Override |
| public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { |
| return 0; |
| } |
| |
| private class SuggestionsCursor implements CrossProcessCursor { |
| Cursor mDatabaseCursor; |
| int mColumnCount; |
| int mCurrentRow; |
| ArrayList<Row> mRows = new ArrayList<Row>(); |
| String mQuery; |
| |
| public SuggestionsCursor(Cursor cursor, String query) { |
| mDatabaseCursor = cursor; |
| mQuery = query; |
| |
| mColumnCount = cursor.getColumnCount(); |
| try { |
| computeRows(); |
| } catch (SQLiteException ex) { |
| // This can happen if the user enters -n (anything starting with -). |
| // sqlite3/fts3 can't handle it. Google for "logic error or missing database fts3" |
| // for commentary on it. |
| mRows.clear(); // assume no results |
| } |
| } |
| |
| public int getCount() { |
| return mRows.size(); |
| } |
| |
| private class Row { |
| private String mSnippet; |
| private int mRowNumber; |
| |
| public Row(int row, String snippet) { |
| mSnippet = snippet.trim(); |
| mRowNumber = row; |
| } |
| public String getSnippet() { |
| return mSnippet; |
| } |
| } |
| |
| /* |
| * Compute rows for rows in the cursor. The cursor can contain duplicates which |
| * are filtered out in the while loop. Using DISTINCT on the result of the |
| * FTS3 snippet function does not work so we do it here in the code. |
| */ |
| private void computeRows() { |
| int snippetColumn = mDatabaseCursor.getColumnIndex("snippet"); |
| |
| int count = mDatabaseCursor.getCount(); |
| String previousSnippet = null; |
| |
| for (int i = 0; i < count; i++) { |
| mDatabaseCursor.moveToPosition(i); |
| String snippet = mDatabaseCursor.getString(snippetColumn); |
| if (!TextUtils.equals(previousSnippet, snippet)) { |
| mRows.add(new Row(i, snippet)); |
| previousSnippet = snippet; |
| } |
| } |
| } |
| |
| private int [] computeOffsets(String offsetsString) { |
| String [] vals = offsetsString.split(" "); |
| |
| int [] retvals = new int[vals.length]; |
| for (int i = retvals.length-1; i >= 0; i--) { |
| retvals[i] = Integer.parseInt(vals[i]); |
| } |
| return retvals; |
| } |
| |
| public void fillWindow(int position, CursorWindow window) { |
| int count = getCount(); |
| if (position < 0 || position > count + 1) { |
| return; |
| } |
| window.acquireReference(); |
| try { |
| int oldpos = getPosition(); |
| int pos = position; |
| window.clear(); |
| window.setStartPosition(position); |
| int columnNum = getColumnCount(); |
| window.setNumColumns(columnNum); |
| while (moveToPosition(pos) && window.allocRow()) { |
| for (int i = 0; i < columnNum; i++) { |
| String field = getString(i); |
| if (field != null) { |
| if (!window.putString(field, pos, i)) { |
| window.freeLastRow(); |
| break; |
| } |
| } else { |
| if (!window.putNull(pos, i)) { |
| window.freeLastRow(); |
| break; |
| } |
| } |
| } |
| ++pos; |
| } |
| moveToPosition(oldpos); |
| } catch (IllegalStateException e){ |
| // simply ignore it |
| } finally { |
| window.releaseReference(); |
| } |
| } |
| |
| public CursorWindow getWindow() { |
| return null; |
| } |
| |
| public boolean onMove(int oldPosition, int newPosition) { |
| return ((CrossProcessCursor)mDatabaseCursor).onMove(oldPosition, newPosition); |
| } |
| |
| /* |
| * These "virtual columns" are columns which don't exist in the underlying |
| * database cursor but are exported by this cursor. For example, we compute |
| * a "word" by taking the substring of the full row text in the words table |
| * using the provided offsets. |
| */ |
| private String [] mVirtualColumns = new String [] { |
| SearchManager.SUGGEST_COLUMN_INTENT_DATA, |
| SearchManager.SUGGEST_COLUMN_INTENT_ACTION, |
| SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, |
| SearchManager.SUGGEST_COLUMN_TEXT_1, |
| }; |
| |
| // Cursor column offsets for the above virtual columns. |
| // These columns exist after the natural columns in the |
| // database cursor. So, for example, the column called |
| // SUGGEST_COLUMN_TEXT_1 comes 3 after mDatabaseCursor.getColumnCount(). |
| private final int INTENT_DATA_COLUMN = 0; |
| private final int INTENT_ACTION_COLUMN = 1; |
| private final int INTENT_EXTRA_DATA_COLUMN = 2; |
| private final int INTENT_TEXT_COLUMN = 3; |
| |
| |
| public int getColumnCount() { |
| return mColumnCount + mVirtualColumns.length; |
| } |
| |
| public int getColumnIndex(String columnName) { |
| for (int i = 0; i < mVirtualColumns.length; i++) { |
| if (mVirtualColumns[i].equals(columnName)) { |
| return mColumnCount + i; |
| } |
| } |
| return mDatabaseCursor.getColumnIndex(columnName); |
| } |
| |
| public String [] getColumnNames() { |
| String [] x = mDatabaseCursor.getColumnNames(); |
| String [] y = new String [x.length + mVirtualColumns.length]; |
| |
| for (int i = 0; i < x.length; i++) { |
| y[i] = x[i]; |
| } |
| |
| for (int i = 0; i < mVirtualColumns.length; i++) { |
| y[x.length + i] = mVirtualColumns[i]; |
| } |
| |
| return y; |
| } |
| |
| public boolean moveToPosition(int position) { |
| if (position >= 0 && position < mRows.size()) { |
| mCurrentRow = position; |
| mDatabaseCursor.moveToPosition(mRows.get(position).mRowNumber); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public boolean move(int offset) { |
| return moveToPosition(mCurrentRow + offset); |
| } |
| |
| public boolean moveToFirst() { |
| return moveToPosition(0); |
| } |
| |
| public boolean moveToLast() { |
| return moveToPosition(mRows.size() - 1); |
| } |
| |
| public boolean moveToNext() { |
| return moveToPosition(mCurrentRow + 1); |
| } |
| |
| public boolean moveToPrevious() { |
| return moveToPosition(mCurrentRow - 1); |
| } |
| |
| public String getString(int column) { |
| // if we're returning one of the columns in the underlying database column |
| // then do so here |
| if (column < mColumnCount) { |
| return mDatabaseCursor.getString(column); |
| } |
| |
| // otherwise we're returning one of the synthetic columns. |
| // the constants like INTENT_DATA_COLUMN are offsets relative to |
| // mColumnCount. |
| Row row = mRows.get(mCurrentRow); |
| switch (column - mColumnCount) { |
| case INTENT_DATA_COLUMN: |
| Uri.Builder b = Uri.parse("content://mms-sms/search").buildUpon(); |
| b = b.appendQueryParameter("pattern", row.getSnippet()); |
| Uri u = b.build(); |
| return u.toString(); |
| case INTENT_ACTION_COLUMN: |
| return Intent.ACTION_SEARCH; |
| case INTENT_EXTRA_DATA_COLUMN: |
| return row.getSnippet(); |
| case INTENT_TEXT_COLUMN: |
| return row.getSnippet(); |
| default: |
| return null; |
| } |
| } |
| |
| public void close() { |
| mDatabaseCursor.close(); |
| } |
| |
| public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { |
| mDatabaseCursor.copyStringToBuffer(columnIndex, buffer); |
| } |
| |
| public void deactivate() { |
| mDatabaseCursor.deactivate(); |
| } |
| |
| public byte[] getBlob(int columnIndex) { |
| return null; |
| } |
| |
| public int getColumnIndexOrThrow(String columnName) |
| throws IllegalArgumentException { |
| return 0; |
| } |
| |
| public String getColumnName(int columnIndex) { |
| return null; |
| } |
| |
| public double getDouble(int columnIndex) { |
| return 0; |
| } |
| |
| public Bundle getExtras() { |
| return Bundle.EMPTY; |
| } |
| |
| public float getFloat(int columnIndex) { |
| return 0; |
| } |
| |
| public int getInt(int columnIndex) { |
| return 0; |
| } |
| |
| public long getLong(int columnIndex) { |
| return 0; |
| } |
| |
| public int getPosition() { |
| return mCurrentRow; |
| } |
| |
| public short getShort(int columnIndex) { |
| return 0; |
| } |
| |
| public boolean getWantsAllOnMoveCalls() { |
| return false; |
| } |
| |
| public boolean isAfterLast() { |
| return mCurrentRow >= mRows.size(); |
| } |
| |
| public boolean isBeforeFirst() { |
| return mCurrentRow < 0; |
| } |
| |
| public boolean isClosed() { |
| return mDatabaseCursor.isClosed(); |
| } |
| |
| public boolean isFirst() { |
| return mCurrentRow == 0; |
| } |
| |
| public boolean isLast() { |
| return mCurrentRow == mRows.size() - 1; |
| } |
| |
| public int getType(int columnIndex) { |
| throw new UnsupportedOperationException(); // TODO revisit |
| } |
| |
| public boolean isNull(int columnIndex) { |
| return false; // TODO revisit |
| } |
| |
| public void registerContentObserver(ContentObserver observer) { |
| mDatabaseCursor.registerContentObserver(observer); |
| } |
| |
| public void registerDataSetObserver(DataSetObserver observer) { |
| mDatabaseCursor.registerDataSetObserver(observer); |
| } |
| |
| public boolean requery() { |
| return false; |
| } |
| |
| public Bundle respond(Bundle extras) { |
| return mDatabaseCursor.respond(extras); |
| } |
| |
| public void setNotificationUri(ContentResolver cr, Uri uri) { |
| mDatabaseCursor.setNotificationUri(cr, uri); |
| } |
| |
| public void unregisterContentObserver(ContentObserver observer) { |
| mDatabaseCursor.unregisterContentObserver(observer); |
| } |
| |
| public void unregisterDataSetObserver(DataSetObserver observer) { |
| mDatabaseCursor.unregisterDataSetObserver(observer); |
| } |
| } |
| } |