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