/* * 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 java.util.HashSet; import java.util.Map; import android.app.SearchManager; import android.content.ContentResolver; import android.content.ContentUris; 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.util.Log; import com.android.mms.data.Contact; /** * 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; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === public final static String VIEW_ACTION = "sprd.intent.action.VIEW"; private final static int SEARCH_TIME_INTERVAL = 500; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === private long mSearchTime; 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) { if (!selectionArgs[0].trim().equals("")) { long time = System.currentTimeMillis(); if (time - mSearchTime < SEARCH_TIME_INTERVAL) { return null; } mSearchTime = time; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === // Uri u = Uri.parse(String.format( // "content://mms-sms/searchSuggest?pattern=%s", // selectionArgs[0])); Uri u = Uri.parse(String.format( "content://mms-sms/search?pattern=%s", selectionArgs[0])); //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === Cursor c = getContext().getContentResolver().query( u, null, null, null, null); return new SuggestionsCursor(c, selectionArgs[0]); } return null; } @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>(); public SuggestionsCursor(Cursor cursor, String query) { mDatabaseCursor = cursor; 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 { public Row(int row, String text, int startOffset, int endOffset) { mText = text; mRowNumber = row; mStartOffset = startOffset; mEndOffset = endOffset; } //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === public Row(int row, int threadId,String text, String text2){ mText = text; mText2 = text2; mRowNumber = row; mThreadId = threadId; } public String getText() { return mText; } public String getText2() { // find name from contact Contact contact = mText2 != null ? Contact.get(mText2, false) : null; String text2 = mText2 != null ? contact.getNameAndNumber() : ""; return text2; } public int getThreadId(){ return mThreadId; } int mThreadId; String mText2; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === String mText; int mRowNumber; int mStartOffset; int mEndOffset; public String getWord() { return mText.substring(mStartOffset, mEndOffset); } } //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === // private void computeRows() { // HashSet<String> got = new HashSet<String>(); // // int textColumn = mDatabaseCursor.getColumnIndex("index_text"); // int offsetsColumn = mDatabaseCursor.getColumnIndex("offsets(words)"); // // int count = mDatabaseCursor.getCount(); // for (int i = 0; i < count; i++) { // mDatabaseCursor.moveToPosition(i); // String message = mDatabaseCursor.getString(textColumn); // // int [] offsets = computeOffsets(mDatabaseCursor.getString(offsetsColumn)); // for (int j = 0; j < offsets.length; j += 4) { //// int columnNumber = offsets[j+0]; //// int termNumber = offsets[j+1]; // int startOffset = offsets[j+2]; // int length = offsets[j+3]; // int endOffset = startOffset + length; // String candidate = message.substring(startOffset, endOffset); // String key = candidate.toLowerCase(); // if (got.contains(key)) { // continue; // } // got.add(key); // mRows.add(new Row(i, message, startOffset, endOffset)); // } // } // } private void computeRows() { final int threadIdPos = mDatabaseCursor.getColumnIndex("thread_id"); final int addressPos = mDatabaseCursor.getColumnIndex("address"); final int bodyPos = mDatabaseCursor.getColumnIndex("body"); int count = mDatabaseCursor.getCount(); for (int i = 0; i < count; i++) { mDatabaseCursor.moveToPosition(i); String text = mDatabaseCursor.getString(bodyPos);// message text content String text2 = mDatabaseCursor.getString(addressPos);// message address int threadId = mDatabaseCursor.getInt(threadIdPos);// message threadId mRows.add(new Row(i,threadId,text,text2)); } } //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === 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 ((CrossProcessCursor)mCursor).getWindow(); CursorWindow window = new CursorWindow(false); return window; } 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, //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === SearchManager.SUGGEST_COLUMN_TEXT_2 //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === }; // 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; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === private final int INTENT_TEXT2_COLUMN = 4; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === 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 (column < mColumnCount) { return mDatabaseCursor.getString(column); } Row row = mRows.get(mCurrentRow); switch (column - mColumnCount) { case INTENT_DATA_COLUMN: //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === // Uri u = Uri.parse("content://mms-sms/search").buildUpon().appendQueryParameter("pattern", row.getWord()).build(); // return u.toString(); return "content://mms-sms/conversations/"+row.getThreadId(); //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === case INTENT_ACTION_COLUMN: //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === // return Intent.ACTION_SEARCH; return VIEW_ACTION; //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === case INTENT_EXTRA_DATA_COLUMN: return getString(getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)); case INTENT_TEXT_COLUMN: //=== fixed CR<NEWMS00107266> by luning at 11-09-09 begin === // return row.getWord(); return row.getText(); case INTENT_TEXT2_COLUMN: return row.getText2(); //=== fixed CR<NEWMS00107266> by luning at 11-09-09 end === default: return null; } } public void abortUpdates() { } public void close() { mDatabaseCursor.close(); } public boolean commitUpdates() { return false; } public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) { return false; } public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { mDatabaseCursor.copyStringToBuffer(columnIndex, buffer); } public void deactivate() { mDatabaseCursor.deactivate(); } public boolean deleteRow() { return false; } 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 hasUpdates() { 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 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 boolean supportsUpdates() { return false; } public void unregisterContentObserver(ContentObserver observer) { mDatabaseCursor.unregisterContentObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDatabaseCursor.unregisterDataSetObserver(observer); } public boolean updateBlob(int columnIndex, byte[] value) { return false; } public boolean updateDouble(int columnIndex, double value) { return false; } public boolean updateFloat(int columnIndex, float value) { return false; } public boolean updateInt(int columnIndex, int value) { return false; } public boolean updateLong(int columnIndex, long value) { return false; } public boolean updateShort(int columnIndex, short value) { return false; } public boolean updateString(int columnIndex, String value) { return false; } public boolean updateToNull(int columnIndex) { return false; } } }