Home | History | Annotate | Download | only in opp
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *
      9  * - Redistributions of source code must retain the above copyright notice,
     10  * this list of conditions and the following disclaimer.
     11  *
     12  * - Redistributions in binary form must reproduce the above copyright notice,
     13  * this list of conditions and the following disclaimer in the documentation
     14  * and/or other materials provided with the distribution.
     15  *
     16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     17  * may be used to endorse or promote products derived from this software
     18  * without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.android.bluetooth.opp;
     34 
     35 import android.content.ContentProvider;
     36 import android.content.ContentValues;
     37 import android.content.Context;
     38 import android.content.Intent;
     39 import android.database.Cursor;
     40 import android.database.SQLException;
     41 import android.content.UriMatcher;
     42 import android.database.sqlite.SQLiteDatabase;
     43 import android.database.sqlite.SQLiteOpenHelper;
     44 import android.database.sqlite.SQLiteQueryBuilder;
     45 import android.net.Uri;
     46 import android.provider.LiveFolders;
     47 import android.util.Log;
     48 
     49 import java.util.ArrayList;
     50 import java.util.HashMap;
     51 import java.util.List;
     52 
     53 /**
     54  * This provider allows application to interact with Bluetooth OPP manager
     55  */
     56 
     57 public final class BluetoothOppProvider extends ContentProvider {
     58 
     59     private static final String TAG = "BluetoothOppProvider";
     60     private static final boolean D = Constants.DEBUG;
     61     private static final boolean V = Constants.VERBOSE;
     62 
     63     /** Database filename */
     64     private static final String DB_NAME = "btopp.db";
     65 
     66     /** Current database version */
     67     private static final int DB_VERSION = 1;
     68 
     69     /** Database version from which upgrading is a nop */
     70     private static final int DB_VERSION_NOP_UPGRADE_FROM = 0;
     71 
     72     /** Database version to which upgrading is a nop */
     73     private static final int DB_VERSION_NOP_UPGRADE_TO = 1;
     74 
     75     /** Name of table in the database */
     76     private static final String DB_TABLE = "btopp";
     77 
     78     /** MIME type for the entire share list */
     79     private static final String SHARE_LIST_TYPE = "vnd.android.cursor.dir/vnd.android.btopp";
     80 
     81     /** MIME type for an individual share */
     82     private static final String SHARE_TYPE = "vnd.android.cursor.item/vnd.android.btopp";
     83 
     84     /** URI matcher used to recognize URIs sent by applications */
     85     private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     86 
     87     /** URI matcher constant for the URI of the entire share list */
     88     private static final int SHARES = 1;
     89 
     90     /** URI matcher constant for the URI of an individual share */
     91     private static final int SHARES_ID = 2;
     92 
     93     static {
     94         sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES);
     95         sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID);
     96     }
     97 
     98     /** The database that lies underneath this content provider */
     99     private SQLiteOpenHelper mOpenHelper = null;
    100 
    101     /**
    102      * Creates and updated database on demand when opening it. Helper class to
    103      * create database the first time the provider is initialized and upgrade it
    104      * when a new version of the provider needs an updated version of the
    105      * database.
    106      */
    107     private final class DatabaseHelper extends SQLiteOpenHelper {
    108 
    109         public DatabaseHelper(final Context context) {
    110             super(context, DB_NAME, null, DB_VERSION);
    111         }
    112 
    113         /**
    114          * Creates database the first time we try to open it.
    115          */
    116         @Override
    117         public void onCreate(final SQLiteDatabase db) {
    118             if (V) Log.v(TAG, "populating new database");
    119             createTable(db);
    120         }
    121 
    122         //TODO: use this function to check garbage transfer left in db, for example,
    123         // a crash incoming file
    124         /*
    125          * (not a javadoc comment) Checks data integrity when opening the
    126          * database.
    127          */
    128         /*
    129          * @Override public void onOpen(final SQLiteDatabase db) {
    130          * super.onOpen(db); }
    131          */
    132 
    133         /**
    134          * Updates the database format when a content provider is used with a
    135          * database that was created with a different format.
    136          */
    137         // Note: technically, this could also be a downgrade, so if we want
    138         // to gracefully handle upgrades we should be careful about
    139         // what to do on downgrades.
    140         @Override
    141         public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) {
    142             if (oldV == DB_VERSION_NOP_UPGRADE_FROM) {
    143                 if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op
    144                     // upgrade.
    145                     return;
    146                 }
    147                 // NOP_FROM and NOP_TO are identical, just in different
    148                 // codelines. Upgrading
    149                 // from NOP_FROM is the same as upgrading from NOP_TO.
    150                 oldV = DB_VERSION_NOP_UPGRADE_TO;
    151             }
    152             Log.i(TAG, "Upgrading downloads database from version " + oldV + " to "
    153                     + newV + ", which will destroy all old data");
    154             dropTable(db);
    155             createTable(db);
    156         }
    157 
    158     }
    159 
    160     private void createTable(SQLiteDatabase db) {
    161         try {
    162             db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID
    163                     + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, "
    164                     + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, "
    165                     + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, "
    166                     + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY
    167                     + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, "
    168                     + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES
    169                     + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, "
    170                     + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED
    171                     + " INTEGER); ");
    172         } catch (SQLException ex) {
    173             Log.e(TAG, "couldn't create table in downloads database");
    174             throw ex;
    175         }
    176     }
    177 
    178     private void dropTable(SQLiteDatabase db) {
    179         try {
    180             db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
    181         } catch (SQLException ex) {
    182             Log.e(TAG, "couldn't drop table in downloads database");
    183             throw ex;
    184         }
    185     }
    186 
    187     @Override
    188     public String getType(Uri uri) {
    189         int match = sURIMatcher.match(uri);
    190         switch (match) {
    191             case SHARES: {
    192                 return SHARE_LIST_TYPE;
    193             }
    194             case SHARES_ID: {
    195                 return SHARE_TYPE;
    196             }
    197             default: {
    198                 if (D) Log.d(TAG, "calling getType on an unknown URI: " + uri);
    199                 throw new IllegalArgumentException("Unknown URI: " + uri);
    200             }
    201         }
    202     }
    203 
    204     private static final void copyString(String key, ContentValues from, ContentValues to) {
    205         String s = from.getAsString(key);
    206         if (s != null) {
    207             to.put(key, s);
    208         }
    209     }
    210 
    211     private static final void copyInteger(String key, ContentValues from, ContentValues to) {
    212         Integer i = from.getAsInteger(key);
    213         if (i != null) {
    214             to.put(key, i);
    215         }
    216     }
    217 
    218     private static final void copyLong(String key, ContentValues from, ContentValues to) {
    219         Long i = from.getAsLong(key);
    220         if (i != null) {
    221             to.put(key, i);
    222         }
    223     }
    224 
    225     @Override
    226     public Uri insert(Uri uri, ContentValues values) {
    227         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    228 
    229         if (sURIMatcher.match(uri) != SHARES) {
    230             if (D) Log.d(TAG, "calling insert on an unknown/invalid URI: " + uri);
    231             throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
    232         }
    233 
    234         ContentValues filteredValues = new ContentValues();
    235 
    236         copyString(BluetoothShare.URI, values, filteredValues);
    237         copyString(BluetoothShare.FILENAME_HINT, values, filteredValues);
    238         copyString(BluetoothShare.MIMETYPE, values, filteredValues);
    239         copyString(BluetoothShare.DESTINATION, values, filteredValues);
    240 
    241         copyInteger(BluetoothShare.VISIBILITY, values, filteredValues);
    242         copyLong(BluetoothShare.TOTAL_BYTES, values, filteredValues);
    243         if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) {
    244             filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE);
    245         }
    246         Integer dir = values.getAsInteger(BluetoothShare.DIRECTION);
    247         Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION);
    248         String address = values.getAsString(BluetoothShare.DESTINATION);
    249 
    250         if (values.getAsInteger(BluetoothShare.DIRECTION) == null) {
    251             dir = BluetoothShare.DIRECTION_OUTBOUND;
    252         }
    253         if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) {
    254             con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED;
    255         }
    256         if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) {
    257             con = BluetoothShare.USER_CONFIRMATION_PENDING;
    258         }
    259         filteredValues.put(BluetoothShare.USER_CONFIRMATION, con);
    260         filteredValues.put(BluetoothShare.DIRECTION, dir);
    261 
    262         filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING);
    263         filteredValues.put(Constants.MEDIA_SCANNED, 0);
    264 
    265         Long ts = values.getAsLong(BluetoothShare.TIMESTAMP);
    266         if (ts == null) {
    267             ts = System.currentTimeMillis();
    268         }
    269         filteredValues.put(BluetoothShare.TIMESTAMP, ts);
    270 
    271         Context context = getContext();
    272         context.startService(new Intent(context, BluetoothOppService.class));
    273 
    274         long rowID = db.insert(DB_TABLE, null, filteredValues);
    275 
    276         Uri ret = null;
    277 
    278         if (rowID != -1) {
    279             context.startService(new Intent(context, BluetoothOppService.class));
    280             ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
    281             context.getContentResolver().notifyChange(uri, null);
    282         } else {
    283             if (D) Log.d(TAG, "couldn't insert into btopp database");
    284             }
    285 
    286         return ret;
    287     }
    288 
    289     @Override
    290     public boolean onCreate() {
    291         mOpenHelper = new DatabaseHelper(getContext());
    292         return true;
    293     }
    294 
    295     @Override
    296     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    297             String sortOrder) {
    298         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    299 
    300         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    301 
    302         int match = sURIMatcher.match(uri);
    303         switch (match) {
    304             case SHARES: {
    305                 qb.setTables(DB_TABLE);
    306                 break;
    307             }
    308             case SHARES_ID: {
    309                 qb.setTables(DB_TABLE);
    310                 qb.appendWhere(BluetoothShare._ID + "=");
    311                 qb.appendWhere(uri.getPathSegments().get(1));
    312                 break;
    313             }
    314             default: {
    315                 if (D) Log.d(TAG, "querying unknown URI: " + uri);
    316                 throw new IllegalArgumentException("Unknown URI: " + uri);
    317             }
    318         }
    319 
    320         if (V) {
    321             java.lang.StringBuilder sb = new java.lang.StringBuilder();
    322             sb.append("starting query, database is ");
    323             if (db != null) {
    324                 sb.append("not ");
    325             }
    326             sb.append("null; ");
    327             if (projection == null) {
    328                 sb.append("projection is null; ");
    329             } else if (projection.length == 0) {
    330                 sb.append("projection is empty; ");
    331             } else {
    332                 for (int i = 0; i < projection.length; ++i) {
    333                     sb.append("projection[");
    334                     sb.append(i);
    335                     sb.append("] is ");
    336                     sb.append(projection[i]);
    337                     sb.append("; ");
    338                 }
    339             }
    340             sb.append("selection is ");
    341             sb.append(selection);
    342             sb.append("; ");
    343             if (selectionArgs == null) {
    344                 sb.append("selectionArgs is null; ");
    345             } else if (selectionArgs.length == 0) {
    346                 sb.append("selectionArgs is empty; ");
    347             } else {
    348                 for (int i = 0; i < selectionArgs.length; ++i) {
    349                     sb.append("selectionArgs[");
    350                     sb.append(i);
    351                     sb.append("] is ");
    352                     sb.append(selectionArgs[i]);
    353                     sb.append("; ");
    354                 }
    355             }
    356             sb.append("sort is ");
    357             sb.append(sortOrder);
    358             sb.append(".");
    359             Log.v(TAG, sb.toString());
    360         }
    361 
    362         Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
    363 
    364         if (ret != null) {
    365             ret.setNotificationUri(getContext().getContentResolver(), uri);
    366             if (V) Log.v(TAG, "created cursor " + ret + " on behalf of ");// +
    367         } else {
    368             if (D) Log.d(TAG, "query failed in downloads database");
    369             }
    370 
    371         return ret;
    372     }
    373 
    374     @Override
    375     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    376         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    377 
    378         int count;
    379         long rowId = 0;
    380 
    381         int match = sURIMatcher.match(uri);
    382         switch (match) {
    383             case SHARES:
    384             case SHARES_ID: {
    385                 String myWhere;
    386                 if (selection != null) {
    387                     if (match == SHARES) {
    388                         myWhere = "( " + selection + " )";
    389                     } else {
    390                         myWhere = "( " + selection + " ) AND ";
    391                     }
    392                 } else {
    393                     myWhere = "";
    394                 }
    395                 if (match == SHARES_ID) {
    396                     String segment = uri.getPathSegments().get(1);
    397                     rowId = Long.parseLong(segment);
    398                     myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) ";
    399                 }
    400 
    401                 if (values.size() > 0) {
    402                     count = db.update(DB_TABLE, values, myWhere, selectionArgs);
    403                 } else {
    404                     count = 0;
    405                 }
    406                 break;
    407             }
    408             default: {
    409                 if (D) Log.d(TAG, "updating unknown/invalid URI: " + uri);
    410                 throw new UnsupportedOperationException("Cannot update URI: " + uri);
    411             }
    412         }
    413         getContext().getContentResolver().notifyChange(uri, null);
    414 
    415         return count;
    416     }
    417 
    418     @Override
    419     public int delete(Uri uri, String selection, String[] selectionArgs) {
    420         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    421         int count;
    422         int match = sURIMatcher.match(uri);
    423         switch (match) {
    424             case SHARES:
    425             case SHARES_ID: {
    426                 String myWhere;
    427                 if (selection != null) {
    428                     if (match == SHARES) {
    429                         myWhere = "( " + selection + " )";
    430                     } else {
    431                         myWhere = "( " + selection + " ) AND ";
    432                     }
    433                 } else {
    434                     myWhere = "";
    435                 }
    436                 if (match == SHARES_ID) {
    437                     String segment = uri.getPathSegments().get(1);
    438                     long rowId = Long.parseLong(segment);
    439                     myWhere += " ( " + BluetoothShare._ID + " = " + rowId + " ) ";
    440                 }
    441 
    442                 count = db.delete(DB_TABLE, myWhere, selectionArgs);
    443                 break;
    444             }
    445             default: {
    446                 if (D) Log.d(TAG, "deleting unknown/invalid URI: " + uri);
    447                 throw new UnsupportedOperationException("Cannot delete URI: " + uri);
    448             }
    449         }
    450         getContext().getContentResolver().notifyChange(uri, null);
    451         return count;
    452     }
    453 }
    454